139 lines
8.3 KiB
Markdown
139 lines
8.3 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
**项目语言:中文。所有注释、文档、commit message、UI 文案均使用中文。**
|
||
|
||
## 项目概述
|
||
|
||
HCI 课程设计项目:**Prompt Envelope Protocol** — 一套让 LLM 上下文在聊天界面中可见的协议与 UI。
|
||
|
||
核心思想:每条聊天消息由若干带类型的 **Segment** 组成(而非原始文本)。每种 Segment 有独立的视觉呈现,让 system prompt、memory、tools、skills、变量、文档、多媒体成为可见的一等元素,而非隐藏的上下文。
|
||
|
||
MVP 为纯前端 React 应用,使用 mock 数据演示。无后端、无真实 LLM 调用。
|
||
|
||
## 命令
|
||
|
||
```bash
|
||
npm run dev # Vite 开发服务器 → http://localhost:5173
|
||
npm run build # tsc 类型检查 + vite 构建
|
||
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
|
||
```
|
||
|
||
## 架构
|
||
|
||
### 数据模型 (`src/types/protocol.ts`)
|
||
|
||
`PromptEnvelope { version, model?, messages: Message[] }` — 顶层协议文档。可选 `model` 字段用于导出时指定模型名(默认 `"gpt-4-turbo"`)。
|
||
|
||
`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`。
|
||
|
||
关键类型细节:
|
||
- `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 系统
|
||
|
||
遵循 Anthropic SKILL.md 规范,实现渐进式披露。管线:
|
||
|
||
```
|
||
SKILL.md 文件 (YAML frontmatter + Markdown body)
|
||
│
|
||
├─ 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 个真实 Anthropic SKILL.md 文件
|
||
│ + toSkillItem() / getRealSkills() / getAllRealSkillItems()
|
||
│
|
||
├─ skills.ts → ALL_SKILLS (Record<string, SkillItem>)
|
||
│ (src/data/skills.ts) 8 个手写 skill:code-review、deep-research、verify、simplify、
|
||
│ loop、summarize、translate、qa
|
||
│ + getSkills(names) 按名称选取
|
||
│
|
||
├─ 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(点击展开)
|
||
```
|
||
|
||
### 渲染管线
|
||
|
||
消息在渲染前经过 `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 + 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` 是共享折叠容器,接收 `title`、`icon`、`color`、`bgColor`、`defaultCollapsed`、`badge` props。
|
||
|
||
`MarkdownRenderer` 是所有文本类组的共享 Markdown 渲染组件,覆盖 h1-h4、p、code、pre、table、blockquote、list 等所有常见元素的 Tailwind 样式。
|
||
|
||
### 导出管线 (`src/utils/export.ts`)
|
||
|
||
`exportToOpenAIFormat(envelope): OpenAIExport` 输出 `{ model, messages: OpenAIMessage[], tools?: OpenAITool[] }`——完整的 OpenAI Chat Completions request body。
|
||
|
||
关键映射:
|
||
- `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/`)
|
||
|
||
6 个场景(A–F),每个是独立的 JSON 文件(`demo-{a..f}.json`),通过 `manifest.json` 索引。默认激活场景 A(`"default": true` 在 manifest 中标记为场景 F,但 ChatContext 初始化为 `useState(0)`)。
|
||
|
||
加载流程:`JSON import (unknown) → validateEnvelope() → hydrateSkills() → PromptEnvelope`
|
||
|
||
### 状态管理
|
||
|
||
`ChatContext`(`src/context/ChatContext.tsx`)持有 `envelope`、`activeDemo`、`demos`。切换场景时替换整个 envelope。默认激活索引 0(场景 A)。ChatInput 已禁用。
|
||
|
||
### 颜色系统
|
||
|
||
| 颜色 | Segment / 用途 |
|
||
|--------|-------------------------------------|
|
||
| 蓝色 | `static_var`(SessionBar + badge) |
|
||
| 灰色 | `system_prompt` |
|
||
| 紫色 | `memory` |
|
||
| 绿色 | `skills` |
|
||
| 橙色 | `tool_overview` |
|
||
| 深色 | `tool_call_request`(代码块) |
|
||
| 绿/红 | `tool_call_result`(成功/失败) |
|
||
| 琥珀 | `role: "tool"` 消息气泡 |
|
||
|
||
### 布局
|
||
|
||
双栏:左侧 `ChatView`(SessionBar + MessageList + ChatInput),右侧 `ProtocolPanel`(双 Tab:OpenAI Format / Raw Protocol,支持复制/下载,底部统计信息)。顶栏有 6 个 Demo 场景切换按钮。
|