Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-05
62 changes: 62 additions & 0 deletions openspec/changes/archive/2026-05-05-mindmap-export/design.md
Original file line number Diff line number Diff line change
@@ -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 导出,增加可视化导出选项。
27 changes: 27 additions & 0 deletions openspec/changes/archive/2026-05-05-mindmap-export/proposal.md
Original file line number Diff line number Diff line change
@@ -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 元素正确渲染
Original file line number Diff line number Diff line change
@@ -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 五个选项
Original file line number Diff line number Diff line change
@@ -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 倍像素密度
Original file line number Diff line number Diff line change
@@ -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 中节点样式(颜色、圆角、阴影、字体大小)与画布显示一致
21 changes: 21 additions & 0 deletions openspec/changes/archive/2026-05-05-mindmap-export/tasks.md
Original file line number Diff line number Diff line change
@@ -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 导出效果
2 changes: 2 additions & 0 deletions openspec/changes/rich-node-content/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-05
39 changes: 39 additions & 0 deletions openspec/changes/rich-node-content/design.md
Original file line number Diff line number Diff line change
@@ -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` 微调。
30 changes: 30 additions & 0 deletions openspec/changes/rich-node-content/proposal.md
Original file line number Diff line number Diff line change
@@ -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 节点尺寸需考虑富内容高度动态变化
12 changes: 12 additions & 0 deletions openspec/changes/rich-node-content/specs/mindmap-data/spec.md
Original file line number Diff line number Diff line change
@@ -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<string, string>`(来源摘录)、`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'`,行为与旧版本一致
Original file line number Diff line number Diff line change
@@ -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` 字段
Original file line number Diff line number Diff line change
@@ -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 内容在预览区渲染展示
27 changes: 27 additions & 0 deletions openspec/changes/rich-node-content/specs/rich-node-content/spec.md
Original file line number Diff line number Diff line change
@@ -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 效果
26 changes: 26 additions & 0 deletions openspec/changes/rich-node-content/tasks.md
Original file line number Diff line number Diff line change
@@ -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 公式渲染
12 changes: 10 additions & 2 deletions openspec/specs/mindmap-panel-layout/spec.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## ADDED Requirements
## Purpose

脑图面板的三栏布局、可调整宽度、工具栏及附属区域的交互规范。

## Requirements

### Requirement: Right panel layout
系统 SHALL 在主内容区右侧提供思维导图面板。面板 SHALL 位于侧边栏和聊天区之后,形成三栏布局。面板 SHALL 仅在全局开关开启时显示。
Expand All @@ -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 在用户创建新对话时弹出对话框,询问思维导图关联方式:不关联、关联到已有图谱(单选下拉)、创建新图谱。同时提供"开启自动同步"复选框。

Expand Down
Loading
Loading