feat: streamline benchmark intake flows

This commit is contained in:
kris
2026-03-22 11:53:14 +08:00
parent 32dea8e3a6
commit 031ba04d4e
2 changed files with 156 additions and 1 deletions

View File

@@ -39,6 +39,7 @@
- 新建项目
- 导入主页并触发内容源同步
- 把当前对标账号直接导入到当前项目,并绑定 Agent 触发同步
- 导入作品链接并触发分析
- 导入文本素材并触发分析
- 上传本地视频并触发分析
@@ -49,6 +50,7 @@
- 从相似候选一键保存对标关系
- 查看任务详情、事件、子任务和 artifacts/result
- 从任务详情直接衔接 AI 视频 / 实拍剪辑 / 文案生成
- 在生产中心 / 发布与复盘常驻最近一次任务详情摘要
- 使用 Agent 生成文案
- 创建 AI 视频任务
- 创建实拍剪辑任务

View File

@@ -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")}`,
`
<div class="panel">
<div class="toolbar">
@@ -1102,6 +1125,20 @@ function renderDiscoveryScreen() {
<div class="mini-card"><small>已绑对标</small><strong>${escapeHtml(formatNumber(linkedAccounts.length))}</strong></div>
</div>
</div>
<div class="panel pad" style="box-shadow:none; margin-top:16px;">
<div class="panel-head"><div><h3>接入当前项目</h3><div class="panel-subtitle"> Agent </div></div><span class="tag ${importedSources.length ? "green" : "blue"}">${escapeHtml(importedSources.length ? "" : "")}</span></div>
${selected ? `
<div class="task-item">
<h4>${escapeHtml(selectedProject?.name || "未选项目")}</h4>
<p>${escapeHtml(importedSources.length ? `当前项目已接入 ${formatNumber(importedSources.length)} 个内容源,可继续同步或换 Agent。` : "当前项目还没有接入这个对标账号,可直接导入主页并绑定 Agent。")}</p>
<div class="task-meta">
<span class="tag">${escapeHtml(selectedProject?.name || "未选项目")}</span>
<span class="tag">${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")}</span>
<span class="tag clickable-tag" data-action="open-import-selected-account">${importedSources.length ? "继续同步" : "导入当前对标"}</span>
</div>
</div>
` : `<div class="task-item"><h4>还没有选中账号</h4><p>先从左侧列表选一个对标账号,再决定是否导入到当前项目。</p></div>`}
</div>
<div class="three-col">
<div class="insight-card">
<h4>账号画像</h4>
@@ -1438,6 +1475,7 @@ function renderProductionScreen() {
`).join("") || (works.length ? "" : `<div class="review-card"><h4>还没有作品</h4><p>先导入内容或跑一次分析任务。</p></div>`)}
</div>
</div>
${renderLastJobDetailCard()}
</div>
</div>
`
@@ -1472,6 +1510,7 @@ function renderReviewScreen() {
`).join("") || `<div class="review-card"><h4>还没有完成任务</h4><p>先去生产中心跑一条链路。</p></div>`}
</div>
</div>
${renderLastJobDetailCard()}
`
);
}
@@ -1601,6 +1640,43 @@ function renderLastActionCard() {
`;
}
function renderLastJobDetailCard() {
const detail = appState.lastJobDetail;
if (!detail?.job) return "";
const previewLinks = getJobPreviewLinks(detail.job);
return `
<div class="panel pad">
<div class="panel-head">
<div>
<h3>最近任务详情</h3>
<div class="panel-subtitle">${escapeHtml(formatDateTime(detail.job.created_at))}</div>
</div>
<span class="tag ${statusTone(detail.job.status)}">${escapeHtml(detail.job.status || "-")}</span>
</div>
<div class="task-item">
<h4>${escapeHtml(detail.job.title || detail.job.id)}</h4>
<p>${escapeHtml(brief(detail.job.style_summary || detail.job.transcript_text || detail.job.error || "暂无摘要", 120))}</p>
<div class="task-meta">
<span class="tag">${escapeHtml(detail.job.line_type || "-")}</span>
${canDeriveAiVideo(detail.job) ? `<span class="tag clickable-tag" data-action="job-to-ai-video" data-job-id="${escapeHtml(detail.job.id)}">做 AI 视频</span>` : ""}
${canDeriveRealCut(detail.job) ? `<span class="tag clickable-tag" data-action="job-to-real-cut" data-job-id="${escapeHtml(detail.job.id)}">做实拍剪辑</span>` : ""}
<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(detail.job.id)}">看详情</span>
</div>
</div>
${previewLinks.length ? `
<div class="list">
${previewLinks.slice(0, 3).map((item) => `
<div class="task-item compact">
<h4>${escapeHtml(item.label.replace(/^result\./, "").replace(/^artifacts\./, ""))}</h4>
<p>${escapeHtml(item.url)}</p>
</div>
`).join("")}
</div>
` : ""}
</div>
`;
}
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;