Files

139 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 个手写 skillcode-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 → MarkdownRendererreact-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`(双 TabOpenAI Format / Raw Protocol,支持复制/下载,底部统计信息)。顶栏有 6 个 Demo 场景切换按钮。