feat: add mobile-first production task deck

This commit is contained in:
kris
2026-03-30 01:24:37 +08:00
parent ae99a4b962
commit 1cb6c3e78f
3 changed files with 142 additions and 0 deletions

View File

@@ -6069,6 +6069,130 @@ function renderPlaybookScreen() {
);
}
function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, recoverableCount, works, recentDocs }) {
const taskCards = [];
if (activeTab === "recovery") {
const nextRecoverable = failedJobs.find((item) => item.recovery.recoverable) || null;
if (nextRecoverable) {
taskCards.push(`
<div class="task-item compact">
<h4>当前要先处理</h4>
<p>${escapeHtml(`${nextRecoverable.job.title || nextRecoverable.job.id} 仍可恢复,建议优先重开。`)}</p>
<div class="task-meta">
<span class="tag ${nextRecoverable.recovery.recoverable ? "green" : "orange"}">${escapeHtml(nextRecoverable.recovery.label)}</span>
${actionTag(nextRecoverable.recovery.actionLabel || "立即恢复", "recover-job", `data-job-id="${escapeHtml(nextRecoverable.job.id)}"`)}
</div>
</div>
`);
}
taskCards.push(`
<div class="task-item compact">
<h4>恢复面板</h4>
<p>${escapeHtml(recoverableCount ? `当前有 ${recoverableCount} 条任务可以直接恢复。` : "当前没有可直接恢复的失败任务。")}</p>
<div class="task-meta">
${actionTag("批量恢复", "batch-recover-jobs")}
${actionTag("恢复记录", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recovery"`)}
</div>
</div>
`);
} else if (activeTab === "recorder") {
const status = appState.liveRecorderStatus || {};
const activeCount = safeArray(status.active_recordings).length;
taskCards.push(`
<div class="task-item compact">
<h4>当前要先处理</h4>
<p>${escapeHtml(status.running ? `Live Recorder 正在运行,当前有 ${activeCount} 路活动录制。` : "先确认 Live Recorder 是否在线,再检查录制源和文件。")}</p>
<div class="task-meta">
<span class="tag ${status.running ? "green" : "orange"}">${escapeHtml(status.running ? "运行中" : "待检查")}</span>
${actionTag("录制维护", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recorder"`)}
</div>
</div>
`);
taskCards.push(`
<div class="task-item compact">
<h4>录制源与文件</h4>
<p>${escapeHtml(`录制源 ${formatNumber(safeArray(appState.liveRecorderSources).length)} 个 · 文件 ${formatNumber(safeArray(appState.liveRecorderFiles).length)}`)}</p>
<div class="task-meta">
${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({
sourceScreen: "production",
sourceActionKey: "production-mobile-recorder-handoff",
intentKey: "production_coordination",
title: "继续处理录制维护",
goal: "继续处理录制维护",
summary: "结合录制维护状态给出下一步动作。",
platform: getPreferredPlatform(),
platformScope: "single_platform",
planSteps: ["读取录制维护状态", "识别当前阻塞项", "生成下一步处理动作"]
}))}
</div>
</div>
`);
} else if (activeTab === "outputs") {
const topWork = works[0] || null;
taskCards.push(`
<div class="task-item compact">
<h4>当前要先处理</h4>
<p>${escapeHtml(topWork ? `先看 ${describeVideo(topWork)} 的结果,再决定是否回到复盘。` : "先看最近产物和学习素材,再决定是否继续复盘或返回生产。")}</p>
<div class="task-meta">
${actionTag("去复盘", "goto-review")}
${actionTag("查看产物", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="outputs"`)}
</div>
</div>
`);
if (recentDocs.length) {
taskCards.push(`
<div class="task-item compact">
<h4>最近学习素材</h4>
<p>${escapeHtml(brief(recentDocs[0].title || recentDocs[0].style_summary || "最近文档", 72))}</p>
<div class="task-meta">
<span class="tag blue">学习素材</span>
<span class="tag">${escapeHtml(recentDocs[0].source_type || "document")}</span>
</div>
</div>
`);
}
} else {
const topJob = (activeJobs.length ? activeJobs : []).slice(0, 1)[0] || null;
taskCards.push(`
<div class="task-item compact">
<h4>当前要先处理</h4>
<p>${escapeHtml(topJob ? `${topJob.title} 还在推进中,建议先看状态再决定是否做 AI 视频或实拍剪辑。` : "先看当前生产队列,再决定是否继续恢复或进入复盘。")}</p>
<div class="task-meta">
${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({
sourceScreen: "production",
sourceActionKey: "production-mobile-queue-handoff",
intentKey: "production_coordination",
title: "继续推进生产队列",
goal: "继续推进生产队列",
summary: "结合当前生产队列给出下一步动作。",
platform: getPreferredPlatform(),
platformScope: "single_platform",
planSteps: ["读取当前生产队列", "识别最该优先推进的项", "生成下一步处理动作"]
}))}
${recoverableCount ? actionTag("看失败恢复", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recovery"`) : actionTag("去复盘", "goto-review")}
</div>
</div>
`);
if (topJob) {
taskCards.push(`
<div class="task-item compact">
<h4>${escapeHtml(topJob.title)}</h4>
<p>${escapeHtml(brief(topJob.style_summary || topJob.transcript_text || topJob.error || "暂无摘要", 84))}</p>
<div class="task-meta">
<span class="tag ${statusTone(topJob.status)}">${escapeHtml(topJob.status)}</span>
<span class="tag">${escapeHtml(topJob.line_type || "analysis")}</span>
</div>
</div>
`);
}
}
return `
<div class="mobile-only production-mobile-task-deck">
${taskCards.slice(0, 2).join("")}
</div>
`;
}
function renderProductionScreen() {
if (!appState.dashboard) {
if (isAutoConnectionPending()) {
@@ -6163,6 +6287,14 @@ function renderProductionScreen() {
<span class="tag ${recoverableCount ? "orange" : "green"}">可恢复 ${escapeHtml(formatNumber(recoverableCount))}</span>
<span class="tag green">产物 ${escapeHtml(formatNumber(works.length))}</span>
</div>
${renderProductionMobileTaskDeck({
activeTab,
activeJobs,
failedJobs,
recoverableCount,
works,
recentDocs
})}
${renderDetailTabs("productionDetailTab", tabs)}
${activeTab === "queue" ? `
<div class="panel pad" style="box-shadow:none;">

View File

@@ -2359,6 +2359,12 @@ tbody tr:hover {
text-align: center;
}
.production-mobile-task-deck {
display: grid;
gap: 10px;
margin-bottom: 14px;
}
.entity-cell {
align-items: flex-start;
}

View File

@@ -207,9 +207,13 @@ test("discovery and production screens expose mobile focus cards with next-step
assert.match(discovery, /导入当前对标/);
assert.match(discovery, /查相似/);
assert.match(production, /mobile-only mobile-flow-focus-card/);
assert.match(APP, /function renderProductionMobileTaskDeck\(/);
assert.match(APP, /mobile-only production-mobile-task-deck/);
assert.match(APP, /当前要先处理/);
assert.match(production, /当前工作流/);
assert.match(production, /批量恢复/);
assert.match(cssMobile, /\.mobile-flow-focus-card/);
assert.match(cssMobile, /\.production-mobile-task-deck/);
});
test("mobile heavy screens mark redundant desktop metric blocks for compact hiding", () => {