feat: tighten main agent landing flows
This commit is contained in:
35
CHANGELOG.md
35
CHANGELOG.md
@@ -2,6 +2,14 @@
|
||||
|
||||
这个文件用于给 Gitea 仓库保留阶段性版本更新记录,方便直接查看每一轮里程碑,不用只依赖零散 commit。
|
||||
|
||||
## 2026-04-04
|
||||
|
||||
### 主 Agent 配置与执行落点继续收口
|
||||
|
||||
- 发现页里三类关键动作现在会落到更精确的业务区域:账号分析会直接切到快照/字段/报告区域,高分作品分析会直接滚到“最近高分拆解”,相似账号生成会直接滚到“相似对标 / 已绑关系”。
|
||||
- 复盘创建/更新完成后,不再只停留在通用成功提示,而是会自动回到“发布与复盘”,并把刚保存的复盘项聚焦出来。
|
||||
- 同一类“保存对标关系”动作也统一改成精确落到关系区域,避免成功后仍让用户自己再找结果在哪。
|
||||
|
||||
## 2026-03-30
|
||||
|
||||
### 主 Agent 治理与运行闭环
|
||||
@@ -186,3 +194,30 @@
|
||||
- 新增“查看执行结果”,会直接打开对应主 Agent run 的结果卡。
|
||||
- 新增“回到主 Agent 查看”,会切到对应 run 的上下文并打开主 Agent 悬浮窗口,方便顺着同一轮执行继续处理。
|
||||
- 前端回归也补上了这两个动作入口和事件处理器,避免后续又退回成只能展示、不能继续操作。
|
||||
|
||||
### 真实动作成功后的落点继续收口
|
||||
|
||||
- `加入跟踪 / 更新跟踪` 成功后,现在会直接切到 `跟踪账号` 工作区,不再只留一条成功提示。
|
||||
- `存对标 / 保存对标关系` 成功后,会直接把找对标详情切到 `关系` 视图,便于继续看刚保存的关系和候选。
|
||||
- `单任务恢复 / 批量恢复` 成功后,会优先打开新恢复出来的任务详情;如果没有拿到新任务 id,也会回到 `生产中心 -> 失败恢复`。
|
||||
- `生成文案` 成功后,会直接回到 `Agent` 工作区的“最近生成”结果区,而不是让用户自己找。
|
||||
|
||||
### 平台 Agent 最近执行字段补齐
|
||||
|
||||
- `recent_execution` 现在除了版本号和摘要,还会带:
|
||||
- `oneliner_profile_version_id`
|
||||
- `platform_agent_profile_version_id`
|
||||
- `recommended_action`
|
||||
- `workstream_key / workstream_label`
|
||||
- 平台 Agent 总览卡和详情弹层会直接利用这些字段渲染“回到业务页”动作,不需要先打开 run 详情再猜下一步。
|
||||
|
||||
### 回归护栏继续加固
|
||||
|
||||
- 前端工作台回归新增了:
|
||||
- 跟踪/对标成功后的页面落点校验
|
||||
- 恢复任务和文案生成的结果落点校验
|
||||
- 平台 Agent 最近执行 `recommended_action / workstream` 渲染校验
|
||||
- 后端治理回归新增了平台 Agent `recent_execution` 新字段断言,锁住:
|
||||
- 精确版本 id
|
||||
- 推荐业务动作
|
||||
- 工作流标签
|
||||
|
||||
@@ -1482,13 +1482,48 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
readiness_label = "可用"
|
||||
else:
|
||||
readiness_label = "待补全"
|
||||
|
||||
def build_recent_execution_payload(base_payload: dict[str, Any], run_row: dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not run_row:
|
||||
return base_payload
|
||||
latest_result = _parse_json(run_row.get("result_json"), {})
|
||||
latest_governance = _parse_json(run_row.get("governance_json"), {})
|
||||
execution_card = (latest_result.get("execution_card") or {}) if isinstance(latest_result, dict) else {}
|
||||
result_sections = (latest_result.get("result_sections") or {}) if isinstance(latest_result, dict) else {}
|
||||
recommended_action = {}
|
||||
if isinstance(latest_result, dict):
|
||||
recommended_action = latest_result.get("recommended_action") or latest_result.get("recommended_preview_action") or {}
|
||||
oneliner_profile_version = (
|
||||
execution_card.get("oneliner_profile_version")
|
||||
or latest_governance.get("oneliner_profile_version")
|
||||
or (latest_governance.get("oneliner_profile") or {}).get("current_version")
|
||||
or {}
|
||||
)
|
||||
platform_profile_version = (
|
||||
execution_card.get("platform_agent_profile")
|
||||
or ((latest_governance.get("platform_agent_profile") or {}).get("current_version") or {})
|
||||
)
|
||||
return {
|
||||
**base_payload,
|
||||
"oneliner_profile_version_id": str(oneliner_profile_version.get("version_id") or oneliner_profile_version.get("id") or "").strip(),
|
||||
"platform_agent_profile_version_id": str(platform_profile_version.get("version_id") or platform_profile_version.get("id") or "").strip(),
|
||||
"recommended_action": {
|
||||
"action": str(recommended_action.get("action") or "").strip(),
|
||||
"screen": str(recommended_action.get("screen") or "").strip(),
|
||||
"label": str(recommended_action.get("label") or "").strip(),
|
||||
"summary": str(recommended_action.get("summary") or "").strip(),
|
||||
},
|
||||
"workstream_key": str(result_sections.get("workstream_key") or "").strip(),
|
||||
"workstream_label": str(result_sections.get("workstream_label") or "").strip(),
|
||||
}
|
||||
|
||||
recent_execution = None
|
||||
current_version = None
|
||||
if row:
|
||||
_ensure_platform_agent_profile_version_seed(row, actor_user_id=account.get("id", ""))
|
||||
current_version = _platform_agent_profile_version_payload(_current_platform_agent_profile_version_row(row))
|
||||
if row and str(row.get("last_run_id") or "").strip():
|
||||
recent_execution = {
|
||||
recent_execution = build_recent_execution_payload({
|
||||
"run_id": str(row.get("last_run_id") or "").strip(),
|
||||
"run_status": str(row.get("last_run_status") or "").strip(),
|
||||
"used_at": str(row.get("last_used_at") or "").strip(),
|
||||
@@ -1498,7 +1533,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"platform_agent_profile_version_no": int(row.get("last_platform_profile_version_no") or 0),
|
||||
"summary": str(row.get("last_execution_summary") or "").strip(),
|
||||
"source_screen": str(row.get("last_source_screen") or "").strip(),
|
||||
}
|
||||
}, legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (str(row.get("last_run_id") or "").strip(),)))
|
||||
if recent_execution is None and str(project_id or "").strip() and str(platform or "").strip():
|
||||
latest_run_row = legacy.db.fetch_one(
|
||||
"""
|
||||
@@ -1517,7 +1552,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
or latest_governance.get("oneliner_profile_version")
|
||||
or {}
|
||||
)
|
||||
recent_execution = {
|
||||
recent_execution = build_recent_execution_payload({
|
||||
"run_id": str(latest_run_row.get("id") or "").strip(),
|
||||
"run_status": str(latest_run_row.get("run_status") or "").strip(),
|
||||
"used_at": str(latest_run_row.get("finished_at") or latest_run_row.get("updated_at") or "").strip(),
|
||||
@@ -1531,7 +1566,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
),
|
||||
"summary": str(latest_run_row.get("status_summary") or "").strip(),
|
||||
"source_screen": str(latest_run_row.get("source_screen") or "").strip(),
|
||||
}
|
||||
}, latest_run_row)
|
||||
return {
|
||||
"id": row["id"] if row else "",
|
||||
"user_id": account["id"],
|
||||
|
||||
@@ -928,10 +928,16 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["run_id"], run_payload["id"])
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["intent_key"], "governance_review")
|
||||
self.assertGreaterEqual(refreshed_douyin["recent_execution"]["oneliner_profile_version_no"], 1)
|
||||
self.assertTrue(refreshed_douyin["recent_execution"]["oneliner_profile_version_id"])
|
||||
self.assertTrue(refreshed_douyin["recent_execution"]["platform_agent_profile_version_id"])
|
||||
self.assertEqual(
|
||||
refreshed_douyin["recent_execution"]["platform_agent_profile_version_no"],
|
||||
rollback_profile_payload["current_version"]["version_no"],
|
||||
)
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["recommended_action"]["action"], "goto-playbook")
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["recommended_action"]["screen"], "playbook")
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["workstream_key"], "playbook")
|
||||
self.assertEqual(refreshed_douyin["recent_execution"]["workstream_label"], "Agent 治理")
|
||||
|
||||
def test_admin_ops_routes_are_live(self) -> None:
|
||||
now = self.db_module.utc_now()
|
||||
|
||||
@@ -61,6 +61,7 @@ const appState = {
|
||||
trackingDigest: null,
|
||||
trackingRefreshNotice: null,
|
||||
reviews: [],
|
||||
reviewFocusId: "",
|
||||
liveRecorderSources: [],
|
||||
liveRecorderStatus: null,
|
||||
liveRecorderFiles: [],
|
||||
@@ -4467,11 +4468,13 @@ function renderPlatformAgentPanel() {
|
||||
<div class="task-meta">
|
||||
<span class="tag blue">${escapeHtml(item.recent_execution.intent_label || "主 Agent 任务")}</span>
|
||||
<span class="tag">${escapeHtml(item.recent_execution.run_status || "done")}</span>
|
||||
${item.recent_execution.workstream_label ? `<span class="tag green">${escapeHtml(item.recent_execution.workstream_label)}</span>` : ""}
|
||||
${item.recent_execution.oneliner_profile_version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(item.recent_execution.oneliner_profile_version_no))}</span>` : ""}
|
||||
${item.recent_execution.platform_agent_profile_version_no ? `<span class="tag">${escapeHtml(item.platform_label || platformLabel(item.platform))} Agent v${escapeHtml(formatNumber(item.recent_execution.platform_agent_profile_version_no))}</span>` : ""}
|
||||
${item.recent_execution.source_screen ? `<span class="tag">${escapeHtml(screenLabel(item.recent_execution.source_screen) || item.recent_execution.source_screen)}</span>` : ""}
|
||||
</div>
|
||||
<div class="task-meta" style="margin-top:8px;">
|
||||
${item.recent_execution.recommended_action?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(item.recent_execution.recommended_action.action)}" ${buildMainAgentLandingAttrs({ runId: item.recent_execution.run_id, screen: item.recent_execution.recommended_action.screen || item.recent_execution.source_screen, title: item.recent_execution.recommended_action.label || "回到业务页", summary: item.recent_execution.recommended_action.summary || item.recent_execution.summary || "" })}>${escapeHtml(item.recent_execution.recommended_action.label || "回到业务页")}</span>` : ""}
|
||||
<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(item.recent_execution.run_id)}">查看执行结果</span>
|
||||
<span class="tag clickable-tag" data-action="open-oneliner-run-context" data-run-id="${escapeHtml(item.recent_execution.run_id)}">回到主 Agent 查看</span>
|
||||
</div>
|
||||
@@ -4687,7 +4690,7 @@ async function saveCandidateAsBenchmark(candidateIndex, relationType = "benchmar
|
||||
});
|
||||
markSavedCandidate(candidate, result.links);
|
||||
rememberAction("候选已存对标", `已把「${candidate.candidate_nickname || candidate.candidate_profile_url || "候选账号"}」加入对标关系。`, "green", result);
|
||||
renderAll();
|
||||
focusDiscoveryRelations();
|
||||
}
|
||||
|
||||
function screenShell(title, subtitle, actionsHtml, bodyHtml) {
|
||||
@@ -5482,7 +5485,7 @@ function renderProjectsScreen() {
|
||||
`${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: intakeHandoffAttrs })}`,
|
||||
`
|
||||
${renderMainAgentLandingNotice("intake")}
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<div class="hero-card mobile-secondary-card" id="review-workspace-anchor">
|
||||
<h3>当前项目</h3>
|
||||
<p>${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}</p>
|
||||
<div class="chip-row" style="margin-top:14px;">
|
||||
@@ -5583,6 +5586,84 @@ function renderDetailTabs(stateKey, tabs) {
|
||||
`;
|
||||
}
|
||||
|
||||
function focusDiscoveryDetailTab(tabValue) {
|
||||
appState.discoveryDetailTab = tabValue;
|
||||
appState.screen = "discovery";
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
document.getElementById("selected-account-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusDiscoveryInsights() {
|
||||
appState.discoveryDetailTab = "snapshots";
|
||||
appState.screen = "discovery";
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
(document.getElementById("douyin-insight-anchor") || document.getElementById("selected-account-anchor"))
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusDiscoveryTopVideoInsights() {
|
||||
appState.discoveryDetailTab = "overview";
|
||||
appState.screen = "discovery";
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
(document.getElementById("top-video-batch-anchor") || document.getElementById("selected-account-anchor"))
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusDiscoveryRelations() {
|
||||
appState.discoveryDetailTab = "relations";
|
||||
appState.screen = "discovery";
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
(document.getElementById("discovery-relations-anchor") || document.getElementById("selected-account-anchor"))
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusTrackingWorkspace() {
|
||||
setScreen("tracking");
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector('[data-screen="tracking"] .mobile-flow-focus-card, [data-screen="tracking"] .panel')?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusProductionDetailTab(tabValue) {
|
||||
appState.productionDetailTab = tabValue;
|
||||
setScreen("production");
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector('[data-screen="production"] .mobile-flow-focus-card, [data-screen="production"] .panel')?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusRecentGeneratedCopy() {
|
||||
appState.playbookDetailTab = "workspace";
|
||||
setScreen("playbook");
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
document.getElementById("recent-generated-copy-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusReviewWorkspace(reviewId = "") {
|
||||
appState.reviewFocusId = reviewId || "";
|
||||
setScreen("review");
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
const selector = reviewId
|
||||
? `[data-review-id="${String(reviewId).replaceAll('"', '\\"')}"]`
|
||||
: "#review-workspace-anchor";
|
||||
(document.querySelector(selector) || document.getElementById("review-workspace-anchor"))
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function renderDiscoveryOverviewSection({ selected, selectedProject, importedSources, tracked, topVideos, reports, latestVideos, currentPlatformLabel, topVideoBatchResult }) {
|
||||
return `
|
||||
<div class="layout-grid grid-main">
|
||||
@@ -5630,7 +5711,7 @@ function renderDiscoveryOverviewSection({ selected, selectedProject, importedSou
|
||||
</div>
|
||||
</div>
|
||||
${topVideoBatchResult ? `
|
||||
<div class="panel pad" style="box-shadow:none; margin-top:16px;">
|
||||
<div class="panel pad" id="top-video-batch-anchor" style="box-shadow:none; margin-top:16px;">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3>最近高分拆解</h3>
|
||||
@@ -5677,7 +5758,7 @@ function renderDiscoveryOverviewSection({ selected, selectedProject, importedSou
|
||||
|
||||
function renderDiscoveryRelationsSection(linkedAccounts, similarCandidates) {
|
||||
return `
|
||||
<div class="layout-grid grid-main">
|
||||
<div class="layout-grid grid-main" id="discovery-relations-anchor">
|
||||
<div class="side-stack">
|
||||
<div class="panel pad" style="box-shadow:none;">
|
||||
<div class="panel-head"><div><h3>已绑关系</h3><div class="panel-subtitle">当前账号已经保存的对标关系</div></div><span class="tag">${escapeHtml(formatNumber(linkedAccounts.length))} 个</span></div>
|
||||
@@ -6059,7 +6140,7 @@ function renderTrackingScreen() {
|
||||
<div class="panel-head"><div><h3>更新日报</h3><div class="panel-subtitle">优先看最近更新的作品摘要</div></div><span class="tag blue">${escapeHtml(formatNumber(digestItems.length))} 条</span></div>
|
||||
<div class="list">
|
||||
${digestItems.map((item) => `
|
||||
<div class="review-card compact">
|
||||
<div class="review-card compact ${review.id === appState.reviewFocusId ? "active" : ""}" data-review-id="${escapeHtml(review.id)}">
|
||||
<h4>${escapeHtml(item.account?.nickname || "账号")} · ${escapeHtml(item.video?.title || item.video?.description || "最新作品")}</h4>
|
||||
<p>${escapeHtml(item.summary || `发布时间 ${formatDateTime(item.video?.published_at)},建议继续判断借鉴点。`)}</p>
|
||||
<div class="task-meta">
|
||||
@@ -6469,6 +6550,7 @@ function renderPlaybookScreen() {
|
||||
<div class="side-stack">
|
||||
<div class="panel pad" style="box-shadow:none;">
|
||||
<div class="panel-head"><div><h3>最近生成</h3><div class="panel-subtitle">当前先承接文案生成结果</div></div></div>
|
||||
<div id="recent-generated-copy-anchor"></div>
|
||||
${appState.lastGeneratedCopy ? `
|
||||
<div class="task-item">
|
||||
<h4>${escapeHtml(appState.lastGeneratedCopy.assistantName)}</h4>
|
||||
@@ -8372,6 +8454,9 @@ function openImportHomepageAction() {
|
||||
});
|
||||
rememberAction("主页同步已启动", `已把主页加入项目,并创建同步任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -8440,6 +8525,9 @@ function openImportSelectedAccountAction() {
|
||||
});
|
||||
rememberAction("对标已接入项目", `已把「${getAccountName(account) || "当前对标"}」接入项目,并创建同步任务 ${job.title || job.id}。`, "green", { source, job });
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -8473,6 +8561,7 @@ function openTrackSelectedAccountAction() {
|
||||
});
|
||||
rememberAction(trackedItem ? "跟踪已更新" : "已加入跟踪", `账号「${getAccountName(account) || "当前对标"}」现在会进入更新日报。`, "green");
|
||||
await bootstrap();
|
||||
focusTrackingWorkspace();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -8507,6 +8596,9 @@ function openImportVideoLinkAction() {
|
||||
});
|
||||
rememberAction("作品分析已启动", `已创建分析任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -8540,6 +8632,9 @@ function openImportTextAction() {
|
||||
});
|
||||
rememberAction("文本分析已启动", `已创建文本分析任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -8572,6 +8667,9 @@ function openUploadVideoAction() {
|
||||
});
|
||||
rememberAction("上传分析已启动", `已上传素材并创建任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9445,11 +9543,13 @@ async function openPlatformAgentDetailAction(platform) {
|
||||
<div class="task-meta">
|
||||
<span class="tag blue">${escapeHtml(profile.recent_execution.intent_label || "主 Agent 任务")}</span>
|
||||
<span class="tag">${escapeHtml(profile.recent_execution.run_status || "done")}</span>
|
||||
${profile.recent_execution.workstream_label ? `<span class="tag green">${escapeHtml(profile.recent_execution.workstream_label)}</span>` : ""}
|
||||
${profile.recent_execution.oneliner_profile_version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(profile.recent_execution.oneliner_profile_version_no))}</span>` : ""}
|
||||
${profile.recent_execution.platform_agent_profile_version_no ? `<span class="tag">${escapeHtml(platformLabel(normalizedPlatform))} Agent v${escapeHtml(formatNumber(profile.recent_execution.platform_agent_profile_version_no))}</span>` : ""}
|
||||
${profile.recent_execution.source_screen ? `<span class="tag">${escapeHtml(screenLabel(profile.recent_execution.source_screen) || profile.recent_execution.source_screen)}</span>` : ""}
|
||||
</div>
|
||||
<div class="task-meta" style="margin-top:8px;">
|
||||
${profile.recent_execution.recommended_action?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(profile.recent_execution.recommended_action.action)}" ${buildMainAgentLandingAttrs({ runId: profile.recent_execution.run_id, screen: profile.recent_execution.recommended_action.screen || profile.recent_execution.source_screen, title: profile.recent_execution.recommended_action.label || "回到业务页", summary: profile.recent_execution.recommended_action.summary || profile.recent_execution.summary || "" })}>${escapeHtml(profile.recent_execution.recommended_action.label || "回到业务页")}</span>` : ""}
|
||||
<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(profile.recent_execution.run_id)}">查看执行结果</span>
|
||||
<span class="tag clickable-tag" data-action="open-oneliner-run-context" data-run-id="${escapeHtml(profile.recent_execution.run_id)}">回到主 Agent 查看</span>
|
||||
</div>
|
||||
@@ -9778,7 +9878,7 @@ function openAnalyzeSelectedAccountAction() {
|
||||
const summary = result?.suggestions?.[0]?.parsed_json?.executive_summary || result?.suggestions?.[0]?.suggestion_text || "已生成新的账号分析。";
|
||||
rememberAction("对标账号分析完成", brief(summary, 120), "green", result);
|
||||
await loadPlatformAccount(platform, account.id);
|
||||
renderAll();
|
||||
focusDiscoveryInsights();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9816,7 +9916,7 @@ function openAnalyzeTopVideosAction() {
|
||||
};
|
||||
rememberAction("高分作品分析完成", `已补分析 ${formatNumber(result.analyzed_count)} 条高分作品。`, "green", result);
|
||||
await loadPlatformAccount(platform, account.id);
|
||||
renderAll();
|
||||
focusDiscoveryTopVideoInsights();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9854,7 +9954,7 @@ function openSimilaritySearchAction() {
|
||||
appState.lastSimilaritySearch = detail;
|
||||
rememberAction("相似账号已生成", `已生成 ${formatNumber(safeArray(detail.candidates).length)} 个候选账号。`, "green", detail);
|
||||
await loadPlatformAccount(platform, account.id);
|
||||
renderAll();
|
||||
focusDiscoveryRelations();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -9904,7 +10004,7 @@ function openBenchmarkLinkAction(defaults = {}) {
|
||||
};
|
||||
}
|
||||
rememberAction("对标关系已保存", "当前账号的对标关系已更新。", "green");
|
||||
renderAll();
|
||||
focusDiscoveryRelations();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -10295,6 +10395,11 @@ function openRecoverJobAction(jobId) {
|
||||
const result = await recoverJobAction(job.id, { mode: "single", job, user_feedback: values.note?.trim() || "" });
|
||||
rememberAction("任务已恢复", `${job.title || job.id} 已重新发起,下一步可以去生产中心继续跟进。`, "green", result);
|
||||
await bootstrap();
|
||||
if (result?.created?.id) {
|
||||
openJobDetailAction(result.created.id);
|
||||
} else {
|
||||
focusProductionDetailTab("recovery");
|
||||
}
|
||||
} finally {
|
||||
setBusy(false, "");
|
||||
}
|
||||
@@ -10380,6 +10485,11 @@ function openBatchRecoverJobsAction() {
|
||||
});
|
||||
}
|
||||
await bootstrap();
|
||||
if (successes[0]?.result?.created?.id) {
|
||||
openJobDetailAction(successes[0].result.created.id);
|
||||
} else {
|
||||
focusProductionDetailTab("recovery");
|
||||
}
|
||||
} finally {
|
||||
setBusy(false, "");
|
||||
}
|
||||
@@ -10420,7 +10530,7 @@ function openGenerateCopyAction(defaults = {}) {
|
||||
usedDocuments: safeArray(result.used_documents).slice(0, 3)
|
||||
};
|
||||
rememberAction("文案生成完成", `已用 Agent「${assistant.name}」生成一版文案。`, "green", result);
|
||||
renderAll();
|
||||
focusRecentGeneratedCopy();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -10466,6 +10576,9 @@ function openCreateAiVideoAction(defaults = {}) {
|
||||
});
|
||||
rememberAction("AI 视频任务已创建", `已创建任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -10505,6 +10618,9 @@ function openCreateRealCutAction(defaults = {}) {
|
||||
});
|
||||
rememberAction("实拍剪辑任务已创建", `已创建任务 ${job.title || job.id}。`, "blue", job);
|
||||
await bootstrap();
|
||||
if (job?.id) {
|
||||
openJobDetailAction(job.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -10680,19 +10796,23 @@ async function deleteLiveRecorderSourceAction(sourceId) {
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(`确认删除「${source.title || source.source_url || "录制源"}」吗?删除后需要重新导入。`)) {
|
||||
return;
|
||||
}
|
||||
setBusy(true, "正在删除录制源...");
|
||||
try {
|
||||
await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
rememberAction("录制源已删除", `${source.title || source.source_url || "录制源"} 已从租户视图中移除。`, "green");
|
||||
await bootstrap();
|
||||
} finally {
|
||||
setBusy(false, "");
|
||||
}
|
||||
openActionModal({
|
||||
title: "确认删除录制源",
|
||||
description: `确认删除「${source.title || source.source_url || "录制源"}」吗?删除后需要重新导入。`,
|
||||
submitLabel: "确认删除",
|
||||
onSubmit: async () => {
|
||||
setBusy(true, "正在删除录制源...");
|
||||
try {
|
||||
await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
rememberAction("录制源已删除", `${source.title || source.source_url || "录制源"} 已从租户视图中移除。`, "green");
|
||||
await bootstrap();
|
||||
} finally {
|
||||
setBusy(false, "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function openLiveRecorderFileAction(fileId) {
|
||||
@@ -10796,6 +10916,7 @@ function openReviewAction(defaults = {}) {
|
||||
});
|
||||
rememberAction(existingReview ? "复盘已更新" : "复盘已创建", `已保存「${review.title}」并回写到项目复盘。`, "green", review);
|
||||
await bootstrap();
|
||||
focusReviewWorkspace(review.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -772,7 +772,11 @@ test("platform agent profiles expose history, rollback, and execution version co
|
||||
assert.match(panel, /open-platform-agent-profile-history/);
|
||||
assert.match(detail, /open-platform-agent-profile-history/);
|
||||
assert.match(panel, /platform_agent_profile_version_no/);
|
||||
assert.match(panel, /recent_execution\.recommended_action\?\.action/);
|
||||
assert.match(panel, /recent_execution\.workstream_label/);
|
||||
assert.match(detail, /platform_agent_profile_version_no/);
|
||||
assert.match(detail, /recent_execution\.recommended_action\?\.action/);
|
||||
assert.match(detail, /recent_execution\.workstream_label/);
|
||||
assert.match(execution, /platformAgentProfile\.version_no/);
|
||||
assert.match(actions, /name === "open-platform-agent-profile-history"/);
|
||||
assert.match(actions, /openPlatformAgentProfileHistoryAction/);
|
||||
@@ -840,6 +844,68 @@ test("tracked-account refresh opens the created sync task when the backend retur
|
||||
assert.match(refresh, /openJobDetailAction\(payload\.sync_job_id\)/);
|
||||
});
|
||||
|
||||
test("job-creating workbench actions jump straight into the created job detail", () => {
|
||||
const importHomepage = extractBetween(APP, "function openImportHomepageAction()", "function openImportSelectedAccountAction()");
|
||||
const importSelected = extractBetween(APP, "function openImportSelectedAccountAction()", "function openTrackSelectedAccountAction()");
|
||||
const importVideo = extractBetween(APP, "function openImportVideoLinkAction()", "function openImportTextAction()");
|
||||
const importText = extractBetween(APP, "function openImportTextAction()", "function openUploadVideoAction()");
|
||||
const uploadVideo = extractBetween(APP, "function openUploadVideoAction()", "function openOneLinerProfileAction()");
|
||||
const aiVideo = extractBetween(APP, "function openCreateAiVideoAction(defaults = {})", "function openCreateRealCutAction(defaults = {})");
|
||||
const realCut = extractBetween(APP, "function openCreateRealCutAction(defaults = {})", "function openLiveRecorderAction()");
|
||||
assert.match(importHomepage, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(importSelected, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(importVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(importText, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(uploadVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(aiVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
assert.match(realCut, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/);
|
||||
});
|
||||
|
||||
test("discovery analysis actions focus the most relevant detail tab after success", () => {
|
||||
const analyzeAccount = extractBetween(APP, "function openAnalyzeSelectedAccountAction()", "function openAnalyzeTopVideosAction()");
|
||||
const analyzeTopVideos = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()");
|
||||
const similaritySearch = extractBetween(APP, "function openSimilaritySearchAction()", "function openBenchmarkLinkAction(defaults = {})");
|
||||
assert.match(APP, /function focusDiscoveryDetailTab\(tabValue\)/);
|
||||
assert.match(APP, /function focusDiscoveryInsights\(\)/);
|
||||
assert.match(APP, /function focusDiscoveryTopVideoInsights\(\)/);
|
||||
assert.match(APP, /function focusDiscoveryRelations\(\)/);
|
||||
assert.match(analyzeAccount, /focusDiscoveryInsights\(\)/);
|
||||
assert.match(analyzeTopVideos, /focusDiscoveryTopVideoInsights\(\)/);
|
||||
assert.match(similaritySearch, /focusDiscoveryRelations\(\)/);
|
||||
});
|
||||
|
||||
test("tracking and benchmark actions land on the most relevant workbench area after success", () => {
|
||||
const trackSelected = extractBetween(APP, "function openTrackSelectedAccountAction()", "function openImportVideoLinkAction()");
|
||||
const saveCandidate = extractBetween(APP, "async function saveCandidateAsBenchmark(candidateIndex, relationType = \"benchmark\")", "function screenShell(title, subtitle, actionsHtml, bodyHtml)");
|
||||
const openBenchmark = extractBetween(APP, "function openBenchmarkLinkAction(defaults = {})", "async function scanAdminOpsAction()");
|
||||
assert.match(APP, /function focusTrackingWorkspace\(\)/);
|
||||
assert.match(APP, /function focusDiscoveryRelations\(\)/);
|
||||
assert.match(trackSelected, /focusTrackingWorkspace\(\)/);
|
||||
assert.match(saveCandidate, /focusDiscoveryRelations\(\)/);
|
||||
assert.match(openBenchmark, /focusDiscoveryRelations\(\)/);
|
||||
});
|
||||
|
||||
test("recovery and copy actions continue into the most useful result view", () => {
|
||||
const singleRecover = extractBetween(APP, "function openRecoverJobAction(jobId)", "function openBatchRecoverJobsAction()");
|
||||
const batchRecover = extractBetween(APP, "function openBatchRecoverJobsAction()", "function openGenerateCopyAction(defaults = {})");
|
||||
const generateCopy = extractBetween(APP, "function openGenerateCopyAction(defaults = {})", "function openCreateAiVideoAction(defaults = {})");
|
||||
assert.match(APP, /function focusProductionDetailTab\(tabValue\)/);
|
||||
assert.match(APP, /function focusRecentGeneratedCopy\(\)/);
|
||||
assert.match(singleRecover, /if \(result\?\.created\?\.id\) \{\s*openJobDetailAction\(result\.created\.id\);/);
|
||||
assert.match(singleRecover, /focusProductionDetailTab\("recovery"\)/);
|
||||
assert.match(batchRecover, /if \(successes\[0\]\?\.result\?\.created\?\.id\) \{\s*openJobDetailAction\(successes\[0\]\.result\.created\.id\);/);
|
||||
assert.match(batchRecover, /focusProductionDetailTab\("recovery"\)/);
|
||||
assert.match(generateCopy, /focusRecentGeneratedCopy\(\)/);
|
||||
});
|
||||
|
||||
test("review actions return to the review workspace with the saved review in focus", () => {
|
||||
const reviewAction = extractBetween(APP, "function openReviewAction(defaults = {})", "document.addEventListener(\"click\", async (event) => {");
|
||||
assert.match(APP, /function focusReviewWorkspace\(reviewId = ""\)/);
|
||||
assert.match(reviewAction, /focusReviewWorkspace\(review\.id\)/);
|
||||
assert.match(APP, /id="review-workspace-anchor"/);
|
||||
assert.match(APP, /data-review-id="\$\{escapeHtml\(review\.id\)\}"/);
|
||||
});
|
||||
|
||||
test("main agent execution cards can jump to oneliner and platform profile history", () => {
|
||||
const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderAutoConnectingScreen(screenTitle, nextStepText)");
|
||||
assert.match(messages, /data-action="open-oneliner-profile-history"/);
|
||||
@@ -907,10 +973,19 @@ test("oneliner panel auto-polls active runs while the floating panel stays open"
|
||||
test("workbench interaction flow avoids browser alerts and keeps failures inside the product shell", () => {
|
||||
const clicks = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
||||
assert.doesNotMatch(APP, /alert\(/);
|
||||
assert.doesNotMatch(APP, /confirm\(/);
|
||||
assert.match(clicks, /name === "show-disabled-reason"[\s\S]*rememberAction\("动作已拦截"/);
|
||||
assert.match(clicks, /name === "auth-refresh" \|\| name === "refresh-data"[\s\S]*presentActionFailure\(error, "刷新数据失败"\)/);
|
||||
});
|
||||
|
||||
test("live recorder delete uses an in-app confirmation sheet instead of browser confirm", () => {
|
||||
const removeSource = extractBetween(APP, "async function deleteLiveRecorderSourceAction(sourceId)", "async function openLiveRecorderFileAction(fileId)");
|
||||
assert.match(removeSource, /openActionModal\(\{/);
|
||||
assert.match(removeSource, /title:\s*"确认删除录制源"/);
|
||||
assert.match(removeSource, /submitLabel:\s*"确认删除"/);
|
||||
assert.doesNotMatch(removeSource, /window\.confirm/);
|
||||
});
|
||||
|
||||
test("live-first workbench flows no longer advertise stale missing-capability placeholders for active routes", () => {
|
||||
assert.doesNotMatch(APP, /当前实例还没有开放 OneLiner 会话接口/);
|
||||
assert.doesNotMatch(APP, /当前实例还没有开放主 Agent 运行层/);
|
||||
|
||||
Reference in New Issue
Block a user