feat(roundtable): 深链策划流程小图 + 流程图弹窗横向/缩略图/深蓝适配

- Step1 消息列表头部:深链 taskId 模式取到目的树后,左侧标题换成「策划流程」
  彩色层级小图(任务→目的→…→叙事,每级配色对齐流程图节点 + 类型小图标),
  点击打开任务目的流程图弹窗;仅第一步、有数据时出现。
- TaskPurposeDialog 流程图:横向(LR)布局 + 右下角缩略图(MiniMap)。
- FlowGraphPanel 深蓝主题适配(随 ThemeContext):容器/头部/画布点阵/连线/图例/
  小地图/节点详情弹窗 + 节点配色(FLOW_NODE_TYPE_META_DARK),浅色红主题不受影响。
- 圆桌侧栏深色兜底:neutral 家族 + 米色 hex(#FAF6F5/#ECE7E4/#E8D0CC) 映射到深蓝
  token(关系图背板、第N步 tag 等不再泛白)。

附带提交工作区其余暂存改动(AI 写作等),不含 config.yaml。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-18 17:39:19 +08:00
1 parent 85cca36ddb
commit 03b6fb724c
40 files changed
+2167 -119

No files matched your search

@@ -15,6 +15,12 @@ import {
getCachedTaskDeeplinkConfig,
} from "@/core/auth/task-deeplink-config";
import { requestCrudService } from "@/service/request-axios-service";
import type {
FlowGraphData,
FlowGraphEdge,
FlowGraphNode,
FlowNodeType,
} from "@/roundtable-planning/lib/flow-graph";
/** 归一化后的目的树节点(用于展示,逐层:目的→行为体→预期行为→驱动因素→叙事)。 */
export interface PurposeTreeNode {
@@ -112,6 +118,83 @@ export function flattenPurposeTree(nodes: PurposeTreeNode[]): PurposeFlatCell[][
return rows;
}
/* --------------------- 目的树 → 流程图(flow-graph) --------------------- */
/**
* 目的树各层级标签 → 流程图节点类型(仅决定配色,见 FLOW_NODE_TYPE_META)。
* 未知层级回退 task。层级名本身仍原样写进节点 group,画布上当小标签显示。
*/
const PURPOSE_LABEL_TO_FLOW_TYPE: Record<string, FlowNodeType> = {
目的: "stage",
行为体: "role",
预期行为: "task",
驱动因素: "decision",
叙事: "deliverable",
};
/** 目的树的一个层级(深度):层级中文名 + 对应流程图节点类型(决定配色)。 */
export interface PurposeLevel {
/** 层级中文名(目的 / 行为体 / 预期行为 / 驱动因素 / 叙事…)。 */
label: string;
/** 流程图节点类型(用于取 FLOW_NODE_TYPE_META 配色,与画布节点一致)。 */
type: FlowNodeType;
}
/**
* 按**深度**抽出目的树的有序层级(每层取该深度第一个非空层级名作代表)。
*
* 目的树每一深度的层级是一致的(深度 0 = 目的、1 = 行为体、… 直到叶子叙事),
* 故逐层 BFS、每层取一个代表标签即可得到「任务策划共几级、分别是什么」,
* 供消息列表上方的「流程示例图」按层级画彩色小图标。
*/
export function derivePurposeLevels(tree: PurposeTreeNode[]): PurposeLevel[] {
const levels: PurposeLevel[] = [];
let frontier = tree;
let depth = 0;
while (frontier.length > 0 && depth < 12) {
const label = frontier.find((n) => n.label?.trim())?.label?.trim() || `层级 ${depth + 1}`;
levels.push({ label, type: PURPOSE_LABEL_TO_FLOW_TYPE[label] ?? "task" });
frontier = frontier.flatMap((n) => n.children ?? []);
depth += 1;
}
return levels;
}
/**
* 把归一化后的目的树转换成 Step 3 流程图组件(FlowGraphPanel)认识的 FlowGraphData。
*
* - 逐层深度优先遍历,给每个节点分配稳定 id(`p1/p2/…`,按遍历顺序);
* - 父→子建一条边(from=父 id);多个根目的各自成一棵子树(画布支持多根);
* - 节点主文本(`text`)作 label,层级名(`label`)作 group(画布顶部小标签),
* 推荐理由并进 detail(点节点弹窗里看)。
* 容错沿用 PurposeTreeNode(normalizeNode 已兜底字段/层级变动),这里不再二次判断。
*/
export function purposeTreeToFlowGraph(
tree: PurposeTreeNode[],
taskId?: string,
): FlowGraphData {
const nodes: FlowGraphNode[] = [];
const edges: FlowGraphEdge[] = [];
let counter = 0;
const walk = (node: PurposeTreeNode, parentId: string | null) => {
const id = `p${++counter}`;
const type = PURPOSE_LABEL_TO_FLOW_TYPE[node.label] ?? "task";
const text = node.text?.trim() || node.label || "(空)";
const detail = node.reason?.trim()
? `${text}\n理由:${node.reason.trim()}`
: node.text?.trim() || undefined;
nodes.push({ id, label: text, type, group: node.label || undefined, detail });
if (parentId) edges.push({ from: parentId, to: id });
node.children?.forEach((child) => walk(child, id));
};
tree.forEach((root) => walk(root, null));
return {
title: taskId ? `任务目的(任务 ${taskId})` : "任务目的",
nodes,
edges,
};
}
/** 从接口/假数据的整包 body 里取出 data 数组并归一化成树。 */
function bodyToTree(body: unknown): PurposeTreeNode[] {
const data =