feat: add direct oneliner follow-up actions
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-04-04 08:22:14 +08:00
parent 294846e603
commit ae4d4cd2ad
6 changed files with 372 additions and 14 deletions

View File

@@ -1996,6 +1996,9 @@ function renderOneLinerExecutionPayloadHtml(payload) {
if (!payload || typeof payload !== "object") {
return `<div class="task-item compact"><h4>没有返回执行结果</h4><p>当前执行器没有附带额外数据。</p></div>`;
}
const recommendedAction = payload.recommended_action && typeof payload.recommended_action === "object"
? payload.recommended_action
: null;
if (payload.result_kind === "main_agent_plan") {
const landingRunId = String(payload.run_id || "").trim();
const landingScreen = String(payload.recommended_action?.screen || "").trim();
@@ -2106,6 +2109,11 @@ function renderOneLinerExecutionPayloadHtml(payload) {
if (payload.job) {
const job = payload.job || {};
const sourceJob = payload.source_job || {};
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "production",
title: job.title || "任务已创建",
summary: recommendedAction?.summary || payload.brief || sourceJob.title || ""
});
return `
<div class="task-item compact">
<h4>${escapeHtml(job.title || "任务已创建")}</h4>
@@ -2113,7 +2121,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
<div class="task-meta">
<span class="tag blue">${escapeHtml(job.line_type || job.source_type || "analysis")}</span>
<span class="tag ${job.status === "completed" ? "green" : "orange"}">${escapeHtml(job.status || "queued")}</span>
${job.id ? `<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(job.id)}">看任务详情</span>` : ""}
${recommendedAction?.action ? actionTag(recommendedAction.label || "看任务详情", recommendedAction.action, recommendedAttrs) : job.id ? `<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(job.id)}">看任务详情</span>` : ""}
<span class="tag clickable-tag" data-action="goto-production">去生产中心</span>
</div>
</div>
@@ -2123,6 +2131,11 @@ function renderOneLinerExecutionPayloadHtml(payload) {
const saved = payload.saved || {};
const item = saved.item || {};
const started = payload.started || {};
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "production",
title: item.binding_title || payload.source_url || "录制源已保存",
summary: recommendedAction?.summary || item.source_url || payload.source_url || ""
});
return `
<div class="task-item compact">
<h4>${escapeHtml(item.binding_title || payload.source_url || "录制源已保存")}</h4>
@@ -2131,12 +2144,17 @@ function renderOneLinerExecutionPayloadHtml(payload) {
<span class="tag blue">${escapeHtml(platformLabel(payload.platform || item.platform || "kuaishou"))}</span>
<span class="tag">${escapeHtml(item.quality || "原画")}</span>
<span class="tag ${started && started.ok === false ? "orange" : "green"}">${escapeHtml(started && started.ok === false ? "启动待重试" : "已同步")}</span>
<span class="tag clickable-tag" data-action="open-live-recorder">打开录制控制</span>
${recommendedAction?.action ? actionTag(recommendedAction.label || "打开录制控制", recommendedAction.action, recommendedAttrs) : `<span class="tag clickable-tag" data-action="open-live-recorder">打开录制控制</span>`}
</div>
</div>
`;
}
if (payload.analyzed_count !== undefined && safeArray(payload.items).length) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "discovery",
title: "OneLiner 已分析高分作品",
summary: recommendedAction?.summary || ""
});
return `
<div class="detail-grid">
<div class="mini-card"><small>平台</small><strong>${escapeHtml(platformLabel(payload.platform || payload.account?.platform || "douyin"))}</strong></div>
@@ -2146,19 +2164,29 @@ function renderOneLinerExecutionPayloadHtml(payload) {
</div>
<div class="list" style="margin-top:12px;">
${safeArray(payload.items).slice(0, 4).map((item) => `
<div class="task-item compact">
<h4>${escapeHtml(item.video_title || "高分作品")}</h4>
<p>${escapeHtml(item.summary_text || "已完成拆解。")}</p>
<div class="task-meta">
<span class="tag blue">得分 ${escapeHtml(formatNumber(item.performance_score || 0))}</span>
${item.latest_job_id ? `<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(item.latest_job_id)}">看原分析</span>` : ""}
<div class="task-item compact">
<h4>${escapeHtml(item.video_title || "高分作品")}</h4>
<p>${escapeHtml(item.summary_text || "已完成拆解。")}</p>
<div class="task-meta">
<span class="tag blue">得分 ${escapeHtml(formatNumber(item.performance_score || 0))}</span>
${item.latest_job_id ? `<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(item.latest_job_id)}">看原分析</span>` : ""}
</div>
</div>
</div>
`).join("")}
`).join("")}
</div>
${recommendedAction?.action ? `
<div class="task-meta" style="margin-top:12px;">
${actionTag(recommendedAction.label || "回到找对标", recommendedAction.action, recommendedAttrs)}
</div>
` : ""}
`;
}
if (payload.route_checks) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: recommendedAction?.screen || "playbook",
title: payload.platform_label || payload.platform || "平台自检",
summary: recommendedAction?.summary || ""
});
return `
<div class="detail-grid">
<div class="mini-card"><small>平台</small><strong>${escapeHtml(payload.platform_label || payload.platform || "-")}</strong></div>
@@ -2191,9 +2219,19 @@ function renderOneLinerExecutionPayloadHtml(payload) {
</div>
</div>
</div>
${recommendedAction?.action ? `
<div class="task-meta" style="margin-top:12px;">
${actionTag(recommendedAction.label || "查看平台 Agent", recommendedAction.action, recommendedAttrs)}
</div>
` : ""}
`;
}
if (payload.strategy && payload.tenant_usage) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "automation",
title: "当前存储状态",
summary: recommendedAction?.summary || ""
});
return `
<div class="detail-grid">
<div class="mini-card"><small>jobs</small><strong>${escapeHtml(payload.tenant_usage?.project_jobs?.human_size || "0B")}</strong></div>
@@ -2201,17 +2239,37 @@ function renderOneLinerExecutionPayloadHtml(payload) {
<div class="mini-card"><small>模型目录</small><strong>${escapeHtml(payload.strategy?.models?.mode || "-")}</strong></div>
<div class="mini-card"><small>录像</small><strong>${escapeHtml(payload.strategy?.live_recorder?.mode || "-")}</strong></div>
</div>
${recommendedAction?.action ? `
<div class="task-meta" style="margin-top:12px;">
${actionTag(recommendedAction.label || "去自动流程", recommendedAction.action, recommendedAttrs)}
</div>
` : ""}
`;
}
if (payload.items || payload.files) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "production",
title: "直播录制状态",
summary: recommendedAction?.summary || ""
});
return `
<div class="detail-grid">
<div class="mini-card"><small>录制源</small><strong>${escapeHtml(formatNumber(safeArray(payload.items).length))}</strong></div>
<div class="mini-card"><small>最近文件</small><strong>${escapeHtml(formatNumber(safeArray(payload.files).length))}</strong></div>
</div>
${recommendedAction?.action ? `
<div class="task-meta" style="margin-top:12px;">
${actionTag(recommendedAction.label || "打开录制维护", recommendedAction.action, recommendedAttrs)}
</div>
` : ""}
`;
}
if (payload.content) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "playbook",
title: "生成文案",
summary: recommendedAction?.summary || ""
});
return `
<div class="task-item compact">
<h4>生成文案</h4>
@@ -2219,11 +2277,17 @@ function renderOneLinerExecutionPayloadHtml(payload) {
<div class="task-meta">
${payload.assistant_id ? `<span class="tag blue">${escapeHtml(payload.assistant_id)}</span>` : ""}
<span class="tag">${escapeHtml(formatNumber(safeArray(payload.used_documents).length))} 个参考素材</span>
${recommendedAction?.action ? actionTag(recommendedAction.label || "继续调文案", recommendedAction.action, recommendedAttrs) : ""}
</div>
</div>
`;
}
if (payload.verdict !== undefined || payload.next_actions !== undefined || payload.highlights !== undefined) {
const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, {
screen: "review",
title: payload.title || "复盘草稿",
summary: recommendedAction?.summary || ""
});
return `
<div class="task-item compact">
<h4>${escapeHtml(payload.title || "复盘草稿")}</h4>
@@ -2232,7 +2296,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
<span class="tag blue">${escapeHtml(platformLabel(payload.platform || "douyin"))}</span>
<span class="tag">${escapeHtml(payload.verdict || "待补充")}</span>
${payload.source_job_id ? `<span class="tag clickable-tag" data-action="open-job-detail" data-job-id="${escapeHtml(payload.source_job_id)}">看任务详情</span>` : ""}
${payload.id ? `<span class="tag clickable-tag" data-action="open-review-edit" data-review-id="${escapeHtml(payload.id)}">打开复盘</span>` : ""}
${recommendedAction?.action ? actionTag(recommendedAction.label || "打开复盘", recommendedAction.action, recommendedAttrs) : payload.id ? `<span class="tag clickable-tag" data-action="open-review-edit" data-review-id="${escapeHtml(payload.id)}">打开复盘</span>` : ""}
</div>
</div>
`;
@@ -7825,12 +7889,40 @@ function extractGeneratedCopy(payload) {
return brief(raw, 2400);
}
function buildRecommendedActionAttrs(recommendedAction, landing = {}) {
const action = recommendedAction && typeof recommendedAction === "object" ? recommendedAction : {};
const attrs = [];
const landingAttrs = buildMainAgentLandingAttrs({
runId: landing.runId || "",
screen: action.screen || landing.screen || "",
title: landing.title || "",
summary: action.summary || landing.summary || ""
});
if (landingAttrs) attrs.push(landingAttrs);
const attrMap = {
job_id: "data-job-id",
review_id: "data-review-id",
platform: "data-platform",
source_id: "data-source-id",
file_id: "data-file-id",
incident_id: "data-incident-id",
run_id: "data-run-id"
};
Object.entries(attrMap).forEach(([key, attr]) => {
const value = action[key];
const text = String(value ?? "").trim();
if (!text) return;
attrs.push(`${attr}="${escapeHtml(text)}"`);
});
return attrs.join(" ");
}
function renderLastActionCard() {
if (!appState.lastAction) return "";
const payload = appState.lastAction.payload || {};
const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null;
const runId = payload?.id || payload?.run_id || "";
const landingAttrs = buildMainAgentLandingAttrs({
const actionAttrs = buildRecommendedActionAttrs(recommendedAction, {
runId,
screen: recommendedAction?.screen || "",
title: appState.lastAction.title || "",
@@ -7851,7 +7943,7 @@ function renderLastActionCard() {
${(runId || recommendedAction?.action) ? `
<div class="task-meta" style="margin-top:10px;">
${runId ? `<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(runId)}">查看结果</span>` : ""}
${recommendedAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(recommendedAction.action)}" ${landingAttrs}>${escapeHtml(recommendedAction.label || "回到对应页面")}</span>` : ""}
${recommendedAction?.action ? actionTag(recommendedAction.label || "回到对应页面", recommendedAction.action, actionAttrs) : ""}
</div>
` : ""}
</div>

View File

@@ -754,6 +754,25 @@ test("main agent result rendering offers a direct route back into the recommende
assert.match(lastAction, /recommended_action/);
});
test("direct oneliner execution results preserve structured follow-up attrs", () => {
const helpers = extractBetween(APP, "function extractGeneratedCopy(payload)", "function renderLastActionCard()");
const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)");
const lastAction = extractBetween(APP, "function renderLastActionCard()", "function getJobRecoveryCategory(job)");
assert.match(helpers, /function buildRecommendedActionAttrs\(recommendedAction, landing = \{\}\)/);
assert.match(helpers, /job_id: "data-job-id"/);
assert.match(helpers, /review_id: "data-review-id"/);
assert.match(helpers, /platform: "data-platform"/);
assert.match(helpers, /source_id: "data-source-id"/);
assert.match(execution, /const recommendedAction = payload\.recommended_action/);
assert.match(execution, /buildRecommendedActionAttrs\(recommendedAction/);
assert.match(execution, /actionTag\(recommendedAction\.label \|\| "看任务详情"/);
assert.match(execution, /actionTag\(recommendedAction\.label \|\| "打开复盘"/);
assert.match(execution, /actionTag\(recommendedAction\.label \|\| "打开录制控制"/);
assert.match(execution, /actionTag\(recommendedAction\.label \|\| "查看平台 Agent"/);
assert.match(lastAction, /buildRecommendedActionAttrs\(recommendedAction/);
assert.match(lastAction, /actionTag\(recommendedAction\.label \|\| "回到对应页面"/);
});
test("platform agent profiles expose history, rollback, and execution version context", () => {
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
const profileEditor = extractBetween(APP, "function openPlatformAgentProfileAction(platform)", "async function openPlatformAgentProfileHistoryAction(platform, preferredVersionId = \"\")");