From 3b4fdbc70a8a39871b41e9e93f107128d020b000 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 6 Apr 2026 16:43:39 +0800 Subject: [PATCH] feat: refresh intake title hints on form changes --- CHANGELOG.md | 2 +- web/storyforge-web-v4/assets/app.js | 36 +++++++++++++++++++ .../tests/workbench-pages.test.mjs | 5 +++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c190b..f3c2d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - 这样主 Agent 结果卡、动作注册表和工作台高频按钮现在共用同一套直执行链,后续回跳与结果落点也更一致。 - `analyze_account / analyze_top_videos` 现在也统一切到 `direct-*`,并且在缺少当前选中账号时会自动回退到旧表单,不会把用户卡死在“缺少上下文”的提示上。 - `direct-search-similar / direct-save-benchmark-link` 现在也会在缺少当前账号或相似候选时自动回退到旧表单,避免“查相似 / 存对标”入口因为上下文不完整直接报错。 -- `导入主页 / 导入作品链接 / 导入文本 / 上传视频` 这批输入型表单现在会按当前项目和当前平台自动推荐标题占位,不再一直停留在通用示例文案,打开就更贴近当前工作流。 +- `导入主页 / 导入作品链接 / 导入文本 / 上传视频` 这批输入型表单现在会按当前项目和当前平台自动推荐标题占位,并且在表单里切换项目或平台时会同步更新,不再一直停留在通用示例文案。 ### 依赖健康卡开始显示服务部署位置 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 836471a..2f3e53e 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -479,6 +479,30 @@ function recommendManualIntakeTitle(project, platform, kind) { return `${platformName}${suffix}`; } +function bindManualIntakeTitleRecommendation(fields, kind, options = {}) { + const titleInput = fields.querySelector('[data-action-field="title"]'); + if (!(titleInput instanceof HTMLInputElement)) return; + const projectField = options.projectField || "projectId"; + const platformField = options.platformField || "platform"; + const defaultPlatform = options.defaultPlatform || "douyin"; + const projectSelect = fields.querySelector(`[data-action-field="${projectField}"]`); + const platformSelect = fields.querySelector(`[data-action-field="${platformField}"]`); + const sync = () => { + const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : ""; + const project = safeArray(appState.dashboard?.projects).find((item) => item.id === projectId) + || getSelectedProject() + || null; + const platform = normalizePlatformValue( + platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(), + defaultPlatform + ); + titleInput.placeholder = recommendManualIntakeTitle(project, platform, kind); + }; + projectSelect?.addEventListener("change", sync); + platformSelect?.addEventListener("change", sync); + sync(); +} + function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); @@ -9270,6 +9294,9 @@ function openImportHomepageAction() { title: "导入主页并同步", description: "适合抖音 / 小红书 / B站 / 快手 / 视频号主页。先建内容源,再触发同步与分析。", submitLabel: "开始同步", + onOpen: ({ fields }) => { + bindManualIntakeTitleRecommendation(fields, "主页对标", { defaultPlatform: "douyin" }); + }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, @@ -9399,6 +9426,9 @@ function openImportVideoLinkAction() { title: "导入作品链接", description: "直接把单条视频链接送进分析链。", submitLabel: "开始分析", + onOpen: ({ fields }) => { + bindManualIntakeTitleRecommendation(fields, "单条作品", { defaultPlatform: "douyin" }); + }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, @@ -9436,6 +9466,9 @@ function openImportTextAction() { title: "导入文本素材", description: "把口播稿、拆解稿或灵感文本直接送进知识与分析链。", submitLabel: "开始分析", + onOpen: ({ fields }) => { + bindManualIntakeTitleRecommendation(fields, "文本素材", { defaultPlatform: "douyin" }); + }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, @@ -9472,6 +9505,9 @@ function openUploadVideoAction() { title: "上传本地视频", description: "上传本地素材,直接进入分析链。", submitLabel: "上传并分析", + onOpen: ({ fields }) => { + bindManualIntakeTitleRecommendation(fields, "本地视频", { defaultPlatform: "douyin" }); + }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 1f9a08a..63b1a61 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -1195,6 +1195,7 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(APP, /function recommendLiveRecorderTitle\(project, platform\)/); assert.match(APP, /function recommendLiveRecorderImportSamples\(platform\)/); assert.match(APP, /function recommendManualIntakeTitle\(project, platform, kind\)/); + assert.match(APP, /function bindManualIntakeTitleRecommendation\(fields, kind, options = \{\}\)/); assert.match(importHomepage, /label: "当前上下文", type: "html"/); assert.match(importHomepage, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(importHomepage, /const defaultPlatform = normalizePlatformValue\(getPreferredPlatform\(\), "douyin"\)/); @@ -1202,6 +1203,7 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(importHomepage, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); assert.match(importHomepage, /value: defaultPlatform/); assert.match(importHomepage, /placeholder: titlePlaceholder/); + assert.match(importHomepage, /onOpen:\s*\(\{ fields \}\) => \{\s*bindManualIntakeTitleRecommendation\(fields, "主页对标", \{ defaultPlatform: "douyin" \}\);/); assert.match(importSelected, /label: "当前上下文", type: "html"/); assert.match(importSelected, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(importSelected, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); @@ -1214,18 +1216,21 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(importVideo, /const titlePlaceholder = recommendManualIntakeTitle\(project, defaultPlatform, "单条作品"\)/); assert.match(importVideo, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); assert.match(importVideo, /placeholder: titlePlaceholder/); + assert.match(importVideo, /onOpen:\s*\(\{ fields \}\) => \{\s*bindManualIntakeTitleRecommendation\(fields, "单条作品", \{ defaultPlatform: "douyin" \}\);/); assert.match(importText, /label: "当前上下文", type: "html"/); assert.match(importText, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(importText, /const defaultPlatform = normalizePlatformValue\(getPreferredPlatform\(\), "douyin"\)/); assert.match(importText, /const titlePlaceholder = recommendManualIntakeTitle\(project, defaultPlatform, "文本素材"\)/); assert.match(importText, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); assert.match(importText, /placeholder: titlePlaceholder/); + assert.match(importText, /onOpen:\s*\(\{ fields \}\) => \{\s*bindManualIntakeTitleRecommendation\(fields, "文本素材", \{ defaultPlatform: "douyin" \}\);/); assert.match(uploadVideo, /label: "当前上下文", type: "html"/); assert.match(uploadVideo, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(uploadVideo, /const defaultPlatform = normalizePlatformValue\(getPreferredPlatform\(\), "douyin"\)/); assert.match(uploadVideo, /const titlePlaceholder = recommendManualIntakeTitle\(project, defaultPlatform, "本地视频"\)/); assert.match(uploadVideo, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); assert.match(uploadVideo, /placeholder: titlePlaceholder/); + assert.match(uploadVideo, /onOpen:\s*\(\{ fields \}\) => \{\s*bindManualIntakeTitleRecommendation\(fields, "本地视频", \{ defaultPlatform: "douyin" \}\);/); 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\) \}\] : \[\]/);