升级外部生成为正式任务列表

将 external_generation_job 扩展为含价格、确认时间、退款流水和事件审计的正式生成任务事实源

新增外部生成任务列表与确认 BFF,并让平台入口按后端任务补弹完成或失败提示

保护画布生成队列超时、快照回写和自动保存覆盖场景

将 worker 计费退款流水写入 externalGenerationJobId,并补充冷备份后 worker/controller 恢复守护
This commit is contained in:
2026-06-24 15:17:12 +08:00
parent ed6b17442a
commit 73a17193d4
66 changed files with 2416 additions and 122 deletions
@@ -1,6 +1,7 @@
import type {
ExternalGenerationJobStatusRecord,
ExternalGenerationQueueOverview,
ExternalGenerationTaskRecord,
} from '../../../packages/shared/src/contracts/externalGeneration';
export type ExternalGenerationQueueStatus = {
@@ -8,6 +9,17 @@ export type ExternalGenerationQueueStatus = {
currentProgress?: number | null;
pendingCount?: number | null;
runningCount?: number | null;
tasks?: ExternalGenerationTaskRecord[] | null;
unacknowledgedTerminalCount?: number | null;
};
export type ExternalGenerationQueueTaskPresentation = {
id: string;
title: string;
statusLabel: string;
priceLabel: string;
detail: string;
tone: 'profile' | 'danger' | 'success';
};
export type ExternalGenerationQueuePresentation = {
@@ -17,15 +29,18 @@ export type ExternalGenerationQueuePresentation = {
runningLabel: string;
pendingCount: number;
runningCount: number;
unacknowledgedTerminalCount: number;
progress: number | null;
tasks: ExternalGenerationQueueTaskPresentation[];
shouldShow: boolean;
};
export function buildExternalGenerationQueueStatus(
overview: ExternalGenerationQueueOverview | null,
job: ExternalGenerationJobStatusRecord | null,
tasks: ExternalGenerationTaskRecord[] | null = null,
): ExternalGenerationQueueStatus | null {
if (!overview && !job) {
if (!overview && !job && !tasks?.length) {
return null;
}
@@ -34,6 +49,9 @@ export function buildExternalGenerationQueueStatus(
currentProgress: job?.progress ?? null,
pendingCount: overview?.pendingCount ?? null,
runningCount: overview?.runningCount ?? null,
unacknowledgedTerminalCount:
overview?.unacknowledgedTerminalCount ?? null,
tasks,
};
}
@@ -94,6 +112,26 @@ export function normalizeExternalGenerationQueueProgress(
return Math.max(0, Math.min(100, Math.round(value)));
}
export function buildExternalGenerationTaskPresentation(
task: ExternalGenerationTaskRecord,
): ExternalGenerationQueueTaskPresentation {
const statusLabel = resolveExternalGenerationQueueStatusLabel(task.status) ?? '待处理';
return {
id: task.jobId,
title: task.requestLabel.trim() || task.jobKind,
statusLabel,
priceLabel:
task.priceMudPoints > 0 ? `${task.priceMudPoints}泥点` : '未计费',
detail: task.error?.trim() || task.phaseDetail || task.updatedAt,
tone:
task.status === 'failed'
? 'danger'
: task.status === 'completed'
? 'success'
: 'profile',
};
}
export function buildExternalGenerationQueuePresentation(
status: ExternalGenerationQueueStatus | null | undefined,
): ExternalGenerationQueuePresentation {
@@ -103,6 +141,9 @@ export function buildExternalGenerationQueuePresentation(
const runningCount = normalizeExternalGenerationQueueCount(
status?.runningCount,
);
const unacknowledgedTerminalCount = normalizeExternalGenerationQueueCount(
status?.unacknowledgedTerminalCount,
);
const progress = normalizeExternalGenerationQueueProgress(
status?.currentProgress,
);
@@ -126,7 +167,17 @@ export function buildExternalGenerationQueuePresentation(
runningLabel: runningCount.toString(),
pendingCount,
runningCount,
unacknowledgedTerminalCount,
progress: progressValue,
shouldShow: Boolean(statusLabel || pendingCount > 0 || runningCount > 0),
tasks: (status?.tasks ?? [])
.slice(0, 5)
.map(buildExternalGenerationTaskPresentation),
shouldShow: Boolean(
statusLabel ||
pendingCount > 0 ||
runningCount > 0 ||
unacknowledgedTerminalCount > 0 ||
(status?.tasks?.length ?? 0) > 0,
),
};
}