diff --git a/CHANGELOG.md b/CHANGELOG.md
index f75c572..5997841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,12 @@
- `生成文案 / 创建 AI 视频 / 创建实拍剪辑 / 写复盘` 这四张高频创作表单,现在也会在顶部展示当前项目和默认 Agent 的上下文摘要。
- 这样高频创作动作不管是 direct-execute 还是必须补信息的表单,都已经统一到一套“先看当前上下文,再继续填写”的工作流体验里。
+### 高优先级创作表单补齐来源任务摘要
+
+- 当 `生成文案 / 创建 AI 视频 / 创建实拍剪辑 / 写复盘` 是围绕某条已完成任务打开时,表单顶部现在会直接展示这条来源任务的摘要。
+- `生成文案` 和 `写复盘` 也会优先继承来源任务的平台,避免用户再手工改一次平台。
+- 这样从任务详情或主 Agent 结果卡继续往下做时,表单第一眼就知道自己承接的是哪条任务。
+
### 主 Agent 抖音相似搜索与对标关系 live 修复
- 修复 `search-similar-accounts` / `save-benchmark-link` 在抖音 live 数据上错误按 `project_id` 查询账号导致的 500。
diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index 44602c2..e7c4558 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -372,6 +372,18 @@ function renderIntakeActionContextHtml(projectId = "", assistantId = "") {
`;
}
+function renderSourceJobContextHtml(job) {
+ if (!job) return "";
+ return `
+
+
+
当前承接任务
+
${escapeHtml(job.title || "未命名任务")} · 平台:${escapeHtml(platformLabel(job.platform || "douyin"))} · 状态:${escapeHtml(job.status || "unknown")}
+
+
+ `;
+}
+
function formatDateTime(value) {
if (!value) return "-";
const date = new Date(value);
@@ -11322,8 +11334,9 @@ function openGenerateCopyAction(defaults = {}) {
submitLabel: "开始生成",
fields: [
{ name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", assistant.id) },
+ ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []),
{ name: "brief", label: "创作需求", type: "textarea", rows: 5, value: defaults.brief || getJobSeedBrief(sourceJob), placeholder: "例如:给创业者写一条 60 字内的短视频开场文案" },
- { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(defaults.platform || "douyin"), options: getPlatformOptions() },
+ { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin"), options: getPlatformOptions() },
{ name: "audience", label: "受众", value: "创业者" },
{ name: "extraRequirements", label: "额外要求", placeholder: "例如:强结论开头,结尾带 CTA" }
],
@@ -11375,6 +11388,7 @@ function openCreateAiVideoAction(defaults = {}) {
submitLabel: "开始生产",
fields: [
{ name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) },
+ ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []),
{ name: "title", label: "任务标题", value: defaults.title || (sourceJob ? `${sourceJob.title} · AI 视频` : ""), placeholder: "例如:创业口播 AI 视频测试" },
{ name: "brief", label: "视频 brief", type: "textarea", rows: 5, value: defaults.brief || getJobSeedBrief(sourceJob), placeholder: "写明主题、风格、镜头和目标受众" },
{ name: "sourceJobId", label: "关联源任务", type: "select", value: defaults.sourceJobId || sourceJob?.id || "", options: [{ value: "", label: "不关联" }, ...getCompletedJobOptions()] },
@@ -11490,6 +11504,7 @@ function openCreateRealCutAction(defaults = {}) {
submitLabel: "开始剪辑",
fields: [
{ name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, assistant?.id || "") },
+ ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []),
{ name: "title", label: "任务标题", value: defaults.title || (sourceJob ? `${sourceJob.title} · 实拍剪辑` : ""), placeholder: "例如:创业素材粗剪" },
{ name: "sourceJobId", label: "源任务", type: "select", value: defaults.sourceJobId || sourceJob?.id || getCompletedJobOptions()[0]?.value || "", options: getCompletedJobOptions() },
{ name: "targetDurationSec", label: "目标时长(秒)", type: "number", value: defaults.targetDurationSec || 60, min: 10, max: 300 },
@@ -11754,10 +11769,11 @@ function openReviewAction(defaults = {}) {
submitLabel: existingReview ? "保存复盘" : "创建复盘",
fields: [
{ name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) },
+ ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []),
{ name: "title", label: "标题", value: existingReview?.title || defaults.title || sourceJob?.title || "", placeholder: "例如:创业口播 3 月 22 日复盘" },
{ name: "sourceJobId", label: "关联任务", type: "select", value: existingReview?.source_job_id || defaults.sourceJobId || sourceJob?.id || "", options: [{ value: "", label: "不关联任务" }, ...getCompletedJobOptions()] },
{ name: "assistantId", label: "负责 Agent", type: "select", value: existingReview?.assistant_id || defaultAssistantId, options: [{ value: "", label: "先不绑定" }, ...assistants] },
- { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(existingReview?.platform || defaults.platform || "douyin"), options: getPlatformOptions() },
+ { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(existingReview?.platform || defaults.platform || sourceJob?.platform || "douyin"), options: getPlatformOptions() },
{ name: "contentType", label: "内容类型", type: "select", value: existingReview?.content_type || "video", options: [
{ value: "video", label: "视频" },
{ value: "image_text", label: "图文" },
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
index 4addbe4..81f4bb4 100644
--- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs
+++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
@@ -1147,6 +1147,7 @@ test("input-heavy intake sheets surface current context and smarter defaults", (
const createRealCut = extractBetween(APP, "function openCreateRealCutAction(defaults = {})", "function openLiveRecorderAction()");
const reviewAction = extractBetween(APP, "function openReviewAction(defaults = {})", "document.addEventListener(\"click\", async (event) => {");
assert.match(APP, /function renderIntakeActionContextHtml\(/);
+ assert.match(APP, /function renderSourceJobContextHtml\(job\)/);
assert.match(importHomepage, /label: "当前上下文", type: "html"/);
assert.match(importHomepage, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/);
assert.match(importHomepage, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/);
@@ -1171,14 +1172,20 @@ test("input-heavy intake sheets surface current context and smarter defaults", (
assert.match(uploadVideo, /placeholder: "默认使用文件名,可选补一个更容易识别的标题"/);
assert.match(generateCopy, /label: "当前上下文", type: "html"/);
assert.match(generateCopy, /renderIntakeActionContextHtml\(project\?\.id \|\| "", assistant\.id\)/);
+ assert.match(generateCopy, /sourceJob \? \[\{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml\(sourceJob\) \}\] : \[\]/);
+ assert.match(generateCopy, /normalizePlatformValue\(defaults\.platform \|\| sourceJob\?\.platform \|\| "douyin"\)/);
assert.match(createAiVideo, /label: "当前上下文", type: "html"/);
assert.match(createAiVideo, /const defaultAssistantId = assistant\?\.id \|\| ""/);
assert.match(createAiVideo, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/);
+ assert.match(createAiVideo, /sourceJob \? \[\{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml\(sourceJob\) \}\] : \[\]/);
assert.match(createRealCut, /label: "当前上下文", type: "html"/);
assert.match(createRealCut, /renderIntakeActionContextHtml\(project\.id, assistant\?\.id \|\| ""\)/);
+ assert.match(createRealCut, /sourceJob \? \[\{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml\(sourceJob\) \}\] : \[\]/);
assert.match(reviewAction, /label: "当前上下文", type: "html"/);
assert.match(reviewAction, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/);
assert.match(reviewAction, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/);
+ assert.match(reviewAction, /sourceJob \? \[\{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml\(sourceJob\) \}\] : \[\]/);
+ assert.match(reviewAction, /normalizePlatformValue\(existingReview\?\.platform \|\| defaults\.platform \|\| sourceJob\?\.platform \|\| "douyin"\)/);
});
test("discovery analysis actions focus the most relevant detail tab after success", () => {