Files
zhuangang/topics/frontend/react/examples/task-panel/taskReducer.ts
T

55 lines
2.3 KiB
TypeScript

// 长任务(例如 Agent 生成报表)的前端状态机:用 reducer 管理,所有状态转换集中在一处,便于测试。
// 自检:node taskReducer-demo.ts
export type TaskStatus = "queued" | "running" | "waiting_input" | "succeeded" | "failed" | "cancelled";
export interface TaskState {
id: string | null;
status: TaskStatus | "idle";
steps: { name: string; done: boolean }[];
result: { title: string; url: string } | null;
error: string | null;
version: number; // 服务端事件的序号:丢弃过期 / 乱序的事件
}
export type TaskEvent =
| { type: "submitted"; id: string }
| { type: "progress"; seq: number; step: string; done: boolean }
| { type: "need_input"; seq: number; question: string }
| { type: "succeeded"; seq: number; result: { title: string; url: string } }
| { type: "failed"; seq: number; error: string; retryable: boolean }
| { type: "cancel" }
| { type: "retry" };
export const initialState: TaskState = { id: null, status: "idle", steps: [], result: null, error: null, version: 0 };
const FINAL: ReadonlySet<TaskState["status"]> = new Set(["succeeded", "failed", "cancelled"]);
export function taskReducer(s: TaskState, e: TaskEvent): TaskState {
switch (e.type) {
case "submitted":
return { ...initialState, id: e.id, status: "queued" };
case "cancel":
return FINAL.has(s.status) ? s : { ...s, status: "cancelled" };
case "retry":
return s.status === "failed" ? { ...s, status: "queued", error: null } : s;
default:
// 服务端事件:终态之后、或者序号不大于当前版本的事件一律丢弃(重连后可能收到重复事件)
if (FINAL.has(s.status) || e.seq <= s.version) return s;
}
switch (e.type) {
case "progress": {
const steps = s.steps.some((x) => x.name === e.step)
? s.steps.map((x) => (x.name === e.step ? { ...x, done: e.done } : x))
: [...s.steps, { name: e.step, done: e.done }];
return { ...s, status: "running", steps, version: e.seq };
}
case "need_input":
return { ...s, status: "waiting_input", version: e.seq };
case "succeeded":
return { ...s, status: "succeeded", result: e.result, version: e.seq };
case "failed":
return { ...s, status: "failed", error: e.retryable ? `${e.error}(可重试)` : e.error, version: e.seq };
}
}