diff --git a/apps/server/src/chat.ts b/apps/server/src/chat.ts index 2a685a5..4c25f96 100644 --- a/apps/server/src/chat.ts +++ b/apps/server/src/chat.ts @@ -64,9 +64,10 @@ export function createChatHandler(ctx: ToolContext) { 8. 固定坐标用 set_pinned 或 create_node 带 pos;不确定时 mindmap_get_schema。 9. 节点属性:标题用 update_text;描述/多标签/多链接/图片URL/时间用 update_node_meta(patch);边用 update_edge_meta 或 set_edge_label。 10. 节点类型 stylePreset:default|card|title|decision|risk|note|muted(title 仅画布锚点,勿新建;card=新闻快照卡片,配图用 imageUrl、摘要用 note、来源用 links)。 -11. 同级排序用 reorder:优先 {"type":"reorder","nodeId":"...","order":10};也可用 afterSiblingId(null=置顶)。 -12. 折叠 set_collapsed 会隐藏该节点主父链下的全部子孙并重新布局;展开后恢复。 -13. 问图:用 mindmap_query(neighbors|path|stats|orphans|missing_meta);答完可用 mindmap_highlight 高亮路径;细节用 mindmap_get_node / mindmap_get_edge。禁止编造图中没有的事实。 +11. 节点图标 icon(create_node / update_node_meta.patch):server|database|cloud|person|folder|doc|link|warning|check|star|gear|globe|router。拓扑排障图:建画布时可用 mindmap_create_canvas({ title, nodeViewMode:"topology" }),节点 icon:"router";nodeViewMode 是画布全局偏好,也可事后 set_prefs 修改。 +12. 同级排序用 reorder:优先 {"type":"reorder","nodeId":"...","order":10};也可用 afterSiblingId(null=置顶)。 +13. 折叠 set_collapsed 会隐藏该节点主父链下的全部子孙并重新布局;展开后恢复。 +14. 问图:用 mindmap_query(neighbors|path|stats|orphans|missing_meta);答完可用 mindmap_highlight 高亮路径;细节用 mindmap_get_node / mindmap_get_edge。禁止编造图中没有的事实。 当前空间上下文(摘要): ${JSON.stringify(spatial, null, 0).slice(0, 6000)} diff --git a/apps/server/src/create-server.ts b/apps/server/src/create-server.ts index 1e2919f..78d9395 100644 --- a/apps/server/src/create-server.ts +++ b/apps/server/src/create-server.ts @@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url"; import type { Server } from "node:http"; import { WebSocketServer, type WebSocket } from "ws"; import { SqliteGraphStore } from "@mind-map/graph-store"; -import { coerceOpInput, OpSchema, type Op } from "@mind-map/shared"; +import { coerceOpInput, OpSchema, NodeViewModeSchema, type Op } from "@mind-map/shared"; import { listSkins } from "@mind-map/dream-skin"; import { GraphError } from "@mind-map/graph-core"; import { z } from "zod"; @@ -163,8 +163,16 @@ export function startMindMapServer( const body = (await c.req.json().catch(() => ({}))) as { title?: string; rootText?: string; + nodeViewMode?: string; + prefs?: { nodeViewMode?: string }; }; - const g = store.createCanvas(body.title, body.rootText); + const viewRaw = body.nodeViewMode ?? body.prefs?.nodeViewMode; + const parsed = NodeViewModeSchema.safeParse(viewRaw); + const g = store.createCanvas( + body.title, + body.rootText, + parsed.success ? { nodeViewMode: parsed.data } : undefined, + ); broadcast({ type: "canvas/created", canvasId: g.canvas.id }); return c.json(clientCanvasPayload(store, g.canvas.id)); }); diff --git a/apps/server/src/tools.ts b/apps/server/src/tools.ts index d3c5b21..a233a3e 100644 --- a/apps/server/src/tools.ts +++ b/apps/server/src/tools.ts @@ -17,6 +17,8 @@ import { nodeFunction, OP_SCHEMA_DOC, OP_TYPES, + NodeIconSchema, + NodeViewModeSchema, } from "@mind-map/shared"; import { hierarchyFingerprint, layout } from "@mind-map/layout-engine"; @@ -481,8 +483,10 @@ function estimateOverlaps( nodeViewMode: string, anchorId: string | null, ) { - const w = nodeViewMode === "card" ? 180 : 120; - const h = nodeViewMode === "card" ? 72 : 36; + const w = + nodeViewMode === "card" ? 180 : nodeViewMode === "topology" ? 88 : 120; + const h = + nodeViewMode === "card" ? 72 : nodeViewMode === "topology" ? 64 : 36; const ids = nodes .filter((n) => !n.deletedAt && n.id !== anchorId && positions[n.id]) .map((n) => n.id); @@ -770,11 +774,30 @@ export async function runTool( rawArgs.rootText != null && String(rawArgs.rootText).trim() ? String(rawArgs.rootText) : undefined; - const g = ctx.store.createCanvas(title, rootText); + const prefsArg = + rawArgs.prefs && typeof rawArgs.prefs === "object" + ? (rawArgs.prefs as Record) + : undefined; + const viewParsed = NodeViewModeSchema.safeParse( + rawArgs.nodeViewMode ?? prefsArg?.nodeViewMode, + ); + const g = ctx.store.createCanvas( + title, + rootText, + viewParsed.success ? { nodeViewMode: viewParsed.data } : undefined, + ); return { ok: true, - canvas: { id: g.canvas.id, title: g.canvas.title, rev: g.canvas.rev }, - hint: "Canvas is empty visually. Create top-level nodes with {type:'create_node', text:'...'} (omit parentId). Use parentId only when nesting under an existing visible node.", + canvas: { + id: g.canvas.id, + title: g.canvas.title, + rev: g.canvas.rev, + prefs: { nodeViewMode: g.canvas.prefs.nodeViewMode }, + }, + hint: + g.canvas.prefs.nodeViewMode === "topology" + ? "Topology canvas: create NE nodes with icon:'router'. Omit parentId for top-level. Prefer straight hierarchy/relation links." + : "Canvas is empty visually. Create top-level nodes with {type:'create_node', text:'...'} (omit parentId). Use parentId only when nesting under an existing visible node.", }; } @@ -1506,20 +1529,12 @@ export async function runTool( "edge_note", "all", ], - nodeIcons: [ - "server", - "database", - "cloud", - "person", - "folder", - "doc", - "link", - "warning", - "check", - "star", - "gear", - "globe", - ], + nodeIcons: NodeIconSchema.options, + nodeViewModes: NodeViewModeSchema.options, + createCanvas: { + nodeViewMode: + "Optional on mindmap_create_canvas (canvas-global). Also changeable later via set_prefs.", + }, edgeLineStyles: ["solid", "dashed", "dotted"], edgeDirections: ["forward", "both", "none"], getSubgraph: { diff --git a/apps/web/public/topo/ne-router.png b/apps/web/public/topo/ne-router.png new file mode 100644 index 0000000..9d5ed53 Binary files /dev/null and b/apps/web/public/topo/ne-router.png differ diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 1ff0455..7131e50 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -238,14 +238,23 @@ export function App() { +