feat: add mobile-first production task deck
This commit is contained in:
@@ -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;">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user