+ 状态:{state.status}
+
+ {state.steps.map((s) => (
+ - {s.done ? "✓" : "…"} {s.name}
+ ))}
+
+ {state.status === "failed" && (
+
+ {state.error}
+
+ )}
+ {state.result && {state.result.title}}
+ {state.status === "running" && }
+
+ );
+}
diff --git a/topics/frontend/react/examples/task-panel/package.json b/topics/frontend/react/examples/task-panel/package.json
new file mode 100644
index 0000000..089153b
--- /dev/null
+++ b/topics/frontend/react/examples/task-panel/package.json
@@ -0,0 +1 @@
+{"type":"module"}
diff --git a/topics/frontend/react/examples/task-panel/taskReducer-demo.ts b/topics/frontend/react/examples/task-panel/taskReducer-demo.ts
new file mode 100644
index 0000000..8268cad
--- /dev/null
+++ b/topics/frontend/react/examples/task-panel/taskReducer-demo.ts
@@ -0,0 +1,21 @@
+// 运行:node taskReducer-demo.ts —— 验证乱序 / 重复 / 迟到事件被丢弃,失败后可重试
+import { initialState, taskReducer, type TaskEvent } from "./taskReducer.ts";
+
+const events: TaskEvent[] = [
+ { type: "submitted", id: "T1" },
+ { type: "progress", seq: 1, step: "查询数据", done: false },
+ { type: "progress", seq: 2, step: "查询数据", done: true },
+ { type: "progress", seq: 1, step: "查询数据", done: false }, // 重连后收到的旧事件:丢弃
+ { type: "progress", seq: 3, step: "生成图表", done: true },
+ { type: "succeeded", seq: 4, result: { title: "9 月报表", url: "/reports/9" } },
+ { type: "failed", seq: 5, error: "迟到的失败事件", retryable: false }, // 终态之后:丢弃
+];
+const final = events.reduce(taskReducer, initialState);
+console.log(final.status, final.steps, final.result);
+if (final.status !== "succeeded" || final.steps.length !== 2 || !final.steps.every((x) => x.done)) {
+ throw new Error("断言失败");
+}
+const failed = [{ type: "submitted", id: "T2" }, { type: "failed", seq: 1, error: "超时", retryable: true }, { type: "retry" }]
+ .reduce((st, ev) => taskReducer(st, ev as TaskEvent), initialState);
+if (failed.status !== "queued" || failed.error !== null) throw new Error("断言失败");
+console.log("重试后:", failed.status);
diff --git a/topics/frontend/react/examples/task-panel/taskReducer.ts b/topics/frontend/react/examples/task-panel/taskReducer.ts
new file mode 100644
index 0000000..cafb291
--- /dev/null
+++ b/topics/frontend/react/examples/task-panel/taskReducer.ts
@@ -0,0 +1,54 @@
+// 长任务(例如 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