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