|
@@ -0,0 +1,73 @@
|
|
|
|
|
+import type { TaskNode } from "@/lib/types";
|
|
|
|
|
+
|
|
|
|
|
+export interface TaskBranch {
|
|
|
|
|
+ task: TaskNode;
|
|
|
|
|
+ children: TaskBranch[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function pathParts(path: string) {
|
|
|
|
|
+ return path.split(".").map((part) => Number.parseInt(part, 10));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function compareTaskPath(left: TaskNode, right: TaskNode) {
|
|
|
|
|
+ const a = pathParts(left.display_path);
|
|
|
|
|
+ const b = pathParts(right.display_path);
|
|
|
|
|
+ const length = Math.max(a.length, b.length);
|
|
|
|
|
+ for (let index = 0; index < length; index += 1) {
|
|
|
|
|
+ const delta = (a[index] ?? -1) - (b[index] ?? -1);
|
|
|
|
|
+ if (delta !== 0) return delta;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildTaskForest(tasks: TaskNode[], visibleIds?: Set<string>): TaskBranch[] {
|
|
|
|
|
+ const visible = tasks.filter((task) => !visibleIds || visibleIds.has(task.task_id));
|
|
|
|
|
+ const byParent = new Map<string | null, TaskNode[]>();
|
|
|
|
|
+ for (const task of visible) {
|
|
|
|
|
+ const siblings = byParent.get(task.parent_task_id) ?? [];
|
|
|
|
|
+ siblings.push(task);
|
|
|
|
|
+ byParent.set(task.parent_task_id, siblings);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const siblings of byParent.values()) siblings.sort(compareTaskPath);
|
|
|
|
|
+ const walk = (task: TaskNode): TaskBranch => ({
|
|
|
|
|
+ task,
|
|
|
|
|
+ children: (byParent.get(task.task_id) ?? []).map(walk),
|
|
|
|
|
+ });
|
|
|
|
|
+ return (byParent.get(null) ?? []).map(walk);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function taskKindLabel(kind: string) {
|
|
|
|
|
+ const labels: Record<string, string> = {
|
|
|
|
|
+ root: "创作总目标",
|
|
|
|
|
+ direction: "创作方向",
|
|
|
|
|
+ "pattern-retrieval": "模式检索",
|
|
|
|
|
+ "external-retrieval": "外部检索",
|
|
|
|
|
+ "candidate-portfolio": "脚本候选集",
|
|
|
|
|
+ compose: "整稿组合",
|
|
|
|
|
+ structure: "脚本结构",
|
|
|
|
|
+ paragraph: "脚本段落",
|
|
|
|
|
+ "element-set": "内容元素",
|
|
|
|
|
+ compare: "候选比较",
|
|
|
|
|
+ };
|
|
|
|
|
+ return labels[kind] ?? kind;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function statusLabel(status: string) {
|
|
|
|
|
+ const labels: Record<string, string> = {
|
|
|
|
|
+ completed: "已验收",
|
|
|
|
|
+ waiting_children: "等待子任务",
|
|
|
|
|
+ needs_replan: "等待 Planner",
|
|
|
|
|
+ in_progress: "执行中",
|
|
|
|
|
+ pending: "待执行",
|
|
|
|
|
+ awaiting_validation: "等待验收",
|
|
|
|
|
+ awaiting_decision: "等待决策",
|
|
|
|
|
+ blocked: "阶段边界",
|
|
|
|
|
+ cancelled: "已取消",
|
|
|
|
|
+ failed: "失败",
|
|
|
|
|
+ stopped: "已停止",
|
|
|
|
|
+ submitted: "已提交",
|
|
|
|
|
+ passed: "通过",
|
|
|
|
|
+ accept: "采用",
|
|
|
|
|
+ };
|
|
|
|
|
+ return labels[status] ?? status;
|
|
|
|
|
+}
|