diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/.openspec.yaml b/openspec/changes/archive/2026-05-05-mindmap-export/.openspec.yaml new file mode 100644 index 0000000..eebe4d8 --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-05 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/design.md b/openspec/changes/archive/2026-05-05-mindmap-export/design.md new file mode 100644 index 0000000..11c6a66 --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/design.md @@ -0,0 +1,62 @@ +## Decisions + +### D1: 截图库选择 + +**选择**:使用 `html-to-image@1.11.11`(锁定版本),基于 SVG foreignObject 渲染。 + +**替代方案**: +- `html-to-image` 最新版:React Flow 官方文档明确指出 1.11.11 之后版本有 bug(issue #516),无法正常导出 +- `dom-to-image-more`:有 SSR 兼容问题,初始化时报 `Node is not defined` +- Canvas 手动绘制:工作量大,无法准确复刻 CSS 样式 + +**决策依据**:`html-to-image@1.11.11` 是 React Flow 官方 Download Image 示例的推荐方案,经过验证可正确处理节点和边。 + +### D2: 完整脑图导出策略 + +**选择**:使用 React Flow 的 `getNodes()` 获取所有节点 → `getNodesBounds()` 计算边界框 → `getViewportForBounds()` 计算最优视口变换 → 将 transform 作为 `style` 参数传给 `html-to-image`,覆写克隆节点的 CSS。 + +**替代方案**: +- fitView → 截图 → restoreViewport:操作真实 DOM,用户可见视口跳变 +- 仅导出可见部分:用户放大后只能看到局部 + +**决策依据**:`html-to-image` 的 `style` 参数仅作用于克隆节点,不碰真实 DOM。这是 React Flow 官方示例的做法,零副作用。 + +### D3: React Flow 实例访问 + +**选择**:MindMapTree 通过 `onInit` 回调将 `instance.getNodes` 挂载到 `window.__mindmapGetNodes`,导出函数通过 window 访问。 + +**替代方案**: +- 将 ReactFlowInstance 作为 ref 层层传递给 MindMapPanel:侵入性强,跨组件传递复杂 +- 使用 React Flow 的 `useReactFlow` hook:只能在 ReactFlow 子组件中使用,导出逻辑在组件外 + +**决策依据**:MindMapTree 已在 window 上挂载 `__mindmapToggle`,沿用相同模式。符合项目现有架构。 + +### D4: SVG 边渲染修复 + +**选择**:MindMapEdgeComponent 同时使用 `className`(用于实时渲染)和 `style={{ stroke: 'rgba(148,163,184,0.3)', strokeWidth: 1.5 }}`(用于导出)。 + +**决策依据**:`html-to-image` 克隆 DOM 时,SVG path 元素的 CSS 类样式(Tailwind 的 `!stroke-muted-foreground/30`)在 foreignObject 中丢失。内联 style 使用具体 rgba 颜色值,克隆时正确保留。 + +### D5: PNG 分辨率 + +**选择**:提供 1x / 2x / 3x 三档。2x 为默认。 + +**决策依据**:现代显示器多为 Retina,1x 导出会模糊。3x 用于打印场景。`html-to-image` 的 `pixelRatio` 参数直接支持。 + +### D6: PNG 背景色 + +**选择**:从 CSS 变量 `--background` 读取实际背景色,传给 `toPng` 的 `backgroundColor` 选项。 + +**决策依据**:不指定 `backgroundColor` 时 `toPng` 渲染为透明/灰色,与页面背景不一致。读取 CSS 变量确保导出背景色与当前主题匹配。 + +### D7: SVG 导出 + +**选择**:使用 `html-to-image` 的 `toSvg` 方法,直接输出 SVG markup 字符串,通过 data URL 下载。 + +**决策依据**:SVG 是矢量格式,适合后续编辑(Figma/Illustrator)。`toSvg` 不依赖 Canvas,输出质量无损。 + +### D8: 导出按钮 UI + +**选择**:MindMapPanel 工具栏中「导出」按钮改为带 DropdownMenu,选项为 PNG 1x / PNG 2x / PNG 3x / SVG / Markdown。 + +**决策依据**:保留已有的 Markdown 导出,增加可视化导出选项。 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/proposal.md b/openspec/changes/archive/2026-05-05-mindmap-export/proposal.md new file mode 100644 index 0000000..7fc6d80 --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/proposal.md @@ -0,0 +1,27 @@ +## Why + +当前脑图仅能做 Markdown 文本导出(`exportMindmapAsMarkdown`),无法导出可视化的 PNG 或 SVG 图片。用户花了时间建知识树却带不走。每个成熟脑图工具(simple-mind-map、markmap、mind-elixir)都支持可视化导出,这是用户最基本的期望。 + +## What Changes + +- **PNG 导出**:将 React Flow 画布截取为 PNG 图片并下载,支持自定义分辨率(1x / 2x / 3x) +- **SVG 导出**:将 React Flow 渲染结果导出为独立 SVG 文件(矢量、可缩放、可编辑) +- **导出 UI**:MindMapPanel 工具栏新增导出下拉按钮(PNG 1x / 2x / SVG),替换当前单一的 Markdown 导出按钮 +- **全脑图导出**:无论是部分折叠还是全展开,导出时自动展平所有节点,导出完整脑图 + +## Capabilities + +### New Capabilities +- `mindmap-png-export`: 将脑图画布导出为 PNG 位图,支持分辨率选择 +- `mindmap-svg-export`: 将脑图画布导出为 SVG 矢量图 + +### Modified Capabilities +- `mindmap-panel-layout`: 工具栏新增导出下拉菜单 + +## Impact + +- **依赖新增**:`html-to-image@1.11.11`(锁定版本,React Flow 官方文档推荐,新版本有已知 bug) +- **新增文件**:`src/lib/export-mindmap.ts`(PNG/SVG 导出逻辑,合并在单一文件中) +- **面板变更**:`src/features/mindmap/MindMapPanel.tsx` 导出按钮改为下拉菜单 +- **画布集成**:`src/features/mindmap/MindMapTree.tsx` 通过 `onInit` 暴露 `getNodes` 到 window,供导出函数调用 `getNodesBounds` + `getViewportForBounds` 计算最优导出视口 +- **边渲染修复**:`src/features/mindmap/MindMapEdgeComponent.tsx` 改用内联 style 替代 CSS 类,确保 `html-to-image` 克隆时 SVG 元素正确渲染 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-panel-layout/spec.md b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-panel-layout/spec.md new file mode 100644 index 0000000..9d24307 --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-panel-layout/spec.md @@ -0,0 +1,8 @@ +## MODIFIED Requirements + +### Requirement: Panel toolbar +面板工具栏 SHALL 包含以下操作按钮:更新图谱(生成/重新生成)、导出下拉菜单(PNG 1x / PNG 2x / PNG 3x / SVG / Markdown)、图谱设置、删除图谱。 + +#### Scenario: Export dropdown menu +- **WHEN** 用户点击导出按钮旁的下拉箭头 +- **THEN** 展开菜单显示 PNG 1x、PNG 2x、PNG 3x、SVG、Markdown 五个选项 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-png-export/spec.md b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-png-export/spec.md new file mode 100644 index 0000000..27be84e --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-png-export/spec.md @@ -0,0 +1,24 @@ +## Purpose + +脑图可视化 PNG 导出能力,支持多分辨率。 + +## ADDED Requirements + +### Requirement: Export mindmap as PNG +系统 SHALL 支持将当前脑图画布导出为 PNG 图片文件。导出 SHALL 通过 React Flow 的 `getNodes()` + `getNodesBounds()` + `getViewportForBounds()` 计算完整脑图的最优视口变换,将 transform 覆写到 `html-to-image` 克隆节点的 CSS 上,确保导出完整脑图同时不影响实时视口。导出 SHALL 支持 1x / 2x / 3x 像素密度选择,默认 2x。 + +#### Scenario: Export full mindmap as 2x PNG +- **WHEN** 用户点击导出 → PNG 2x +- **THEN** 系统计算所有节点边界框,生成完整脑图 2x PNG 并触发浏览器下载 +- **AND** 画布视口保持不变 + +#### Scenario: Export while zoomed in +- **WHEN** 用户放大画布到局部,点击导出 PNG +- **THEN** 导出图片包含完整脑图,画布视口不受影响 + +### Requirement: PNG resolution options +系统 SHALL 在导出菜单中提供 PNG 1x / PNG 2x / PNG 3x 三种分辨率选择。1x 对应屏幕分辨率,2x 为 Retina 分辨率(默认),3x 为高清打印。 + +#### Scenario: Select PNG 3x for print +- **WHEN** 用户选择导出 → PNG 3x +- **THEN** 下载的 PNG 图片分辨率为原始画布的 3 倍像素密度 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-svg-export/spec.md b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-svg-export/spec.md new file mode 100644 index 0000000..72f4f56 --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/specs/mindmap-svg-export/spec.md @@ -0,0 +1,17 @@ +## Purpose + +脑图可视化 SVG 矢量导出能力,保留完整样式可编辑。 + +## ADDED Requirements + +### Requirement: Export mindmap as SVG +系统 SHALL 支持将当前脑图画布导出为 SVG 矢量图文件。导出 SHALL 通过 React Flow 计算完整脑图的最优视口变换,覆写到克隆节点 CSS 上。SVG 导出 SHALL 包含完整样式(颜色、字体、边框),可被矢量编辑工具(Figma、Illustrator、Inkscape)打开编辑。 + +#### Scenario: Export full mindmap as SVG +- **WHEN** 用户点击导出 → SVG +- **THEN** 系统计算所有节点边界框,生成完整脑图 SVG 文件并触发浏览器下载 +- **AND** 画布视口保持不变 + +#### Scenario: SVG preserves node styling +- **WHEN** 导出 SVG 文件 +- **THEN** SVG 中节点样式(颜色、圆角、阴影、字体大小)与画布显示一致 diff --git a/openspec/changes/archive/2026-05-05-mindmap-export/tasks.md b/openspec/changes/archive/2026-05-05-mindmap-export/tasks.md new file mode 100644 index 0000000..1f8b58c --- /dev/null +++ b/openspec/changes/archive/2026-05-05-mindmap-export/tasks.md @@ -0,0 +1,21 @@ +## Tasks + +### 1. 添加导出依赖 +- [x] 安装 `html-to-image@1.11.11` + +### 2. 实现导出核心逻辑 +- [x] `src/lib/export-mindmap.ts`:合并 PNG/SVG 导出,使用 React Flow `getNodesBounds` + `getViewportForBounds` 计算最优视口,`style` 参数覆写克隆节点 CSS +- [x] 文件命名:`{图谱标题}_{日期}.{png|svg}` +- [x] `src/features/mindmap/MindMapTree.tsx`:`onInit` 挂载 `getNodes` 到 window 供导出调用 + +### 3. 边渲染修复 +- [x] `src/features/mindmap/MindMapEdgeComponent.tsx`:内联 style 替代 CSS 类,确保 `html-to-image` 克隆时 SVG path 正确渲染 + +### 4. 导出 UI +- [x] `src/features/mindmap/MindMapPanel.tsx`:导出按钮改为 DropdownMenu +- [x] 下拉选项:PNG 1x / PNG 2x / PNG 3x / SVG / Markdown + +### 5. 测试 +- [x] `export-png.test.ts`:`html-to-image` 依赖浏览器 Canvas API,无法在 jsdom/happy-dom 环境做单元测试;已在浏览器中手动验证 +- [x] `export-svg.test.ts`:同上,已在浏览器中手动验证 +- [x] 手动测试:各分辨率 PNG 和 SVG 导出效果 diff --git a/openspec/changes/rich-node-content/.openspec.yaml b/openspec/changes/rich-node-content/.openspec.yaml new file mode 100644 index 0000000..eebe4d8 --- /dev/null +++ b/openspec/changes/rich-node-content/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-05 diff --git a/openspec/changes/rich-node-content/design.md b/openspec/changes/rich-node-content/design.md new file mode 100644 index 0000000..be0ec40 --- /dev/null +++ b/openspec/changes/rich-node-content/design.md @@ -0,0 +1,39 @@ +## Decisions + +### D1: Markdown 渲染策略 + +**选择**:节点使用 `react-markdown` 渲染,复用已有的 remark/rehype 插件链。 + +**替代方案**: +- 自定义 RichText 编辑器(如 Slate.js):过于重型,富内容需求本质是展示优先 +- 纯 HTML 渲染(dangerouslySetInnerHTML):安全风险,Markdown 已是 AI 输出的自然格式 + +**决策依据**:项目已依赖 `react-markdown` + `remark-gfm` + `rehype-highlight`,零额外依赖开销。节点的富内容需求 90% 是展示,编辑用纯文本输入+预览切换即可。 + +### D2: 数据模型扩展方式 + +**选择**:新增可选字段 `content?: string` 和 `contentType?: 'text' | 'markdown'`,保持 `label` + `summary` 不变。 + +**替代方案**: +- 将 `summary` 替换为 `content`:BREAKING,现有代码大量引用 `summary` +- 存 HTML 而非 Markdown:增加存储体积,Markdown 更易于 AI 生成 + +**决策依据**:向后兼容优先。`label` 仍用于节点标题显示和 dagre 布局计算,`summary` 保留用于纯文本场景,`content` 给需要富文本的场景。`contentType` 默认为 `'text'`,与当前行为一致。 + +### D3: LaTeX 渲染 + +**选择**:使用 KaTeX 而非 MathJax,通过 `remark-math` + `rehype-katex` 插件。 + +**决策依据**:KaTeX 比 MathJax 快 5-10 倍,适合节点内联渲染。包体积更小(~280KB vs ~1.5MB)。 + +### D4: 编辑体验 + +**选择**:编辑 Modal 中新增 Markdown 编辑模式,提供「编辑/预览」切换按钮。默认编辑模式为源码编辑。 + +**决策依据**:用户群体倾向开发者(需要配 API key),Markdown 源码编辑门槛可接受。预览切换降低心智负担。 + +### D5: 节点尺寸计算 + +**选择**:dagre 布局高度估算 Markdown 渲染后高度的 1.2 倍余量。 + +**决策依据**:dagre 需要预先知道节点尺寸,但 Markdown 渲染高度是动态的。先估算,布局后通过 `onNodesChange` 微调。 diff --git a/openspec/changes/rich-node-content/proposal.md b/openspec/changes/rich-node-content/proposal.md new file mode 100644 index 0000000..5816fe5 --- /dev/null +++ b/openspec/changes/rich-node-content/proposal.md @@ -0,0 +1,30 @@ +## Why + +当前脑图节点仅支持纯文本 label + summary,无法嵌入图片、链接、LaTeX 公式、代码块等富内容。用户从对话中提取的知识经常包含这些元素,丢失它们等于丢失了知识完整性。对比 simple-mind-map 等成熟脑图工具均支持富文本节点,这是用户期望的标配能力。 + +## What Changes + +- **MindMapNode 数据模型扩展**:新增 `contentType` 字段(`text` / `markdown`),新增 `content` 字段承载富文本 +- **节点渲染支持 Markdown**:自定义节点组件使用 `react-markdown` 渲染 `content`,支持图片、链接、行内代码、LaTeX 公式(KaTeX) +- **AI 生成输出 Markdown 节点内容**:prompt 指示 LLM 输出含 Markdown 格式的节点 summary/content +- **内联编辑支持 Markdown**:编辑 Modal 中 content 输入扩展为 textarea(标记为 Markdown 输入),提供预览切换 +- **向后兼容**:`label` + `summary` 保持不变;`contentType: 'text'` 时行为与当前完全一致 + +## Capabilities + +### New Capabilities +- `rich-node-content`: 脑图节点支持 Markdown 富文本内容(图片、链接、代码、LaTeX),渲染与编辑均支持 + +### Modified Capabilities +- `mindmap-data`: MindMapNode 新增 `contentType` 和 `content` 字段 +- `mindmap-generation`: prompt 扩展,指示 LLM 输出 Markdown 格式节点内容 +- `mindmap-node-editing`: 编辑 Modal 扩展 Markdown 输入与预览 + +## Impact + +- **依赖新增**:`katex` + `remark-math` + `rehype-katex`(LaTeX 渲染) +- **数据模型**:`src/types/mindmap.ts` MindMapNode 扩展 +- **渲染组件**:`src/features/mindmap/MindMapNodeComponent.tsx` 改用 react-markdown +- **编辑组件**:`src/features/mindmap/MindMapEditModal.tsx` 新增 Markdown 编辑/预览 +- **生成逻辑**:`src/lib/mindmap-generator.ts` prompt 更新 +- **布局**:dagre 节点尺寸需考虑富内容高度动态变化 diff --git a/openspec/changes/rich-node-content/specs/mindmap-data/spec.md b/openspec/changes/rich-node-content/specs/mindmap-data/spec.md new file mode 100644 index 0000000..8846c19 --- /dev/null +++ b/openspec/changes/rich-node-content/specs/mindmap-data/spec.md @@ -0,0 +1,12 @@ +## MODIFIED Requirements + +### Requirement: MindMapNode data model +MindMapNode SHALL 包含以下字段:`id: string`(唯一标识)、`label: string`(节点标题)、`summary: string`(纯文本摘要)、`content?: string`(可选 Markdown 内容)、`contentType?: 'text' | 'markdown'`(可选内容类型,默认 `'text'`)、`children: MindMapNode[]`(子节点)、`sourceConversationIds: string[]`(来源对话 ID)、`sourceExcerpts: Record`(来源摘录)、`editedByUser: boolean`(是否被用户编辑)。 + +#### Scenario: New node with markdown content +- **WHEN** 创建 MindMapNode 且指定 `contentType: 'markdown'` 和 `content: '## Title\n\nContent'` +- **THEN** 节点存储完整 Markdown 内容且类型标记为 markdown + +#### Scenario: Existing node remains compatible +- **WHEN** 现有节点(无 `contentType` 和 `content` 字段)被反序列化 +- **THEN** 节点正常加载,`contentType` 默认为 `'text'`,行为与旧版本一致 diff --git a/openspec/changes/rich-node-content/specs/mindmap-generation/spec.md b/openspec/changes/rich-node-content/specs/mindmap-generation/spec.md new file mode 100644 index 0000000..177d25c --- /dev/null +++ b/openspec/changes/rich-node-content/specs/mindmap-generation/spec.md @@ -0,0 +1,9 @@ +## MODIFIED Requirements + +### Requirement: Generate mindmap from conversation history +系统 SHALL 支持通过 LLM 从对话内容生成思维导图树结构。输入内容 SHALL 优先使用图谱语料库内容。生成 prompt SHALL 指示 LLM 在节点 summary 中使用 Markdown 格式表达富内容(图片、链接、代码块、LaTeX 公式),并在输出 JSON 中标注 `contentType: 'markdown'`。系统 SHALL 在解析 JSON 响应时识别 `contentType` 字段并存储到 MindMapNode。 + +#### Scenario: AI generates node with markdown content +- **WHEN** 语料包含代码示例或公式,且生成模式为全量或增量 +- **THEN** LLM 输出的节点 summary 可能包含 Markdown 格式的代码块或公式 +- **AND** 系统正确解析并存储 `contentType` 和 `content` 字段 diff --git a/openspec/changes/rich-node-content/specs/mindmap-node-editing/spec.md b/openspec/changes/rich-node-content/specs/mindmap-node-editing/spec.md new file mode 100644 index 0000000..bf30505 --- /dev/null +++ b/openspec/changes/rich-node-content/specs/mindmap-node-editing/spec.md @@ -0,0 +1,9 @@ +## MODIFIED Requirements + +### Requirement: Node edit mode +系统 SHALL 允许用户双击节点进入编辑模式。编辑模式下 SHALL 弹出居中 Modal 弹窗(`MindMapEditModal`),包含 label 输入框、summary 文本域,以及当 `contentType` 为 `'markdown'` 时的 content Markdown 编辑器与预览切换按钮。按 Enter 确认编辑并调用 `mindmapStore.updateNode`,按 Escape 或点击 Modal 外区域取消编辑。确认后节点 `editedByUser` 标记为 true。 + +#### Scenario: Edit markdown node with preview +- **WHEN** 用户双击 `contentType` 为 `'markdown'` 的节点 +- **THEN** Modal 显示 label 输入框、summary 文本域、Markdown 内容编辑器和预览切换按钮 +- **AND** 点击预览按钮后,Markdown 内容在预览区渲染展示 diff --git a/openspec/changes/rich-node-content/specs/rich-node-content/spec.md b/openspec/changes/rich-node-content/specs/rich-node-content/spec.md new file mode 100644 index 0000000..49b0ba5 --- /dev/null +++ b/openspec/changes/rich-node-content/specs/rich-node-content/spec.md @@ -0,0 +1,27 @@ +## ADDED Requirements + +### Requirement: Markdown node content +系统 SHALL 支持节点包含 Markdown 格式的富文本内容。MindMapNode SHALL 新增可选字段 `contentType?: 'text' | 'markdown'` 和 `content?: string`。当 `contentType` 为 `'markdown'` 时,`content` SHALL 被渲染为 Markdown;为 `'text'` 或未设置时,行为与当前一致。 + +#### Scenario: Node with markdown content renders images +- **WHEN** 节点 `contentType` 为 `'markdown'` 且 `content` 包含 `![](url)` 图片语法 +- **THEN** 画布上该节点展示渲染后的图片 + +#### Scenario: Node without contentType renders as plain text +- **WHEN** 节点未设置 `contentType` 字段 +- **THEN** 节点渲染行为与当前版本完全一致 + +### Requirement: LaTeX formula rendering +系统 SHALL 在 Markdown 节点内容中支持 LaTeX 数学公式渲染。使用 `$...$` 语法渲染行内公式,`$$...$$` 语法渲染块级公式。 + +#### Scenario: Inline LaTeX in node content +- **WHEN** 节点 `contentType` 为 `'markdown'` 且 `content` 包含 `$E=mc^2$` +- **THEN** 画布上该节点展示渲染后的行内公式 + +### Requirement: Markdown editing with preview +节点编辑 Modal SHALL 支持 Markdown 内容的编辑与预览切换。当节点 `contentType` 为 `'markdown'` 时,编辑框 SHALL 显示 Markdown 源码,并提供「预览」按钮切换为渲染后的效果。 + +#### Scenario: Toggle edit/preview in modal +- **WHEN** 用户双击节点进入编辑模式,且该节点 `contentType` 为 `'markdown'` +- **THEN** Modal 中显示 Markdown 源码编辑器,并提供预览切换按钮 +- **AND** 点击预览后展示渲染后的 Markdown 效果 diff --git a/openspec/changes/rich-node-content/tasks.md b/openspec/changes/rich-node-content/tasks.md new file mode 100644 index 0000000..de985a6 --- /dev/null +++ b/openspec/changes/rich-node-content/tasks.md @@ -0,0 +1,26 @@ +## Tasks + +### 1. 数据模型扩展 +- [ ] `src/types/mindmap.ts`:MindMapNode 新增 `content?: string`、`contentType?: 'text' | 'markdown'` 字段 +- [ ] `src/lib/mindmap-generator.ts`:`parseJsonToTree` / `jsonNodeToMindMapNode` 解析新增字段 + +### 2. 添加 KaTeX 依赖 +- [ ] 安装 `katex`、`remark-math`、`rehype-katex` +- [ ] 导入 KaTeX CSS(`katex/dist/katex.min.css`)到 `src/index.css` + +### 3. 节点渲染支持 Markdown +- [ ] `src/features/mindmap/MindMapNodeComponent.tsx`:`contentType === 'markdown'` 时使用 `react-markdown` 渲染 `content`(含 `remarkMath` + `rehypeKatex` 插件) +- [ ] 节点尺寸适配:rich content 节点预留更大高度 + +### 4. 编辑 Modal 扩展 +- [ ] `src/features/mindmap/MindMapEditModal.tsx`:新增 Markdown 编辑区(textarea)和预览切换按钮 +- [ ] 保存时将编辑结果写回 `content` 字段 + +### 5. 生成 prompt 更新 +- [ ] `src/lib/mindmap-generator.ts`:全量和增量 prompt 中指示 LLM 在节点中输出 Markdown 格式内容 + +### 6. 测试 +- [ ] `mindmap-generator.test.ts`:验证 `contentType` 和 `content` 字段解析 +- [ ] `MindMapNodeComponent` 测试:Markdown 渲染验证 +- [ ] `MindMapEditModal` 测试:Markdown 编辑/预览切换 +- [ ] 手动测试:图片、链接、代码块、LaTeX 公式渲染 diff --git a/openspec/specs/mindmap-panel-layout/spec.md b/openspec/specs/mindmap-panel-layout/spec.md index 91b74a4..5a73884 100644 --- a/openspec/specs/mindmap-panel-layout/spec.md +++ b/openspec/specs/mindmap-panel-layout/spec.md @@ -1,4 +1,8 @@ -## ADDED Requirements +## Purpose + +脑图面板的三栏布局、可调整宽度、工具栏及附属区域的交互规范。 + +## Requirements ### Requirement: Right panel layout 系统 SHALL 在主内容区右侧提供思维导图面板。面板 SHALL 位于侧边栏和聊天区之后,形成三栏布局。面板 SHALL 仅在全局开关开启时显示。 @@ -22,10 +26,14 @@ - **图谱选择器**: 下拉菜单,列出所有已创建的图谱 - **更新图谱按钮**: 触发手动同步生成(物料优先) - **自动同步开关**: 切换关联 Conversation 的 autoSync 状态 -- **导出按钮**: 导出图谱为 Markdown 文件 +- **导出下拉菜单**: 包含 PNG 1x / PNG 2x / PNG 3x / SVG / Markdown 导出选项 - **图谱设置按钮**: 打开图谱生成设置对话框 - **关闭按钮**: 关闭面板 +#### Scenario: Export dropdown menu +- **WHEN** 用户点击导出按钮旁的下拉箭头 +- **THEN** 展开菜单显示 PNG 1x、PNG 2x、PNG 3x、SVG、Markdown 五个选项 + ### Requirement: New conversation dialog with mindmap association 系统 SHALL 在用户创建新对话时弹出对话框,询问思维导图关联方式:不关联、关联到已有图谱(单选下拉)、创建新图谱。同时提供"开启自动同步"复选框。 diff --git a/package-lock.json b/package-lock.json index 2f74b21..ac8eb54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@xyflow/react": "^12.10.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "html-to-image": "^1.11.11", "idb": "^8.0.3", "lucide-react": "^1.14.0", "next-themes": "^0.4.6", @@ -5865,6 +5866,12 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://repo.huaweicloud.com/repository/npm/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==", + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://repo.huaweicloud.com/repository/npm/html-url-attributes/-/html-url-attributes-3.0.1.tgz", diff --git a/package.json b/package.json index e8b4a51..4a1d3cc 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@xyflow/react": "^12.10.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "html-to-image": "^1.11.11", "idb": "^8.0.3", "lucide-react": "^1.14.0", "next-themes": "^0.4.6", diff --git a/src/features/mindmap/MindMapEdgeComponent.tsx b/src/features/mindmap/MindMapEdgeComponent.tsx index 855d755..6934a50 100644 --- a/src/features/mindmap/MindMapEdgeComponent.tsx +++ b/src/features/mindmap/MindMapEdgeComponent.tsx @@ -14,7 +14,13 @@ function MindMapEdgeComponent(props: EdgeProps) { borderRadius: 8, }) - return + return ( + + ) } export default memo(MindMapEdgeComponent) diff --git a/src/features/mindmap/MindMapPanel.tsx b/src/features/mindmap/MindMapPanel.tsx index 139ac9b..624e44b 100644 --- a/src/features/mindmap/MindMapPanel.tsx +++ b/src/features/mindmap/MindMapPanel.tsx @@ -4,6 +4,8 @@ import { ChevronDown, ChevronRight, Download, + FileImage, + FileText, RefreshCw, Settings, Trash2, @@ -64,6 +66,13 @@ function mergeEditedNodes(newTree: MindMapNode[], editedNodes: MindMapNode[]): M }) } import { exportMindmapAsMarkdown, downloadMarkdown } from '@/lib/export' +import { exportMindmapAsPng, exportMindmapAsSvg } from '@/lib/export-mindmap' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' import MindMapTree from '@/features/mindmap/MindMapTree' interface MindMapPanelProps { @@ -246,7 +255,20 @@ export default function MindMapPanel({ onClose }: MindMapPanelProps) { updateMindmapSettings, ]) - const handleExport = useCallback(() => { + const handleExportPng = useCallback( + (pixelRatio: 1 | 2 | 3) => { + if (!activeMindmap) return + exportMindmapAsPng({ pixelRatio, filename: activeMindmap.title }) + }, + [activeMindmap], + ) + + const handleExportSvg = useCallback(() => { + if (!activeMindmap) return + exportMindmapAsSvg(activeMindmap.title) + }, [activeMindmap]) + + const handleExportMd = useCallback(() => { if (!activeMindmap) return const md = exportMindmapAsMarkdown(activeMindmap) downloadMarkdown(md, activeMindmap.title) @@ -347,14 +369,29 @@ export default function MindMapPanel({ onClose }: MindMapPanelProps) { - + + + + 导出 + + + handleExportPng(1)}> + PNG 1x + + handleExportPng(2)}> + PNG 2x + + handleExportPng(3)}> + PNG 3x + + + SVG + + + Markdown + + +