diff --git a/CHANGELOG.md b/CHANGELOG.md
index 900df2c..0c4a8b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -689,3 +689,4 @@
- 首页 `1 主 2 次` 动作里把 `视频录制` 抬成了高频次级动作,当前项目有生产任务时能更快进入录制维护入口。
- `AI 视频` 表单开始直接显示“当前项目最近使用的视频引擎”,像 `Seedance 2.0 · seedance-2.0-pro` 这类信息会在打开表单时直接可见,并保留跳到火山配置状态的入口。
- `生产中心` 现在把 `导入主页 / 导入作品 / 导入文本 / 上传视频` 这批接入入口统一接进了顶部动作区和生产队列工作流卡,不用离开生产中心也能开始接入素材。
+- `生产中心` 的 `生产队列` 首屏现在会直接显示 `接入与录制` 概况:最近内容源同步条数、录制源数量、录制服务状态,以及一组直达 `导入主页 / 导入作品 / 视频录制` 的动作。
diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index 51358d9..888c18c 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -7559,7 +7559,7 @@ function renderPlaybookScreen() {
);
}
-function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, recoverableCount, works, recentDocs }) {
+function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, recoverableCount, works, recentDocs, contentSyncCount = 0, recorderSourceCount = 0, recorderRunning = false }) {
const taskCards = [];
if (activeTab === "recovery") {
const nextRecoverable = failedJobs.find((item) => item.recovery.recoverable) || null;
@@ -7663,18 +7663,17 @@ function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, rec
`);
- if (topJob) {
- taskCards.push(`
-
-
${escapeHtml(topJob.title)}
-
${escapeHtml(brief(topJob.style_summary || topJob.transcript_text || topJob.error || "暂无摘要", 84))}
-
- ${escapeHtml(topJob.status)}
- ${escapeHtml(topJob.line_type || "analysis")}
-
+ taskCards.push(`
+
+
接入与录制
+
${escapeHtml(`最近内容源同步 ${formatNumber(contentSyncCount)} 条 · 录制源 ${formatNumber(recorderSourceCount)} 个${recorderRunning ? ",录制服务正在运行。" : ",录制服务待确认。"} `)}
+
+ ${actionTag("导入主页", "open-import-homepage")}
+ ${actionTag("导入作品", "open-import-video-link")}
+ ${actionTag("视频录制", "focus-live-recorder-maintenance")}
- `);
- }
+
+ `);
}
return `
@@ -7696,6 +7695,9 @@ function renderProductionScreen() {
const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length;
const recentDocs = appState.documents.slice(0, 3);
const works = getProductionWorks(6);
+ const contentSyncCount = jobs.filter((item) => item.line_type === "content_source_sync").length;
+ const recorderSourceCount = safeArray(appState.liveRecorderSources).length;
+ const recorderRunning = Boolean(appState.liveRecorderStatus?.running);
const isMobileUi = isMobileViewport();
const productionHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "production",
@@ -7735,12 +7737,13 @@ function renderProductionScreen() {
分析任务
最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 条
实拍剪辑
最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} 条
AI 视频
最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))} 条
-
内容源同步
最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "content_source_sync").length))} 条
+
接入与录制
同步 ${escapeHtml(formatNumber(contentSyncCount))} 条 · 录制源 ${escapeHtml(formatNumber(recorderSourceCount))} 个
分析 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))}
实拍 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))}
AI 视频 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))}
+ 录制 ${escapeHtml(formatNumber(recorderSourceCount))}
${renderQuotaBlockingNotice()}
@@ -7788,7 +7791,10 @@ function renderProductionScreen() {
failedJobs,
recoverableCount,
works,
- recentDocs
+ recentDocs,
+ contentSyncCount,
+ recorderSourceCount,
+ recorderRunning
})}
${renderDetailTabs("productionDetailTab", tabs)}
${activeTab === "queue" ? `
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
index 3bea86c..5f9c1c7 100644
--- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs
+++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
@@ -496,12 +496,19 @@ test("mobile discovery and production simplify duplicated top-level actions", ()
test("production queue promotes intake entrypoints so import flows are reachable without leaving production", () => {
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
+ const mobileDeck = extractBetween(APP, "function renderProductionMobileTaskDeck(", "function renderProductionScreen()");
assert.match(production, /const intakeEntryActionsHtml =/);
assert.match(production, /接入素材<\/h4>/);
assert.match(production, /把主页、作品链接、文本素材或本地视频先接进项目/);
assert.match(production, /先从导入主页、作品、文本或上传视频开始接入素材/);
+ assert.match(production, /接入与录制<\/h4>/);
+ assert.match(production, /同步 .* 条 · 录制源 .* 个/);
+ assert.match(production, /录制 \${escapeHtml\(formatNumber\(recorderSourceCount\)\)}/);
assert.match(production, /actionTag\("导入作品", "open-import-video-link"\)/);
assert.match(production, /actionTag\("上传视频", "open-upload-video"\)/);
+ assert.match(mobileDeck, /接入与录制<\/h4>/);
+ assert.match(mobileDeck, /actionTag\("导入主页", "open-import-homepage"\)/);
+ assert.match(mobileDeck, /actionTag\("视频录制", "focus-live-recorder-maintenance"\)/);
});
test("discovery page promotes selected-account actions into direct execute flows", () => {