11
This commit is contained in:
@@ -54,7 +54,7 @@ function buildModelConfig() {
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
let apiUrl = pick("BAILIAN_BASE_URL", "BAILIAN_BASE_URL");
|
let apiUrl = pick("LLM_BASE_URL", "LLM_BASE_URL");
|
||||||
if (apiUrl) {
|
if (apiUrl) {
|
||||||
apiUrl = apiUrl.replace(/\/$/, "");
|
apiUrl = apiUrl.replace(/\/$/, "");
|
||||||
if (!apiUrl.includes("/chat/completions")) {
|
if (!apiUrl.includes("/chat/completions")) {
|
||||||
@@ -63,7 +63,7 @@ function buildModelConfig() {
|
|||||||
: `${apiUrl}/v1/chat/completions`;
|
: `${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";
|
const asrMode = pick("ASR_MODE", "asr_mode") || "online";
|
||||||
|
|
||||||
let ossConfig = parseJson(pick("OSS_CONFIG"));
|
let ossConfig = parseJson(pick("OSS_CONFIG"));
|
||||||
@@ -90,9 +90,10 @@ function buildModelConfig() {
|
|||||||
providerId: pick("LLM_PROVIDER_ID", "PROVIDER_ID") || "bailian",
|
providerId: pick("LLM_PROVIDER_ID", "PROVIDER_ID") || "bailian",
|
||||||
modelId: pick("LLM_MODEL_ID", "MODEL_ID"),
|
modelId: pick("LLM_MODEL_ID", "MODEL_ID"),
|
||||||
apiUrl,
|
apiUrl,
|
||||||
apiKey: pick("BAILIAN_API_KEY", "API_KEY"),
|
apiKey: pick("LLM_API_KEY", "API_KEY") || "",
|
||||||
type: pick("LLM_TYPE", "TYPE") || "bailian",
|
type: pick("LLM_TYPE", "TYPE") || "bailian",
|
||||||
asrMode,
|
asrMode,
|
||||||
|
apiModelId,
|
||||||
aliyunApiKey: pick("BAILIAN_API_KEY", "BAILIAN_API_KEY"),
|
aliyunApiKey: pick("BAILIAN_API_KEY", "BAILIAN_API_KEY"),
|
||||||
ossConfig: ossConfig || undefined,
|
ossConfig: ossConfig || undefined,
|
||||||
asrServerInfo: asrServerInfo || undefined,
|
asrServerInfo: asrServerInfo || undefined,
|
||||||
|
|||||||
@@ -49,22 +49,26 @@ async function rewriteWithAI(content, modelConfig, rewriteConfig, customPrompt)
|
|||||||
rewriteConfig || {},
|
rewriteConfig || {},
|
||||||
customPrompt
|
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({
|
const result = await pipelineNative.openaiChat({
|
||||||
apiUrl: modelConfig.apiUrl,
|
apiUrl: modelConfig.apiUrl,
|
||||||
apiKey: modelConfig.apiKey,
|
apiKey: modelConfig.apiKey,
|
||||||
model: modelConfig.modelId,
|
model: modelConfig.apiModelId,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: "你是专业的短视频文案改写助手。" },
|
{ role: "system", content: "你是专业的短视频文案改写助手。请把用户提供的文案改写,要求保持原文的风格和主题,但使用不同的表达方式。" },
|
||||||
{ role: "user", content: prompt },
|
{ role: "user", content: prompt },
|
||||||
],
|
],
|
||||||
temperature:
|
temperature: 0.8,
|
||||||
typeof modelConfig.temperature === "number" ? modelConfig.temperature : 0.8,
|
max_tokens: 2e3,
|
||||||
max_tokens: modelConfig.maxTokens || 2000,
|
|
||||||
});
|
});
|
||||||
if (!result?.success) {
|
if (!result?.success) {
|
||||||
log("error", "rewrite.failed", { error: result?.error });
|
log("error", "rewrite.failed", { error: result?.error });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
log("info", "pipeline.rewriteOk", { result: result.content });
|
||||||
return result.content;
|
return result.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,16 @@ function signOss(secret, str) {
|
|||||||
return crypto.createHmac("sha1", secret).update(str).digest("base64");
|
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) {
|
async function aliyunOssUpload(localPath, cfg) {
|
||||||
const ak = cfg.accessKeyId;
|
const ak = cfg.accessKeyId;
|
||||||
const sk = cfg.accessKeySecret;
|
const sk = cfg.accessKeySecret;
|
||||||
@@ -196,7 +206,12 @@ async function aliyunOssUpload(localPath, cfg) {
|
|||||||
res.on("data", (c) => chunks.push(c));
|
res.on("data", (c) => chunks.push(c));
|
||||||
res.on("end", () => {
|
res.on("end", () => {
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
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 {
|
} else {
|
||||||
resolve({
|
resolve({
|
||||||
success: false,
|
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 = {}) {
|
async function fetchJson(url, options = {}) {
|
||||||
const res = await fetch(url, options);
|
const res = await fetch(url, options);
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
@@ -235,6 +283,14 @@ async function aliyunAsrFiletrans(audioUrl, opts) {
|
|||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return { success: false, error: "缺少 DashScope 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(
|
const submit = await fetchJson(
|
||||||
"https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription",
|
"https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription",
|
||||||
{
|
{
|
||||||
@@ -246,13 +302,11 @@ async function aliyunAsrFiletrans(audioUrl, opts) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model,
|
model,
|
||||||
input: { file_urls: [audioUrl] },
|
input,
|
||||||
parameters: {
|
parameters: {
|
||||||
channel_id: [0],
|
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;
|
const status = poll?.output?.task_status;
|
||||||
log("info", "voice recognition status",status);
|
log("info", "voice recognition status",status);
|
||||||
if (status === "SUCCEEDED") {
|
if (status === "SUCCEEDED") {
|
||||||
const transUrl = poll?.output?.results?.[0]?.transcription_url;
|
const transUrl = extractTranscriptionUrl(poll?.output);
|
||||||
if (!transUrl) {
|
if (!transUrl) {
|
||||||
log("error", "voice recognition transUrl",transUrl);
|
log("error", "voice recognition output", poll?.output);
|
||||||
return { success: false, error: "ASR 成功但无 transcription_url" };
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "ASR 成功但无 transcription_url(期望 output.result 或 output.results[0])",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const trans = await fetchJson(transUrl);
|
const trans = await fetchJson(transUrl);
|
||||||
let fullText = "";
|
const fullText = parseTranscriptionText(trans);
|
||||||
if (trans.transcripts && trans.transcripts.length) {
|
|
||||||
fullText = trans.transcripts.map((t) => t.text || "").filter(Boolean).join("\n");
|
|
||||||
} else if (trans.text) {
|
|
||||||
fullText = trans.text;
|
|
||||||
}
|
|
||||||
return { success: true, text: fullText };
|
return { success: true, text: fullText };
|
||||||
}
|
}
|
||||||
if (status === "FAILED" || status === "UNKNOWN") {
|
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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: poll?.output?.message || "ASR 任务失败",
|
error: detail || "ASR 任务失败",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
scripts/nodejs/script_generate.js
Normal file
32
scripts/nodejs/script_generate.js
Normal file
@@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -56,10 +56,12 @@ ${content}
|
|||||||
}
|
}
|
||||||
|
|
||||||
static buildRewritePrompt(content, config = {}, customPrompt) {
|
static buildRewritePrompt(content, config = {}, customPrompt) {
|
||||||
if (customPrompt && String(customPrompt).trim()) {
|
// if (customPrompt && String(customPrompt).trim()) {
|
||||||
return this.replacePromptVariables(customPrompt, content);
|
// return this.replacePromptVariables(customPrompt, content);
|
||||||
}
|
// }
|
||||||
return this.buildDefaultPrompt(content, config);
|
|
||||||
|
// return this.buildDefaultPrompt(content, config);
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user