mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-27 15:13:37 +08:00
chore: 从版本控制移除 tests/doc/,文件本地保留
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
40c5aa74b4
commit
e948773e46
@ -1,105 +0,0 @@
|
||||
# 开发指南
|
||||
|
||||
## 启动开发环境
|
||||
|
||||
```bash
|
||||
./docs/script/start-dev.sh
|
||||
```
|
||||
|
||||
浏览器打开 `http://localhost:1420`
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
### 新增测试
|
||||
|
||||
| 测试文件 | 测试数 | 覆盖内容 |
|
||||
|----------|:---:|------|
|
||||
| `desktop/src/components/chat/PlantUMLRenderer.test.tsx` | 5 | SVG 渲染、预览弹窗、空 SVG 回退、错误状态 |
|
||||
| `src/server/__tests__/workspace-service.test.ts` | +1 | 软连接检测专项测试 |
|
||||
|
||||
### 适配测试
|
||||
|
||||
| 测试文件 | 适配内容 |
|
||||
|----------|----------|
|
||||
| `desktop/src/stores/workspacePanelStore.test.ts` | `showHidden` 参数、`isSymlink` 字段 |
|
||||
| `desktop/src/components/workspace/WorkspacePanel.test.tsx` | `isSymlink` 类型、`showHidden` 参数 |
|
||||
| `src/server/__tests__/sessions.test.ts` | `isSymlink` 字段 |
|
||||
|
||||
### 已知 Flaky 测试(与本次改动无关)
|
||||
|
||||
- `WorkspacePanel > opens to all files when the current turn has no changed files`
|
||||
- `WorkspacePanel > lazy loads the root tree...`
|
||||
- `WorkspacePanel > can expand long file previews...`
|
||||
- `workspacePanelStore > opens preview tabs...`(前提交 `showHidden` 参数导致 spy 调用次数变化)
|
||||
- `H5AccessService > enable generates a token...`(环境依赖)
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 桌面端
|
||||
cd desktop && bun run lint && bun run test -- --run
|
||||
|
||||
# 服务端
|
||||
cd .. && bun run check:server
|
||||
```
|
||||
|
||||
## 数据流
|
||||
|
||||
### 文件树加载
|
||||
|
||||
```
|
||||
WorkspacePanel UI 点击展开
|
||||
→ workspacePanelStore.toggleTreeNode(sessionId, path)
|
||||
→ workspacePanelStore.loadTree(sessionId, path, { showHidden })
|
||||
→ sessionsApi.getWorkspaceTree(sessionId, path, showHidden)
|
||||
→ GET /api/sessions/{id}/workspace/tree?showHidden=...
|
||||
→ workspaceService.readTree(sessionId, relativePath, { showHidden })
|
||||
→ fs.readdir({ withFileTypes: true })
|
||||
→ filter + map → WorkspaceTreeEntry { name, path, isDirectory, isSymlink }
|
||||
```
|
||||
|
||||
### 文件监听
|
||||
|
||||
```
|
||||
ws/handler.ts open()
|
||||
→ workspaceService.startWatcher(sessionId, workDir)
|
||||
→ fs.watch(workDir, { recursive: true })
|
||||
→ 200ms debounce
|
||||
→ sendToSession({ type: 'file_changed', sessionId, path })
|
||||
→ workspacePanelStore handleFileChanged()
|
||||
→ 刷新展开的树节点 + 刷新匹配的预览标签
|
||||
|
||||
ws/handler.ts close()
|
||||
→ workspaceService.stopWatcher(sessionId)
|
||||
```
|
||||
|
||||
### PlantUML 渲染
|
||||
|
||||
```
|
||||
MarkdownRenderer (识别 @startuml 代码块)
|
||||
→ PlantUMLRenderer
|
||||
→ POST /api/settings/plantuml/render { code }
|
||||
→ handlePlantumlRender
|
||||
→ spawn('java', ['-Djava.awt.headless=true', '-jar', jarPath, '-tsvg', '-pipe'])
|
||||
→ stdin @startuml...@enduml → stdout SVG
|
||||
→ DOMPurify.sanitize(svg)
|
||||
→ dangerouslySetInnerHTML
|
||||
```
|
||||
|
||||
### KaTeX 渲染
|
||||
|
||||
```
|
||||
MarkdownRenderer (marked 解析)
|
||||
→ 识别 $...$ (inline) / $$...$$ (block)
|
||||
→ katex.renderToString(tex, { throwOnError: false })
|
||||
→ 替换 marked HTML 占位符
|
||||
→ dangerouslySetInnerHTML
|
||||
```
|
||||
|
||||
## API 端点
|
||||
|
||||
| 方法 | 路径 | 说明 | 改动 |
|
||||
|------|------|------|------|
|
||||
| POST | `/api/settings/plantuml/render` | PlantUML 渲染(pipe 模式) | 后端重写 |
|
||||
| GET | `/api/sessions/{id}/workspace/tree` | 文件树(增加 `showHidden` 参数) | 参数新增 |
|
||||
| WS | `file_changed` | 文件变化通知 | 消息类型新增 |
|
||||
@ -1,253 +0,0 @@
|
||||
# 功能集成测试文档
|
||||
|
||||
> 本文档用于验证 `feature/workspace-enhancements` 分支的全部新增功能:
|
||||
> 隐藏文件控制、软连接显示、Mermaid 流程图、PlantUML 图表、KaTeX 公式渲染。
|
||||
|
||||
---
|
||||
|
||||
## 一、Mermaid 流程图
|
||||
|
||||
### 1.1 文件树加载流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户点击展开目录] --> B{是否为目录?}
|
||||
B -->|是| C[workspacePanelStore.toggleTreeNode]
|
||||
B -->|否| D[workspacePanelStore.openPreview]
|
||||
C --> E[loadTree: sessionId, path, showHidden]
|
||||
E --> F[GET /api/sessions/:id/workspace/tree]
|
||||
F --> G[workspaceService.readTree]
|
||||
G --> H[fs.readdir withFileTypes]
|
||||
H --> I{entry 是否为 symlink?}
|
||||
I -->|是| J[标记 isSymlink: true]
|
||||
I -->|否| K[标记 isSymlink: false]
|
||||
J --> L[UI 显示 link 图标]
|
||||
K --> L
|
||||
L --> M["返回 WorkspaceTreeEntry[]"]
|
||||
M --> N["React 渲染文件树"]
|
||||
```
|
||||
|
||||
### 1.2 文件监听流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 前端
|
||||
participant WS as WebSocket Server
|
||||
participant Watcher as fs.watch
|
||||
participant Store as workspacePanelStore
|
||||
|
||||
Client->>WS: WebSocket 连接建立
|
||||
WS->>Watcher: startWatcher(sessionId, workDir)
|
||||
Note over Watcher: fs.watch(workDir, recursive:true)
|
||||
|
||||
Watcher-->>WS: 文件变更事件
|
||||
Note over WS: 200ms 防抖
|
||||
WS->>Client: { type: 'file_changed', sessionId, path }
|
||||
|
||||
Client->>Store: handleFileChanged(sessionId, path)
|
||||
Store->>Store: 刷新展开的树节点
|
||||
Store->>Store: 刷新匹配的预览标签
|
||||
Client->>WS: WebSocket 断开
|
||||
WS->>Watcher: stopWatcher(sessionId)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、PlantUML 图表
|
||||
|
||||
### 2.1 系统架构图
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
!theme plain
|
||||
|
||||
package "桌面端 (desktop/)" {
|
||||
component [React App] as React
|
||||
component [PlantUMLRenderer] as PUML
|
||||
component [MermaidRenderer] as MMD
|
||||
component [MarkdownRenderer] as MD
|
||||
}
|
||||
|
||||
package "服务端 (src/server/)" {
|
||||
component [API Server :3456] as API
|
||||
component [PlantUML Pipe] as Pipe
|
||||
component [WebSocket] as WS
|
||||
}
|
||||
|
||||
package "后端渲染" {
|
||||
component [Java JVM] as Java
|
||||
component [plantuml.jar] as Jar
|
||||
}
|
||||
|
||||
React --> MD : Markdown 解析
|
||||
MD --> PUML : @startuml 代码块
|
||||
MD --> MMD : mermaid 代码块
|
||||
PUML --> API : POST /api/settings/plantuml/render
|
||||
API --> Pipe : spawn java -jar -pipe
|
||||
Pipe --> Java : stdin @startuml...@enduml
|
||||
Java --> Jar : 渲染
|
||||
Jar --> Java : SVG
|
||||
Java --> Pipe : stdout
|
||||
Pipe --> API : SVG string
|
||||
API --> PUML : { svg: "..." }
|
||||
PUML --> React : DOMPurify.sanitize + dangerouslySetInnerHTML
|
||||
WS --> React : file_changed 消息
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.2 组件生命周期
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
state "空闲" as Idle
|
||||
state "加载中" as Loading
|
||||
state "渲染失败" as Error
|
||||
state "渲染成功" as Success
|
||||
state "回退代码块" as Fallback
|
||||
|
||||
[*] --> Idle : 组件挂载
|
||||
Idle --> Loading : code prop 变化
|
||||
Loading --> Success : SVG 返回
|
||||
Loading --> Error : 渲染异常
|
||||
Loading --> Fallback : SVG 为 null(无 jar 配置)
|
||||
Success --> Loading : code 再次变化
|
||||
Error --> Loading : code 再次变化
|
||||
Fallback --> Loading : code 再次变化
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、KaTeX 公式渲染
|
||||
|
||||
### 3.1 行内公式
|
||||
|
||||
爱因斯坦质能方程:$E = mc^2$
|
||||
|
||||
欧拉公式:$e^{i\pi} + 1 = 0$
|
||||
|
||||
二次方程求根公式:$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$
|
||||
|
||||
### 3.2 块级公式
|
||||
|
||||
**贝叶斯定理:**
|
||||
|
||||
$$
|
||||
P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)}
|
||||
$$
|
||||
|
||||
**傅里叶变换:**
|
||||
|
||||
$$
|
||||
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) \cdot e^{-2\pi i x \xi} \, dx
|
||||
$$
|
||||
|
||||
**矩阵乘法复杂度分析:**
|
||||
|
||||
$$
|
||||
C_{i,j} = \sum_{k=1}^{n} A_{i,k} \cdot B_{k,j} \quad \Rightarrow \quad O(n^3)
|
||||
$$
|
||||
|
||||
**梯度下降更新规则:**
|
||||
|
||||
$$
|
||||
\theta_{t+1} = \theta_t - \eta \cdot \nabla_{\theta} J(\theta_t)
|
||||
$$
|
||||
|
||||
---
|
||||
|
||||
## 四、代码块展示
|
||||
|
||||
### 4.1 TypeScript — 软连接检测
|
||||
|
||||
```typescript
|
||||
// src/server/services/workspaceService.ts
|
||||
export type WorkspaceTreeEntry = {
|
||||
name: string
|
||||
path: string
|
||||
isDirectory: boolean
|
||||
isSymlink: boolean
|
||||
}
|
||||
|
||||
function readTree(sessionId: string, relativePath = '', showHidden = false) {
|
||||
const entries = await fs.readdir(resolvedPath, { withFileTypes: true })
|
||||
return entries
|
||||
.filter(filterByShowHidden(showHidden))
|
||||
.sort(directoriesFirst)
|
||||
.map((entry) => ({
|
||||
name: entry.name,
|
||||
path: buildRelativePath(entry),
|
||||
isDirectory: entry.isDirectory(),
|
||||
isSymlink: entry.isSymbolicLink(),
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 React 组件 — PlantUML 渲染
|
||||
|
||||
```tsx
|
||||
// desktop/src/components/chat/PlantUMLRenderer.tsx
|
||||
export function PlantUMLRenderer({ code }: Props) {
|
||||
const [svg, setSvg] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
api.post('/api/settings/plantuml/render', { code })
|
||||
.then((result) => {
|
||||
if (result.svg) {
|
||||
setSvg(DOMPurify.sanitize(result.svg, {
|
||||
ADD_TAGS: ['foreignObject'],
|
||||
USE_PROFILES: { svg: true, svgFilters: true },
|
||||
}))
|
||||
}
|
||||
})
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setLoading(false))
|
||||
}, [code])
|
||||
|
||||
if (loading) return <LoadingSpinner />
|
||||
if (error) return <ErrorBanner message={error} />
|
||||
if (!svg) return <CodeViewer code={code} language="plantuml" />
|
||||
return <div dangerouslySetInnerHTML={{ __html: svg }} />
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Shell 脚本 — 启动命令
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# docs/script/start-dev.sh — 一键启动开发环境
|
||||
|
||||
SERVER_PORT=3456
|
||||
|
||||
echo "==> 启动后端..."
|
||||
bun run src/server/index.ts &
|
||||
SERVER_PID=$!
|
||||
|
||||
sleep 2
|
||||
|
||||
echo "==> 启动前端..."
|
||||
cd desktop && bun run dev &
|
||||
DESKTOP_PID=$!
|
||||
|
||||
# 清理
|
||||
trap "kill $SERVER_PID $DESKTOP_PID" EXIT
|
||||
wait
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、软连接测试
|
||||
|
||||
本目录下包含两个软连接,用于验证文件树的 `isSymlink` 显示功能:
|
||||
|
||||
| 软连接 | 目标 |
|
||||
|--------|------|
|
||||
| `symlink-summary` | `~/工作总结` |
|
||||
| `symlink-ai-summary` | `~/个人总结/AI使用总结/ai使用分析/中英文 思维链对比分析_20260511.md` |
|
||||
|
||||
> 在文件树中展开本目录,软连接条目旁应显示 `🔗` 图标。
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
# 功能改动说明
|
||||
|
||||
## 分支:feature/workspace-enhancements
|
||||
|
||||
### 一、文件树增强
|
||||
|
||||
#### 1.1 隐藏文件控制
|
||||
|
||||
**入口**:文件树工具栏 `👁` 按钮
|
||||
|
||||
**行为**:
|
||||
- 默认关闭(`showHiddenFiles: false`),不显示 `.` 开头的文件/目录
|
||||
- 开启后仅过滤 `.git`,其余隐藏文件可见
|
||||
- 状态通过 `workspacePanelStore.showHiddenFiles` 管理
|
||||
- 传递给后端 `readTree` 的 `showHidden` 参数
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/server/services/workspaceService.ts` | `readTree` 添加 `showHidden` 参数,默认 `false` 过滤 `.` 前缀,`true` 仅过滤 `.git` |
|
||||
| `src/server/api/sessions.ts` | 从 query string 读取 `showHidden` 参数传给 `readTree` |
|
||||
| `desktop/src/api/sessions.ts` | `getWorkspaceTree` 添加 `showHidden` 参数,拼接到请求 query |
|
||||
| `desktop/src/stores/workspacePanelStore.ts` | 添加 `showHiddenFiles` 状态 + `toggleShowHiddenFiles` 方法,`loadTree` 传递参数 |
|
||||
| `desktop/src/components/workspace/WorkspacePanel.tsx` | 工具栏区域添加 `👁` 按钮 |
|
||||
|
||||
#### 1.2 软连接支持
|
||||
|
||||
**行为**:
|
||||
- 文件树中软连接条目显示 `🔗` (link) 图标
|
||||
- 通过 `Dirent.isSymbolicLink()` 检测,无需额外 stat 调用
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/server/services/workspaceService.ts` | `WorkspaceTreeEntry` 添加 `isSymlink: boolean`,`readTree` 中调用 `entry.isSymbolicLink()` |
|
||||
| `desktop/src/api/sessions.ts` | 前端 `WorkspaceTreeEntry` 类型同步添加 `isSymlink` |
|
||||
| `desktop/src/components/workspace/WorkspacePanel.tsx` | 文件节点和目录节点添加 `link` 图标(`isSymlink` 为 `true` 时) |
|
||||
|
||||
#### 1.3 文件监听
|
||||
|
||||
**行为**:
|
||||
- WebSocket 连接建立后,对会话 workDir 启动 `fs.watch({ recursive: true })`
|
||||
- 文件变化 200ms 防抖后通过 `sendToSession` 推送 `{ type: 'file_changed', sessionId, path }`
|
||||
- 前端收到后自动刷新展开的树节点和预览标签
|
||||
- 不监听 `node_modules`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/server/ws/events.ts` | `ServerMessage` 添加 `file_changed` 消息类型 |
|
||||
| `src/server/services/workspaceService.ts` | 新增 `startWatcher(sessionId, workDir)` / `stopWatcher(sessionId)` 方法 |
|
||||
| `src/server/ws/handler.ts` | `open()` 中启动 watcher,`close()` 中停止 watcher |
|
||||
| `desktop/src/stores/workspacePanelStore.ts` | 注册 `file_changed` handler,刷新树和预览标签 |
|
||||
|
||||
---
|
||||
|
||||
### 二、PlantUML 渲染
|
||||
|
||||
#### 2.1 前端渲染组件
|
||||
|
||||
**行为**:
|
||||
- Markdown 中 `@@@plantuml` / `@startuml ... @enduml` 代码块自动渲染为 SVG 图表
|
||||
- 渲染结果经过 `DOMPurify.sanitize()` 安全清洗
|
||||
- 渲染失败时显示错误信息
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `desktop/src/components/chat/PlantUMLRenderer.tsx` | 渲染组件:调用后端 API 获取 SVG,DOMPurify 清洗,error 状态 |
|
||||
| `desktop/src/components/markdown/MarkdownRenderer.tsx` | 添加 `shouldRenderAsPlantUML` 判定,分发到 PlantUMLRenderer |
|
||||
| `desktop/src/components/chat/PlantUMLRenderer.test.tsx` | 5 个测试:SVG 渲染、头部、预览弹窗、空 SVG 回退、错误状态 |
|
||||
|
||||
#### 2.2 后端 Pipe 渲染
|
||||
|
||||
**行为**:
|
||||
- 从 `execFile + 临时文件` 改为 `spawn -pipe` 模式
|
||||
- stdin 写入 PlantUML 源码,stdout 读取 SVG
|
||||
- `-Djava.awt.headless=true` 避免 macOS Dock 图标
|
||||
- 去掉临时文件 I/O
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/server/api/settings.ts` | `handlePlantumlRender` 改用 `spawn('java', ['-Djava.awt.headless=true', '-jar', jarPath, '-tsvg', '-pipe'])` |
|
||||
|
||||
---
|
||||
|
||||
### 三、KaTeX 公式渲染
|
||||
|
||||
**行为**:
|
||||
- Markdown 中 `$...$` 行内公式和 `$$...$$` 块级公式自动渲染
|
||||
- 通过 `marked` 标记扩展在渲染 Pipeline 中处理
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `desktop/src/components/markdown/MarkdownRenderer.tsx` | 添加 KaTeX 渲染逻辑,`katex.renderToString` 处理行内/块级公式 |
|
||||
|
||||
---
|
||||
|
||||
### 四、开发工具
|
||||
|
||||
#### 4.1 start-dev.sh
|
||||
|
||||
**位置**:`docs/script/start-dev.sh`
|
||||
|
||||
**行为**:
|
||||
- 启动后端 `bun run src/server/index.ts` (port 3456)
|
||||
- 启动前端 `cd desktop && bun run dev` (Vite port 1420)
|
||||
- 浏览器打开 `http://localhost:1420`
|
||||
|
||||
#### 4.2 Vite Proxy
|
||||
|
||||
**位置**:`desktop/vite.config.ts`
|
||||
|
||||
**行为**:
|
||||
- `/api/*`, `/health` → `http://127.0.0.1:3456`
|
||||
- `/ws` → `ws://127.0.0.1:3456`
|
||||
@ -1 +0,0 @@
|
||||
/Users/yangzexuan/个人总结/AI使用总结/ai使用分析/中英文 思维链对比分析_20260511.md
|
||||
@ -1 +0,0 @@
|
||||
/Users/yangzexuan/工作总结
|
||||
Loading…
x
Reference in New Issue
Block a user