feat: refine task and demo workflow UI

This commit is contained in:
2026-07-22 11:07:12 +08:00
parent 752ea3f944
commit 33c5fb9b9c
11 changed files with 1703 additions and 210 deletions

View File

@@ -0,0 +1,161 @@
import { useMemo } from 'react';
import { trpc } from '@/lib/trpc';
type AnalysisResultRow = {
id: number;
taskId?: number | null;
type: string;
pluginId?: string | null;
status: string;
createdAt: string | Date;
updatedAt: string | Date;
resultData: unknown;
};
function asRecord(value: unknown): Record<string, unknown> | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
return value as Record<string, unknown>;
}
function numberFromUnknown(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
export type DemoFlowStepStatus = 'pending' | 'processing' | 'completed' | 'error' | 'cancelled';
export interface DemoFlowStep {
id: number;
index: number;
role: 'registration' | 'measurement';
pluginId: string | null;
status: DemoFlowStepStatus;
progress?: number;
createdAt: string | Date;
updatedAt: string | Date;
}
export interface DemoFlowState {
active: boolean;
runId: string | null;
total: number;
completed: number;
steps: DemoFlowStep[];
overallStatus: DemoFlowStepStatus | 'idle';
currentStep: DemoFlowStep | null;
overallProgress: number;
}
const EMPTY_STATE: DemoFlowState = {
active: false,
runId: null,
total: 0,
completed: 0,
steps: [],
overallStatus: 'idle',
currentStep: null,
overallProgress: 0,
};
function isActiveStatus(step: DemoFlowStep): boolean {
return step.status === 'processing' || step.status === 'pending';
}
export function useDemoFlowState(taskId: number | null): DemoFlowState {
const { data: results = [] } = trpc.analysisResult.list.useQuery(
{ taskId: taskId ?? 0 },
{ enabled: taskId != null, refetchInterval: 3000 },
);
return useMemo(() => {
if (taskId == null || results.length === 0) return EMPTY_STATE;
const rows = results as AnalysisResultRow[];
const steps: DemoFlowStep[] = [];
let activeRunId: string | null = null;
let declaredTotal = 0;
for (const r of rows) {
const data = asRecord(r.resultData);
if (!data) continue;
const runId = data.demoFlowRunId;
if (typeof runId !== 'string' || runId.length === 0) continue;
if (activeRunId == null) {
activeRunId = runId;
} else if (activeRunId !== runId) {
// 只关注最新的一次 demo 流(createdAt 最新的在最前)
continue;
}
const index = numberFromUnknown(data.demoFlowIndex) ?? steps.length + 1;
const roleRaw = data.demoFlowRole;
const role = roleRaw === 'registration' ? 'registration' : 'measurement';
const progress = numberFromUnknown(data.progress);
const stepTotal = numberFromUnknown(data.demoFlowTotal) ?? 0;
if (stepTotal > declaredTotal) declaredTotal = stepTotal;
steps.push({
id: r.id,
index,
role,
pluginId: r.pluginId ?? null,
status: r.status as DemoFlowStepStatus,
progress,
createdAt: r.createdAt,
updatedAt: r.updatedAt,
});
}
if (steps.length === 0) return EMPTY_STATE;
steps.sort((a, b) => a.index - b.index);
const total = Math.max(steps.length, declaredTotal);
// 用 pending 占位步骤补齐尚未创建结果记录的后续步骤
for (let i = steps.length + 1; i <= total; i++) {
steps.push({
id: -i,
index: i,
role: i === 1 ? 'registration' : 'measurement',
pluginId: null,
status: 'pending',
createdAt: steps[0]?.createdAt ?? new Date(),
updatedAt: steps[0]?.updatedAt ?? new Date(),
});
}
const completed = steps.filter((s) => s.status === 'completed').length;
const active = steps.some(isActiveStatus);
const currentStep =
steps.find((s) => s.status === 'processing') ??
steps.find((s) => s.status === 'pending') ??
steps[steps.length - 1] ??
null;
let overallStatus: DemoFlowState['overallStatus'] = 'idle';
if (steps.some((s) => s.status === 'processing')) overallStatus = 'processing';
else if (steps.some((s) => s.status === 'pending')) overallStatus = 'pending';
else if (steps.every((s) => s.status === 'completed')) overallStatus = 'completed';
else if (steps.some((s) => s.status === 'error')) overallStatus = 'error';
else if (steps.every((s) => s.status === 'cancelled')) overallStatus = 'cancelled';
else if (steps.some((s) => s.status === 'cancelled')) overallStatus = 'cancelled';
const doneProgress = completed / total;
const currentProgress = currentStep && isActiveStatus(currentStep)
? (currentStep.progress ?? 0) / total
: 0;
const overallProgress = Math.min(100, Math.round((doneProgress + currentProgress) * 100));
return {
active,
runId: activeRunId,
total,
completed,
steps,
overallStatus,
currentStep,
overallProgress,
};
}, [results, taskId]);
}