npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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) — locale prop 支持 zh-CN / en-US,可自定义消息字典
  • 打字机效果 — 基于 requestAnimationFrame 的逐字展开,自动对齐 Markdown 安全边界
  • CSS 隔离 — 所有类名 ycw- 前缀,CSS 变量驱动主题,不污染宿主样式
  • Adapter 模式 — 认证、API 地址、Token 刷新完全由消费方注入,零耦合

安装

npm install @jetv/chat-widget

Peer 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:

  1. renderAgentHeaderActions — host fully owns the slot.
  2. onInterjectRequest — trigger-only button; host owns composer/delivery.
  3. interjectToCoordinator / interjectToAgent — legacy card-local composer.
  4. 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,评估后明确不在当前版本实现,原因与下一步写在这里,避免每次盘点缺口时被重复提起:

  1. TodoTask.agentInstanceId / 行级归属 agent 头像(报告 P1.5):设计稿要求 Todo 每行显示负责该任务的 agent 头像,但 TodoTask 当前数据结构(后端 checklist 协议)里没有归属 agent 字段——这不是 SDK 能单侧补的字段,需要后端先在 checklist 协议里加 agent_instance_id。下一步:由需要此能力的团队发起 ADR,与 jetagents 后端团队对齐字段后再排 SDK 工作量。
  2. 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