feat: 更新 CLAUDE.md 文档,完善数据模型和渲染管线说明,修正命令行测试描述
This commit is contained in:
@@ -17,7 +17,7 @@ MVP 为纯前端 React 应用,使用 mock 数据演示。无后端、无真实
|
||||
```bash
|
||||
npm run dev # Vite 开发服务器 → http://localhost:5173
|
||||
npm run build # tsc 类型检查 + vite 构建
|
||||
npm test # vitest 运行全部测试(26 个,位于 export.test.ts 和 parseSkill.test.ts)
|
||||
npm test # vitest 运行全部测试(2 个文件,export.test.ts + parseSkill.test.ts,共 ~26 个用例)
|
||||
npm run preview # 预览生产构建
|
||||
npx vitest run src/__tests__/export.test.ts # 运行单个测试文件
|
||||
npx vitest run src/__tests__/parseSkill.test.ts
|
||||
@@ -27,107 +27,112 @@ npx vitest run src/__tests__/parseSkill.test.ts
|
||||
|
||||
### 数据模型 (`src/types/protocol.ts`)
|
||||
|
||||
`PromptEnvelope { version, model, messages: Message[] }` — 顶层协议文档。
|
||||
`PromptEnvelope { version, model?, messages: Message[] }` — 顶层协议文档。可选 `model` 字段用于导出时指定模型名(默认 `"gpt-4-turbo"`)。
|
||||
|
||||
`Message { id, role: "system"|"user"|"assistant"|"tool", segments: Segment[], timestamp }` — 单条聊天消息。
|
||||
`Message { id, role: "system"|"user"|"assistant"|"tool", segments: Segment[], timestamp }` — 单条聊天消息。`role: "tool"` 用于独立的工具调用消息。
|
||||
|
||||
`Segment` 是基于 `kind` 的可辨识联合类型,共 11 种变体:`text`、`static_var`、`system_prompt`、`memory`、`skills`、`tool_overview`、`tool_call_request`、`tool_call_result`、`document`、`long_text`、`media`。
|
||||
|
||||
部分 Segment 类型(MemorySegment、SkillSegment)有可选 `description` 字段,用于在 UI 中解释这些项的用途。
|
||||
|
||||
ToolItem 有可选 `schema` 字段(JSON Schema 对象),在工具总览面板中可展开查看。
|
||||
|
||||
DocumentSegment 有可选 `parsedContent` 字段,点击「查看解析」可展开 AI 对文档内容的提取结果。
|
||||
关键类型细节:
|
||||
- `StaticVarSegment` 有 `name`、`value`、可选 `description` — 会话级变量,如 `{{current_date}}` → `"2026年6月8日"`。**不在消息气泡内渲染**,由 `MessageList.extractSessionVars()` 提取到 `SessionBar` 横栏中
|
||||
- `SkillItem` 有 `name`、`description`(L1 始终可见)、`body`(L2 触发时加载的 Markdown 正文)——遵循 Anthropic SKILL.md 规范
|
||||
- `ToolItem` 有可选 `schema` 字段(JSON Schema 对象),导出时作为 `tools[].function.parameters`
|
||||
- `DocumentSegment` 有可选 `parsedContent` 字段,「查看解析」时用 `MarkdownRenderer` 展示
|
||||
- `MemorySegment`、`SkillSegment` 有可选 `description` 字段,在 UI 中解释其用途
|
||||
|
||||
### Skills 系统
|
||||
|
||||
Skills 遵循 Anthropic SKILL.md 规范,实现渐进式披露:
|
||||
遵循 Anthropic SKILL.md 规范,实现渐进式披露。管线:
|
||||
|
||||
```
|
||||
SKILL.md 文件 (YAML frontmatter + Markdown body)
|
||||
│
|
||||
├─ parseSkillMarkdown() → ParsedSkill { name, description, body, ... }
|
||||
│ (src/utils/parseSkill.ts) YAML 通过极简手写解析器提取(不依赖外部库)
|
||||
├─ parseSkillMarkdown() → ParsedSkill { name, description, body, bodyLineCount, ... }
|
||||
│ (src/utils/parseSkill.ts) 极简手写 YAML 解析器(不依赖外部库),提取顶层 string 键值
|
||||
│
|
||||
├─ skills-loader.ts → PARSED_SKILLS (Record<string, ParsedSkill>)
|
||||
│ (src/data/skills-loader.ts) 通过 Vite ?raw 导入 4 个真实 SKILL.md 文件
|
||||
│ (src/data/skills-loader.ts) 通过 Vite ?raw 导入 4 个真实 Anthropic SKILL.md 文件
|
||||
│ + toSkillItem() / getRealSkills() / getAllRealSkillItems()
|
||||
│
|
||||
├─ skills.ts → ALL_SKILLS (Record<string, SkillItem>)
|
||||
│ (src/data/skills.ts) 8 个内置自定义 skill,用于 Demo C/D/E
|
||||
│ (src/data/skills.ts) 8 个手写 skill:code-review、deep-research、verify、simplify、
|
||||
│ loop、summarize、translate、qa
|
||||
│ + getSkills(names) 按名称选取
|
||||
│
|
||||
└─ UI: SkillsView → L1 名称+描述(始终可见),L2 body(点击展开)
|
||||
├─ demos-loader.ts → buildSkillLookup() 合并两个源 → hydrateSkills(envelope)
|
||||
│ (src/data/demos/demos-loader.ts) 按 name 匹配,补全 JSON 中只包含 name+description 的 skill items
|
||||
│ + validateEnvelope() 运行时类型守卫
|
||||
│ + loadEnvelope() = validateEnvelope + hydrateSkills
|
||||
│
|
||||
└─ UI: SkillsView → SkillDisclosure 逐项展开:L1 name+description(始终可见),L2 body(点击展开)
|
||||
```
|
||||
|
||||
**两类 skill 数据源:**
|
||||
- `src/data/skills.ts` — 8 个手写 SkillItem(code-review、deep-research、verify 等),通过 `getSkills(names)` 按名称选取
|
||||
- `src/data/skills-loader.ts` — 4 个真实 Anthropic SKILL.md 文件(webapp-testing、pdf、doc-coauthoring、mcp-builder),通过 `parseSkillMarkdown()` 解析,`getRealSkills(names)` 获取
|
||||
|
||||
**parseSkillMarkdown 测试:** 位于 `src/__tests__/parseSkill.test.ts`,覆盖 YAML 解析、body 提取、必填字段校验、可选字段、统计信息等 7 个用例。
|
||||
|
||||
### 渲染管线
|
||||
|
||||
消息在渲染前经过 `MessageList` 两步预处理:
|
||||
|
||||
1. **`extractSessionVars()`** — 从所有消息中提取 `static_var` segment,构建 `varMap`,并将其从消息体中移除。变量展示在 `SessionBar`(对话区顶部横栏)
|
||||
2. **`extractToolMessages()`** — 将 `tool_call_request`/`tool_call_result` segment 拆分为独立 `role: "tool"` 消息
|
||||
|
||||
处理后的消息按 Segment kind 分发渲染:
|
||||
|
||||
```
|
||||
SegmentRenderer (按 segment.kind 分发)
|
||||
├─ TextSegmentView → MarkdownRenderer(react-markdown + GFM)
|
||||
├─ StaticVarBadge → 蓝色内联 pill 标签
|
||||
├─ SystemPromptView → CollapsiblePanel(灰色,默认折叠)
|
||||
├─ MemoryView → CollapsiblePanel(紫色,默认折叠)
|
||||
├─ SkillsView → CollapsiblePanel(绿色,默认折叠)
|
||||
├─ ToolOverviewView → CollapsiblePanel(橙色,默认折叠,可展开 JSON Schema)
|
||||
├─ ToolCallRequestView → 深色终端风格代码块,参数以标签化键值行展示
|
||||
├─ ToolCallResultView → 绿色/红色状态条(失败时默认展开,成功时默认折叠)
|
||||
├─ DocumentCard → 文件图标 + 文件名 + 大小 + 摘要预览
|
||||
├─ LongTextView → 折叠态展示前2行预览 + 展开态用 MarkdownRenderer 渲染
|
||||
└─ MediaView → 图标 + 类型标签 + alt 文本
|
||||
├─ TextSegmentView → MarkdownRenderer(react-markdown + remark-gfm,所有 HTML 元素自定义 Tailwind)
|
||||
├─ static_var → 返回 null(已由 SessionBar 提取)
|
||||
├─ SystemPromptView → CollapsiblePanel(灰色),支持 {{var}} 模板可视化渲染
|
||||
├─ MemoryView → CollapsiblePanel(紫色)
|
||||
├─ SkillsView → CollapsiblePanel(绿色), SkillDisclosure 逐项逐层展开
|
||||
├─ ToolOverviewView → CollapsiblePanel(橙色),ToolItemRow 可独立展开 JSON Schema
|
||||
├─ ToolCallRequestView → 深色终端风格,参数以标签化键值行展示 + 长值折叠
|
||||
├─ ToolCallResultView → 绿/红状态条(失败默认展开,成功默认折叠)
|
||||
├─ DocumentCard → 文件图标 + 文件名 + 大小 + snippet,可选「查看解析」展开 Markdown
|
||||
├─ LongTextView → 折叠态前2行预览 + 展开态用 MarkdownRenderer 渲染
|
||||
└─ MediaView → 图片有 URL 时直接渲染缩略图;加载失败/无URL/非图片则图标占位
|
||||
```
|
||||
|
||||
`CollapsiblePanel` 是共享折叠容器组件。折叠/展开状态通过 `useState(segment.collapsed)` 管理——协议数据提供默认值,UI 控制运行时切换。
|
||||
`CollapsiblePanel` 是共享折叠容器,接收 `title`、`icon`、`color`、`bgColor`、`defaultCollapsed`、`badge` props。
|
||||
|
||||
`MarkdownRenderer` 封装 `react-markdown` + `remark-gfm`。所有 HTML 元素(h1–h4、p、code、pre、table、blockquote 等)均有自定义 Tailwind 样式,针对聊天气泡内嵌场景做了紧凑化处理。
|
||||
|
||||
### 状态管理
|
||||
|
||||
`ChatContext`(`src/context/ChatContext.tsx`)持有当前 `envelope` 和 `activeDemo` 索引。切换 Demo 场景时替换整个 envelope。ChatInput 已禁用(MVP 无真实输入)。
|
||||
`MarkdownRenderer` 是所有文本类组的共享 Markdown 渲染组件,覆盖 h1-h4、p、code、pre、table、blockquote、list 等所有常见元素的 Tailwind 样式。
|
||||
|
||||
### 导出管线 (`src/utils/export.ts`)
|
||||
|
||||
`exportToOpenAIFormat(envelope)` 输出 OpenAI 兼容 JSON,结构为 `{ model, messages, tools? }`:
|
||||
`exportToOpenAIFormat(envelope): OpenAIExport` 输出 `{ model, messages: OpenAIMessage[], tools?: OpenAITool[] }`——完整的 OpenAI Chat Completions request body。
|
||||
|
||||
- 结构性 Segment(`system_prompt`、`memory`、`skills`、`tool_overview`)提取为一条 `role: "system"` 的头部消息
|
||||
- `tool_overview` 的 items 转为顶层 `tools` 数组(OpenAI function-calling 格式)
|
||||
- `tool_call_request` → 带 `tool_calls` 数组的 assistant 消息;`tool_call_result` → `role: "tool"` 消息
|
||||
- `static_var` 展开为变量值
|
||||
- `text` 和 `long_text` 原文输出
|
||||
- `media` 用 `altText` 作为回退文本
|
||||
- `segmentToText()` 对结构性/tool Segment 返回 `null`——它们在消息级别统一处理
|
||||
关键映射:
|
||||
- `system_prompt`/`memory`/`skills` → 合并为一条 leading `role: "system"` 消息
|
||||
- `system_prompt` 中的 `{{var}}` 占位符在导出时展开
|
||||
- `tool_overview` → 顶层 `tools[]`,`item.schema` 映射为 `tool.function.parameters`
|
||||
- `tool_call_request` → `role: "assistant"` 消息带 `tool_calls[]`(自动生成 call_id)
|
||||
- `tool_call_result` → 独立 `role: "tool"` 消息,通过 call_id 与请求配对
|
||||
- `static_var` → user/assistant 消息中展开为值;system 消息中只参与模板展开,**不**直接出现在 system 消息正文中
|
||||
- 含图片 media 的 user 消息 → 多模态 `content` 数组 (`[{type: "text"}, {type: "image_url"}]`)
|
||||
- `document` / `long_text` → 原文输出(带 `[Document: ...]` 标注)
|
||||
- `segmentToText()` 对结构性/tool Segment 返回 `null`——在消息级别统一处理
|
||||
|
||||
### Demo 数据 (`src/data/demos.ts` → `src/data/demos/`)
|
||||
### Demo 数据 (`src/data/demos/`)
|
||||
|
||||
6 个场景(A–F),每个场景是一个完整的 `PromptEnvelope`,对话内容为中文。默认激活索引 4(场景 F——真实 Anthropic Skills 加载)。
|
||||
6 个场景(A–F),每个是独立的 JSON 文件(`demo-{a..f}.json`),通过 `manifest.json` 索引。默认激活场景 A(`"default": true` 在 manifest 中标记为场景 F,但 ChatContext 初始化为 `useState(0)`)。
|
||||
|
||||
| 索引 | 场景 | 内容 |
|
||||
|------|--------|---------------------------------------------------|
|
||||
| 0 | 场景 A | 基础对话 + System Prompt + Memory |
|
||||
| 1 | 场景 B | 工具调用:请求 → 执行(成功 & 失败) |
|
||||
| 2 | 场景 C | 文档解析:点击「查看解析」看 AI 如何提取文档内容 |
|
||||
| 3 | 场景 D | 综合:覆盖全部 11 种 Segment |
|
||||
| 4 | 场景 F | 真实 Anthropic Skills(SKILL.md 文件加载 + parseSkillMarkdown 解析) |
|
||||
| 5 | 场景 E | 日志分析:异常检测 + 安全审计 + 性能分析 |
|
||||
加载流程:`JSON import (unknown) → validateEnvelope() → hydrateSkills() → PromptEnvelope`
|
||||
|
||||
每个场景文件位于 `src/data/demos/demo-{a..f}.ts`,`demos.ts` 仅做聚合导出。
|
||||
### 状态管理
|
||||
|
||||
`ChatContext`(`src/context/ChatContext.tsx`)持有 `envelope`、`activeDemo`、`demos`。切换场景时替换整个 envelope。默认激活索引 0(场景 A)。ChatInput 已禁用。
|
||||
|
||||
### 颜色系统
|
||||
|
||||
| 颜色 | 对应 Segment |
|
||||
|--------|--------------------------------------|
|
||||
| 蓝色 | `static_var` |
|
||||
| 颜色 | Segment / 用途 |
|
||||
|--------|-------------------------------------|
|
||||
| 蓝色 | `static_var`(SessionBar + badge) |
|
||||
| 灰色 | `system_prompt` |
|
||||
| 紫色 | `memory` |
|
||||
| 绿色 | `skills` |
|
||||
| 橙色 | `tool_overview` |
|
||||
| 深色 | `tool_call_request`(代码块) |
|
||||
| 绿/红 | `tool_call_result`(成功/失败) |
|
||||
| 琥珀 | `role: "tool"` 消息气泡 |
|
||||
|
||||
### 布局
|
||||
|
||||
双栏:左侧 `ChatView`(MessageList + ChatInput),右侧 `ProtocolPanel`(实时 OpenAI Format JSON 视图,支持复制/下载)。顶栏有 Demo 场景切换按钮。
|
||||
双栏:左侧 `ChatView`(SessionBar + MessageList + ChatInput),右侧 `ProtocolPanel`(双 Tab:OpenAI Format / Raw Protocol,支持复制/下载,底部统计信息)。顶栏有 6 个 Demo 场景切换按钮。
|
||||
|
||||
Reference in New Issue
Block a user