扩展外部生成Worker队列

新增外部生成队列概览和单任务状态契约

将跳一跳、拼消消、敲木鱼图片生成动作接入worker队列

前端生成等待页展示当前任务和队列数量

更新外部生成worker运维文档和团队决策记录
This commit is contained in:
2026-06-12 23:15:55 +08:00
parent 3bccfd1a83
commit 951caac32d
43 changed files with 1913 additions and 67 deletions

View File

@@ -31,8 +31,16 @@ interface CustomWorldGenerationViewProps {
idleBadgeLabel?: string;
structuredEmptyText?: string;
hideBatchModule?: boolean;
queueStatus?: ExternalGenerationQueueStatus | null;
}
export type ExternalGenerationQueueStatus = {
currentStatus?: 'queued' | 'running' | 'completed' | 'failed' | null;
currentProgress?: number | null;
pendingCount?: number | null;
runningCount?: number | null;
};
function formatDuration(ms: number) {
const safeMs = Math.max(0, Math.round(ms));
const totalSeconds = Math.ceil(safeMs / 1000);
@@ -85,6 +93,49 @@ function getStepStatusLabel(step: { status: string }) {
return '待处理';
}
function resolveQueueStatusLabel(
status: ExternalGenerationQueueStatus['currentStatus'],
) {
if (status === 'queued') {
return '排队中';
}
if (status === 'running') {
return '生成中';
}
if (status === 'failed') {
return '生成失败';
}
if (status === 'completed') {
return '已完成';
}
return null;
}
function hasQueueStatus(status: ExternalGenerationQueueStatus | null | undefined) {
return Boolean(
status &&
(status.currentStatus ||
typeof status.pendingCount === 'number' ||
typeof status.runningCount === 'number'),
);
}
function formatQueueCount(value: number | null | undefined) {
return Math.max(0, Math.round(value ?? 0)).toString();
}
function formatQueueProgress(value: number | null | undefined) {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return null;
}
return `${Math.max(0, Math.min(100, Math.round(value)))}%`;
}
function resolveCurrentGenerationStep(
progress: CustomWorldGenerationProgress | null,
) {
@@ -111,6 +162,7 @@ export function CustomWorldGenerationView({
activeBadgeLabel = '世界建设中',
idleBadgeLabel = '等待操作',
hideBatchModule = false,
queueStatus = null,
}: CustomWorldGenerationViewProps) {
void hideBatchModule;
const progressValue = getProgressPercentage(progress);
@@ -131,6 +183,11 @@ export function CustomWorldGenerationView({
: '校准中';
const elapsedText =
progress != null ? formatDuration(progress.elapsedMs) : '启动中';
const queueStatusLabel = resolveQueueStatusLabel(
queueStatus?.currentStatus ?? null,
);
const queueProgressText = formatQueueProgress(queueStatus?.currentProgress);
const shouldShowQueueStatus = hasQueueStatus(queueStatus);
return (
<div className="relative isolate z-[1] -mx-3 -my-3 flex h-[calc(100%+1.5rem)] min-h-0 flex-col overflow-hidden bg-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-4 text-[#3d1f10] sm:mx-0 sm:my-0 sm:h-full sm:rounded-[2rem] sm:px-5 sm:pt-5">
@@ -167,6 +224,21 @@ export function CustomWorldGenerationView({
/>
</div>
{shouldShowQueueStatus ? (
<div className="mt-3 flex flex-wrap items-center gap-2 rounded-[1.25rem] border border-white/70 bg-white/72 px-3 py-2 text-xs font-semibold text-[#6b3a1d] shadow-[0_14px_34px_rgba(121,70,33,0.10)] backdrop-blur-md sm:px-4">
{queueStatusLabel ? (
<span className="rounded-full bg-[#fff4dc] px-2.5 py-1 text-[#8a4c1e]">
{queueProgressText
? `${queueStatusLabel} ${queueProgressText}`
: queueStatusLabel}
</span>
) : null}
<span> {formatQueueCount(queueStatus?.pendingCount)}</span>
<span className="h-1 w-1 rounded-full bg-[#d4a15d]" />
<span> {formatQueueCount(queueStatus?.runningCount)}</span>
</div>
) : null}
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:justify-end">
{!isGenerating ? (
<PlatformActionButton