From ec35927fa5f4bc58f5eab92eed35fcef146e1c12 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 6 Apr 2026 07:30:32 +0800 Subject: [PATCH] feat: tailor live recorder sheet defaults --- CHANGELOG.md | 6 +++ web/storyforge-web-v4/assets/app.js | 54 +++++++++++++++---- .../tests/workbench-pages.test.mjs | 22 ++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcef7b..4239fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## 2026-04-05 +### 直播录制表单开始按当前项目和平台推荐默认标题与导入样例 + +- `新增录制源` 现在会按当前项目和当前平台自动带出更合理的默认录制名称,不再每次都从空白标题开始。 +- `编辑录制源` 的占位标题也会跟着当前项目和平台变化,方便快速补齐那些原本没有手工命名的录制源。 +- `导入 URL 配置` 会按当前偏好平台切换导入样例和说明文案,让抖音/快手场景在第一眼看到的例子就更贴近当前工作流。 + ### 导入主页、导入当前对标、加入跟踪表单补齐上下文摘要 - `导入主页 / 导入当前对标 / 加入跟踪` 这三张仍需用户补信息的表单,现在和 `导入作品 / 导入文本 / 上传视频` 一样,都会在顶部展示 `当前项目 / 默认 Agent / 默认知识库` 的上下文摘要。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 0f9c8d7..23e5982 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -444,6 +444,30 @@ function recommendRealCutObjective(sourceJob) { return "保留高信息密度片段,输出适合短视频平台的粗剪结果"; } +function recommendLiveRecorderTitle(project, platform) { + const normalized = normalizePlatformValue(platform, "kuaishou"); + const projectName = String(project?.name || "").trim(); + const platformName = platformLabel(normalized); + if (projectName) return `${brief(projectName, 12)} · ${platformName}直播跟踪`; + return `${platformName}直播跟踪`; +} + +function recommendLiveRecorderImportSamples(platform) { + const normalized = normalizePlatformValue(platform, "kuaishou"); + if (normalized === "douyin") { + return [ + "https://live.douyin.com/1234567890", + "# 关闭的源会以 # 开头", + "原画, https://live.douyin.com/9988776655, 抖音主场跟踪" + ].join("\n"); + } + return [ + "https://live.kuaishou.com/u/abcdef", + "# 关闭的源会以 # 开头", + "高清, https://live.kuaishou.com/u/abcdef, 快手主场跟踪" + ].join("\n"); +} + function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); @@ -11605,6 +11629,9 @@ function openLiveRecorderCreateAction() { const status = getIntegrationDetail("live_recorder"); const project = getSelectedProject() || appState.dashboard?.projects?.[0] || null; const assistants = getAssistantOptions(project?.id || ""); + const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; + const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "kuaishou"); + const defaultTitle = recommendLiveRecorderTitle(project, defaultPlatform); openActionModal({ title: "新增录制源", description: status.reachable @@ -11612,11 +11639,12 @@ function openLiveRecorderCreateAction() { : "当前 NAS 录制服务不可达,先检查集成健康。", submitLabel: "保存录制源", fields: [ + { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", defaultAssistantId) }, { type: "html", label: "当前租户", html: renderLiveRecorderSummaryHtml() }, { name: "projectId", label: "归属项目", type: "select", value: project?.id || "", options: getProjectOptions() }, - { name: "assistantId", label: "关联 Agent", type: "select", value: assistants[0]?.value || "", options: [{ value: "", label: "暂不绑定" }, ...assistants] }, - { name: "platform", label: "平台", type: "select", value: "kuaishou", options: getPlatformOptions() }, - { name: "title", label: "录制名称", placeholder: "例如:A 类目直播跟踪" }, + { name: "assistantId", label: "关联 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, + { name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() }, + { name: "title", label: "录制名称", value: defaultTitle, placeholder: defaultTitle }, { name: "quality", label: "清晰度", type: "select", value: "原画", options: ["原画", "蓝光", "超清", "高清", "标清", "流畅"].map((item) => ({ value: item, label: item })) }, { name: "sourceUrl", label: "直播源", type: "url", placeholder: "https://..." }, { name: "autoStart", label: "导入后立即开始", type: "checkbox", value: true } @@ -11659,11 +11687,14 @@ function openLiveRecorderSourceAction(sourceId) { } const currentProject = getSelectedProject() || safeArray(appState.dashboard?.projects).find((item) => item.id === source.project_id) || appState.dashboard?.projects?.[0] || null; const assistants = getAssistantOptions(currentProject?.id || source.project_id || ""); + const defaultAssistantId = source.assistant_id || getSelectedAssistant()?.id || assistants[0]?.value || ""; + const titlePlaceholder = recommendLiveRecorderTitle(currentProject, source.platform || "kuaishou"); openActionModal({ title: "编辑录制源", description: "可以更新项目归属、Agent、标题、清晰度和启停状态;链接本身若要变更,请删除后重建。", submitLabel: "保存修改", fields: [ + { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(currentProject?.id || source.project_id || "", defaultAssistantId) }, { type: "html", label: "源信息", @@ -11682,8 +11713,8 @@ function openLiveRecorderSourceAction(sourceId) { ` }, { name: "projectId", label: "归属项目", type: "select", value: source.project_id || currentProject?.id || "", options: getProjectOptions() }, - { name: "assistantId", label: "关联 Agent", type: "select", value: source.assistant_id || "", options: [{ value: "", label: "暂不绑定" }, ...assistants] }, - { name: "title", label: "录制名称", value: source.title || "", placeholder: "例如:A 类目直播跟踪" }, + { name: "assistantId", label: "关联 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, + { name: "title", label: "录制名称", value: source.title || "", placeholder: titlePlaceholder }, { name: "quality", label: "清晰度", type: "select", value: source.quality || "原画", options: ["原画", "蓝光", "超清", "高清", "标清", "流畅"].map((item) => ({ value: item, label: item })) }, { name: "enabled", label: "启用录制源", type: "checkbox", value: Boolean(source.enabled) } ], @@ -11706,16 +11737,17 @@ function openLiveRecorderSourceAction(sourceId) { } function openLiveRecorderImportAction() { - const samples = [ - "https://live.douyin.com/1234567890", - "# 关闭的源会以 # 开头", - "高清, https://live.kuaishou.com/u/abcdef, 测试录制源" - ].join("\n"); + const project = getSelectedProject() || appState.dashboard?.projects?.[0] || null; + const assistants = getAssistantOptions(project?.id || ""); + const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; + const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "kuaishou"); + const samples = recommendLiveRecorderImportSamples(defaultPlatform); openActionModal({ title: "导入 URL 配置", - description: "按行粘贴直播源,支持用逗号附带清晰度和标题,注释行会被视为停用源。", + description: `按行粘贴${platformLabel(defaultPlatform)}直播源,支持用逗号附带清晰度和标题,注释行会被视为停用源。`, submitLabel: "导入并同步", fields: [ + { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", defaultAssistantId) }, { name: "raw", label: "配置文本", diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 2d27fde..2eaada1 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -1145,6 +1145,9 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( const generateCopy = extractBetween(APP, "function openGenerateCopyAction(defaults = {})", "function openCreateAiVideoAction(defaults = {})"); const createAiVideo = extractBetween(APP, "function openCreateAiVideoAction(defaults = {})", "function openCreateRealCutAction(defaults = {})"); const createRealCut = extractBetween(APP, "function openCreateRealCutAction(defaults = {})", "function openLiveRecorderAction()"); + const liveRecorderCreate = extractBetween(APP, "function openLiveRecorderCreateAction()", "function openLiveRecorderSourceAction(sourceId)"); + const liveRecorderSource = extractBetween(APP, "function openLiveRecorderSourceAction(sourceId)", "function openLiveRecorderImportAction()"); + const liveRecorderImport = extractBetween(APP, "function openLiveRecorderImportAction()", "async function toggleLiveRecorderSourceAction("); const reviewAction = extractBetween(APP, "function openReviewAction(defaults = {})", "document.addEventListener(\"click\", async (event) => {"); assert.match(APP, /function renderIntakeActionContextHtml\(/); assert.match(APP, /function renderSourceJobContextHtml\(job\)/); @@ -1155,6 +1158,8 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(APP, /function recommendAiVideoShotDuration\(sourceJob\)/); assert.match(APP, /function recommendDerivativeJobTitle\(sourceJob, suffix, fallback\)/); assert.match(APP, /function recommendRealCutObjective\(sourceJob\)/); + assert.match(APP, /function recommendLiveRecorderTitle\(project, platform\)/); + assert.match(APP, /function recommendLiveRecorderImportSamples\(platform\)/); assert.match(importHomepage, /label: "当前上下文", type: "html"/); assert.match(importHomepage, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(importHomepage, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/); @@ -1199,6 +1204,23 @@ test("input-heavy intake sheets surface current context and smarter defaults", ( assert.match(createRealCut, /value: defaults\.targetDurationSec \|\| recommendRealCutDuration\(sourceJob\)/); assert.match(createRealCut, /recommendAspectRatioForPlatform\(defaultPlatform\)/); assert.match(createRealCut, /value: defaults\.objective \|\| recommendRealCutObjective\(sourceJob\)/); + assert.match(liveRecorderCreate, /label: "当前上下文", type: "html"/); + assert.match(liveRecorderCreate, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); + assert.match(liveRecorderCreate, /const defaultPlatform = normalizePlatformValue\(getPreferredPlatform\(\), "kuaishou"\)/); + assert.match(liveRecorderCreate, /const defaultTitle = recommendLiveRecorderTitle\(project, defaultPlatform\)/); + assert.match(liveRecorderCreate, /renderIntakeActionContextHtml\(project\?\.id \|\| "", defaultAssistantId\)/); + assert.match(liveRecorderCreate, /value: defaultTitle, placeholder: defaultTitle/); + assert.match(liveRecorderSource, /label: "当前上下文", type: "html"/); + assert.match(liveRecorderSource, /const defaultAssistantId = source\.assistant_id \|\| getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); + assert.match(liveRecorderSource, /const titlePlaceholder = recommendLiveRecorderTitle\(currentProject, source\.platform \|\| "kuaishou"\)/); + assert.match(liveRecorderSource, /renderIntakeActionContextHtml\(currentProject\?\.id \|\| source\.project_id \|\| "", defaultAssistantId\)/); + assert.match(liveRecorderSource, /placeholder: titlePlaceholder/); + assert.match(liveRecorderImport, /label: "当前上下文", type: "html"/); + assert.match(liveRecorderImport, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); + assert.match(liveRecorderImport, /const defaultPlatform = normalizePlatformValue\(getPreferredPlatform\(\), "kuaishou"\)/); + assert.match(liveRecorderImport, /const samples = recommendLiveRecorderImportSamples\(defaultPlatform\)/); + assert.match(liveRecorderImport, /renderIntakeActionContextHtml\(project\?\.id \|\| "", defaultAssistantId\)/); + assert.match(liveRecorderImport, /按行粘贴\$\{platformLabel\(defaultPlatform\)\}直播源/); assert.match(reviewAction, /label: "当前上下文", type: "html"/); assert.match(reviewAction, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/); assert.match(reviewAction, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/);