diff --git a/src/components/workflow/HitScriptDialog.vue b/src/components/workflow/HitScriptDialog.vue new file mode 100644 index 0000000..28200c4 --- /dev/null +++ b/src/components/workflow/HitScriptDialog.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/components/workflow/Step01IpLearning.vue b/src/components/workflow/Step01IpLearning.vue index ed295b4..21ee70b 100644 --- a/src/components/workflow/Step01IpLearning.vue +++ b/src/components/workflow/Step01IpLearning.vue @@ -4,6 +4,7 @@ import { storeToRefs } from "pinia"; import { useWorkflowStore } from "../../stores/workflow.js"; import AddIpBrainDialog from "./AddIpBrainDialog.vue"; import VideoLinkDialog from "./VideoLinkDialog.vue"; +import HitScriptDialog from "./HitScriptDialog.vue"; const workflow = useWorkflowStore(); const { @@ -23,9 +24,11 @@ const { ipTopicsRewriting, ipTopicRewriteProgress, rewrittenTopics, + hitScriptGenerating, } = storeToRefs(workflow); const topicFeedback = ref({ severity: "", message: "" }); +const hitScriptFeedback = ref({ severity: "", message: "" }); function onSelectBenchmark(id) { topicFeedback.value = { severity: "", message: "" }; @@ -63,6 +66,25 @@ function onOpenIpBrain() { function topicCount(benchmark) { return benchmark?.topicLibrary?.length ?? 0; } + +async function onGenerateHitScript() { + hitScriptFeedback.value = { severity: "", message: "" }; + const result = await workflow.generateHitScript(); + if (result.message) { + hitScriptFeedback.value = { + severity: result.ok ? "success" : "error", + message: result.message, + }; + } +} + +function onHitScriptFeedback(result) { + if (!result?.message) return; + hitScriptFeedback.value = { + severity: result.ok ? "success" : "error", + message: result.message, + }; +} diff --git a/src/stores/workflow.js b/src/stores/workflow.js index d66ff2b..d716577 100644 --- a/src/stores/workflow.js +++ b/src/stores/workflow.js @@ -83,6 +83,16 @@ export const useWorkflowStore = defineStore("workflow", { scriptGenerating: false, + /** 爆款文案(customPrompt 模式)生成中 */ + hitScriptGenerating: false, + + /** 爆款文案方案列表(最多 3 条) */ + hitScriptOptions: [], + + hitScriptSelectedIndex: 0, + + hitScriptModalVisible: false, + legalChecking: false, legalModalVisible: false, @@ -355,6 +365,24 @@ export const useWorkflowStore = defineStore("workflow", { }, + async generateHitScript() { + + return executeGenerateHitScript(this); + + }, + + applyHitScriptOption(index) { + + return applyHitScriptOptionToEditor(this, index); + + }, + + closeHitScriptModal() { + + this.hitScriptModalVisible = false; + + }, + async runLegalCheck() { return executeLegalCheck(this); @@ -664,6 +692,146 @@ const DEFAULT_SCRIPT_PROMPT = `请基于以下视频原文案,创作一个新 请直接输出仿写后的文案:`; +/** + * 解析爆款文案模型返回(对齐 zhenqianba ii) + * @param {string} raw + * @returns {string[]} + */ +export function parseHitScriptOptions(raw) { + const text = String(raw || ""); + const md = text.match(/```(?:json)?\s*([\s\S]*?)```/i); + const body = (md?.[1] || text).trim(); + try { + const parsed = JSON.parse(body); + const list = Array.isArray(parsed) + ? parsed + : Array.isArray(parsed?.options) + ? parsed.options + : Array.isArray(parsed?.data) + ? parsed.data + : []; + const options = list + .map((item) => + typeof item === "string" ? item : item?.content || item?.text || "", + ) + .map((s) => String(s).trim()) + .filter(Boolean); + if (options.length) { + return Array.from(new Set(options)).slice(0, 3); + } + } catch (err) { + console.warn("[爆款文案] JSON 解析失败,转用文本拆分:", err); + } + const fallback = body + .split(/\n(?=(?:方案|选项)\s*[一二三123])|\n{2,}/) + .map((line) => + line.replace(/^(?:方案|选项)\s*[一二三123][::.\s-]*/g, "").trim(), + ) + .filter(Boolean); + return Array.from(new Set(fallback)).slice(0, 3); +} + +/** + * 构建爆款文案提示词(对齐 zhenqianba Ti) + * @param {ReturnType} store + */ +export function buildHitScriptPrompt(store) { + const lines = []; + lines.push( + "你是一个爆款短视频文案专家,擅长创作高播放量、高互动的短视频口播文案。", + ); + lines.push("请根据以下信息输出 3 个不同方向的爆款短视频文案方案。"); + lines.push(`【视频类型】${store.videoType || "口播文案"}`); + lines.push(`【文案类型】${store.copyType || "人设型"}`); + const persona = String(store.industryPersona || "").trim(); + if (persona) lines.push(`【行业+人设】${persona}`); + const product = String(store.productBusiness || "").trim(); + if (product) lines.push(`【产品/业务】${product}`); + const selling = String(store.sellingPrice || "").trim(); + if (selling) lines.push(`【卖点+价格】${selling}`); + const other = String(store.otherRequirements || "").trim(); + if (other) lines.push(`【其他要求】${other}`); + const count = store.targetWords || 300; + lines.push(""); + lines.push("要求:"); + lines.push("1. 开头3秒必须有强吸引力的钩子,一开口就能留住用户"); + lines.push(`2. 内容贴合「${store.copyType || "人设型"}」风格特点`); + lines.push("3. 语言自然口语化,适合短视频口播"); + lines.push("4. 结尾要有引导互动的话术(关注、点赞、评论、收藏)"); + lines.push("5. 不使用表情符号和特殊符号"); + lines.push(`6. 字数控制在${count}字左右`); + lines.push("7. 3 个方案要明显区分角度,不要只改几个词"); + lines.push( + '8. 只返回 JSON,格式必须是 {"options":["方案1","方案2","方案3"]},不要输出 markdown,不要补充解释', + ); + return lines.join("\n"); +} + +/** + * 生成爆款文案:调用 LLM 产出 3 个方案(对齐 zhenqianba Ti) + * @param {ReturnType} store + */ +export async function executeGenerateHitScript(store) { + const cfgReady = await ensureLlmConfigReady(); + if (!cfgReady.ok) { + return { ok: false, message: cfgReady.message }; + } + + const prompt = buildHitScriptPrompt(store); + store.hitScriptGenerating = true; + store.hitScriptOptions = []; + + try { + const result = await invoke("run_nodejs_script", { + scriptName: SCRIPT_GENERATE, + params: { + messages: [{ role: "user", content: prompt }], + temperature: 0.8, + max_tokens: 4000, + }, + }); + + if (!result?.success || !result?.content) { + const err = result?.error || "文案生成失败,请重试"; + return { ok: false, message: String(err) }; + } + + const options = parseHitScriptOptions(result.content); + if (!options.length) { + return { ok: false, message: "模型未返回可用文案方案" }; + } + + store.hitScriptOptions = options; + store.hitScriptSelectedIndex = 0; + store.hitScriptModalVisible = true; + return { + ok: true, + message: `已生成 ${options.length} 个爆款文案方案,请在弹窗中选择`, + }; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err || "未知错误"); + return { ok: false, message: `生成文案失败:${msg}` }; + } finally { + store.hitScriptGenerating = false; + } +} + +/** + * 将所选爆款文案填入编辑区(对齐 zhenqianba Bn) + * @param {ReturnType} store + * @param {number} index + */ +export function applyHitScriptOptionToEditor(store, index) { + const i = Number(index); + const option = store.hitScriptOptions[i]; + if (!option) { + return { ok: false, message: "请选择文案方案" }; + } + store.scriptContent = String(option).trim(); + store.hitScriptModalVisible = false; + return { ok: true, message: "已将所选文案填入编辑区" }; +} + /** * 构建撰写文案提示词(对齐 zhenqianba generateScriptFromVideoContent) * @param {ReturnType} store