use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapProjection { pub schema: String, pub map_id: String, pub root_node: String, #[serde(default)] pub nodes: Vec, #[serde(default)] pub edges: Vec, #[serde(default)] pub layout_hints: Value, pub revision: u64, pub owner: MindmapProjectionOwner, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapProjectionNode { pub node_id: String, pub title: String, #[serde(skip_serializing_if = "Option::is_none")] pub parent_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapProjectionEdge { pub edge_id: String, pub from_node_id: String, pub to_node_id: String, pub edge_kind: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum MindmapProjectionOwner { RustKernel, CompatBlob, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum MindmapProjectionSource { Kernel, CompatBlob, AdapterCache, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapKernelProjection { pub schema: String, pub document_id: String, pub mindmap_id: String, pub root_node_id: String, pub revision: u64, #[serde(default)] pub nodes: Vec, #[serde(default)] pub edges: Vec, #[serde(default)] pub summaries: Vec, #[serde(default)] pub associative_lines: Vec, #[serde(default)] pub layout: Value, #[serde(default)] pub theme: Value, #[serde(default)] pub view: Value, pub capabilities: MindmapKernelCapabilities, pub source: MindmapProjectionSource, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapKernelNode { pub node_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub parent_id: Option, pub text: String, #[serde(default)] pub order: i64, #[serde(default)] pub collapsed: bool, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub style: BTreeMap, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub refs: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapKernelEdge { pub edge_id: String, pub from_node_id: String, pub to_node_id: String, pub edge_kind: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapSummary { pub summary_id: String, pub parent_node_id: String, #[serde(default)] pub node_ids: Vec, pub text: String, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub style: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapAssociativeLine { pub line_id: String, pub from_node_id: String, pub to_node_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub text: Option, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub style: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapKernelCapabilities { pub can_edit: bool, pub can_edit_text: bool, pub can_insert_child: bool, pub can_insert_sibling_after: bool, pub can_delete_node: bool, pub can_move_node: bool, pub can_patch_view: bool, pub can_set_layout: bool, pub can_set_theme: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapAdapterProjection { pub schema: String, pub runtime: String, pub root: Value, #[serde(default)] pub layout: Value, #[serde(default)] pub theme: Value, #[serde(default)] pub theme_config: Value, #[serde(default)] pub view: Value, #[serde(default)] pub config: Value, #[serde(default)] pub compat_payload: Value, pub kernel_revision: u64, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "camelCase")] pub enum MindmapKernelCommand { UpdateText { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, text: String, }, InsertChild { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, #[serde(rename = "parentNodeId", alias = "parent_node_id", alias = "parentId")] parent_node_id: String, node: MindmapNodeInput, }, InsertSiblingAfter { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, #[serde(rename = "targetNodeId", alias = "target_node_id")] target_node_id: String, node: MindmapNodeInput, }, DeleteNode { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, }, MoveNode { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, #[serde( rename = "newParentNodeId", alias = "new_parent_node_id", alias = "newParentId" )] new_parent_node_id: String, #[serde(rename = "order", alias = "sortOrder", alias = "sort_order")] order: Option, }, PatchView { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, patch: Value, }, SetLayout { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, layout: Value, }, SetTheme { #[serde(rename = "mindmapId", alias = "mindmap_id", alias = "mapId")] mindmap_id: String, theme: Value, #[serde(default)] #[serde(rename = "themeConfig", alias = "theme_config")] theme_config: Value, }, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "camelCase")] pub enum MindmapCommand { CreateNode { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "parentId", alias = "parent_id")] parent_id: String, node: MindmapNodeInput, }, RenameNode { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, title: String, }, MoveNode { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, #[serde(rename = "newParentId", alias = "new_parent_id")] new_parent_id: String, #[serde(rename = "sortOrder", alias = "sort_order")] sort_order: Option, }, DeleteNode { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, }, SetLayout { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "layoutHints", alias = "layout_hints")] layout_hints: Value, }, AttachPageRef { #[serde(rename = "mapId", alias = "map_id")] map_id: String, #[serde(rename = "nodeId", alias = "node_id")] node_id: String, #[serde(rename = "pageId", alias = "page_id")] page_id: String, }, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapNodeRef { pub kind: String, #[serde(skip_serializing_if = "Option::is_none")] pub asset_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub file_url: Option, #[serde(skip_serializing_if = "Option::is_none")] pub page: Option, #[serde(skip_serializing_if = "Option::is_none")] pub slide: Option, #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] pub snippet: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MindmapNodeData { #[serde(skip_serializing_if = "Option::is_none")] pub uid: Option, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hyperlink: Option, #[serde(skip_serializing_if = "Option::is_none")] pub note: Option, #[serde(skip_serializing_if = "Option::is_none")] pub refs: Option>, #[serde(flatten, default)] pub extra: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct MindmapTreeNode { pub data: MindmapNodeData, #[serde(default)] pub children: Vec, #[serde(flatten, default)] pub extra: BTreeMap, } /// 兼容 DTO:对应 simple-mind-map / KMind 风格树节点。 /// 长期事实源应使用 `MindmapKernelProjection` 与 kernel command。 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MindmapNodeInput { #[serde(skip_serializing_if = "Option::is_none")] pub uid: Option, pub text: String, #[serde(skip_serializing_if = "Option::is_none")] pub hyperlink: Option, #[serde(skip_serializing_if = "Option::is_none")] pub note: Option, #[serde(skip_serializing_if = "Option::is_none")] pub refs: Option>, } /// 兼容 DTO:用于历史 AI 工具和旧 blob 操作。 /// 新的 leptos-mindmap 写面应优先使用 `MindmapKernelCommand`。 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "op", rename_all = "camelCase")] pub enum MindmapOp { #[serde(alias = "add_child")] AddChild { #[serde(rename = "parentUid")] #[serde(alias = "parent_uid")] parent_uid: String, node: MindmapNodeInput, }, #[serde(alias = "add_sibling_after")] AddSiblingAfter { #[serde(rename = "targetUid")] #[serde(alias = "target_uid")] target_uid: String, node: MindmapNodeInput, }, #[serde(alias = "update_node", alias = "updateNode")] UpdateText { uid: String, #[serde(alias = "value")] text: String, }, #[serde(alias = "set_link", alias = "set_hyperlink")] SetHyperlink { #[serde(alias = "id")] uid: String, #[serde(alias = "url")] hyperlink: Option, }, #[serde(alias = "set_refs")] SetRefs { #[serde(alias = "id")] uid: String, refs: Vec, }, #[serde(alias = "append_note")] AppendNote { #[serde(alias = "id")] uid: String, #[serde(alias = "note")] markdown: String, }, #[serde(alias = "delete_node")] DeleteNode { #[serde(alias = "id")] uid: String, }, }