diff --git a/CHANGELOG.md b/CHANGELOG.md index fc21b8e..dc8aac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -645,3 +645,4 @@ - 公网 collector 示例配置改为显式禁用 `local_model`,并把 `ASR` 桥接端口切到 `127.0.0.1:28088` - 新增 Windows `ASR HTTP` 服务资产,兼容 StoryForge 当前 `/transcribe` 协议,便于把 ASR 迁到 Windows 主机 `192.168.31.18` - Windows 端新增 `ASR` 启动脚本、云端桥接脚本与计划任务注册脚本,并放通 `8088` 入站,保证局域网和公网都可直连该 `ASR` 服务 +- 创作类表单的来源任务联动继续收口:`写复盘` 现在切换来源任务时,会同步推荐更合适的负责 Agent,并即时刷新顶部当前上下文摘要,避免标题、平台已经切过去了但负责人和上下文还停在旧任务上。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 1dddf9f..7afdddc 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -547,8 +547,12 @@ function bindCreativeSourceJobRecommendations(fields, options = {}) { const sourceJobSelect = fields.querySelector('[data-action-field="sourceJobId"]'); if (!(sourceJobSelect instanceof HTMLSelectElement)) return; const sourceContext = fields.querySelector('[data-action-field="sourceJobContext"] .sheet-html'); + const contextHtml = fields.querySelector('[data-action-field="context"] .sheet-html'); + const assistantSelect = fields.querySelector('[data-action-field="assistantId"]'); const platformSelect = fields.querySelector(`[data-action-field="${options.platformField || "platform"}"]`); const defaultPlatform = options.defaultPlatform || "douyin"; + const defaultAssistantId = String(options.defaultAssistantId || "").trim(); + const projectId = String(options.projectId || getSelectedProject()?.id || "").trim(); const managedFields = []; const resolveSourceJob = () => getCompletedJobById(sourceJobSelect.value) || options.sourceJob || null; const recommendedPlatform = (job) => normalizePlatformValue(job?.platform || getPreferredPlatform(), defaultPlatform); @@ -575,6 +579,18 @@ function bindCreativeSourceJobRecommendations(fields, options = {}) { if (platformSelect instanceof HTMLSelectElement) { registerManagedField(platformSelect, (job) => recommendedPlatform(job), "change"); } + if (assistantSelect instanceof HTMLSelectElement) { + registerManagedField(assistantSelect, (job) => { + const preferredAssistantId = String(job?.assistant_id || defaultAssistantId).trim(); + if (preferredAssistantId && safeArray(Array.from(assistantSelect.options)).some((item) => item.value === preferredAssistantId)) { + return preferredAssistantId; + } + if (defaultAssistantId && safeArray(Array.from(assistantSelect.options)).some((item) => item.value === defaultAssistantId)) { + return defaultAssistantId; + } + return assistantSelect.value || ""; + }, "change"); + } const audienceInput = fields.querySelector('[data-action-field="audience"]'); if (audienceInput instanceof HTMLInputElement) { registerManagedField(audienceInput, (job) => { @@ -619,9 +635,14 @@ function bindCreativeSourceJobRecommendations(fields, options = {}) { if (field.dataset.recommendationMode === "manual") return; applyManagedValue(field, compute(sourceJob)); }); + if (contextHtml instanceof HTMLElement) { + const assistantId = assistantSelect instanceof HTMLSelectElement ? assistantSelect.value : defaultAssistantId; + contextHtml.innerHTML = renderIntakeActionContextHtml(projectId, assistantId); + } }; sourceJobSelect.addEventListener("change", sync); platformSelect?.addEventListener("change", sync); + assistantSelect?.addEventListener("change", sync); sync(); } @@ -12333,7 +12354,13 @@ function openReviewAction(defaults = {}) { { name: "notes", label: "备注", type: "textarea", rows: 4, value: existingReview?.notes || "", placeholder: "补充团队讨论、平台环境、发布时间段等信息" } ], onOpen: ({ fields }) => { - bindCreativeSourceJobRecommendations(fields, { sourceJob, defaultPlatform: normalizePlatformValue(existingReview?.platform || defaults.platform || sourceJob?.platform || "douyin"), titleSuffix: "复盘" }); + bindCreativeSourceJobRecommendations(fields, { + sourceJob, + defaultPlatform: normalizePlatformValue(existingReview?.platform || defaults.platform || sourceJob?.platform || "douyin"), + titleSuffix: "复盘", + projectId: project.id, + defaultAssistantId: existingReview?.assistant_id || defaultAssistantId + }); }, onSubmit: async (values) => { if (!values.title?.trim()) throw new Error("请填写复盘标题"); diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 5ced9b9..e399c12 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -1198,6 +1198,10 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(APP, /function bindManualIntakeTitleRecommendation\(fields, kind, options = \{\}\)/); assert.match(APP, /function bindActionContextRecommendation\(fields, options = \{\}\)/); assert.match(APP, /function bindCreativeSourceJobRecommendations\(fields, options = \{\}\)/); + assert.match(APP, /const contextHtml = fields\.querySelector\('\[data-action-field="context"] \.sheet-html'\);/); + assert.match(APP, /const assistantSelect = fields\.querySelector\('\[data-action-field="assistantId"]'\);/); + assert.match(APP, /registerManagedField\(assistantSelect, \(job\) => \{/); + assert.match(APP, /contextHtml\.innerHTML = renderIntakeActionContextHtml\(projectId, assistantId\);/); assert.match(APP, /function bindLiveRecorderSheetRecommendations\(fields, options = \{\}\)/); assert.match(importHomepage, /label: "当前上下文", type: "html"/); assert.match(importHomepage, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); @@ -1292,6 +1296,8 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(reviewAction, /value: existingReview\?\.title \|\| defaults\.title \|\| recommendDerivativeJobTitle\(sourceJob, "复盘", ""\)/); assert.match(reviewAction, /normalizePlatformValue\(existingReview\?\.platform \|\| defaults\.platform \|\| sourceJob\?\.platform \|\| "douyin"\)/); assert.match(reviewAction, /onOpen:\s*\(\{ fields \}\) => \{\s*bindCreativeSourceJobRecommendations\(fields, \{/); + assert.match(reviewAction, /projectId:\s*project\.id/); + assert.match(reviewAction, /defaultAssistantId:\s*existingReview\?\.assistant_id \|\| defaultAssistantId/); }); test("discovery analysis actions focus the most relevant detail tab after success", () => {