feat: refine task and demo workflow UI
This commit is contained in:
161
client/src/hooks/useDemoFlowState.ts
Normal file
161
client/src/hooks/useDemoFlowState.ts
Normal 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]);
|
||||
}
|
||||
Reference in New Issue
Block a user