55 lines
2.3 KiB
TypeScript
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 };
|
|
}
|
|
}
|