@jetv/chat-widget
v0.4.7
Published
JetPave ChatWidget SDK — Agent conversation UI component
Readme
@jetv/chat-widget
Agent 对话 UI 组件 — 为 AI Agent 系统提供开箱即用的聊天界面。
特性
- SSE 实时流式渲染 — POST-based Server-Sent Events,支持认证、自动重连
- Streamdown Markdown — 代码高亮、Mermaid 图表、LaTeX 数学公式、CJK 排版优化
- 多轮对话管理 — 轮次分隔、清屏滚动、动态置顶(带防抖与滞后阈值)
- 子 Agent 嵌套 — 折叠式子 Agent 卡片,支持
call_agent工具链路可视化 - 工具调用卡片 — MCP 工具生命周期(生成 → 执行 → 完成),buffering 动画
- Todo/Checklist — 实时任务进度面板,按 Phase → Group → Task 三级结构展示
- HITL 表单 — Human-in-the-Loop 交互,支持 JSON Schema / HTML Schema 动态表单
- 富文档查看器 — PDF、图片、代码、3D/STEP、KiCad 原理图/PCB、Markdown、HTML 等 20+ 格式(CWRF-001/003)
- 溯源图谱 — 3D 推理图谱可视化,支持节点过滤、深度控制、详情面板(CWRF-004)
- 选区交互 — 文档选区工具条、Ask/Copy 拦截、上下文引用芯片(CWRF-002)
- Headless Controller —
useChatWidgetController将业务逻辑与 UI 分离,支持自定义渲染(CWRF-004) - 国际化 (i18n) —
localeprop 支持zh-CN/en-US,可自定义消息字典 - 打字机效果 — 基于
requestAnimationFrame的逐字展开,自动对齐 Markdown 安全边界 - CSS 隔离 — 所有类名
ycw-前缀,CSS 变量驱动主题,不污染宿主样式 - Adapter 模式 — 认证、API 地址、Token 刷新完全由消费方注入,零耦合
安装
npm install @jetv/chat-widgetPeer Dependencies(必需):
{
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"antd": "^5.0.0 || ^6.0.0"
}可选 Peer Dependencies(溯源 3D 图谱):
{
"three": ">=0.150.0",
"react-force-graph-3d": "^1.29.0",
"three-spritetext": "^1.10.0"
}快速开始
import { ChatWidget, DefaultJetAgentsAdapter } from '@jetv/chat-widget'
import '@jetv/chat-widget/style.css'
const adapter = new DefaultJetAgentsAdapter({
baseUrl: 'https://your-jetagents-server.com',
getToken: () => localStorage.getItem('access_token'),
refreshToken: async () => {
const res = await fetch('/api/auth/refresh', { method: 'POST' })
const { token } = await res.json()
localStorage.setItem('access_token', token)
return token
},
onAuthFailure: () => window.location.href = '/login',
})
function App() {
return (
<ChatWidget
adapter={adapter}
sessionId={123}
config={{
theme: 'light',
showPinnedArea: true,
enableTypewriter: true,
typewriterSpeed: 30,
displayLevels: [0, 1, 2],
}}
onReady={(api) => console.log('Widget ready', api)}
onError={(err) => console.error(err)}
height="100vh"
renderFooter={(api) => (
<input
onKeyDown={(e) => {
if (e.key === 'Enter' && api) {
api.sendMessage(e.currentTarget.value, 'agent-666')
e.currentTarget.value = ''
}
}}
placeholder="输入消息..."
/>
)}
/>
)
}自定义 Adapter
DefaultJetAgentsAdapter 适配 JetAgents 后端 API。对于其他后端系统,实现 ChatWidgetAdapter 接口即可:
import type { ChatWidgetAdapter } from '@jetv/chat-widget'
class MyAdapter implements ChatWidgetAdapter {
async createSession(agentId: string) { /* ... */ }
async getSessionDetail(sessionId: number) { /* ... */ }
async getSessionHistory(sessionId: number) { /* ... */ }
async getResourceContent(sessionId: number, resourceId: string) { /* ... */ }
async submitHITLResponse(response) { /* ... */ }
getStreamUrl(): string { return '/api/stream' }
getAuthHeaders(): Record<string, string> { return {} }
}详细接口定义见 docs/02-adapter-interface.md。完整 API 文档见 docs/11-api-reference.md。
Headless Controller(CWRF-004)
如果需要完全自定义 UI,可直接使用 useChatWidgetController Hook:
import { useChatWidgetController } from '@jetv/chat-widget'
function MyCustomChat({ adapter }) {
const {
executionStatus,
connectionStatus,
rounds,
sendMessage,
widgetAPI,
} = useChatWidgetController({
adapter,
sessionId: 123,
onStatusChange: (s) => console.log('status:', s),
onRoundChange: (r) => console.log('new round:', r),
onConnectionChange: (c) => console.log('connection:', c),
})
return (
<div>
{rounds.map((round) => (
<div key={round.id}>{/* 自定义轮次渲染 */}</div>
))}
</div>
)
}子 Agent 独立渲染(悬浮聚焦 / 单卡展示场景)
ConversationView 默认把协调者(level-0 root agent)与该轮全部子 Agent 卡片
按时间顺序排列在同一个滚动区内(showRootAgentCard={true} 时进一步用
RootReplyCard 托盘包裹)。如果宿主需要"脱离这条对话流、单独把某一个子 Agent
的完整内容渲染成一张独立卡片"(例如点击某子 Agent 卡头按钮后弹出悬浮覆盖层、
或在右侧栏单独展示),SDK 导出了两个纯函数,替宿主完成"在某一轮消息里定位这个
子 Agent 完整消息组"的分组逻辑,宿主只需把结果喂给已导出的 ChildAgentCard:
import { findAgentGroupInRound, ChildAgentCard } from '@jetv/chat-widget'
import type { Round } from '@jetv/chat-widget'
function SingleAgentOverlay({ round, agentInstanceId }: { round: Round; agentInstanceId: number }) {
const group = findAgentGroupInRound(round, agentInstanceId)
if (!group) return null // 该轮暂无此 agent 的消息(未到达 / 已被过滤)
return (
<ChildAgentCard
message={group.firstChild}
children={group.children}
defaultExpanded
interactionId={round.interactionId}
allRoundMessages={round.messages}
endedAgentInstanceIds={round.endedAgentInstanceIds}
/>
)
}| 导出 | 签名 | 说明 |
|------|------|------|
| findAgentGroupInRound | (round: Round, agentInstanceId: number) => ChildGroupEntry \| null | 在指定轮次里查找某个子 Agent 实例的完整消息组;未找到返回 null |
| groupChildMessages | (children: AggregatedMessage[]) => ChildGroupEntry[] | 按 agentInstanceId 对一组子消息分组;ConversationView 内部渲染多个子 Agent 卡片时也用它 |
| ChildGroupEntry | { agentInstanceId: number; firstChild: AggregatedMessage; children: AggregatedMessage[] } | 上述两个函数的返回元素类型 |
SDK 只负责"定位数据 + 提供可复用的展示组件",是否弹窗、覆盖层定位、层级管理、 关闭交互等编排逻辑由宿主自行实现(与
renderHeaderActions插槽的既定分工一致, 见docs/04-components.md)。
对话流细粒度定制(sticky 偏移 / 置顶带扩展 / 附件大小 / 运行态判定)
ConversationView 与 RoundsView 都支持以下可选 prop(RoundsView 因无置顶区,只支持第一项):
| Prop | 组件 | 类型 | 说明 |
|------|------|------|------|
| stickyHeaderTopOffset | ConversationView / RoundsView | number | 卡头 sticky 的 top 偏移(px),写入 --ycw-agent-header-sticky-top;宿主置顶了自己的任务清单/工具条时用它让开高度。默认不传 = 0px。 |
| pinnedMaxLines | ConversationView | number | 置顶用户消息按行数截断(透传给 PinnedArea.maxLines),不传则维持像素 maxHeight 启发式。 |
| pinnedTodoDefaultExpanded | ConversationView | boolean | 置顶区内 Todo 卡默认是否展开(透传给 PinnedArea.todoDefaultExpanded)。 |
FileAttachment 新增 fileSize?: number(字节),随附件消息全链路贯通(上传 → marker | size: N → 解析 → chip 展示 formatFileSize)。
renderAgentHeaderActions(ctx) 收到的 ctx 新增两个字段,复用卡片内部已有的展开状态机,不必自己另起一份:
renderAgentHeaderActions={(ctx) => (
<button onClick={ctx.toggleExpand}>
{ctx.expanded ? '收起' : '展开'}
</button>
)}判断某个子 Agent 是否仍在运行(用于插话按钮禁用态等场景),用 isAgentRunning 而不要自己重新解析 observedInstanceStates——与卡头状态徽章保证同一权威信号:
import { isAgentRunning } from '@jetv/chat-widget'
const disabled = isAgentRunning(agentInstanceId, poolObservation.instanceStates)插话按钮(协调者活体投递 + 子 Agent 讨论定向修订)
AgentHeaderActionsContext 新增 interject? / interjectEnabled? / interjectBusy? 三个字段;InterjectButton 组件消费它们渲染一个内置的卡头插话按钮,点击后展开 InterjectComposer 输入框,提交时按卡头类型自动路由到正确的投递路径——不是占位文字,是两条真正打后端的路径:
- 协调者(root)卡头 →
interjectToCoordinator(body)→adapter.interjectMessage:活体投递到正在运行的协调者交互,非抢占(不打断当前回合,运行中 root 在下一个决策边界吃进新消息)。 - 子 Agent 卡头 →
interjectToAgent({ agentInstanceId, agentAlias }, body)→adapter.createDiscussion(initial_mention_targets定向agent_instance_id):经讨论机制发起一条定向给该子 Agent 的修订消息,子 Agent 收到后可据此调整任务方向。
<ChatWidget> 用户(最常见路径)——开箱即用,两个新 prop 都是可选的:
<ChatWidget
adapter={adapter}
sessionId={sessionId}
enableInterjectButton // 默认 true;传 false 完全关闭内置插话按钮
showRootAgentCard // 默认 false;见下方"重要约束"
/>ConversationView / RoundsView 用户(自建滚动编排的宿主)——需要自己持有 useInterject() 并把返回值传下去(这两个组件是纯展示组件,不持有 adapter/session):
import { useRef } from 'react'
import { useInterject, ConversationView } from '@jetv/chat-widget'
import type { ConversationViewHandle } from '@jetv/chat-widget'
// 以下代码位于宿主 React 组件内部。
const conversationViewRef = useRef<ConversationViewHandle>(null)
const { interjectToCoordinator, interjectToAgent, interjectBusy } = useInterject({
adapter, sessionId, resolveClientId, poolAgents, currentUser,
})
// sendMessage 由 useChatWidgetController() 或宿主的等价编排提供。
const handleSubmit = async (message: string) => {
conversationViewRef.current?.prepareClearScreen()
await sendMessage(message)
}
<ConversationView
ref={conversationViewRef}
rounds={rounds}
executionStatus={executionStatus}
showRootAgentCard
interjectToCoordinator={interjectToCoordinator}
interjectToAgent={interjectToAgent}
interjectBusy={interjectBusy}
resolveClientId={resolveClientId}
/>把 handleSubmit 交给宿主 composer;它会在第一个异步边界之前同步执行
prepareClearScreen(),然后才发送新用户消息。
嵌入式 ConversationView 宿主还必须满足以下集成清单:
[ ] A bounded-height ancestor chain reaches .ycw-message-list
[ ] Flex children in that chain use min-height: 0 where required
[ ] .ycw-message-list-container keeps overflow: hidden
[ ] .ycw-clear-screen-spacer is not forced to height: 0
[ ] prepareClearScreen() runs before sending the new user message
[ ] scrollScopeKey is passed when one mounted view switches unscoped datasets在上述布局契约范围内,以下 class 是稳定的嵌入式宿主集成 selector:
.ycw-message-list:实际的纵向滚动视口;有界高度的祖先链必须能传递到这里。.ycw-message-list-container:滚动视口的 flex 容器;必须保留overflow: hidden。.ycw-clear-screen-spacer:SDK 计算并写入高度的清屏垫片;宿主不得覆盖或强制其高度。
稳定性只覆盖这些 selector 名称及其文档化布局职责,不承诺内部 DOM 层级。分隔线与滚动 正确性不得依赖重排或覆盖 SDK 内部 DOM 顺序,也不得覆盖清屏垫片高度;宿主样式应只使用 文档承诺的 class 与 token。
jetV 迁移仅作为包含此修复的 SDK 版本发布后的指导:升级 SDK(或 workspace link)后
传入当前 cid 作为 scrollScopeKey,保留发送前调用 prepareClearScreen() 的顺序及
上述布局约束,移除宿主侧 spacer 高度、手工分隔线、滚动位置和插话轮次清洗 workaround,
再覆盖第二轮发送、向上浏览历史、短响应向下滚动、重复 root/定向插话、原位 agent 聚焦
及 split-preview 开关场景。SDK 发布前不要据此修改 jetV。
Header action precedence:
renderAgentHeaderActions— host fully owns the slot.onInterjectRequest— trigger-only button; host owns composer/delivery.interjectToCoordinator/interjectToAgent— legacy card-local composer.- no interject props — no action.
ChatWidget uses the trigger-only path when renderFooter is present. Its
footer consumer can read api.interject to render the active target and submit
it, and DefaultComposer supports this controller out of the box. Without a
footer, ChatWidget preserves the legacy card-local composer so the button never
becomes a dead control.
The clientId and deliveryMode used for a request are frozen at click time.
Non-text payloads (attachments, references, or skills) are rejected without
deleting the draft, so the user can remove the unsupported payload and retry.
重要约束(既有架构限制,非本功能引入): 子 Agent 的插话按钮开箱即用(ChildAgentCard 始终渲染,与 showRootAgentCard 无关);但协调者(root)的插话按钮只有在 showRootAgentCard={true} 时才会出现——协调者卡头动作插槽只存在于 RootReplyCard 内,<ChatWidget> 默认走扁平根消息渲染(无卡片、无动作插槽),这是发布 0.4.5(Lane A)时就有的既有限制,本功能未改变它。
已知取舍: 插话失败时的报错通过 InterjectComposer 自身的错误提示条展示给用户(始终生效);但不会经过 <ChatWidget onInteraction> 的交互事件流上报——ChatWidgetInteractionEvent.type 是一个封闭的具名交互事件枚举,没有通用 'error' 分支,本次未扩展该公共 API 面。如需要遥测,请在 useInterject({ onInteraction: ... }) 直接传入自己的错误上报回调(useInterject 本身接受任意形状的 { type: 'error', source, message } 回调,与 <ChatWidget> 的 onInteraction 是两条独立通道)。
与 interruptAndSend(抢占式打断重跑)的区别:见下方"已评估暂不实现的能力"一节——插话按钮做的是非抢占投递/讨论定向,不是打断当前回合重新规划。
直接构造对话流组件 props(类型导出)
import type {
ChildAgentCardProps,
PinnedAreaProps,
TodoCardProps,
QueuedMessage,
ModelCapabilities,
InterjectRequest,
InterjectResponse,
} from '@jetv/chat-widget'| 类型 | 用途 |
|------|------|
| ChildAgentCardProps | 独立渲染单个子 Agent 卡片(见上一节 SingleAgentOverlay 示例)时的完整 props 契约。 |
| PinnedAreaProps / TodoCardProps | 自绘置顶区或 Todo 卡时复用同一套 prop 类型。 |
| QueuedMessage | useChatWidgetController 排队消息(ChatWidgetAPI.queuedMessages)的元素类型。 |
| ModelCapabilities | QueuedMessage.modelCapabilities 字段类型。 |
| InterjectRequest / InterjectResponse | adapter.interjectMessage 的请求/响应契约,自定义 composer 插话按钮时直接引用。 |
| InterjectOutcome / InterjectTarget / UseInterjectArgs | useInterject() 的返回值/参数类型;自定义插话 UI(不用内置 InterjectButton)时直接引用。 |
| InterjectButtonProps / InterjectButtonCtx | 内置 InterjectButton 的 props 类型;InterjectButtonCtx 是它消费的 AgentHeaderActionsContext 结构化子集。 |
| InterjectComposerProps | 内置 InterjectComposer(提交输入框)的 props 类型。 |
| BuildDefaultInterjectRenderActionsArgs | buildDefaultInterjectRenderActions() 的参数类型。 |
稳定 class 契约(思考过程 / 附件 chip 自定义样式)
以下两类内容目前不提供渲染插槽(设计稿视觉尚未定稿,强行加插槽容易与后续官方视觉冲突), 改为承诺以下 class 名称稳定,宿主可用普通 CSS 覆盖样式(不得依赖内部 DOM 结构/层级,仅保证 class 名不变):
思考过程(ThinkChunk):
| Class | 说明 |
|---|---|
| .ycw-think-chunk | 根容器;展开态叠加 .ycw-expanded,思考进行中叠加 .ycw-thinking |
| .ycw-think-header | 可点击的折叠条(标题 + 展开箭头) |
| .ycw-think-title / .ycw-think-icon / .ycw-think-text | 标题行内的图标与文案 |
| .ycw-thinking-dots | "思考中…"动画省略号 |
| .ycw-think-preview | 思考中的滚动预览区(.ycw-think-preview-open / .ycw-think-preview-closed) |
| .ycw-think-content | 展开态的完整内容区 |
用户消息附件 chip(UserMessageContent,随 FileAttachment.fileSize 展示大小):
| Class | 说明 |
|---|---|
| .ycw-user-attach-grid | 附件卡片外层网格 |
| .ycw-user-attach-card | 单个附件卡片 |
| .ycw-user-attach-icon | 文件类型图标(背景色随 fileTypeColor 变化) |
| .ycw-user-attach-body / .ycw-user-attach-name / .ycw-user-attach-meta | 文件名 + 大小/扩展名行 |
composer 内待发送附件的预览 chip(RichInput)另有一套前缀为 .ycw-ri-attachment-* 的 class(.ycw-ri-attachment-chip / .ycw-ri-attachment-icon / .ycw-ri-attachment-name / .ycw-ri-attachment-path / .ycw-ri-attachment-remove),同一原则:class 名稳定,DOM 结构不承诺。
如需要比 CSS 覆盖更深的定制(完全自定义 markup),请在 issue 里附上具体设计稿,SDK 团队会评估是否升级为正式插槽(renderThinking? / renderAttachment?)。
已评估暂不实现的能力(需外部决策,非 SDK 单侧工程缺口)
以下两项来自 chat-widget-interface-gap-report.md §4,评估后明确不在当前版本实现,原因与下一步写在这里,避免每次盘点缺口时被重复提起:
TodoTask.agentInstanceId/ 行级归属 agent 头像(报告 P1.5):设计稿要求 Todo 每行显示负责该任务的 agent 头像,但TodoTask当前数据结构(后端 checklist 协议)里没有归属 agent 字段——这不是 SDK 能单侧补的字段,需要后端先在 checklist 协议里加agent_instance_id。下一步:由需要此能力的团队发起 ADR,与 jetagents 后端团队对齐字段后再排 SDK 工作量。interruptAndSend/InterjectRequest.preempt抢占式打断重跑(报告 P2.8):0.4.6已经把插话从"占位按钮"升级成两条真正投递的路径(协调者活体投递interjectToCoordinator+ 子 Agent 讨论定向修订interjectToAgent,见「插话按钮」一节),但两条路径都是非抢占的——协调者投递等运行中 root 到下一个决策边界才吃进新消息,子 Agent 定向修订是另起一条讨论、不打断其当前回合。是否还需要"打断当前回合 + 携带新消息重规划"的 Cursor 式抢占原语,仍是产品语义决策,不是工程实现缺口——报告原文即写"请 chat-widget 团队确认目标语义"。下一步:产品侧对"是否需要抢占式打断"给出明确结论后,SDK 再评估interruptAndSend(client_id, body)的实现(涉及stopAgent→ 等待 checkpoint 退出 → 以新消息 reinvoke 的组合原语,以及executionStatus需覆盖"被打断"中间态)。
溯源图谱(CWRF-004)
溯源组件可作为子路径单独导入(需安装 three.js 可选依赖):
import { ProvenanceGraphDrawerContent, useProvenanceGraph } from '@jetv/chat-widget/provenance'
function ProvenancePanel({ sessionId }) {
const { graph, loading } = useProvenanceGraph({
sessionId,
enabled: !!sessionId,
fetcher: (sid, params) => myApi.getProvenanceGraph(sid, params),
})
if (loading) return <Spin />
return <ProvenanceGraphDrawerContent graph={graph} />
}API 参考
ChatWidgetProps
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| adapter | ChatWidgetAdapter | 必填 | 后端适配器实例 |
| sessionId | string \| number | — | 会话 ID,传入后自动加载历史 |
| config | ChatWidgetConfig | DEFAULT_CONFIG | UI 配置 |
| renderHeaderExtra | (sessionId) => ReactNode | — | 头部右侧额外内容插槽 |
| renderFooter | (api) => ReactNode | — | 底部输入区插槽 |
| onReady | (api: ChatWidgetAPI) => void | — | Widget 就绪回调 |
| onArtifactClick | (artifact: Artifact) => void | — | 产出物点击回调 |
| onHITLSubmit | (response) => Promise<void> | — | HITL 表单提交回调 |
| onStatusChange | (status: ExecutionStatus) => void | — | 执行状态变化回调 |
| onError | (error: Error) => void | — | 错误回调 |
| locale | 'zh-CN' \| 'en-US' | 'zh-CN' | UI 语言 |
| renderToolResult | (data: ToolCallData) => ReactNode | — | 自定义工具结果渲染 |
| onSelectionAsk | (ref: FileReference) => void | — | 文档选区"提问"回调 |
| onBeforeCopy | (ref: FileReference) => void | — | 文档选区"复制"拦截 |
| height | number \| string | 600 | 容器高度 |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | 主题 |
| enableInterjectButton | boolean | true | 是否渲染内置插话按钮(协调者活体投递 / 子 Agent 讨论定向修订)。 |
| showRootAgentCard | boolean | false | 是否用 RootReplyCard 包裹协调者回复;协调者插话按钮仅在此为 true 时可见(见「插话按钮」一节的重要约束)。 |
ChatWidgetAPI
通过 onReady 或 renderFooter 获取的 API 对象:
| 方法 | 签名 | 说明 |
|------|------|------|
| sendMessage | (message: string, agentId?: string) => void | 发送消息并建立 SSE 连接 |
| getCurrentRound | () => Round \| null | 获取当前轮次 |
| getAllRounds | () => Round[] | 获取全部轮次 |
| scrollToBottom | () => void | 滚动到底部 |
| getStatus | () => ExecutionStatus | 获取执行状态 |
构建
npm run build # 生产构建(ESM + CJS + DTS)
npm run dev # 开发模式(watch)
npm run test # 运行测试
npm run typecheck # TypeScript 类型检查发布
本地发布用 npm login 交互登录(凭证由 npm 保存在用户目录,不要把 token 明文写进仓库内的 .npmrc);CI 发布用 CI 注入的环境变量 token(如 NODE_AUTH_TOKEN)。
npm login # 一次性登录(或由 CI 注入 token)
npm version patch # 升版本
npm publish --access public目录结构
chat-widget/
├── src/
│ ├── index.ts # 公共导出
│ ├── types.ts # 类型定义
│ ├── errors.ts # 错误类型
│ ├── ChatWidget.tsx # 主组件(thin shell)
│ ├── ChatWidget.css # 主样式(ycw- 前缀)
│ ├── controller/ # Headless Controller (CWRF-004)
│ │ ├── useChatWidgetController.ts
│ │ └── index.ts
│ ├── hooks/
│ │ ├── useSSE.ts # SSE 连接 Hook
│ │ ├── useMessageAggregator.ts # 消息聚合 Hook
│ │ └── useAttentionManager.ts # 注意力通知 Hook
│ ├── adapters/
│ │ └── DefaultJetAgentsAdapter.ts # JetAgents 默认适配器
│ ├── provenance/ # 溯源图谱 (CWRF-004)
│ │ ├── api.ts # ProvenanceGraphFetcher
│ │ ├── GraphBuilder.ts # 图构建器
│ │ ├── components/ # 3D 图谱、面板、过滤器
│ │ └── hooks/ # useProvenanceGraph
│ ├── components/
│ │ ├── FileViewer/ # 富文档查看器 (CWRF-001/003)
│ │ ├── SplitPanel/ # 分屏面板 (CWRF-002)
│ │ ├── ContextBridge/ # 选区交互 (CWRF-002)
│ │ ├── PinnedArea.tsx
│ │ ├── MessageContent.tsx
│ │ ├── ChildAgentCard.tsx
│ │ ├── ToolCardBuffering.tsx
│ │ ├── TodoCard.tsx
│ │ ├── PlanCard.tsx
│ │ └── SchemaFormRenderer.tsx
│ └── i18n/ # 国际化字典
├── tests/ # 集成测试
├── docs/ # 设计文档
├── tsup.config.ts # 构建配置(双入口:main + provenance)
├── vitest.config.ts # 测试配置
└── package.json文档目录
| 编号 | 文档 | 内容 | |------|------|------| | 01 | 架构设计 | 整体架构、数据流、消息路由规则、滚动与置顶 | | 02 | Adapter 接口规范 | ChatWidgetAdapter 接口、DefaultJetAgentsAdapter | | 03 | SSE 协议规范 | SSEMessage 结构、NotificationType、控制消息 | | 04 | 组件详解 | 各组件职责、渲染逻辑、Props | | 05 | 主题与样式 | CSS 变量、Light/Dark 主题、动画 | | 06 | 性能优化 | RAF 批量更新、Map 索引、延迟拼接、打字机 | | 07 | 时序图与架构图 | Mermaid 时序图、状态机、组件关系图、消息路由流程图 | | 08 | 错误处理与重连 | 错误类型、401 Token 刷新、自动重连、容错策略 | | 09 | 消息聚合引擎深度解析 | 索引体系、分段策略、短残片回溯、MCP 聚合、chunk 去重 | | 10 | 测试策略 | 测试分层、Mock 工厂、E2E 蓝图、覆盖率要求 | | 11 | API 参考文档 | 完整 Props/API/类型定义、导出清单 | | 12 | 原始设计规范 | 原始设计背景、消息协议、验收标准(移植自 JetForge) | | 13 | UI 设计规范 | 颜色、排版、间距、动画、响应式、无障碍(移植自 JetForge) |
License
Proprietary — JetV
