@blade-hq/agent-react 2612.0.0-beta.0 → 2612.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -258
- package/dist/chat/AgentLoopBlock.d.ts +14 -0
- package/dist/{components → chat}/AskUserQuestionBlock.d.ts +3 -7
- package/dist/chat/AssistantTurnBlock.d.ts +50 -0
- package/dist/chat/AttachmentPreviewDialog.d.ts +26 -0
- package/dist/chat/ChatPanel.d.ts +64 -0
- package/dist/chat/CompactionCard.d.ts +6 -0
- package/dist/chat/ConnectionBanner.d.ts +10 -0
- package/dist/chat/CurrentPlanPanel.d.ts +20 -0
- package/dist/chat/FileDeliveryCards.d.ts +26 -0
- package/dist/chat/FinalArtifactCards.d.ts +8 -0
- package/dist/chat/ImageLightbox.d.ts +11 -0
- package/dist/chat/LightComposer.d.ts +7 -0
- package/dist/chat/LiteMessageList.d.ts +82 -0
- package/dist/chat/MessageActions.d.ts +6 -0
- package/dist/chat/MessageContextPills.d.ts +6 -0
- package/dist/chat/MessageFileAttachmentList.d.ts +13 -0
- package/dist/chat/MessageList.d.ts +54 -0
- package/dist/chat/PostChatFollowupBlock.d.ts +11 -0
- package/dist/{components → chat}/RenderErrorBoundary.d.ts +0 -1
- package/dist/chat/ResultFeedback.d.ts +11 -0
- package/dist/chat/SessionReplayBar.d.ts +12 -0
- package/dist/chat/TextAttachmentPills.d.ts +10 -0
- package/dist/chat/ToolCallBlock.d.ts +48 -0
- package/dist/chat/ToolUiCard.d.ts +6 -0
- package/dist/chat/TurnNavRail.d.ts +17 -0
- package/dist/chat/UserMessageBubble.d.ts +20 -0
- package/dist/chat/WorkflowRunBlock.d.ts +13 -0
- package/dist/chat/ai-elements/reasoning.d.ts +32 -0
- package/dist/chat/ai-elements/shimmer.d.ts +15 -0
- package/dist/chat/assistant-turn-questions.d.ts +14 -0
- package/dist/chat/card/CardContext.d.ts +7 -0
- package/dist/chat/chat-theme.d.ts +19 -0
- package/dist/chat/customization.d.ts +214 -0
- package/dist/chat/display-utils.d.ts +48 -0
- package/dist/{components/PostChatFollowupBlock.d.ts → chat/followup-interaction.d.ts} +5 -17
- package/dist/chat/index.d.ts +100 -0
- package/dist/chat/lib/citations.d.ts +30 -0
- package/dist/chat/lib/code-language.d.ts +5 -0
- package/dist/chat/lib/loopback.d.ts +8 -0
- package/dist/chat/lib/markdown-asset-url.d.ts +5 -0
- package/dist/chat/lib/preview-dispatch.d.ts +24 -0
- package/dist/chat/lib/skill-name.d.ts +9 -0
- package/dist/chat/lib/skill-visibility.d.ts +8 -0
- package/dist/chat/lib/ui-meta.d.ts +5 -0
- package/dist/chat/lib/use-async-value.d.ts +8 -0
- package/dist/chat/markdown/ExternalAnchor.d.ts +9 -0
- package/dist/chat/markdown/MarkdownContent.d.ts +40 -0
- package/dist/chat/markdown/text.d.ts +11 -0
- package/dist/chat/message-list-blocks.d.ts +92 -0
- package/dist/chat/plan/PlanSummaryCard.d.ts +10 -0
- package/dist/chat/plan/extract-plan-messages.d.ts +4 -0
- package/dist/chat/plan/parse-plan-messages.d.ts +10 -0
- package/dist/chat/plan/parse-plan-tree.d.ts +16 -0
- package/dist/chat/plan/types.d.ts +56 -0
- package/dist/chat/reasoning-visibility.d.ts +2 -0
- package/dist/chat/runtime/actions.d.ts +22 -0
- package/dist/chat/runtime/background-task-dismissals.d.ts +10 -0
- package/dist/chat/runtime/composer-channels.d.ts +46 -0
- package/dist/chat/runtime/environment.d.ts +152 -0
- package/dist/chat/runtime/input-cache-reset.d.ts +8 -0
- package/dist/chat/runtime/input-services.d.ts +111 -0
- package/dist/chat/runtime/session-source.d.ts +49 -0
- package/dist/chat/runtime/use-chat-actions.d.ts +19 -0
- package/dist/chat/runtime/use-session-replay.d.ts +16 -0
- package/dist/chat/scroll-anchor.d.ts +46 -0
- package/dist/chat/tool-renderers/BashRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/FileEditRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/FileReadRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/KbSearchRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/SearchRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/SubmitResultRenderer.d.ts +2 -0
- package/dist/chat/tool-renderers/kb-search-result.d.ts +19 -0
- package/dist/chat/tool-renderers/registry.d.ts +5 -0
- package/dist/chat/tool-renderers/shared.d.ts +30 -0
- package/dist/chat/tool-renderers/write-preview-scroll.d.ts +5 -0
- package/dist/chat-OCLOVB3H.css +175 -0
- package/dist/chat.css +175 -0
- package/dist/chunk-6ZDOQEWK.js +49365 -0
- package/dist/chunk-6ZDOQEWK.js.map +1 -0
- package/dist/chunk-FQQJP2GJ.js +26986 -0
- package/dist/chunk-FQQJP2GJ.js.map +1 -0
- package/dist/{chunk-G3PMV62Z.js → chunk-PR4QN5HX.js} +9 -2
- package/dist/chunk-WCMGQJLE.js +258 -0
- package/dist/chunk-WCMGQJLE.js.map +1 -0
- package/dist/components/AgentChat.d.ts +3 -3
- package/dist/components/ChatView.d.ts +2 -2
- package/dist/components/ConnectorPanel.d.ts +1 -1
- package/dist/components/ContextCard.d.ts +1 -1
- package/dist/components/LlmChat.d.ts +1 -1
- package/dist/components/McpAppCard.d.ts +1 -1
- package/dist/components/PlanUpdateBlock.d.ts +0 -8
- package/dist/components/PluginConnectorList.d.ts +1 -1
- package/dist/components/ReplayBar.d.ts +1 -1
- package/dist/components/SessionPluginConfigDialog.d.ts +1 -1
- package/dist/components/SessionPluginConfigForm.d.ts +1 -1
- package/dist/components/SessionPluginIcon.d.ts +1 -1
- package/dist/components/SessionPluginSelector.d.ts +1 -1
- package/dist/components/SessionQueuePanel.d.ts +1 -1
- package/dist/components/ToolUiCardView.d.ts +1 -1
- package/dist/components/plugin-config-draft.d.ts +1 -1
- package/dist/components/plugin-config-schema.d.ts +1 -1
- package/dist/components/plugin-connector.d.ts +1 -1
- package/dist/components/use-session-plugin-activation.d.ts +1 -1
- package/dist/context.d.ts +11 -2
- package/dist/embed/blade-chat-element.d.ts +14 -1
- package/dist/embed/entry.d.ts +8 -78
- package/dist/hooks/use-replay.d.ts +1 -1
- package/dist/hooks/use-turn-navigation.d.ts +1 -1
- package/dist/host.d.ts +54 -0
- package/dist/index.d.ts +11 -69
- package/dist/index.js +12 -63525
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +60 -0
- package/dist/internal.js +656 -0
- package/dist/internal.js.map +1 -0
- package/dist/lib/mount-renderers.d.ts +25 -0
- package/dist/lib/use-effect-event.d.ts +3 -0
- package/dist/rich-input/BackgroundTasksPill.d.ts +3 -0
- package/dist/rich-input/ChatInput.d.ts +59 -0
- package/dist/rich-input/ComposerAttachmentViewport.d.ts +3 -0
- package/dist/rich-input/ConnectButton.d.ts +24 -0
- package/dist/rich-input/FileCompletionMenu.d.ts +44 -0
- package/dist/rich-input/FileSizeLimitDialog.d.ts +9 -0
- package/dist/rich-input/ModelSelector.d.ts +10 -0
- package/dist/rich-input/PlanningToggle.d.ts +7 -0
- package/dist/rich-input/ProgressCircle.d.ts +12 -0
- package/dist/rich-input/ReasoningModeSelect.d.ts +9 -0
- package/dist/rich-input/SkillCompletionMenu.d.ts +9 -0
- package/dist/rich-input/SkillStatusBar.d.ts +15 -0
- package/dist/rich-input/extensions/file-mention.d.ts +12 -0
- package/dist/rich-input/extensions/kb-mention.d.ts +18 -0
- package/dist/rich-input/extensions/skill-mention.d.ts +48 -0
- package/dist/rich-input/extensions/workflow-mention.d.ts +10 -0
- package/dist/rich-input/index.d.ts +48 -0
- package/dist/rich-input/input-keyboard.d.ts +13 -0
- package/dist/rich-input/lib/attachment-upload.d.ts +10 -0
- package/dist/rich-input/lib/background-tasks-view.d.ts +31 -0
- package/dist/rich-input/lib/client-query.d.ts +32 -0
- package/dist/rich-input/lib/composer-content.d.ts +42 -0
- package/dist/rich-input/lib/image-input-availability.d.ts +3 -0
- package/dist/rich-input/lib/kb-suggestions.d.ts +37 -0
- package/dist/rich-input/lib/reasoning-levels.d.ts +1 -0
- package/dist/rich-input/lib/session-data.d.ts +20 -0
- package/dist/rich-input/lib/skill-suggestions.d.ts +19 -0
- package/dist/rich-input/lib/token-estimate.d.ts +2 -0
- package/dist/rich-input/lib/token-pressure.d.ts +8 -0
- package/dist/rich-input/lib/upload-results.d.ts +8 -0
- package/dist/rich-input/lib/user-home.d.ts +33 -0
- package/dist/rich-input/model/model-data.d.ts +6 -0
- package/dist/rich-input/model/resolve-model.d.ts +59 -0
- package/dist/rich-input/model/use-composer-model.d.ts +24 -0
- package/dist/rich-input/services.d.ts +22 -0
- package/dist/rich-input/use-dismiss-skill-suggestions-on-mode-change.d.ts +3 -0
- package/dist/rich-input/use-input-history.d.ts +14 -0
- package/dist/rich-input/voice/VoiceWaveform.d.ts +10 -0
- package/dist/rich-input/voice/use-tiptap-voice-input.d.ts +47 -0
- package/dist/rich-input/voice/use-voice-input.d.ts +2 -0
- package/dist/rich-input/voice/voice-input-support.d.ts +5 -0
- package/dist/rich-input.js +29853 -0
- package/dist/rich-input.js.map +1 -0
- package/dist/style.css +30 -64
- package/dist/style.full.css +210 -65
- package/dist/{webapi-W3IB5DO4.js → webapi-E7K2CNM7.js} +2 -2
- package/package.json +12 -3
- package/public-api.md +206 -3603
- package/dist/components/AgentLoopBlock.d.ts +0 -8
- package/dist/components/AgentTaskCard.d.ts +0 -12
- package/dist/components/AssistantTurnBlock.d.ts +0 -36
- package/dist/components/ChatSurface.d.ts +0 -199
- package/dist/components/ConnectionBanner.d.ts +0 -8
- package/dist/components/MarkdownContent.d.ts +0 -30
- package/dist/components/MessageList.d.ts +0 -62
- package/dist/components/Shimmer.d.ts +0 -7
- package/dist/components/ToolCallBlock.d.ts +0 -41
- package/dist/components/TurnNavRail.d.ts +0 -23
- package/dist/components/UserMessageBubble.d.ts +0 -25
- package/dist/components/display-utils.d.ts +0 -4
- /package/dist/{chunk-G3PMV62Z.js.map → chunk-PR4QN5HX.js.map} +0 -0
- /package/dist/{webapi-W3IB5DO4.js.map → webapi-E7K2CNM7.js.map} +0 -0
package/README.md
CHANGED
|
@@ -43,6 +43,23 @@ export function App() {
|
|
|
43
43
|
|
|
44
44
|
Props 见 `BladeProviderProps`;在 Provider 外调用本包任何 hook 都会抛错。
|
|
45
45
|
|
|
46
|
+
### 宿主动作(host)
|
|
47
|
+
|
|
48
|
+
聊天界面里有些动作只有宿主知道怎么做:在工作区打开文件、打开预览、跳到另一个会话或项目、上传附件、列出可提及的技能和知识库。把它们作为 `host` 传进来(类型 `BladeHost`,附件与清单条目见 `HostAttachment`、`HostCatalogItem`):
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
const host: BladeHost = {
|
|
52
|
+
openSession: (sessionId) => navigate(`/chat/${sessionId}`),
|
|
53
|
+
openWorkspaceFile: ({ sessionId, path }) => openFile(sessionId, path),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
<BladeProvider client={client} host={host}>{children}</BladeProvider>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
每个动作都可选:**没提供的动作,对应的按钮和菜单项不显示**。`host` 请传稳定引用(模块常量或 `useMemo`)。
|
|
60
|
+
|
|
61
|
+
> 当前版本的 `ChatView` 还没有调用这些动作,传入后界面暂时没有变化;聊天组件与内置 Web 统一后(#3061)会接入。
|
|
62
|
+
|
|
46
63
|
## useBladeClient
|
|
47
64
|
|
|
48
65
|
```tsx
|
|
@@ -74,26 +91,6 @@ await session?.send("你好")
|
|
|
74
91
|
- 必须覆盖连接窗口内事件时,用 `onSessionConnected` 在历史加载和房间订阅开始前完成订阅,并返回取消订阅函数。
|
|
75
92
|
- 同一 `sessionId` 的多次调用复用同一 `AgentSession` 实例(引用计数);卸载后延迟释放,路由抖动不会断连。
|
|
76
93
|
|
|
77
|
-
## SessionMemoryToggle
|
|
78
|
-
|
|
79
|
-
把当前会话是否使用记忆放进宿主自己的会话设置面板:
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
const [memoryEnabled, setMemoryEnabled] = useState(session.memory_enabled !== false)
|
|
83
|
-
|
|
84
|
-
<SessionMemoryToggle
|
|
85
|
-
sessionId={session.id}
|
|
86
|
-
enabled={memoryEnabled}
|
|
87
|
-
onSaved={(_sessionId, enabled) => setMemoryEnabled(enabled)}
|
|
88
|
-
onError={(error) => toast.error(String(error))}
|
|
89
|
-
/>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
组件默认使用 `BladeProvider` 的 client;已有独立 client 装配层的宿主也可以显式传
|
|
93
|
-
`client={client}`。同一 client、同一会话的多个开关共享保存锁,即使切换会话或同时存在多个
|
|
94
|
-
设置入口,也不会并发提交相互覆盖。服务端确认后的权威值通过 `onSaved` 返回。
|
|
95
|
-
全部属性见 `SessionMemoryToggleProps`。
|
|
96
|
-
|
|
97
94
|
## useReplay
|
|
98
95
|
|
|
99
96
|
会话回放(演示 / 彩排):拿一个已经聊完的会话当素材,重现当时的回复和工具调用,
|
|
@@ -179,43 +176,28 @@ await replay.exitToAutonomous() // 退出回放,之后真的运
|
|
|
179
176
|
全部 props 见 `ChatViewProps`。样式说明:
|
|
180
177
|
|
|
181
178
|
智能体调用 `update_plan` 新增或更新任务时,`ChatView` 会在输入框上方自动展开最新任务进度,
|
|
182
|
-
5
|
|
183
|
-
使用 `CurrentPlanPanel` / `PlanUpdateBlock`,或用 `getPlanUpdateDisplayState`、
|
|
184
|
-
`parsePlanUpdate` 和 `pickCurrentPlanStep` 复用同一套判定。
|
|
185
|
-
|
|
186
|
-
后台任务自动唤醒会话时,`ChatView` 显示简短的 `BackgroundWakeNotice`,说明助手为何继续处理。卡片由明确的后台来源标记生成,流式和历史加载一致;自定义消息列表可以复用该组件及 agent-client 的 `isBackgroundWakeMessage`,无需解析内部提示文本。
|
|
187
|
-
|
|
188
|
-
`ChatView` 的普通展示模式不显示只供诊断的项目说明、可用能力和当前工作环境。需要自建诊断界面时,可直接使用独立的 `ContextCard`,属性见 `ContextCardProps`;状态到文案的映射来自 agent-client 的 `getContextDisplayState`,不需要接入方重复判断。一次输入通常会同时注入十几项上下文,把渲染序列里连续的几项用 `ContextGroupCard`(属性见 `ContextGroupCardProps`)折成一行更合适;哪几项算一组由 agent-client 的 `groupAdjacentContextRuns` 判定,折叠行文案来自 `getContextGroupDisplayState` / `ContextGroupDisplayState`。底层数据类型为 `ContextProjectionData` / `ContextProjectionFields`,展示结果为 `ContextDisplayState`,并保留 `ContextAction`、`ContextSourceInfo` 和 `contextProjectionData` 给 headless 消费方。
|
|
189
|
-
|
|
190
|
-
记忆引用提示可通过 `MemoryRefsHint` 展示,`collectMemoryRefs` 用于从一轮消息中聚合引用,保证宿主自定义消息布局与 SDK 默认界面保持一致。
|
|
191
|
-
|
|
192
|
-
MCP App 卡片(`tool_ui` block)在默认消息流里自动渲染,无需配置;自建消息布局时可用 `ToolUiCardView` 渲染单个卡片,并传入 `sessionId` 启用共享 AppBridge。卡片的实例、内容解析与终态(archived 留档不重放)判定来自 agent-client 的共享层(`collectInlineToolUiCards` / `resolveToolUiCardContent` 等)。preview 卡片不进消息流,由 `toolPreview` 事件推给宿主自建面板。 文件引用卡片(`resource-file`)只带 `sourcePath`,`ToolUiCardView` 在 `BladeProvider` 下由父页面带鉴权读取当前文件,再放入无令牌、无同源权限的 iframe 渲染;同一路径的新卡片不会自动重载当前页面,用户点击卡片上方的“刷新”按钮才重新读取。自建面板也应按路径去重、首次打开时读取,并保留用户操作中的页面直到手动刷新。
|
|
179
|
+
5 秒后平滑收起;连续更新会重新计时,用户手动展开或收起后以用户选择为准。
|
|
193
180
|
|
|
194
181
|
`onFollowupInteraction` 使用 `FollowupInteractionEvent`,覆盖下一步建议展示/采纳、成果展示/打开/下载以及结果评分。SDK 不内置分析服务;宿主回调抛错也不会中断用户点击或下载。
|
|
195
182
|
文件成果以文件名为文本的标准下载链接展示;Vue / 纯 HTML 使用的 `<blade-chat>` 与 `ChatView` 行为一致。
|
|
196
183
|
|
|
197
184
|
- 必须引入一份样式,按宿主有没有 Tailwind 二选一:
|
|
198
185
|
- **`style.full.css`**(默认选它):CSS 变量、容器查询、Markdown 排版等兜底 + 编译好的 Tailwind 产物,自包含,宿主没装 Tailwind 也是完整视觉。
|
|
199
|
-
- **`style.css`**:只有兜底那部分(Tailwind
|
|
186
|
+
- **`style.css`**:只有兜底那部分(Tailwind 类表达不了的规则),**不含主题变量**。宿主自己装了 Tailwind、且 `content` 配了扫描 `node_modules/@blade-hq/agent-react` 时用它,可以少一份重复 CSS;主题变量(下面「CSS 变量清单」)与 `bg-popover` 这类语义色工具类要由宿主自己提供。选错不会报错,只是布局、配色、工具块和思考块会退化成裸 HTML——组件的视觉本来就只定义一次,都在它的 Tailwind 类上。
|
|
200
187
|
- 两份都用同一套层序 `@layer properties, theme, base, components, utilities, blade-chat-overrides;`(在 `src/scoped-preflight.css` 里声明一次;`properties` 是 Tailwind 的 `--tw-*` 初始值层,必须排在最前)。兜底规则在 `components` 层;Tailwind 工具类在 `utilities` 层;SDK 里那几条**必须压过自己工具类**的规则(窄容器里放宽用户消息、移动端聚焦不缩放、Markdown 与代码块排版)在最后一层 `blade-chat-overrides`(名字带 SDK 前缀,避免宿主已有的同名层把全局层序定到别处)——放进 `components` 会被同元素的工具类盖掉,等于没写。**宿主写的普通 CSS 不分层,按层序赢过这里所有层,与加载顺序无关,也不需要 `!important`**:这是「接入方覆盖必胜」的实现方式,改 `src/style.css` 时不要把规则挪出这两层。
|
|
201
188
|
- 两份都不含 Tailwind preflight 这类全局 reset,引入后不会影响宿主页面自己的排版;组件需要的那点重置限定在 `.blade-chat` 子树内(见 `src/scoped-preflight.css`)。Shadow DOM 版(`<blade-chat>`)不受此限,它注入的是带 preflight 的完整产物。
|
|
202
|
-
- 颜色走 CSS
|
|
189
|
+
- 颜色走 CSS 变量,可整体换肤;深色页面要在聊天根节点或它的祖先元素上写 `data-theme="dark"`(或给 `ChatView` 传 `theme="dark"`),否则拿到的是默认的浅色变量。
|
|
203
190
|
- `renderers.toolCall` 返回 `null` 时回落到默认工具卡片。
|
|
204
191
|
- **内边距按容器宽度伸缩**,嵌进窄侧栏(300–400px)时会自动收窄,不看浏览器视口。代价是聊天容器需要有确定宽度——放在 flex/grid 里被拉伸,或显式给宽度都可以;放进 `width: fit-content`、`inline-flex` 这类由内容决定宽度的父容器会塌缩。
|
|
205
192
|
|
|
206
193
|
## 纯 LLM 模式(不建会话)
|
|
207
194
|
|
|
208
195
|
有些应用只要「一问一答」——分类、改写、总结,不需要智能体、不需要会话、也不需要用户登录
|
|
209
|
-
Blade。这时把 `ChatView` 切到 `mode="llm"
|
|
196
|
+
Blade。这时把 `ChatView` 切到 `mode="llm"`,界面完全一样:
|
|
210
197
|
|
|
211
198
|
```tsx
|
|
212
199
|
<ChatView /> // 默认:智能体模式,行为不变
|
|
213
200
|
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model }} /> // 切成纯 LLM
|
|
214
|
-
<LlmChat baseURL="/api/llm" model={model} /> // 或者直接用(LlmChatProps)
|
|
215
|
-
<AgentChat sessionId={id} /> // 智能体那侧同样可以直接用(AgentChatProps)
|
|
216
|
-
|
|
217
|
-
`AgentChat` 还支持 `sessionPluginControls(sessionId)` 插槽,宿主可复用
|
|
218
|
-
`@blade-hq/agent-client` 的会话插件状态与激活控制。
|
|
219
201
|
```
|
|
220
202
|
|
|
221
203
|
**协议就是 OpenAI 的**:`POST {baseURL}/chat/completions`,`stream: true`,读
|
|
@@ -252,23 +234,26 @@ models.filter((m) => m.serviceModelId)
|
|
|
252
234
|
每一轮工具调用都会渲染成和智能体模式一样的工具卡片。
|
|
253
235
|
|
|
254
236
|
```tsx
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
237
|
+
<ChatView
|
|
238
|
+
mode="llm"
|
|
239
|
+
llm={{
|
|
240
|
+
baseURL: "/api/llm",
|
|
241
|
+
model,
|
|
242
|
+
system: "你是订单助手",
|
|
243
|
+
tools: [{ type: "function", function: { name: "query_orders", parameters: schema } }], // ChatCompletionTool
|
|
244
|
+
onToolCall: async ({ name, arguments: args }) => {
|
|
245
|
+
if (name !== "query_orders") throw new Error(`没有这个函数:${name}`)
|
|
246
|
+
// 参数是模型生成的不可信输入:先校验类型,再用 URLSearchParams 拼,
|
|
247
|
+
// 直接塞进查询串的话,一个 `李航&limit=all` 就能多带出参数
|
|
248
|
+
const { owner } = JSON.parse(args || "{}")
|
|
249
|
+
if (typeof owner !== "string") throw new Error("owner 必须是字符串")
|
|
250
|
+
const query = new URLSearchParams({ owner })
|
|
251
|
+
const resp = await fetch(`/api/orders?${query}`)
|
|
252
|
+
if (!resp.ok) throw new Error(`订单接口返回 ${resp.status}`)
|
|
253
|
+
return resp.json()
|
|
254
|
+
},
|
|
270
255
|
}}
|
|
271
|
-
|
|
256
|
+
onLlmReady={(handle) => setChat(handle)} // LlmChatHandle:insertText / send / reset
|
|
272
257
|
/>
|
|
273
258
|
```
|
|
274
259
|
|
|
@@ -278,19 +263,17 @@ models.filter((m) => m.serviceModelId)
|
|
|
278
263
|
改过之后会显示「已自定义」,也能一键恢复默认。不传就一个节点都不渲染。
|
|
279
264
|
|
|
280
265
|
```tsx
|
|
281
|
-
<LlmChat baseURL="/api/llm" model={model} advanced />
|
|
282
|
-
<LlmChat baseURL="/api/llm" model={model} advanced={{ apiKey: true, storage: "memory" }} />
|
|
283
266
|
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model, advanced: true }} />
|
|
267
|
+
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model, advanced: { apiKey: true, storage: "memory" } }} />
|
|
284
268
|
```
|
|
285
269
|
|
|
286
|
-
`
|
|
270
|
+
`advanced` 对象可细调:`baseURL` / `model` 各自能不能改(默认都能)、`apiKey` 要不要
|
|
287
271
|
出现(**默认不出现**,打开意味着密钥存在浏览器里,只适合受控场景)、`storage` 是记住选择
|
|
288
272
|
(`local`,默认)还是只在本次有效(`memory`)、`storageKey` 自定义存储键。用户改了会回调
|
|
289
|
-
`onOverrideChange
|
|
273
|
+
`llm.onOverrideChange`,宿主想自己也记一份时用。
|
|
290
274
|
|
|
291
275
|
其余可选项见 `LlmChatOptions`:`headers`、`historyTurns`(默认 12 轮)、`temperature`、
|
|
292
|
-
`extraBody`、`fetchImpl
|
|
293
|
-
`UseLlmChatResult`:`messages` / `isStreaming` / `error` / `send` / `stop` / `reset`)。
|
|
276
|
+
`extraBody`、`fetchImpl`。
|
|
294
277
|
|
|
295
278
|
纯 LLM 模式不做会话持久化、文件上传、提问卡片、记忆和检查点——那些是智能体协议的能力,
|
|
296
279
|
需要就用默认模式。
|
|
@@ -300,48 +283,13 @@ models.filter((m) => m.serviceModelId)
|
|
|
300
283
|
单独渲染一段智能体消息 Markdown(含代码复制按钮、XSS 消毒;HTML 经 sanitize 白名单处理,不接受任意 raw HTML):
|
|
301
284
|
|
|
302
285
|
```tsx
|
|
303
|
-
<MarkdownContent
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
<MarkdownContent sessionId={sessionId}>{markdownText}</MarkdownContent>
|
|
286
|
+
<MarkdownContent>{markdownText}</MarkdownContent>
|
|
308
287
|
```
|
|
309
288
|
|
|
310
|
-
`
|
|
311
|
-
|
|
312
|
-
## Canonical 引用消息
|
|
313
|
-
|
|
314
|
-
`ChatView` 会自动识别 ship-attack 会话中的 `[引用]` / `[用户输入]` canonical
|
|
315
|
-
消息,并按原顺序展示引用来源、快照和用户输入。自建消息列表时可用
|
|
316
|
-
`parseWhatIfPrompt` 解析同一格式,再交给 `WhatIfUserBubble` 渲染;
|
|
317
|
-
`onQuoteClick` 可接入宿主的步骤跳转。
|
|
289
|
+
传 `sessionId` 时,正文里相对路径的图片按该会话工作区里的文件解析:
|
|
318
290
|
|
|
319
291
|
```tsx
|
|
320
|
-
|
|
321
|
-
return parsed ? (
|
|
322
|
-
<WhatIfUserBubble parsed={parsed} onQuoteClick={jumpToStep} />
|
|
323
|
-
) : null
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## isAgentComputerCommand / isAgentComputerToolCall / classifyAgentComputerLaunchOutcome
|
|
327
|
-
|
|
328
|
-
判断智能体是否在沙盒里启动了通用 GUI 应用镜像(`computer launch`)——后端 blade-agent 通过 `computer` CLI 驱动一块虚拟屏幕运行任意 GUI 软件,这些函数用于自行接入方判断要不要展示对应的实时画面入口(比如自建一个类似「电脑镜像」的标签页)。当前 SDK 侧还没有内置这块 UI,只导出判定规则,避免各接入方重新猜一遍匹配逻辑。
|
|
329
|
-
|
|
330
|
-
`isAgentComputerCommand`/`isAgentComputerToolCall` 只回答"这条命令是不是一次 launch 尝试",不回答"这次 launch 最终成不成功"——待处理、已失败的 launch 也会命中。要判断真实结果(比如决定要不要展示画面入口),用 `classifyAgentComputerLaunchOutcome`:它会解析工具调用的 `status`/`result`,返回 `"pending" | "succeeded" | "failed" | "unknown"`。`"unknown"` 表示 `status` 已经是终态但 `result` 字段本身缺失(常见于会话整理/compaction 之后的历史投影,归档时不会重新序列化完整 result),跟"result 回来了、里面确实没有成功标记"的 `"failed"` 是两种不同性质的证据——接入方要按自己的场景决定怎么处理(展示类场景可以偏宽松,当作足够展示;自动跳转/替用户做决定的场景应该偏保守,当作跟 `"failed"` 一样处理)。
|
|
331
|
-
|
|
332
|
-
```ts
|
|
333
|
-
import {
|
|
334
|
-
isAgentComputerCommand,
|
|
335
|
-
isAgentComputerToolCall,
|
|
336
|
-
classifyAgentComputerLaunchOutcome,
|
|
337
|
-
} from "@blade-hq/agent-react"
|
|
338
|
-
|
|
339
|
-
isAgentComputerCommand("computer launch --exec /opt/apps/foo/bar") // true
|
|
340
|
-
isAgentComputerCommand("computer status") // false(只认 launch,不认查询类子命令)
|
|
341
|
-
|
|
342
|
-
isAgentComputerToolCall(toolCall.arguments) // 工具调用的 JSON 参数版本
|
|
343
|
-
|
|
344
|
-
classifyAgentComputerLaunchOutcome(toolCall) // "pending" | "succeeded" | "failed" | "unknown"
|
|
292
|
+
<MarkdownContent sessionId={sessionId}>{markdownText}</MarkdownContent>
|
|
345
293
|
```
|
|
346
294
|
|
|
347
295
|
## 选择模型
|
|
@@ -360,23 +308,23 @@ SDK 的 ChatView / `<blade-chat>` 不内置模型选择器(默认用后端配
|
|
|
360
308
|
|
|
361
309
|
1. **深浅主题**:`<ChatView theme="dark" />` / `<blade-chat theme="dark">` 切内置深色,不传或 `"light"` 为浅色。宿主已经在祖先元素上写了 `data-theme="dark"` 时不用传,`ChatView` 会跟着变。`<blade-chat>` 渲染在 Shadow DOM 内,页面上的 `data-theme` 对它无效,只认 `theme` 属性。
|
|
362
310
|
|
|
363
|
-
2. **CSS 变量换肤**(React 与 `<blade-chat>` 通用):所有颜色走 `--primary`、`--background`、`--muted` 等变量(完整清单见下面的「CSS
|
|
311
|
+
2. **CSS 变量换肤**(React 与 `<blade-chat>` 通用):所有颜色走 `--primary`、`--background`、`--muted` 等变量(完整清单见下面的「CSS 变量清单」,默认值随 `style.full.css` 与 `blade-agent.js` 一起提供,和内置 Web 是同一套取值)。页面直接覆盖即可穿透 Shadow DOM:
|
|
364
312
|
|
|
365
313
|
```css
|
|
366
314
|
blade-chat { --primary: 262 83% 58%; height: 640px; }
|
|
367
315
|
```
|
|
368
316
|
|
|
369
|
-
3. **classNames props**(React):`ChatView` 的 `classNames` 把自定义 class(含你自己构建的 Tailwind 工具类)挂到各个区块上,见 `ChatViewClassNames`。SDK 内部用 `cn`(clsx + tailwind-merge)拼接,你传的类在最后一个参数,所以 `classNames={{
|
|
317
|
+
3. **classNames props**(React):`ChatView` 的 `classNames` 把自定义 class(含你自己构建的 Tailwind 工具类)挂到各个区块上,见 `ChatViewClassNames`。SDK 内部用 `cn`(clsx + tailwind-merge)拼接,你传的类在最后一个参数,所以 `classNames={{ chatInputRoot: "p-0" }}` 会直接盖掉组件内部的内边距,与 CSS 加载顺序无关:
|
|
370
318
|
|
|
371
319
|
```tsx
|
|
372
320
|
<ChatView
|
|
373
321
|
sessionId={id}
|
|
374
322
|
classNames={{
|
|
375
323
|
root: "h-[640px] rounded-2xl border",
|
|
376
|
-
|
|
324
|
+
userMessage: "[&_.blade-chat-user-bubble]:bg-sky-100",
|
|
377
325
|
assistantText: "text-[15px] leading-7",
|
|
378
|
-
|
|
379
|
-
|
|
326
|
+
chatInputRoot: "px-2",
|
|
327
|
+
chatInputInner: "max-w-4xl",
|
|
380
328
|
}}
|
|
381
329
|
/>
|
|
382
330
|
```
|
|
@@ -399,38 +347,26 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
399
347
|
|
|
400
348
|
### classNames 覆盖哪些区块
|
|
401
349
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
| `classNames` 键 |
|
|
405
|
-
| --- | --- |
|
|
406
|
-
| `root` |
|
|
407
|
-
| `
|
|
408
|
-
| `
|
|
409
|
-
| `
|
|
410
|
-
| `
|
|
411
|
-
| `
|
|
412
|
-
| `
|
|
413
|
-
| `
|
|
414
|
-
| `
|
|
415
|
-
| `
|
|
416
|
-
| `
|
|
417
|
-
| `
|
|
418
|
-
| `
|
|
419
|
-
| `
|
|
420
|
-
| `
|
|
421
|
-
| `
|
|
422
|
-
| `toolRow` | `.blade-chat-tool-row` | 执行过程里的工具行(智能体模式下工具调用都走这条路径) |
|
|
423
|
-
| `agentLoop` | `.blade-chat-agent-loop` | 子智能体行 |
|
|
424
|
-
| `askCard` | `.blade-chat-ask-card` | 提问卡 |
|
|
425
|
-
| `askOption` | `.blade-chat-ask-option` | 提问卡的每个选项 |
|
|
426
|
-
| `planPanel` | `.blade-chat-plan` | 输入框上方的计划面板 |
|
|
427
|
-
| `queuePanel` | `.blade-chat-queue` | 会话队列面板 |
|
|
428
|
-
| `followup` | `.blade-chat-followup` | 本轮小结与跟进区 |
|
|
429
|
-
| `followupAction` | `.blade-chat-followup-action` | 跟进建议按钮 |
|
|
430
|
-
| `chatInput` | `.blade-chat-input` | 输入区外层 |
|
|
431
|
-
| `inputInner` | `.blade-chat-input-inner` | 输入框卡片(边框、圆角、底色) |
|
|
432
|
-
| `textarea` | `.blade-chat-textarea` | 输入框 |
|
|
433
|
-
| `inputToolbar` | `.blade-chat-input-toolbar` | 输入框右侧按钮组 |
|
|
350
|
+
`classNames` 是便利功能,键名跟随组件结构,不冻结;需要稳定抓手时用下面的锚点类名或 `part`。
|
|
351
|
+
|
|
352
|
+
| `classNames` 键 | 落点(锚点) |
|
|
353
|
+
| --- | --- |
|
|
354
|
+
| `root` | 聊天根容器(`.blade-chat`) |
|
|
355
|
+
| `viewerBanner` | 查看分享会话时的只读提示条 |
|
|
356
|
+
| `messageListRoot` | 消息区(`.blade-chat-messages`) |
|
|
357
|
+
| `messageListViewport` | 消息区滚动内容,管内边距(`.blade-chat-messages-scroll`) |
|
|
358
|
+
| `messageListContent` | 消息内容列,管居中与最大宽度(`.blade-chat-messages-content`) |
|
|
359
|
+
| `messageListInner` | 内容列里逐轮排列的容器,管轮次间距 |
|
|
360
|
+
| `emptyState` | 空状态(`.blade-chat-empty`) |
|
|
361
|
+
| `userMessage` | 用户消息行(`.blade-chat-user-row`) |
|
|
362
|
+
| `errorMessage` | 消息流里的出错块(`.blade-chat-error-row`) |
|
|
363
|
+
| `assistantTurn` | 一轮助手回复(`.blade-chat-assistant-turn`) |
|
|
364
|
+
| `assistantText` | 助手正文(`.blade-chat-assistant-text`) |
|
|
365
|
+
| `toolCall` | 执行过程里的工具行与子智能体行(`.blade-chat-tool-row`、`.blade-chat-agent-loop`) |
|
|
366
|
+
| `chatInputRoot` | 输入区外层(`.blade-chat-input`) |
|
|
367
|
+
| `chatInputInner` | 输入框、计划面板与队列面板共用的内容宽度 |
|
|
368
|
+
| `queuePanelRoot` | 会话队列面板(`.blade-chat-queue`);改输入区左右内边距时在这里同步 `--blade-chat-queue-gutter` |
|
|
369
|
+
| `skillStatusBarRoot`、`skillStatusBarInner` | 富输入菜单里的技能状态条(第一方内置 Web 用) |
|
|
434
370
|
|
|
435
371
|
同一批锚点也是普通 CSS 的抓手:锚点只负责「可被选中」,视觉规则不在它身上,所以对你的锚点写 `.blade-chat-user-bubble { ... }` 不会被 SDK 的样式盖掉(SDK 的规则都在 `@layer components` / `@layer blade-chat-overrides`,Tailwind 工具类在 `@layer utilities`,你的样式不分层,按 CSS 层序本来就赢)。
|
|
436
372
|
|
|
@@ -443,7 +379,7 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
443
379
|
| 聊天根容器 | `.blade-chat` |
|
|
444
380
|
| 登录引导 / 连接状态条 / 出错条 / 回放条 | `.blade-chat-login`、`.blade-chat-banner`、`.blade-chat-error-bar`、`.blade-chat-replay-bar` |
|
|
445
381
|
| 消息区 | `.blade-chat-messages`、`.blade-chat-messages-scroll`、`.blade-chat-messages-content`、`.blade-chat-empty`、`.blade-chat-scroll-bottom`、`.blade-chat-scroll-bottom-label` |
|
|
446
|
-
| 用户消息 | `.blade-chat-user-row`、`.blade-chat-user-col`、`.blade-chat-user-bubble`、`.blade-chat-file-card
|
|
382
|
+
| 用户消息 | `.blade-chat-user-row`、`.blade-chat-user-col`、`.blade-chat-user-bubble`、`.blade-chat-file-card` |
|
|
447
383
|
| 助手回复 | `.blade-chat-assistant-turn`、`.blade-chat-assistant-text`、`.blade-chat-thinking`、`.blade-chat-memory-refs`、`.blade-chat-render-error` |
|
|
448
384
|
| 工具调用 | `.blade-chat-tool-row`(执行过程里的工具行)、`.blade-chat-agent-loop`(子智能体行) |
|
|
449
385
|
| 提问卡 | `.blade-chat-ask-card`、`.blade-chat-ask-option` |
|
|
@@ -451,8 +387,8 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
451
387
|
| 跟进区 | `.blade-chat-followup`、`.blade-chat-followup-action` |
|
|
452
388
|
| 输入区 | `.blade-chat-input`、`.blade-chat-input-inner`、`.blade-chat-textarea`、`.blade-chat-input-toolbar` |
|
|
453
389
|
| 上下文卡片 | `.blade-chat-context-card`、`.blade-chat-context-summary`、`.blade-chat-context-icon`、`.blade-chat-context-copy`、`.blade-chat-context-title`、`.blade-chat-context-status`、`.blade-chat-context-chevron`、`.blade-chat-context-detail`、`.blade-chat-context-group-items` |
|
|
454
|
-
| Markdown 与代码块 | `.blade-chat-markdown`、`.blade-chat-prose`、`.blade-chat-codeblock`、`.blade-chat-codeblock-header`、`.blade-chat-codeblock-body` |
|
|
455
|
-
| 其他 | `.blade-chat-host`(`<blade-chat>` 内的挂载点)、`.blade-chat-advanced`、`.blade-chat-mcp-app`、`.blade-chat-error-row
|
|
390
|
+
| Markdown 与代码块 | `.blade-chat-markdown`、`.blade-chat-prose`、`.blade-chat-table-wrapper`、`.blade-chat-codeblock`、`.blade-chat-codeblock-header`、`.blade-chat-codeblock-body` |
|
|
391
|
+
| 其他 | `.blade-chat-host`(`<blade-chat>` 内的挂载点)、`.blade-chat-advanced`、`.blade-chat-mcp-app`、`.blade-chat-error-row` |
|
|
456
392
|
|
|
457
393
|
### part 清单(`<blade-chat>` 的 Shadow DOM 专用)
|
|
458
394
|
|
|
@@ -480,7 +416,7 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
480
416
|
|
|
481
417
|
### CSS 变量清单
|
|
482
418
|
|
|
483
|
-
颜色是 `hsl()` 的三个分量(形如 `223 100% 54%`),几何是长度值。宿主盖掉这些变量即可整体换肤(React 下写在 `.blade-chat`
|
|
419
|
+
颜色是 `hsl()` 的三个分量(形如 `223 100% 54%`),几何是长度值。宿主盖掉这些变量即可整体换肤(React 下写在 `.blade-chat` 上——默认值就声明在这一层,写在祖先元素上会被盖掉;聊天区之外的 SDK 内容(Portal 到 body 的浮层、单独使用的组件)取 `:root` 上的值,宿主没声明时用零优先级的默认值;`<blade-chat>` 下写在元素上,变量能穿透 Shadow DOM,深浅主题下都生效):
|
|
484
420
|
|
|
485
421
|
| 分组 | 变量 |
|
|
486
422
|
| --- | --- |
|
|
@@ -490,9 +426,9 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
490
426
|
| 布局(SDK 按容器宽度推导) | `--blade-chat-gutter`(消息区与输入区的左右留白)、`--blade-chat-turn-gap`(轮次间距)。两个变量声明在 `.blade-chat` 上,**设在祖先元素上不生效**(元素上的声明永远赢过继承值)。React 下直接对 `.blade-chat` 写规则,或用 `classNames.root` 传一个设该变量的工具类(如 `"[--blade-chat-gutter:2rem]"`);`<blade-chat>` 下用第 4 层的内嵌 `<style>` |
|
|
491
427
|
| 布局(接入方按需覆盖) | `--blade-chat-queue-gutter`(队列面板左右留白,默认跟随 gutter)、`--blade-chat-pin-spacer`(消息顶置的底部补白,组件自己写)、`--blade-chat-input-top-border`(输入区上边框,组件自己写) |
|
|
492
428
|
|
|
493
|
-
|
|
429
|
+
深色主题的变量:`<blade-chat>` 挂在 `:host([theme="dark"])` 上(只声明在宿主元素这一层,所以元素上的覆盖在深色下同样生效);React 下挂在带 `data-theme="dark"` 的 `.blade-chat` 或其祖先之内的 `.blade-chat` 上。
|
|
494
430
|
|
|
495
|
-
上面这些是**会改变 SDK 组件外观**的变量。另外随主题一起声明的还有 `--radius`、`--input`、`--destructive-foreground
|
|
431
|
+
上面这些是**会改变 SDK 组件外观**的变量。另外随主题一起声明的还有 `--radius`、`--input`、`--destructive-foreground`:它们不在冻结的变量清单里。`--radius` 决定 Tailwind 圆角尺度(`rounded-md` 等由它推导);`--input`、`--destructive-foreground` 供宿主在聊天容器里自绘 UI 时直接用。
|
|
496
432
|
|
|
497
433
|
### 区块级渲染器(renderers)
|
|
498
434
|
|
|
@@ -512,132 +448,26 @@ blade-chat::part(input-toolbar) { gap: 12px; }
|
|
|
512
448
|
```
|
|
513
449
|
|
|
514
450
|
- `toolCall`:`(toolCall: ToolCallInfo) => ReactNode | null`,见 `ToolCallRenderer`。
|
|
451
|
+
- `tool`:按工具名替换工具行展开后的详情,形如 `{ my_tool: MyToolDetail }`;组件拿到 `ToolRendererProps`(`toolCall`、`sessionId`),工具行本身(名称、状态、耗时)仍由 SDK 渲染。
|
|
515
452
|
- `userMessage`:`UserMessageRendererProps`——只替换用户消息气泡,消息行(`.blade-chat-user-row`)与图片/文件附件仍由 SDK 渲染;消息顶置依赖那个行容器,替换掉它新消息就不会被顶到视口顶部。
|
|
516
453
|
- `assistantText`:`AssistantTextRendererProps`——只替换助手正文,思考块、工具卡、附件照旧。
|
|
517
454
|
- `askUserQuestion`:`AskUserQuestionRendererProps`——`data` 是 `AskUserQuestionData`,`onAnswer` 与默认卡片同一个回调契约;照常作答才算完成这次提问。**提问工具优先走这个渲染器**,只有没配它时才回落到 `toolCall`(避免 `toolCall` 先拿到提问、而它没有作答回调)。
|
|
518
455
|
- `planUpdate`:`PlanUpdateRendererProps`——`plan` 是最近一次成功 `update_plan` 的解析结果;还没有解析出计划时渲染器不会被调用,回落到默认面板的「正在更新任务进度…」。
|
|
519
456
|
|
|
520
|
-
## 自定义消息列表滚动
|
|
521
|
-
|
|
522
|
-
`ChatView`、`AgentChat` 和 `LlmChat` 已经处理好用户发言顶置,不用额外配置。自建消息列表时可用 `useMessagePin` 复用同一套触发、补白、视口 resize 和手动滚动接管逻辑;调用方只需提供滚动容器、目标消息与 spacer 的读写函数。完整参数见 [public-api.md](./public-api.md#usemessagepin-function)。
|
|
523
|
-
|
|
524
|
-
## 打字机式显示节流
|
|
525
|
-
|
|
526
|
-
`ChatView`、`AgentChat` 和 `LlmChat` 已经内置这套节流,不用额外配置。服务端到达速率天然不均匀(一次推 5 个字、一次推 20 个字),直接原样渲染会显得忽快忽慢;`useTypewriterReveal(targetText, isLive, resetKey)` 按估算到达速度、留一个很小的固定滞后量匀速播放,返回 `{ displayedText, isRevealing, flushNow }`——自建消息列表时可以直接复用。`isLive` 传 `false`(历史加载 / 回放)时永远整段直接显示;`resetKey`(比如消息的 `entry_id`)变化时播放状态从头开始;`flushNow()` 用于用户点了停止时立即整段显示已到达内容。完整参数见 [public-api.md](./public-api.md#usetypewriterreveal-function)。
|
|
527
|
-
|
|
528
|
-
## 长会话历史导航(useTurnNavigation / TurnNavRail)
|
|
529
|
-
|
|
530
|
-
`AgentChat` 已经内置这条轨道,不用额外配置:右侧刻度轨列出**整条会话**的用户发言(目录来自服务端
|
|
531
|
-
`/turn-index`,所以窗口外的旧发言也点得到),点已加载的发言直接滚动并短暂高亮,点窗口外的发言发
|
|
532
|
-
一次定位请求(服务端返回「从它到最新」的窗口)后滚动到目标。
|
|
533
|
-
|
|
534
|
-
自建聊天界面时用 `useTurnNavigation` 复用同一套时序(滚动 / 加载态 / 高亮):宿主提供
|
|
535
|
-
`TurnNavigationSource`(目录怎么来、定位怎么发、刷新信号)与 `getLoadedEntryElement` /
|
|
536
|
-
`revealEntry` / `beforeSeek` 三个列表侧回调(合起来就是 `TurnNavigationOptions`),返回
|
|
537
|
-
`TurnNavigationResult`(`items`、`loadingEntryId`、
|
|
538
|
-
`error`、`failedEntryId`、`activeEntryId`、`selectEntry`)。`TurnNavRail` 是配套的刻度轨组件
|
|
539
|
-
(`TurnNavRailProps`),样式与交互可直接用或照抄;定位高亮的类名是 `TURN_NAV_HIGHLIGHT_CLASS`。
|
|
540
|
-
目录刷新信号用 `latestUserSpeechEntryId(turns)` 计算——只认最新一条用户发言,往前翻页补进的更早
|
|
541
|
-
发言不该作废正在进行的定位;没有目录接口的会话(静态历史 / 分享页)用 `turnNavItemsFromMessages`
|
|
542
|
-
把已加载的窗口转成兜底条目。
|
|
543
|
-
|
|
544
457
|
## 纯静态 HTML / Vue?
|
|
545
458
|
|
|
546
|
-
不需要本包——用后端托管的单文件产物 `<script src=".../sdk/blade-agent.js">` + `<blade-chat>` 自定义元素,行为与 `ChatView`
|
|
547
|
-
|
|
548
|
-
## 附录:连接器与插件选择
|
|
549
|
-
|
|
550
|
-
- **连接器面板**:`ConnectorPanel`、`ConnectorPanelProps`、`PluginConnectorList`、`PluginConnectorListProps`、`ConfiguringPlugin`
|
|
551
|
-
- **插件行与触发器**:`PluginConnectorItem`、`PluginConnectorSelection`、`selectionFromItem`、`triggerSummary`、`TriggerSummary`、`TRIGGER_ICON_LIMIT`、`mergeConnectorItems`、`catalogToConnectorItems`、`filterConnectorItems`
|
|
552
|
-
- **触发器上的头像堆叠**:`PluginTriggerStack`、`PluginTriggerStackProps`(前三枚插件头像 + `+N`,宿主自己画触发器时用它,不要另写一份折叠)
|
|
553
|
-
- **启用时序**:`useSessionPluginActivation`、`UseSessionPluginActivationResult`
|
|
554
|
-
|
|
555
|
-
## 附录:re-export 自 @blade-hq/agent-client
|
|
556
|
-
|
|
557
|
-
为方便单包引入,本包 re-export 了 agent-client 的全部公开声明(`BladeClient`、`AgentSession`、`SessionState`、协议类型等),文档一律见 [agent-client 的 README](../agent-client/README.md)。完整签名见 [public-api.md](./public-api.md)。
|
|
558
|
-
|
|
559
|
-
本包自有声明:`BladeProvider`、`BladeProviderProps`、`useBladeClient`、`useAgentSession`、`UseAgentSessionOptions`、`UseAgentSessionResult`、`useMessagePin`、`UseMessagePinOptions`、`useTypewriterReveal`、`UseTypewriterRevealResult`、`useReplay`、`UseReplayResult`、`ReplayMismatch`、`ReplayBar`、`ReplayBarProps`、`ReplayMismatchPrompt`、`ReplayMismatchPromptProps`、`ChatView`、`ChatViewProps`、`ChatViewClassNames`、`ChatViewRenderers`、`ChatViewSlots`、`FollowupInteractionEvent`、`ToolCallRenderer`、`CurrentPlanPanel`、`PlanUpdateBlock`、`PlanUpdateData`、`PlanUpdateDisplayState`、`PlanStepStatus`、`getPlanUpdateDisplayState`、`isPlanUpdateTool`、`parsePlanUpdate`、`pickCurrentPlanStep`、`PLAN_AUTO_COLLAPSE_MS`、`MarkdownContent`、`MarkdownContentProps`、`MarkdownRenderer`、`MarkdownRendererProps`、`MarkdownComponents`、`AllowedTags`、`DEFAULT_ALLOWED_TAGS`、`CodeBlockOverrides`、`LinkSafetyConfig`、`RawHtmlPlugin`、`parseWhatIfPrompt`、`ParsedWhatIfPrompt`、`WhatIfQuote`、`WhatIfUserBubble`、`WhatIfUserBubbleProps`、`SessionQueuePanel`、`SessionQueuePanelProps`、`isQueuePanelVisible`、`ExternalAnchor`、`stripSystemReminders`。
|
|
560
|
-
|
|
561
|
-
## 会话消息队列
|
|
562
|
-
|
|
563
|
-
智能体会话的等待消息由服务端持久化,`ChatView` / `AgentChat` 已在输入框上方渲染队列区域(空队列不占位,条目数超过三条可折叠)。输入框不再有「直接插入 / 排队执行」开关:会话空闲时提交直接开始处理,运行中提交默认入队;暂停时在面板上显示原因和「继续执行」。面板数据来自 `useAgentSession` 状态里的 `queue`(`SessionQueueSnapshot`),可编辑/删除/立即补充的判定来自 agent-client 的 `canEditQueuedMessage` / `canCancelQueuedMessage` / `canDeliverQueuedMessage`,文案用 `queuedMessageStatusLabel` / `queuePauseReasonLabel`。
|
|
564
|
-
|
|
565
|
-
自建布局可直接使用 `<SessionQueuePanel>`,属性类型为 `SessionQueuePanelProps`:传 `snapshot`、`canDeliver`、可选的 `pendingItemId`(提交中的条目禁用操作)与 `notice`(冲突/失败说明),以及 `onResume` / `onCancel` / `onEdit` / `onMove` / `onReorder` / `onDeliver` 回调。上移/下移与拖动排序最终都提交当前 pending 条目的完整 id 顺序。冲突时队列由服务端快照刷新,`ChatView` 会把未保存的正文放回输入框,不会静默丢掉用户编辑的内容。
|
|
566
|
-
|
|
567
|
-
面板比输入框窄一圈、贴在它上方,所以宽度必须跟输入框一致:用可选属性 `contentWidthClassName` 传入输入框内层的宽度类(不传则按本包输入框的 `max-w-[748px]`)。左右内边距同理——默认值对应本包输入框的 `--blade-chat-gutter`;如果你给输入框根节点换了左右 padding(例如改成 `px-10`),必须用 `rootClassName` 在面板根节点重设 `--blade-chat-queue-gutter` 成同一个值,否则面板会跟输入框错开一圈、在两者相接的缝上露出来。
|
|
568
|
-
|
|
569
|
-
输入框的 `mergesWithPanelAbove` 为 true 时上两角改直角,跟面板拼成一条完整轮廓;否则输入框的上角圆弧会从较窄的面板两侧露出来,形成两个尖角。**这个值要取面板上报的状态,不要在宿主侧用 `isQueuePanelVisible` 复算**:面板有本地编辑态(编辑到一半、那条等待消息被服务端拿走时仍要留在原地保住草稿,见 `detachedEdit`),这时 snapshot 里没有条目、宿主复算会得到 false,输入框就会在面板还显示着的时候把上角恢复成圆角。用 `onVisibleChange` 拿到面板的真实占位状态,再传给输入框或 `ChatSurface` 的 `queuePanelVisible`。本包的 `AgentChat` 已按这条接好,自建布局照做即可。
|
|
570
|
-
|
|
571
|
-
拼轮廓的前提是两者**真的相邻**。中间夹着别的内容时(本包的 MCP 暂存内容、内置 Web 的 `composerBanner`),面板下两角与输入框上两角都要圆起来——直角是"被对方接住"的表示,露出直角却中间空着一块,看起来像被削掉一角。面板的 `mergesWithInputBelow` 与输入框的 `mergesWithPanelAbove` 要取同一个判据;`ChatSurface` 会用 context 把这一位传给面板,自建布局直接传即可。
|
|
572
|
-
|
|
573
|
-
`PollingBackoff` 从 agent-client 重导出,供自定义只读轮询计算退避时间;它不发送或重放请求。用法见 [agent-client 轮询说明](../agent-client/README.md#只读轮询退避)。
|
|
574
|
-
|
|
575
|
-
### 会话插件
|
|
459
|
+
不需要本包——用后端托管的单文件产物 `<script src=".../sdk/blade-agent.js">` + `<blade-chat>` 自定义元素,行为与 `ChatView` 一致。Vue 项目用 `@blade-hq/agent-vue` 的 `<BladeChat>` 组件;区块定制用挂载式渲染器(`<blade-chat>` 元素的 `renderers` 属性),详见 agent-client README「`<blade-chat>` 的挂载式渲染器」。
|
|
576
460
|
|
|
577
|
-
|
|
461
|
+
## 第一方内部入口
|
|
578
462
|
|
|
579
|
-
|
|
463
|
+
- `@blade-hq/agent-react/internal`:内置 Web 与浏览器插件用的聊天内部块、判定逻辑和连接器组件。
|
|
464
|
+
- `@blade-hq/agent-react/rich-input`:富输入(Tiptap 编辑器、提及、`/` 菜单、语音、模型选择)。
|
|
465
|
+
- `@blade-hq/agent-react/chat.css`:聊天正文的 Markdown 排版与动画,给自己编译 Tailwind 的第一方宿主(内置 Web、浏览器插件)引入;`style.full.css` 与 `blade-agent.js` 已经包含它。
|
|
580
466
|
|
|
581
|
-
|
|
467
|
+
三者都随包发布,但**不属于公开面**:不写进本文档与 `public-api.md`,版本之间可以随时改动或删除。
|
|
582
468
|
|
|
583
|
-
|
|
584
|
-
### 连接器面板
|
|
469
|
+
## 附录:公开声明
|
|
585
470
|
|
|
586
|
-
|
|
587
|
-
参数类型为 `ConnectorPanelProps`。面板本身只管插件分区,电脑与外部账号由宿主通过
|
|
588
|
-
`renderComputerSection` / `renderExternalAccounts` / `renderFooterActions` 注入——那些入口
|
|
589
|
-
要跳宿主自己的路由,SDK 不硬编码内部路径。`sessionId` 传 `null` 表示"首页":此时开关只记录
|
|
590
|
-
本次输入的待选(`PluginConnectorSelection`、`onPendingPluginsChange`),不激活任何东西、
|
|
591
|
-
不建会话、不启沙盒;`pendingDisabledReason` 用于说明该模式下为什么用不了待选。
|
|
471
|
+
完整签名见 [public-api.md](./public-api.md)。本包公开声明:`BladeProvider`、`BladeProviderProps`、`BladeHost`、`HostAttachment`、`HostCatalogItem`、`useBladeClient`、`useAgentSession`、`UseAgentSessionOptions`、`UseAgentSessionResult`、`useReplay`、`UseReplayResult`、`ReplayMismatch`、`ReplayBar`、`ReplayBarProps`、`ReplayMismatchPrompt`、`ReplayMismatchPromptProps`、`ChatView`、`ChatViewProps`、`ChatViewClassNames`、`ChatViewRenderers`、`ChatViewSlots`、`UserMessageRendererProps`、`AssistantTextRendererProps`、`AskUserQuestionRendererProps`、`AskUserQuestionData`、`PlanUpdateRendererProps`、`PlanUpdateData`、`PlanStepStatus`、`ToolCallRenderer`、`ToolRendererProps`、`FollowupInteractionEvent`、`LlmChatHandle`、`LlmChatOptions`、`ChatCompletionTool`、`LlmToolCall`、`MarkdownContent`、`MarkdownContentProps`。
|
|
592
472
|
|
|
593
|
-
|
|
594
|
-
"该显示成什么"全部来自 `PluginConnectorItem`、`mergeConnectorItems`、`catalogToConnectorItems`、
|
|
595
|
-
`filterConnectorItems`,判定不各写一份。`sessionPluginLabel` 决定
|
|
596
|
-
一行读起来是什么名字。
|
|
597
|
-
|
|
598
|
-
启用时序由 `useSessionPluginActivation` 收口(返回值类型 `UseSessionPluginActivationResult`):
|
|
599
|
-
**先准备配置、再提交激活**。缺必填配置时开关不落到已启用、立刻弹表单,保存由服务端确认配置
|
|
600
|
-
完整才提交;取消、校验失败、读取失败都保持关闭,不会先写 `active=true` 再补偿。慢准备/慢激活
|
|
601
|
-
期间开关仍可点,语义是取消这次意图而不是再排一次队。配置弹窗由 `ConfiguringPlugin` 描述。
|
|
602
|
-
|
|
603
|
-
触发器要在不打开面板时就能显示"选了哪几个、几个":用 `selectionFromItem` 把一行转成
|
|
604
|
-
`PluginConnectorSelection`,再用 `triggerSummary` 折成 `TriggerSummary`(最多 `TRIGGER_ICON_LIMIT`
|
|
605
|
-
个图标,其余折成数量;没有图标 token 的项不占图标位但仍计数)。
|
|
606
|
-
|
|
607
|
-
### MCP Apps
|
|
608
|
-
|
|
609
|
-
`McpAppCard` 使用官方 AppBridge 渲染归档卡片,内置 Web 消费同一组件。在 `BladeProvider` 下传入会话 ID 与来源 ID,也可直接提供 `BladeClient`。`McpAppMessage` 表示 App 提供的文本;`McpAppMessageContext` 将其交给当前对话的待发送区。用户可在 `ChatView` 查看、移除并确认发送。`update-model-context` 会把 App 提交的当前状态经 `sessions.updateMcpAppContext` 存为会话上下文:只落库、不追加聊天、不启动 agent turn,正在运行的一轮保持启动时版本,下一个新的运行才读取;冲突(409)时重读最新 revision 后重提同一份状态一次。
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
### 子智能体卡片
|
|
613
|
-
|
|
614
|
-
`AgentTaskCard` 提供共享的可折叠子智能体卡片,`AgentTaskCardProps` 接受任务 `title`、`status`、最新动作 `activity` 和详情 `children`。`AgentTaskStatus` 包含 `running`、`waiting_input`、`completed`、`failed`、`cancelled`;进入 `waiting_input` 时自动展开,提示图标持续轻缓闪动,并遵守系统减少动态效果设置。回答表单由宿主通过 `children` 提供,回答后应更新状态以停止提示。
|
|
615
|
-
|
|
616
|
-
### 对话分支选择器
|
|
617
|
-
|
|
618
|
-
`SessionBranchPicker`(属性类型 `SessionBranchPickerProps`)提供与内置 Web 共用的 Git 风格对话树、消息预览、切换中禁用和错误提示,可放进宿主的弹窗或侧栏。`tree` 使用 `client.sessions.getSessionCheckpoints(sessionId)` 的返回值;`disabled` 应来自该会话的运行状态或只读权限,不能仅依据当前标签页是否收到流式消息。
|
|
619
|
-
|
|
620
|
-
```tsx
|
|
621
|
-
<SessionBranchPicker
|
|
622
|
-
tree={checkpoints}
|
|
623
|
-
disabled={isRunning || readOnly}
|
|
624
|
-
onCheckout={async (checkpointId, position) => {
|
|
625
|
-
const result = await client.sessions.checkoutSession(
|
|
626
|
-
sessionId, checkpointId, position, "conversation",
|
|
627
|
-
)
|
|
628
|
-
await refreshConversation()
|
|
629
|
-
setDraft(result.position === "before" ? result.content : "")
|
|
630
|
-
setCheckpoints(await client.sessions.getSessionCheckpoints(sessionId))
|
|
631
|
-
}}
|
|
632
|
-
/>
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
示例中的刷新消息、设置草稿与保存检查点状态由宿主实现。消息圆点回到发送前(`before`),虚线圆点切到分支末尾(`turn_end`)。宿主弹窗应明确说明:这些操作只修改对话记录,不修改、删除或恢复工作目录中的文件;请求使用 `conversation` 模式,不使用文件恢复模式。
|
|
636
|
-
|
|
637
|
-
### 会话图片
|
|
638
|
-
|
|
639
|
-
`MessageImage` 是聊天组件共用的图片组件,属性类型为 `MessageImageProps`。它支持原生 `<img>` 属性,并从 `BladeProvider` 获取客户端;也可显式传入 `client`。会话资源使用带鉴权的独立请求,公开分享图片使用分享令牌授权,在接近可视区域时加载预览,卸载后回收对象 URL。传入 `original` 可用于原图预览。普通外部图片 URL 仍直接由浏览器加载。
|
|
640
|
-
|
|
641
|
-
```tsx
|
|
642
|
-
<MessageImage src={imageUrl} alt="附件图片" width={width} height={height} />
|
|
643
|
-
```
|
|
473
|
+
`@blade-hq/agent-client` 不再从本包转出,`BladeClient`、`AgentSession` 等直接从 `@blade-hq/agent-client` import。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ToolCallInfo } from "@blade-hq/agent-client/internal";
|
|
3
|
+
interface Props {
|
|
4
|
+
toolCall: ToolCallInfo;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
reasoning?: string;
|
|
7
|
+
hideAskUserQuestions?: boolean;
|
|
8
|
+
/** 初始展开(工作流步骤卡默认摊开,老 #1841 体验);等待回答仍强制展开。 */
|
|
9
|
+
defaultExpanded?: boolean;
|
|
10
|
+
/** 自定义卡头描述渲染(工作流用它把 {{ref}} 画成引用胶囊)。 */
|
|
11
|
+
renderDescription?: (description: string) => ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare function AgentLoopBlock({ toolCall, sessionId, reasoning, hideAskUserQuestions, defaultExpanded, renderDescription, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AskUserAnswerData } from "@blade-hq/agent-client";
|
|
2
|
+
export type { AskUserAnswerData };
|
|
2
3
|
interface OptionItem {
|
|
3
4
|
label: string;
|
|
4
5
|
description: string;
|
|
@@ -23,20 +24,15 @@ interface Props {
|
|
|
23
24
|
sessionStatus: string;
|
|
24
25
|
answerData?: AskUserAnswerData | undefined;
|
|
25
26
|
onAnswer?: (answer: string, toolCallId: string, answerData: AskUserAnswerData) => void;
|
|
26
|
-
className?: string;
|
|
27
|
-
optionClassName?: string;
|
|
28
27
|
}
|
|
29
|
-
|
|
30
|
-
export declare function AskUserQuestionBlock({ data, answered, toolCallId, sessionStatus, answerData, onAnswer, className, optionClassName, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
31
|
-
/** 解析 AskUserQuestion 的工具结果/参数,容错 LLM 把 questions 输出为字符串的情况。 */
|
|
28
|
+
export declare function AskUserQuestionBlock({ data, answered, toolCallId, sessionStatus, answerData, onAnswer, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
32
29
|
export declare function parseAskUserQuestion(toolResult: string | null | undefined): AskUserQuestionData | null;
|
|
33
30
|
export interface AskUserQuestionError {
|
|
34
31
|
message: string;
|
|
35
32
|
detail: string | null;
|
|
36
33
|
}
|
|
37
34
|
/**
|
|
38
|
-
* Keep this parser synchronized with
|
|
35
|
+
* Keep this parser synchronized with agent-react/AskUserQuestionBlock.tsx.
|
|
39
36
|
* The built-in Web cannot import agent-react, and this is not a public SDK protocol API.
|
|
40
37
|
*/
|
|
41
38
|
export declare function parseAskUserQuestionError(toolResult: string | null | undefined): AskUserQuestionError | null;
|
|
42
|
-
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChatMessage, MemoryRefInfo } from "@blade-hq/agent-client/internal";
|
|
2
|
+
import type { AskUserAnswerData } from "./AskUserQuestionBlock";
|
|
3
|
+
import { type ChatViewCustomization } from "./customization";
|
|
4
|
+
export { collectTurnMessages, getPersistentAskUserQuestions } from "./assistant-turn-questions";
|
|
5
|
+
interface Props {
|
|
6
|
+
turnKey: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
messages: ChatMessage[];
|
|
9
|
+
isStreaming?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 打字机式显示节流下,"正文还没播完"跟"思考过程本身有没有说完"是两条
|
|
12
|
+
* 独立时间线——正文通常比思考过程晚播完很久。"正在思考"/"已思考" 这个
|
|
13
|
+
* 标签只该看思考过程自己有没有播完,不能被 isStreaming(含正文节流)
|
|
14
|
+
* 拖着一起等正文播完才翻,否则会出现"正文都在吐字好几秒了,还显示正在
|
|
15
|
+
* 思考"这种跟视觉进度对不上的观感。不传时按旧行为处理(等价于思考过程
|
|
16
|
+
* 跟 isStreaming 同步)。
|
|
17
|
+
*/
|
|
18
|
+
isReasoningRevealing?: boolean;
|
|
19
|
+
isLastTurn?: boolean;
|
|
20
|
+
askAnswers?: Record<string, AskUserAnswerData>;
|
|
21
|
+
onAnswer?: (answer: string, toolCallId: string, answerData: AskUserAnswerData) => void;
|
|
22
|
+
sessionStatus?: string;
|
|
23
|
+
level?: 1 | 2;
|
|
24
|
+
forceExpanded?: boolean;
|
|
25
|
+
customization?: ChatViewCustomization;
|
|
26
|
+
}
|
|
27
|
+
export type TurnDisplayMode = "compact" | "detail";
|
|
28
|
+
export declare function defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall, isStreaming, }: {
|
|
29
|
+
forceExpanded: boolean;
|
|
30
|
+
hasActionableToolCall: boolean;
|
|
31
|
+
isStreaming: boolean;
|
|
32
|
+
}): TurnDisplayMode;
|
|
33
|
+
export declare function resolveTurnDisplayMode({ isStreaming: _isStreaming, displayMode, }: {
|
|
34
|
+
isStreaming: boolean;
|
|
35
|
+
displayMode: TurnDisplayMode;
|
|
36
|
+
}): TurnDisplayMode;
|
|
37
|
+
export declare function formatExecutionDuration(durationMs: number): string;
|
|
38
|
+
export declare function getExecutionDurationMs({ messages, isStreaming, now, }: {
|
|
39
|
+
messages: ChatMessage[];
|
|
40
|
+
isStreaming: boolean;
|
|
41
|
+
now?: number;
|
|
42
|
+
}): number;
|
|
43
|
+
declare function AssistantTurnBlockBase({ sessionId, messages: rawMessages, isStreaming, isReasoningRevealing, askAnswers, onAnswer, sessionStatus, level, forceExpanded, customization, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
export declare const AssistantTurnBlock: import("react").MemoExoticComponent<typeof AssistantTurnBlockBase>;
|
|
45
|
+
/** 一轮消息里引用到的记忆(按 id 去重,保持首次出现的顺序)。 */
|
|
46
|
+
export declare function collectMemoryRefs(messages: ChatMessage[]): MemoryRefInfo[];
|
|
47
|
+
export declare function MemoryRefsHint({ refs: rawRefs, className }: {
|
|
48
|
+
refs: MemoryRefInfo[];
|
|
49
|
+
className?: string;
|
|
50
|
+
}): import("react/jsx-runtime").JSX.Element;
|