From 031ba04d4e8099c2031c9a748f3414dd057309fa Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 22 Mar 2026 11:53:14 +0800 Subject: [PATCH] feat: streamline benchmark intake flows --- web/storyforge-web-v4/README.md | 2 + web/storyforge-web-v4/assets/app.js | 155 +++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/web/storyforge-web-v4/README.md b/web/storyforge-web-v4/README.md index 8d7a108..0a51f0a 100644 --- a/web/storyforge-web-v4/README.md +++ b/web/storyforge-web-v4/README.md @@ -39,6 +39,7 @@ - 新建项目 - 导入主页并触发内容源同步 +- 把当前对标账号直接导入到当前项目,并绑定 Agent 触发同步 - 导入作品链接并触发分析 - 导入文本素材并触发分析 - 上传本地视频并触发分析 @@ -49,6 +50,7 @@ - 从相似候选一键保存对标关系 - 查看任务详情、事件、子任务和 artifacts/result - 从任务详情直接衔接 AI 视频 / 实拍剪辑 / 文案生成 +- 在生产中心 / 发布与复盘常驻最近一次任务详情摘要 - 使用 Agent 生成文案 - 创建 AI 视频任务 - 创建实拍剪辑任务 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 020b699..a35d3dc 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -614,6 +614,27 @@ function getProjectStats(projectId) { return { knowledgeBases, assistants, jobs, sources }; } +function getContentSourcesForAccount(account) { + if (!account) return []; + const profileUrl = String(account.profile_url || "").trim(); + const douyinId = String(account.douyin_id || "").trim(); + const nickname = String(account.nickname || "").trim(); + return safeArray(appState.contentSources).filter((source) => { + const sourceUrl = String(source.source_url || "").trim(); + const handle = String(source.handle || "").trim(); + const title = String(source.title || "").trim(); + return ( + (profileUrl && sourceUrl === profileUrl) || + (douyinId && handle === douyinId) || + (nickname && title.includes(nickname)) + ); + }); +} + +function getCurrentProjectSourcesForAccount(account, projectId) { + return getContentSourcesForAccount(account).filter((source) => source.project_id === projectId); +} + function getSelectedAccount() { return appState.selectedWorkspace?.account || appState.accounts.find((item) => item.id === appState.selectedAccountId) @@ -1021,10 +1042,12 @@ function renderDiscoveryScreen() { const topVideos = getHighScoreVideos(3); const latestVideos = getLatestVideos(2); const similarCandidates = safeArray(appState.lastSimilaritySearch?.candidates).slice(0, 5); + const selectedProject = getSelectedProject(); + const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || ""); return screenShell( "找对标", "这里已经接入真实抖音账号列表和单账号详情。", - `${button("导入主页", "open-import-homepage")} ${button("账号分析", "analyze-selected-account")} ${button("高分分析", "analyze-top-videos")} ${button("查相似", "open-similar-search")} ${button("存对标", "open-benchmark-link", "primary")}`, + `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account")} ${button("账号分析", "analyze-selected-account")} ${button("高分分析", "analyze-top-videos")} ${button("查相似", "open-similar-search")} ${button("存对标", "open-benchmark-link", "primary")}`, `
@@ -1102,6 +1125,20 @@ function renderDiscoveryScreen() {
已绑对标${escapeHtml(formatNumber(linkedAccounts.length))}
+
+

接入当前项目

把当前对标导入到项目,并绑定 Agent 做持续同步
${escapeHtml(importedSources.length ? "已接入" : "未接入")}
+ ${selected ? ` +
+

${escapeHtml(selectedProject?.name || "未选项目")}

+

${escapeHtml(importedSources.length ? `当前项目已接入 ${formatNumber(importedSources.length)} 个内容源,可继续同步或换 Agent。` : "当前项目还没有接入这个对标账号,可直接导入主页并绑定 Agent。")}

+
+ ${escapeHtml(selectedProject?.name || "未选项目")} + ${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")} + ${importedSources.length ? "继续同步" : "导入当前对标"} +
+
+ ` : `

还没有选中账号

先从左侧列表选一个对标账号,再决定是否导入到当前项目。

`} +

账号画像

@@ -1438,6 +1475,7 @@ function renderProductionScreen() { `).join("") || (works.length ? "" : `

还没有作品

先导入内容或跑一次分析任务。

`)}
+ ${renderLastJobDetailCard()} ` @@ -1472,6 +1510,7 @@ function renderReviewScreen() { `).join("") || `

还没有完成任务

先去生产中心跑一条链路。

`} + ${renderLastJobDetailCard()} ` ); } @@ -1601,6 +1640,43 @@ function renderLastActionCard() { `; } +function renderLastJobDetailCard() { + const detail = appState.lastJobDetail; + if (!detail?.job) return ""; + const previewLinks = getJobPreviewLinks(detail.job); + return ` +
+
+
+

最近任务详情

+
${escapeHtml(formatDateTime(detail.job.created_at))}
+
+ ${escapeHtml(detail.job.status || "-")} +
+
+

${escapeHtml(detail.job.title || detail.job.id)}

+

${escapeHtml(brief(detail.job.style_summary || detail.job.transcript_text || detail.job.error || "暂无摘要", 120))}

+
+ ${escapeHtml(detail.job.line_type || "-")} + ${canDeriveAiVideo(detail.job) ? `做 AI 视频` : ""} + ${canDeriveRealCut(detail.job) ? `做实拍剪辑` : ""} + 看详情 +
+
+ ${previewLinks.length ? ` +
+ ${previewLinks.slice(0, 3).map((item) => ` +
+

${escapeHtml(item.label.replace(/^result\./, "").replace(/^artifacts\./, ""))}

+

${escapeHtml(item.url)}

+
+ `).join("")} +
+ ` : ""} +
+ `; +} + function requireSelectedProject() { const project = getSelectedProject(); if (!project) throw new Error("请先创建项目"); @@ -1680,6 +1756,79 @@ function openImportHomepageAction() { }); } +function openImportSelectedAccountAction() { + const account = requireSelectedAccountRow(); + const project = requireSelectedProject(); + const assistants = getAssistantOptions(project.id); + const currentSources = getCurrentProjectSourcesForAccount(account, project.id); + const currentSource = currentSources[0]; + const kb = getProjectKnowledgeBases(project.id)[0]; + openActionModal({ + title: currentSource ? "继续同步当前对标" : "导入当前对标", + description: currentSource + ? "当前项目里已经有这个对标账号,继续触发同步并可切换绑定 Agent。" + : "把当前选中的对标账号加入项目,并绑定 Agent 进入持续同步。", + submitLabel: currentSource ? "继续同步" : "导入并同步", + fields: [ + { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, + { name: "platform", label: "平台", type: "select", value: "douyin", options: [ + { value: "douyin", label: "抖音" }, + { value: "xiaohongshu", label: "小红书" }, + { value: "bilibili", label: "哔哩哔哩" }, + { value: "youtube", label: "YouTube" }, + { value: "kuaishou", label: "快手" }, + { value: "wechat_video", label: "微信视频号" } + ] }, + { name: "title", label: "内容源标题", value: currentSource?.title || `${account.nickname || account.douyin_id || "对标账号"} 对标主页` }, + { name: "handle", label: "账号标识", value: currentSource?.handle || account.douyin_id || "" }, + { name: "sourceUrl", label: "主页链接", type: "url", value: currentSource?.source_url || account.profile_url || "", placeholder: "https://..." }, + { name: "assistantId", label: "绑定 Agent", type: "select", value: getSelectedAssistant()?.id || assistants[0]?.value || "", options: [{ value: "", label: "暂不绑定" }, ...assistants] }, + { name: "maxItems", label: "最多同步作品数", type: "number", value: Number(currentSource?.metadata?.max_items || 6), min: 1, max: 20 }, + { name: "skipExisting", label: "跳过已存在作品", type: "checkbox", value: true }, + { name: "autoAnalyze", label: "同步后自动分析", type: "checkbox", value: true } + ], + onSubmit: async (values) => { + if (!values.sourceUrl?.trim()) throw new Error("请先填写主页链接"); + const projectId = values.projectId || project.id; + const source = currentSource && currentSource.project_id === projectId + ? currentSource + : await storyforgeFetch("/v2/content-sources", { + method: "POST", + body: { + project_id: projectId, + source_kind: "creator_account", + platform: values.platform || "douyin", + handle: values.handle || "", + source_url: values.sourceUrl.trim(), + title: values.title || values.handle || account.nickname || "对标主页", + metadata: { + imported_from_account_id: account.id, + imported_from_workspace: "discovery" + } + } + }); + const job = await storyforgeFetch("/v2/pipelines/content-source-sync", { + method: "POST", + body: { + project_id: projectId, + knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", + assistant_id: values.assistantId || "", + content_source_id: source.id, + platform: values.platform || "douyin", + handle: values.handle || account.douyin_id || "", + source_url: values.sourceUrl.trim(), + title: values.title || account.nickname || values.handle || "对标主页", + max_items: Number(values.maxItems || 6), + skip_existing: Boolean(values.skipExisting), + auto_trigger_analysis: Boolean(values.autoAnalyze) + } + }); + rememberAction("对标已接入项目", `已把「${account.nickname || account.douyin_id || "当前对标"}」接入项目,并创建同步任务 ${job.title || job.id}。`, "green", { source, job }); + await bootstrap(); + } + }); +} + function openImportVideoLinkAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); @@ -2243,6 +2392,10 @@ document.addEventListener("click", async (event) => { openImportHomepageAction(); return; } + if (name === "open-import-selected-account") { + openImportSelectedAccountAction(); + return; + } if (name === "open-import-video-link") { openImportVideoLinkAction(); return;