feat: refresh live recorder sheet defaults
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-07 12:44:50 +08:00
parent b67876a458
commit 76affab96b
3 changed files with 92 additions and 0 deletions

View File

@@ -11,6 +11,12 @@
- `来源任务` 摘要区也会跟着联动更新,切换任务后第一眼就能看到当前承接的是哪条任务。
- 为了支持这层联动,输入型表单里的 HTML 字段现在也带了稳定的 `data-action-field` 标记,后续继续做表单智能化和回归锁定会更稳。
### 直播录制表单开始跟随项目和平台动态刷新
- `新增录制源 / 编辑录制源` 现在会在切换项目或平台时动态刷新录制名称占位,并同步更新可选 Agent 列表,不再停留在打开表单时的默认值。
- `导入 URL 配置` 现在会在切换平台时实时刷新说明文案和样例配置,抖音/快手两种场景可以直接在同一张表单里切换预设。
- 这套联动同样保留“手动改过就不再覆盖”的原则,避免自动推荐把用户已经输入的内容冲掉。
## 2026-04-06
### 主 Agent 高注意图动作统一切到直执行入口

View File

@@ -593,6 +593,67 @@ function bindCreativeSourceJobRecommendations(fields, options = {}) {
sync();
}
function bindLiveRecorderSheetRecommendations(fields, options = {}) {
const projectSelect = fields.querySelector('[data-action-field="projectId"]');
const platformSelect = fields.querySelector('[data-action-field="platform"]');
const titleInput = fields.querySelector('[data-action-field="title"]');
const assistantSelect = fields.querySelector('[data-action-field="assistantId"]');
const rawTextarea = fields.querySelector('[data-action-field="raw"]');
const defaultPlatform = options.defaultPlatform || "kuaishou";
const syncAssistants = () => {
if (!(assistantSelect instanceof HTMLSelectElement) || !(projectSelect instanceof HTMLSelectElement)) return;
const projectId = projectSelect.value || "";
const assistants = getAssistantOptions(projectId);
const currentValue = assistantSelect.value || "";
assistantSelect.innerHTML = [
{ value: "", label: "暂不绑定" },
...assistants
].map((item) => `
<option value="${escapeHtml(item.value)}" ${item.value === currentValue ? "selected" : ""}>${escapeHtml(item.label)}</option>
`).join("");
const stillExists = safeArray(assistants).some((item) => item.value === currentValue);
if (!stillExists && currentValue) {
assistantSelect.value = "";
}
};
const syncTitle = () => {
if (!(titleInput instanceof HTMLInputElement)) return;
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 = recommendLiveRecorderTitle(project, platform);
};
const syncRawSamples = () => {
if (!(rawTextarea instanceof HTMLTextAreaElement)) return;
if (rawTextarea.dataset.recommendationMode === "manual") return;
const platform = normalizePlatformValue(
platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(),
defaultPlatform
);
rawTextarea.dataset.recommendationApplying = "1";
rawTextarea.value = recommendLiveRecorderImportSamples(platform);
rawTextarea.dataset.recommendationMode = "auto";
delete rawTextarea.dataset.recommendationApplying;
};
rawTextarea?.addEventListener("input", () => {
if (rawTextarea.dataset.recommendationApplying === "1") return;
rawTextarea.dataset.recommendationMode = "manual";
});
const sync = () => {
syncAssistants();
syncTitle();
syncRawSamples();
};
projectSelect?.addEventListener("change", sync);
platformSelect?.addEventListener("change", sync);
sync();
}
function formatDateTime(value) {
if (!value) return "-";
const date = new Date(value);
@@ -11887,6 +11948,9 @@ function openLiveRecorderCreateAction() {
{ name: "sourceUrl", label: "直播源", type: "url", placeholder: "https://..." },
{ name: "autoStart", label: "导入后立即开始", type: "checkbox", value: true }
],
onOpen: ({ fields }) => {
bindLiveRecorderSheetRecommendations(fields, { defaultPlatform });
},
onSubmit: async (values) => {
if (!values.sourceUrl?.trim()) throw new Error("请填写直播源链接");
const saved = await storyforgeFetch("/v2/live-recorder/sources", {
@@ -11956,6 +12020,9 @@ function openLiveRecorderSourceAction(sourceId) {
{ name: "quality", label: "清晰度", type: "select", value: source.quality || "原画", options: ["原画", "蓝光", "超清", "高清", "标清", "流畅"].map((item) => ({ value: item, label: item })) },
{ name: "enabled", label: "启用录制源", type: "checkbox", value: Boolean(source.enabled) }
],
onOpen: ({ fields }) => {
bindLiveRecorderSheetRecommendations(fields, { defaultPlatform: source.platform || "kuaishou" });
},
onSubmit: async (values) => {
const saved = await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, {
method: "PATCH",
@@ -11986,6 +12053,7 @@ function openLiveRecorderImportAction() {
submitLabel: "导入并同步",
fields: [
{ name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", defaultAssistantId) },
{ name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() },
{
name: "raw",
label: "配置文本",
@@ -11995,6 +12063,20 @@ function openLiveRecorderImportAction() {
placeholder: "一行一个 URL支持 # 注释和 逗号分隔的清晰度/标题"
}
],
onOpen: ({ fields, description }) => {
bindLiveRecorderSheetRecommendations(fields, { defaultPlatform });
const platformSelect = fields.querySelector('[data-action-field="platform"]');
const syncDescription = () => {
if (!(description instanceof HTMLElement)) return;
const platform = normalizePlatformValue(
platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(),
defaultPlatform
);
description.textContent = `按行粘贴${platformLabel(platform)}直播源,支持用逗号附带清晰度和标题,注释行会被视为停用源。`;
};
platformSelect?.addEventListener("change", syncDescription);
syncDescription();
},
onSubmit: async (values) => {
if (!String(values.raw || "").trim()) throw new Error("请先粘贴配置文本");
const saved = await storyforgeFetch("/v2/live-recorder/url-config/import", {

View File

@@ -1197,6 +1197,7 @@ test("input-heavy intake sheets surface current context and smarter defaults", (
assert.match(APP, /function recommendManualIntakeTitle\(project, platform, kind\)/);
assert.match(APP, /function bindManualIntakeTitleRecommendation\(fields, kind, options = \{\}\)/);
assert.match(APP, /function bindCreativeSourceJobRecommendations\(fields, options = \{\}\)/);
assert.match(APP, /function bindLiveRecorderSheetRecommendations\(fields, 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"\)/);
@@ -1263,17 +1264,20 @@ test("input-heavy intake sheets surface current context and smarter defaults", (
assert.match(liveRecorderCreate, /const defaultTitle = recommendLiveRecorderTitle\(project, defaultPlatform\)/);
assert.match(liveRecorderCreate, /renderIntakeActionContextHtml\(project\?\.id \|\| "", defaultAssistantId\)/);
assert.match(liveRecorderCreate, /value: defaultTitle, placeholder: defaultTitle/);
assert.match(liveRecorderCreate, /onOpen:\s*\(\{ fields \}\) => \{\s*bindLiveRecorderSheetRecommendations\(fields, \{/);
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(liveRecorderSource, /onOpen:\s*\(\{ fields \}\) => \{\s*bindLiveRecorderSheetRecommendations\(fields, \{/);
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(liveRecorderImport, /onOpen:\s*\(\{ fields, description \}\) => \{\s*bindLiveRecorderSheetRecommendations\(fields, \{/);
assert.match(reviewAction, /label: "当前上下文", type: "html"/);
assert.match(reviewAction, /const defaultAssistantId = getSelectedAssistant\(\)\?\.id \|\| assistants\[0\]\?\.value \|\| ""/);
assert.match(reviewAction, /renderIntakeActionContextHtml\(project\.id, defaultAssistantId\)/);