feat: direct-execute copy generation from job details
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-05 07:43:41 +08:00
parent ad05a4dfbc
commit 10b3c0cf42
3 changed files with 77 additions and 7 deletions

View File

@@ -418,6 +418,12 @@
- 任务恢复链里的失败提示统一成“先补信息 / 需人工处理”,不再弹出“暂不支持自动恢复”这类生硬口径。
- `额度` 页把“后续再接真实套餐”改成当前就能落地的套餐表达,明确按预算、动作池和项目阶段去配置套餐。
### 文案生成也并进 direct-execute
- `任务详情 -> 用摘要写文案` 和旧的 `job-to-generate-copy` 现在都会直接走 `OneLiner` 执行器,不再先弹回传统文案表单。
- 这条链执行成功后,会把本轮生成结果直接回写到 `Agent -> 最近生成`,并自动回到对应锚点,用户不用再自己寻找结果。
- 前端回归新增了这条 direct-execute 与结果回写断言,避免后续又退回“执行了但最近生成不更新”的半成品状态。
### 套餐档位与恢复引导继续补齐
- `额度` 页和租户额度编辑弹层新增了 `套餐档位``预算预警阈值`,现在能直接按试用、增长、规模、自定义四档去配置项目套餐。

View File

@@ -2472,11 +2472,16 @@ async function runDirectWorkbenchAction(executorKey, options = {}) {
payload: options.payload || {},
showResultModal: false
});
await followRecommendedActionResult(result, {
platform,
refreshWorkbench: options.refreshWorkbench !== false,
discoveryFocus: options.discoveryFocus || ""
});
if (typeof options.afterResult === "function") {
await options.afterResult(result);
}
if (options.followRecommendedAction !== false) {
await followRecommendedActionResult(result, {
platform,
refreshWorkbench: options.refreshWorkbench !== false,
discoveryFocus: options.discoveryFocus || ""
});
}
return result;
} catch (error) {
presentActionFailure(error, options.errorTitle || "执行失败");
@@ -8127,6 +8132,19 @@ function extractGeneratedCopy(payload) {
return brief(raw, 2400);
}
function cacheGeneratedCopyResult(payload, options = {}) {
const executionPayload = payload?.payload || payload || {};
const assistantId = executionPayload.assistant_id || options.assistantId || getSelectedAssistant()?.id || "";
const assistant = safeArray(appState.assistants).find((item) => item.id === assistantId) || getSelectedAssistant() || null;
appState.lastGeneratedCopy = {
assistantId: assistantId || "",
assistantName: assistant?.name || options.assistantName || "当前 Agent",
prompt: String(options.prompt || options.brief || executionPayload.brief || "").trim(),
content: extractGeneratedCopy(executionPayload),
usedDocuments: safeArray(executionPayload.used_documents).slice(0, 3)
};
}
function buildRecommendedActionAttrs(recommendedAction, landing = {}) {
const action = recommendedAction && typeof recommendedAction === "object" ? recommendedAction : {};
const attrs = [];
@@ -11088,7 +11106,7 @@ function openJobDetailAction(jobId) {
) : ""}
${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "继续做 AI 视频") : ""}
${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "继续做实拍剪辑") : ""}
${actionTag("用摘要写文案", "job-to-generate-copy", `data-job-id="${escapeHtml(job.id)}"`)}
${actionTag("用摘要写文案", "direct-generate-copy", `data-job-id="${escapeHtml(job.id)}"`)}
</div>
`
},
@@ -12354,6 +12372,26 @@ document.addEventListener("click", async (event) => {
openGenerateCopyAction();
return;
}
if (name === "direct-generate-copy") {
const jobId = action.dataset.jobId || "";
const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null;
const brief = detail ? `基于任务「${detail.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(detail)}` : "";
closeActionModal();
await runDirectWorkbenchAction("generate-copy", {
busyLabel: "正在生成文案...",
errorTitle: "生成文案失败",
followRecommendedAction: false,
payload: {
source_job_id: jobId,
brief
},
afterResult: async (result) => {
cacheGeneratedCopyResult(result, { brief });
focusRecentGeneratedCopy();
}
});
return;
}
if (name === "open-ai-video") {
openCreateAiVideoAction();
return;
@@ -12521,7 +12559,20 @@ document.addEventListener("click", async (event) => {
const jobId = action.dataset.jobId || "";
const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null;
closeActionModal();
openGenerateCopyAction({ sourceJob: detail, brief: detail ? `基于任务「${detail.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(detail)}` : "" });
const brief = detail ? `基于任务「${detail.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(detail)}` : "";
await runDirectWorkbenchAction("generate-copy", {
busyLabel: "正在生成文案...",
errorTitle: "生成文案失败",
followRecommendedAction: false,
payload: {
source_job_id: jobId,
brief
},
afterResult: async (result) => {
cacheGeneratedCopyResult(result, { brief });
focusRecentGeneratedCopy();
}
});
return;
}
if (name === "create-project") {

View File

@@ -381,6 +381,19 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
assert.ok(!clickActions.includes('else if (backendSupports("/v2/oneliner/sessions"))'));
});
test("job detail and follow-up flows use direct generate-copy execution and persist recent copy results", () => {
const jobDetail = extractBetween(APP, "function openJobDetailAction(jobId) {", "function openRecoverJobAction(");
const clickActions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(APP, /function cacheGeneratedCopyResult\(payload, options = \{\}\)/);
assert.match(jobDetail, /actionTag\("用摘要写文案", "direct-generate-copy"/);
assert.match(clickActions, /name === "direct-generate-copy"[\s\S]*runDirectWorkbenchAction\("generate-copy"/);
assert.match(clickActions, /name === "direct-generate-copy"[\s\S]*followRecommendedAction:\s*false/);
assert.match(clickActions, /name === "direct-generate-copy"[\s\S]*cacheGeneratedCopyResult\(result,\s*\{\s*brief\s*\}\)/);
assert.match(clickActions, /name === "direct-generate-copy"[\s\S]*focusRecentGeneratedCopy\(\)/);
assert.match(clickActions, /name === "job-to-generate-copy"[\s\S]*runDirectWorkbenchAction\("generate-copy"/);
});
test("discovery and production screens expose compact mobile flow summaries", () => {
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");