8.3 KiB
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 调用。
命令
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.parametersDocumentSegment有可选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 两步预处理:
extractSessionVars()— 从所有消息中提取static_varsegment,构建varMap,并将其从消息体中移除。变量展示在SessionBar(对话区顶部横栏)extractToolMessages()— 将tool_call_request/tool_call_resultsegment 拆分为独立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→ 合并为一条 leadingrole: "system"消息system_prompt中的{{var}}占位符在导出时展开tool_overview→ 顶层tools[],item.schema映射为tool.function.parameterstool_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 场景切换按钮。