111
This commit is contained in:
@@ -8,17 +8,14 @@ const desktopConfig = require("./desktop_config.js");
|
|||||||
|
|
||||||
const KEYWORD_GROUPS = ["重点词/成语词", "描述词", "行动词", "情感词"];
|
const KEYWORD_GROUPS = ["重点词/成语词", "描述词", "行动词", "情感词"];
|
||||||
|
|
||||||
const DEFAULT_TITLE_TAG_PROMPT = `你是短视频标题与标签助手。分析以下文案内容,生成:
|
const ELECTRON_DEFAULT_TITLE_TAG_PROMPT =
|
||||||
1. 一个最吸引人的标题(不超过30字,若需多个建议可写在 title 字段内用换行分隔)
|
"你是短视频标题与标签助手。分析文案内容 {{content}},生成5个吸引眼球的标题建议、10个相关标签、4个分组关键词(每组:重点词/成语词、描述词、行动词、情感词,每个分组只要0-2个词不要多)。";
|
||||||
2. 8-10 个相关话题标签(带 # 号)
|
|
||||||
3. 四个分组关键词,每组 0-2 个词:重点词/成语词、描述词、行动词、情感词
|
|
||||||
|
|
||||||
文案内容:
|
const JSON_OUTPUT_SUFFIX = `
|
||||||
{{content}}
|
|
||||||
|
|
||||||
请只返回 JSON,不要 markdown,格式如下:
|
请只返回 JSON,不要 markdown,格式如下:
|
||||||
{
|
{
|
||||||
"title": "标题",
|
"title": "最推荐标题(多个标题建议可用换行分隔)",
|
||||||
"tags": ["#标签1", "#标签2"],
|
"tags": ["#标签1", "#标签2"],
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"重点词/成语词": ["词1"],
|
"重点词/成语词": ["词1"],
|
||||||
@@ -28,12 +25,28 @@ const DEFAULT_TITLE_TAG_PROMPT = `你是短视频标题与标签助手。分析
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
function logInfo(tag, payload) {
|
||||||
|
if (payload === undefined) {
|
||||||
|
console.info(tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof payload === "string") {
|
||||||
|
console.info(`${tag} - ${payload}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info(`${tag} - ${JSON.stringify(payload)}`);
|
||||||
|
}
|
||||||
|
|
||||||
function buildPrompt(template, content) {
|
function buildPrompt(template, content) {
|
||||||
let prompt = String(template || "").trim();
|
let prompt = String(template || "").trim();
|
||||||
if (!prompt) prompt = DEFAULT_TITLE_TAG_PROMPT;
|
if (!prompt) prompt = ELECTRON_DEFAULT_TITLE_TAG_PROMPT;
|
||||||
return prompt
|
prompt = prompt
|
||||||
.replace(/\{\{content\}\}/g, content)
|
.replace(/\{\{content\}\}/g, content)
|
||||||
.replace(/\{content\}/g, content);
|
.replace(/\{content\}/g, content);
|
||||||
|
if (!/json|JSON/.test(prompt)) {
|
||||||
|
prompt += JSON_OUTPUT_SUFFIX;
|
||||||
|
}
|
||||||
|
return prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanJsonText(raw) {
|
function cleanJsonText(raw) {
|
||||||
@@ -76,6 +89,16 @@ function normalizeKeywordGroup(val) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeTitle(val) {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val
|
||||||
|
.map((t) => String(t || "").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
return String(val || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
function parseTitleTagsContent(content) {
|
function parseTitleTagsContent(content) {
|
||||||
let title = "";
|
let title = "";
|
||||||
let tags = "";
|
let tags = "";
|
||||||
@@ -83,7 +106,8 @@ function parseTitleTagsContent(content) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(cleanJsonText(content));
|
const parsed = JSON.parse(cleanJsonText(content));
|
||||||
title = String(parsed.title || "").trim();
|
logInfo("ipAgent.generateTitleTags.cleanedContent", cleanJsonText(content));
|
||||||
|
title = normalizeTitle(parsed.title || parsed.titles);
|
||||||
tags = normalizeTags(parsed.tags);
|
tags = normalizeTags(parsed.tags);
|
||||||
const kw = parsed.keywords;
|
const kw = parsed.keywords;
|
||||||
if (kw && typeof kw === "object" && !Array.isArray(kw)) {
|
if (kw && typeof kw === "object" && !Array.isArray(kw)) {
|
||||||
@@ -95,7 +119,12 @@ function parseTitleTagsContent(content) {
|
|||||||
} else if (typeof kw === "string") {
|
} else if (typeof kw === "string") {
|
||||||
keywords["重点词/成语词"] = normalizeKeywordGroup(kw);
|
keywords["重点词/成语词"] = normalizeKeywordGroup(kw);
|
||||||
}
|
}
|
||||||
} catch {
|
logInfo("ipAgent.generateTitleTags.parsed", { title, tags, keywords });
|
||||||
|
} catch (err) {
|
||||||
|
logInfo("ipAgent.generateTitleTags.jsonParseFailed", {
|
||||||
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
content,
|
||||||
|
});
|
||||||
const lines = String(content).split("\n");
|
const lines = String(content).split("\n");
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (/标题|Title/i.test(line)) {
|
if (/标题|Title/i.test(line)) {
|
||||||
@@ -121,6 +150,16 @@ function parseTitleTagsContent(content) {
|
|||||||
return { title, tags, keywords };
|
return { title, tags, keywords };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateLlmConfig(modelConfig) {
|
||||||
|
if (!modelConfig.apiKey) {
|
||||||
|
return { ok: false, error: "缺少配置 LLM_API_KEY(desktop_configs 表)" };
|
||||||
|
}
|
||||||
|
if (!modelConfig.apiUrl) {
|
||||||
|
return { ok: false, error: "缺少配置 LLM_BASE_URL(或 LLM_API_URL)" };
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
globalThis.__nodejsMain = async function main(params) {
|
globalThis.__nodejsMain = async function main(params) {
|
||||||
const p = params || {};
|
const p = params || {};
|
||||||
const content = String(p.content || p.script || "").trim();
|
const content = String(p.content || p.script || "").trim();
|
||||||
@@ -129,12 +168,39 @@ globalThis.__nodejsMain = async function main(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const modelConfig = desktopConfig.buildModelConfig();
|
const modelConfig = desktopConfig.buildModelConfig();
|
||||||
const check = desktopConfig.validateModelConfig(modelConfig);
|
const check = validateLlmConfig(modelConfig);
|
||||||
if (!check.ok) {
|
if (!check.ok) {
|
||||||
return { success: false, error: check.error };
|
return { success: false, error: check.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = buildPrompt(p.titleTagPrompt, content);
|
const prompt = buildPrompt(p.titleTagPrompt, content);
|
||||||
|
const requestPayload = {
|
||||||
|
model: modelConfig.apiModelId,
|
||||||
|
messages: [{ role: "user", content: prompt }],
|
||||||
|
};
|
||||||
|
|
||||||
|
logInfo("ipAgent.generateTitleTags", {
|
||||||
|
scriptLength: content.length,
|
||||||
|
modelConfig: {
|
||||||
|
providerId: modelConfig.providerId,
|
||||||
|
modelId: modelConfig.apiModelId,
|
||||||
|
apiUrl: modelConfig.apiUrl,
|
||||||
|
apiHost: "",
|
||||||
|
apiKey: modelConfig.apiKey,
|
||||||
|
type: modelConfig.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logInfo("ipAgent.generateTitleTags.REQUEST", {
|
||||||
|
apiUrl: modelConfig.apiUrl,
|
||||||
|
model: modelConfig.apiModelId,
|
||||||
|
promptLength: prompt.length,
|
||||||
|
hasApiKey: Boolean(modelConfig.apiKey),
|
||||||
|
});
|
||||||
|
logInfo("ipAgent.generateTitleTags.REQUEST_FULL_PROMPT", prompt);
|
||||||
|
logInfo(
|
||||||
|
"ipAgent.generateTitleTags.REQUEST_FULL_PAYLOAD",
|
||||||
|
JSON.stringify(requestPayload, null, 2),
|
||||||
|
);
|
||||||
|
|
||||||
globalThis.__native?.emitProgress?.("正在生成标题、标签和关键词…");
|
globalThis.__native?.emitProgress?.("正在生成标题、标签和关键词…");
|
||||||
|
|
||||||
@@ -142,24 +208,35 @@ globalThis.__nodejsMain = async function main(params) {
|
|||||||
apiUrl: modelConfig.apiUrl,
|
apiUrl: modelConfig.apiUrl,
|
||||||
apiKey: modelConfig.apiKey,
|
apiKey: modelConfig.apiKey,
|
||||||
model: modelConfig.apiModelId,
|
model: modelConfig.apiModelId,
|
||||||
messages: [{ role: "user", content: prompt }],
|
messages: requestPayload.messages,
|
||||||
temperature: typeof p.temperature === "number" ? p.temperature : 0.7,
|
temperature: typeof p.temperature === "number" ? p.temperature : 0.7,
|
||||||
max_tokens: p.max_tokens || 2000,
|
max_tokens: p.max_tokens || 2000,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chat.success || !chat.content) {
|
if (!chat.success || !chat.content) {
|
||||||
|
logInfo("ipAgent.generateTitleTags.api.error", chat.error || "模型返回为空");
|
||||||
return { success: false, error: chat.error || "模型返回为空" };
|
return { success: false, error: chat.error || "模型返回为空" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logInfo("ipAgent.generateTitleTags.RESPONSE_CONTENT", chat.content);
|
||||||
|
logInfo("ipAgent.generateTitleTags.rawContent", { content: chat.content });
|
||||||
|
|
||||||
const parsed = parseTitleTagsContent(chat.content);
|
const parsed = parseTitleTagsContent(chat.content);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
|
logInfo("ipAgent.generateTitleTags.emptyResult", { content: chat.content });
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "解析结果为空,请检查模型返回格式",
|
error: "解析结果为空,AI返回内容: " + chat.content,
|
||||||
rawContent: chat.content,
|
rawContent: chat.content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logInfo("ipAgent.generateTitleTags.success", {
|
||||||
|
title: parsed.title,
|
||||||
|
tags: parsed.tags,
|
||||||
|
keywords: parsed.keywords,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
title: parsed.title,
|
title: parsed.title,
|
||||||
|
|||||||
Reference in New Issue
Block a user