diff --git a/scripts/nodejs/desktop_config.js b/scripts/nodejs/desktop_config.js index 2794976..fbdb175 100644 --- a/scripts/nodejs/desktop_config.js +++ b/scripts/nodejs/desktop_config.js @@ -54,7 +54,7 @@ function buildModelConfig() { return ""; }; - let apiUrl = pick("BAILIAN_BASE_URL", "BAILIAN_BASE_URL"); + let apiUrl = pick("LLM_BASE_URL", "LLM_BASE_URL"); if (apiUrl) { apiUrl = apiUrl.replace(/\/$/, ""); if (!apiUrl.includes("/chat/completions")) { @@ -63,7 +63,7 @@ function buildModelConfig() { : `${apiUrl}/v1/chat/completions`; } } - + let apiModelId = pick("LLM_MODEL_ID", "LLM_MODEL_ID") || "doubao-seed-2-0-mini-260215"; const asrMode = pick("ASR_MODE", "asr_mode") || "online"; let ossConfig = parseJson(pick("OSS_CONFIG")); @@ -90,9 +90,10 @@ function buildModelConfig() { providerId: pick("LLM_PROVIDER_ID", "PROVIDER_ID") || "bailian", modelId: pick("LLM_MODEL_ID", "MODEL_ID"), apiUrl, - apiKey: pick("BAILIAN_API_KEY", "API_KEY"), + apiKey: pick("LLM_API_KEY", "API_KEY") || "", type: pick("LLM_TYPE", "TYPE") || "bailian", asrMode, + apiModelId, aliyunApiKey: pick("BAILIAN_API_KEY", "BAILIAN_API_KEY"), ossConfig: ossConfig || undefined, asrServerInfo: asrServerInfo || undefined, diff --git a/scripts/nodejs/douyin_pipeline.js b/scripts/nodejs/douyin_pipeline.js index 788fb25..49a21dd 100644 --- a/scripts/nodejs/douyin_pipeline.js +++ b/scripts/nodejs/douyin_pipeline.js @@ -49,22 +49,26 @@ async function rewriteWithAI(content, modelConfig, rewriteConfig, customPrompt) rewriteConfig || {}, customPrompt ); + log("info", "pipeline.rewritePrompt", { prompt: prompt }); + log("info", "pipeline.apiUrl", { apiUrl: modelConfig.apiUrl }); + log("info", "pipeline.apiKey", { apiKey: modelConfig.apiKey }); + log("info", "pipeline.apiModelId", { apiModelId: modelConfig.apiModelId }); const result = await pipelineNative.openaiChat({ apiUrl: modelConfig.apiUrl, apiKey: modelConfig.apiKey, - model: modelConfig.modelId, + model: modelConfig.apiModelId, messages: [ - { role: "system", content: "你是专业的短视频文案改写助手。" }, + { role: "system", content: "你是专业的短视频文案改写助手。请把用户提供的文案改写,要求保持原文的风格和主题,但使用不同的表达方式。" }, { role: "user", content: prompt }, ], - temperature: - typeof modelConfig.temperature === "number" ? modelConfig.temperature : 0.8, - max_tokens: modelConfig.maxTokens || 2000, + temperature: 0.8, + max_tokens: 2e3, }); if (!result?.success) { log("error", "rewrite.failed", { error: result?.error }); return null; } + log("info", "pipeline.rewriteOk", { result: result.content }); return result.content; } diff --git a/scripts/nodejs/pipeline_native.js b/scripts/nodejs/pipeline_native.js index ab46caf..3dc6546 100644 --- a/scripts/nodejs/pipeline_native.js +++ b/scripts/nodejs/pipeline_native.js @@ -157,6 +157,16 @@ function signOss(secret, str) { return crypto.createHmac("sha1", secret).update(str).digest("base64"); } +/** 私有桶:ASR 需可公网 GET 的签名 URL(对齐 Electron generateSignedUrl) */ +function ossSignedGetUrl(cfg, objectKey, expiresSec = 3600) { + const expireTime = Math.floor(Date.now() / 1000) + expiresSec; + const resource = `/${cfg.bucket}/${objectKey}`; + const stringToSign = `GET\n\n\n${expireTime}\n${resource}`; + const signature = encodeURIComponent(signOss(cfg.accessKeySecret, stringToSign)); + const host = `${cfg.bucket}.${cfg.region}.aliyuncs.com`; + return `https://${host}/${objectKey}?OSSAccessKeyId=${encodeURIComponent(cfg.accessKeyId)}&Expires=${expireTime}&Signature=${signature}`; +} + async function aliyunOssUpload(localPath, cfg) { const ak = cfg.accessKeyId; const sk = cfg.accessKeySecret; @@ -196,7 +206,12 @@ async function aliyunOssUpload(localPath, cfg) { res.on("data", (c) => chunks.push(c)); res.on("end", () => { if (res.statusCode >= 200 && res.statusCode < 300) { - resolve({ success: true, url }); + const signedUrl = ossSignedGetUrl( + { accessKeyId: ak, accessKeySecret: sk, bucket, region }, + objectKey, + 3600, + ); + resolve({ success: true, url: signedUrl }); } else { resolve({ success: false, @@ -211,6 +226,39 @@ async function aliyunOssUpload(localPath, cfg) { }); } +/** filetrans: output.result;批量模型: output.results[0] */ +function extractTranscriptionUrl(output) { + if (!output || typeof output !== "object") return null; + if (output.result?.transcription_url) return output.result.transcription_url; + const results = output.results; + if (Array.isArray(results) && results[0]?.transcription_url) { + return results[0].transcription_url; + } + return null; +} + +function parseTranscriptionText(trans) { + let fullText = ""; + if (trans?.transcripts?.length) { + for (const t of trans.transcripts) { + if (t.sentences?.length) { + for (const s of t.sentences) { + if (s.text) { + if (fullText) fullText += "\n"; + fullText += s.text; + } + } + } else if (t.text) { + if (fullText) fullText += "\n"; + fullText += t.text; + } + } + } else if (trans?.text) { + fullText = trans.text; + } + return fullText; +} + async function fetchJson(url, options = {}) { const res = await fetch(url, options); const text = await res.text(); @@ -235,6 +283,14 @@ async function aliyunAsrFiletrans(audioUrl, opts) { if (!apiKey) { return { success: false, error: "缺少 DashScope apiKey" }; } + const fileUrl = String(audioUrl || "").trim(); + if (!/^https?:\/\//i.test(fileUrl)) { + return { success: false, error: "ASR 需要可访问的 http(s) 音频 URL(请检查 OSS 上传与签名)" }; + } + const input = + model === "qwen3-asr-flash-filetrans" + ? { file_url: fileUrl } + : { file_urls: [fileUrl] }; const submit = await fetchJson( "https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription", { @@ -246,13 +302,11 @@ async function aliyunAsrFiletrans(audioUrl, opts) { }, body: JSON.stringify({ model, - input: { file_urls: [audioUrl] }, + input, parameters: { channel_id: [0], - // 音频通道ID - enable_itn: false - // 是否启用逆文本归一化 - } + enable_itn: false, + }, }), } ); @@ -270,24 +324,27 @@ async function aliyunAsrFiletrans(audioUrl, opts) { const status = poll?.output?.task_status; log("info", "voice recognition status",status); if (status === "SUCCEEDED") { - const transUrl = poll?.output?.results?.[0]?.transcription_url; + const transUrl = extractTranscriptionUrl(poll?.output); if (!transUrl) { - log("error", "voice recognition transUrl",transUrl); - return { success: false, error: "ASR 成功但无 transcription_url" }; + log("error", "voice recognition output", poll?.output); + return { + success: false, + error: "ASR 成功但无 transcription_url(期望 output.result 或 output.results[0])", + }; } const trans = await fetchJson(transUrl); - let fullText = ""; - if (trans.transcripts && trans.transcripts.length) { - fullText = trans.transcripts.map((t) => t.text || "").filter(Boolean).join("\n"); - } else if (trans.text) { - fullText = trans.text; - } + const fullText = parseTranscriptionText(trans); return { success: true, text: fullText }; } if (status === "FAILED" || status === "UNKNOWN") { + const detail = + poll?.output?.message || + poll?.output?.code || + poll?.message || + (poll?.output ? JSON.stringify(poll.output).slice(0, 300) : null); return { success: false, - error: poll?.output?.message || "ASR 任务失败", + error: detail || "ASR 任务失败", }; } } diff --git a/scripts/nodejs/script_generate.js b/scripts/nodejs/script_generate.js new file mode 100644 index 0000000..755a684 --- /dev/null +++ b/scripts/nodejs/script_generate.js @@ -0,0 +1,32 @@ +"use strict"; + +/** + * 仅调用 OpenAI 兼容 Chat API 生成文案(撰写文案按钮) + * 由 `/api/v1/nodejs-scripts?name=script_generate.js` 下发 + */ + +const pipelineNative = require("./pipeline_native.js"); +const desktopConfig = require("./desktop_config.js"); + +globalThis.__nodejsMain = async function (params) { + const p = params || {}; + const modelConfig = desktopConfig.buildModelConfig(); + const check = desktopConfig.validateModelConfig(modelConfig); + if (!check.ok) { + return { success: false, error: check.error }; + } + + const messages = p.messages; + if (!Array.isArray(messages) || messages.length === 0) { + return { success: false, error: "缺少 messages" }; + } + + return pipelineNative.openaiChat({ + apiUrl: modelConfig.apiUrl, + apiKey: modelConfig.apiKey, + model: modelConfig.apiModelId, + messages, + temperature: typeof p.temperature === "number" ? p.temperature : 0.8, + max_tokens: p.max_tokens || p.maxTokens || 2000, + }); +}; diff --git a/scripts/nodejs/video_rewrite_optimizer.js b/scripts/nodejs/video_rewrite_optimizer.js index 124a64b..350daf7 100644 --- a/scripts/nodejs/video_rewrite_optimizer.js +++ b/scripts/nodejs/video_rewrite_optimizer.js @@ -56,10 +56,12 @@ ${content} } static buildRewritePrompt(content, config = {}, customPrompt) { - if (customPrompt && String(customPrompt).trim()) { - return this.replacePromptVariables(customPrompt, content); - } - return this.buildDefaultPrompt(content, config); + // if (customPrompt && String(customPrompt).trim()) { + // return this.replacePromptVariables(customPrompt, content); + // } + + // return this.buildDefaultPrompt(content, config); + return content; } }