@opentiny/tiny-robot-chat 0.0.0 → 0.4.1

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.
Files changed (4) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +915 -4
  3. package/package.json +39 -30
  4. package/index.js +0 -2
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 - present OpenTiny Authors.
4
+ Copyright (c) 2025 - present Huawei Cloud Computing Technologies Co., Ltd.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,917 @@
1
- # @opentiny/tiny-robot-chat
1
+ # TinyRobot Chat
2
2
 
3
- This is a bootstrap release used to establish trusted publishing for the
4
- `@opentiny/tiny-robot-chat` package. It does not expose any public APIs yet.
3
+ ## 1. 这是什么
5
4
 
6
- The functional package will be published by the TinyRobot release workflow.
5
+ `@opentiny/tiny-robot-chat` 是一个基于 Vue 3 的聊天页面组件包,提供:
6
+
7
+ - 完整聊天页面;
8
+ - 会话列表和会话操作;
9
+ - 流式回答与取消;
10
+ - 模型选择、深度思考和联网搜索开关;
11
+ - MCP 服务和工具管理。
12
+
13
+ Runtime 是聊天页面使用的状态和操作集合。Provider 是模型服务的连接配置。MCP 是供模型调用外部能力的工具服务。
14
+
15
+ | 场景 | 推荐入口 | 说明 |
16
+ | ------------- | ------------------------------ | ------------------------------------------- |
17
+ | 新项目 | `useLocalChatRuntime + TrChat` | 默认方式,包负责组装会话、模型和 MCP 运行时 |
18
+ | 已有 Kit 会话 | `useKitChatRuntime + TrChat` | 复用已有会话和发送逻辑 |
19
+ | 自有状态管理 | `TrChatUI` | 仅提供界面,需要自行管理数据与事件 |
20
+
21
+ ## 2. 安装和准备
22
+
23
+ 仓库使用 pnpm,根目录声明的版本为 `pnpm@10.34.5`。在仓库外的 Vue 3 项目中,将包加入项目依赖:
24
+
25
+ ```bash
26
+ pnpm add @opentiny/tiny-robot-chat vue
27
+ ```
28
+
29
+ 在本仓库的 workspace 应用中,依赖写法为:
30
+
31
+ ```json
32
+ {
33
+ "dependencies": {
34
+ "@opentiny/tiny-robot-chat": "workspace:*"
35
+ }
36
+ }
37
+ ```
38
+
39
+ 使用前需要准备:
40
+
41
+ - Vue 3;
42
+ - 一个可访问的模型服务,或兼容协议的后端代理;
43
+ - 可选的模型服务 API Key。
44
+
45
+ 浏览器端的 `VITE_` 环境变量会进入浏览器代码。不要在生产环境直接放置长期密钥。模型服务和 MCP 服务需要允许浏览器跨域访问,或者通过后端 / BFF 转发请求。BFF 是 Backend For Frontend,指专门给前端使用的后端接口。
46
+
47
+ ## 3. 快速开始
48
+
49
+ 下面的示例使用内置 `openai` Provider,不依赖额外的 Kit 导入:
50
+
51
+ ```vue
52
+ <script setup lang="ts">
53
+ import { TrChat, useLocalChatRuntime, type ChatProviderConfig } from '@opentiny/tiny-robot-chat'
54
+
55
+ const modelProviders: ChatProviderConfig[] = [
56
+ {
57
+ type: 'openai',
58
+ apiKey: import.meta.env.VITE_OPENAI_API_KEY,
59
+ models: [
60
+ {
61
+ id: 'gpt-4.1-mini',
62
+ label: 'GPT-4.1 mini',
63
+ },
64
+ ],
65
+ },
66
+ ]
67
+
68
+ const runtime = useLocalChatRuntime({
69
+ modelProviders,
70
+ })
71
+ </script>
72
+
73
+ <template>
74
+ <TrChat :runtime="runtime" />
75
+ </template>
76
+ ```
77
+
78
+ 在项目根目录创建 `.env.local`:
79
+
80
+ ```env
81
+ VITE_OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
82
+ ```
83
+
84
+ 然后执行项目 `package.json` 中已有的开发脚本启动项目(通常是 `pnpm dev`)。未配置 `apiUrl` 时使用 Provider 的内置默认地址;生产环境可以将 `apiUrl` 指向自己的后端或 BFF,由后端代发模型请求。
85
+
86
+ 这个示例不需要在 `conversation.useMessageOptions` 中提供 `responseProvider`,因为 `modelProviders` 会提供响应请求所需的 Provider。`useLocalChatRuntime` 会将会话、模型和完整聊天页面连接起来。
87
+
88
+ 第一次发送非空消息时,如果当前没有会话,Runtime 会自动创建会话,并使用消息文本生成标题。未配置 `storage` 时,Kit 会使用默认的 LocalStorage 策略保存会话和消息;`useLocalChatRuntime` 同时会开启消息自动保存流程。
89
+
90
+ 发送过程中,页面的取消操作会调用 Runtime 的 `abort`。Runtime 不检查 API Key 是否存在:有 Key 时发送默认 Bearer 认证,没有 Key 时直接请求配置的 `apiUrl`。认证失败由上游模型服务或后端代理返回,并按普通请求错误处理。
91
+
92
+ 聊天页面需要放在有明确高度的容器中,例如让应用根节点或外层布局提供高度。
93
+
94
+ 需要自定义存储策略,或需要显式配置 LocalStorage、IndexedDB 时,再直接安装并导入 Kit:
95
+
96
+ ```bash
97
+ pnpm add @opentiny/tiny-robot-kit
98
+ ```
99
+
100
+ 例如,为不同案例配置独立的 LocalStorage key:
101
+
102
+ ```ts
103
+ import { localStorageStrategyFactory } from '@opentiny/tiny-robot-kit'
104
+
105
+ const runtime = useLocalChatRuntime({
106
+ conversation: {
107
+ storage: localStorageStrategyFactory({
108
+ key: 'tiny-robot-doubao-conversations',
109
+ }),
110
+ },
111
+ modelProviders,
112
+ })
113
+ ```
114
+
115
+ 也可以将 `indexedDBStorageStrategyFactory` 或自定义 `ConversationStorageStrategy` 传给 `conversation.storage`。具体参数以 `@opentiny/tiny-robot-kit` 文档为准。
116
+
117
+ ## 4. 配置模型
118
+
119
+ ### 4.1 最小模型配置
120
+
121
+ `ChatProviderConfig` 的最小配置如下:
122
+
123
+ ```ts
124
+ import type { ChatProviderConfig } from '@opentiny/tiny-robot-chat'
125
+
126
+ const modelProviders: ChatProviderConfig[] = [
127
+ {
128
+ type: 'openai',
129
+ apiKey: import.meta.env.VITE_OPENAI_API_KEY,
130
+ models: [
131
+ {
132
+ id: 'gpt-4.1-mini',
133
+ label: 'GPT-4.1 mini',
134
+ },
135
+ ],
136
+ },
137
+ ]
138
+ ```
139
+
140
+ `apiKey` 是可选的。配置后会在请求中作为默认 Bearer 认证发送;不配置时可以使用无认证的自定义 `apiUrl`,例如由后端代理完成认证。Key 会随前端代码和请求到达浏览器用户,只适合本地开发或可信环境。生产环境不要在浏览器中放置长期密钥。
141
+
142
+ 同一组 Provider 中的模型 ID 以及多个 Provider 之间的模型 ID 都必须唯一。模型列表中的第一个模型会作为初始选择项。
143
+
144
+ ### 4.2 支持的服务类型
145
+
146
+ `type` 只接受 `openai`、`deepseek` 和 `qwen`。默认地址和内置请求映射如下:
147
+
148
+ | `type` | 默认服务地址 | 可用内置能力 |
149
+ | ---------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
150
+ | `openai` | `https://api.openai.com/v1/chat/completions` | 默认没有额外的能力请求体映射;可通过模型配置提供自定义 `featureBody` |
151
+ | `deepseek` | `https://api.deepseek.com/chat/completions` | `thinking` 会映射为 `thinking.type`;请求支持 `reasoning_effort` 的 `high`、`max` |
152
+ | `qwen` | `https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions` | `thinking` 映射为 `enable_thinking`,`search` 映射为 `enable_search` |
153
+
154
+ 这些是包内的默认请求映射,不代表服务端一定允许对应字段。服务端返回的 HTTP 错误会作为请求错误处理。
155
+
156
+ ### 4.3 多个模型和能力开关
157
+
158
+ 能力通过模型的 `capabilities` 声明:
159
+
160
+ ```ts
161
+ const modelProviders: ChatProviderConfig[] = [
162
+ {
163
+ type: 'qwen',
164
+ apiKey: import.meta.env.VITE_DASHSCOPE_API_KEY,
165
+ models: [
166
+ {
167
+ id: 'qwen-model-a',
168
+ label: 'Qwen Model A',
169
+ capabilities: {
170
+ thinking: true,
171
+ search: true,
172
+ },
173
+ },
174
+ {
175
+ id: 'qwen-model-b',
176
+ label: 'Qwen Model B',
177
+ capabilities: {
178
+ thinking: true,
179
+ },
180
+ },
181
+ ],
182
+ },
183
+ ]
184
+ ```
185
+
186
+ - `thinking` 表示深度思考开关。
187
+ - `search` 表示联网搜索开关。
188
+ - 只有当前模型声明支持的开关才会显示在页面中。
189
+ - 切换模型后,当前模型不支持的开关会关闭。
190
+ - 当前页面提供 `thinking` 和 `search` 开关,不提供 reasoning effort 选择器;模型的 `reasoning.defaultEffort` 决定初始 effort,DeepSeek 预设默认值为 `high`。
191
+
192
+ `capabilities` 控制界面可用性;实际请求字段由 Provider 默认映射和模型的 `featureBody` 决定。
193
+
194
+ ### 4.4 自定义服务地址与请求头
195
+
196
+ `apiUrl` 可以传服务根地址,也可以传完整的 `/chat/completions` 地址。运行时会去除末尾斜杠;当地址不是以 `/chat/completions` 结尾时,会追加该路径。
197
+
198
+ 三种常见接入方式如下:
199
+
200
+ | 配置 | 行为 |
201
+ | ------------------------- | ---------------------------------------------- |
202
+ | 只配置 `apiKey` | 使用 Provider 内置默认地址,并发送 Bearer 认证 |
203
+ | 配置 `apiUrl` 和 `apiKey` | 请求自定义服务,并默认发送 Bearer 认证 |
204
+ | 只配置 `apiUrl` | 请求后端代理,不由 Chat 包处理认证 |
205
+
206
+ ```ts
207
+ const modelProviders: ChatProviderConfig[] = [
208
+ {
209
+ type: 'openai',
210
+ apiUrl: 'https://api.example.com/v1',
211
+ apiKey: import.meta.env.VITE_MODEL_API_KEY,
212
+ headers: {
213
+ 'X-Client-Name': 'tiny-robot-chat',
214
+ },
215
+ models: [
216
+ {
217
+ id: 'custom-model',
218
+ label: 'Custom Model',
219
+ },
220
+ ],
221
+ },
222
+ ]
223
+ ```
224
+
225
+ 请求会使用 `POST` 和流式响应。`headers` 会与 `Content-Type` 合并;Provider 的 API Key 会作为默认 Bearer Authorization 请求头发送。如果 `headers` 已提供 `Authorization`,则保留自定义值。
226
+
227
+ ## 5. 配置 MCP 工具服务
228
+
229
+ MCP 是让模型在回答时调用外部工具服务的配置方式。Chat 提供两种入口:
230
+
231
+ | 入口 | 适合场景 | 说明 |
232
+ | ------------ | ---------------------------------------------------- | ----------------------------------------- |
233
+ | `mcpServers` | 远程 Streamable HTTP MCP Server | 推荐的默认方式,Chat 自动创建 MCP Adapter |
234
+ | `mcp` | stdio、旧 SSE、OAuth、企业网关、连接复用、自定义权限 | 高级方式,宿主自己提供 Adapter |
235
+
236
+ 默认 `mcpServers` 只面向 Streamable HTTP。生产环境中,带密钥、OAuth、内网访问、审计或权限过滤的 MCP Server,建议通过 BFF 代理后再交给 `mcpServers`。BFF 是 Backend For Frontend,指专门给前端使用的后端接口;在 MCP 场景里,它通常负责保存密钥,并把前端请求安全转发给真正的 MCP Server。
237
+
238
+ ### 5.1 选择接入方式
239
+
240
+ | MCP 服务形态 | 推荐接入 | 原因 |
241
+ | ------------------------------------------ | ------------------------------------- | --------------------------------- |
242
+ | 公共 Streamable HTTP MCP,浏览器可直接访问 | `mcpServers` 直连 | 最少配置,Chat 自动发现工具并调用 |
243
+ | 需要 API Key、Bearer Token 或 OAuth | `mcpServers` 指向 BFF | 密钥和 token 留在后端 |
244
+ | 公司内网、企业网关、权限系统、审计系统 | `mcpServers` 指向 BFF,或自定义 `mcp` | 由宿主控制认证和权限 |
245
+ | 本地 stdio MCP | 自定义 `mcp` | 浏览器不能启动本地进程 |
246
+ | 旧 SSE MCP | 自定义 `mcp` | 默认 Adapter 不内置旧协议 |
247
+ | 模型平台托管 MCP | 放在模型 Provider 或后端请求链路中 | Chat 不管理平台内部 MCP 连接 |
248
+
249
+ 普通 Web 项目优先使用 `mcpServers`;只有默认 Adapter 覆盖不了协议、认证或生命周期时,再使用高级 `mcp`。
250
+
251
+ ### 5.2 最小示例
252
+
253
+ `mcpServers` 是只读数组,每项必须包含 `id`、`name` 和 `baseUrl`:
254
+
255
+ ```ts
256
+ import type { ChatMcpServers } from '@opentiny/tiny-robot-chat'
257
+
258
+ const mcpServers: ChatMcpServers = [
259
+ {
260
+ id: 'maps',
261
+ name: 'Maps',
262
+ baseUrl: 'https://mcp.example.com/maps',
263
+ },
264
+ ]
265
+ ```
266
+
267
+ 在第 3 节的快速开始示例中加入 `mcpServers` 属性即可:
268
+
269
+ ```ts
270
+ const runtime = useLocalChatRuntime({
271
+ modelProviders,
272
+ mcpServers,
273
+ })
274
+ ```
275
+
276
+ 也可以在已有的 `responseProvider` 配置上加入 `mcpServers`。`modelProviders` 与 `conversation.useMessageOptions.responseProvider` 不能同时提供。
277
+
278
+ ### 5.3 生产推荐:BFF 代理
279
+
280
+ 浏览器直连 MCP Server 只适合无敏感凭证、允许 CORS 的服务。生产环境中,只要 MCP Server 需要 API Key、Bearer Token、OAuth、内网访问或权限过滤,就推荐使用 BFF:
281
+
282
+ ```txt
283
+ Browser Chat
284
+ -> /api/mcp/maps
285
+ -> Remote MCP Server
286
+ ```
287
+
288
+ 前端只配置自己的 BFF 地址:
289
+
290
+ ```ts
291
+ const mcpServers: ChatMcpServers = [
292
+ {
293
+ id: 'maps',
294
+ name: 'Maps',
295
+ description: '地图、路线、位置和周边查询',
296
+ baseUrl: '/api/mcp/maps',
297
+ },
298
+ ]
299
+ ```
300
+
301
+ BFF 负责:
302
+
303
+ - 保存 API Key、OAuth token 和长期凭证;
304
+ - 向远程 MCP Server 注入 `Authorization` 或其他认证请求头;
305
+ - 处理 token refresh、权限过滤、审计和限流;
306
+ - 解决浏览器 CORS 限制;
307
+ - 只把当前用户允许使用的 MCP 能力暴露给前端。
308
+
309
+ Chat 包不会读取环境变量,不会保存密钥,也不会自动生成认证 header。
310
+
311
+ ### 5.4 高德地图 MCP 示例
312
+
313
+ 如果你接入的是 DashScope 托管的高德地图 MCP,Server 地址通常类似:
314
+
315
+ ```ts
316
+ const mcpServers: ChatMcpServers = [
317
+ {
318
+ id: 'amap-maps',
319
+ name: '高德地图',
320
+ description: '地图、导航、地理编码、天气、路径规划和周边搜索',
321
+ baseUrl: 'https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/mcp',
322
+ },
323
+ ]
324
+ ```
325
+
326
+ 这个服务可以使用 API Key 直连,也可以通过 BFF 代理:
327
+
328
+ ```ts
329
+ const mcpServers: ChatMcpServers = [
330
+ {
331
+ id: 'amap-maps',
332
+ name: '高德地图',
333
+ description: '地图、导航、地理编码、天气、路径规划和周边搜索',
334
+ baseUrl: '/api/mcp/amap-maps',
335
+ },
336
+ ]
337
+ ```
338
+
339
+ 本地直连时,可以显式传入请求头:
340
+
341
+ ```ts
342
+ const dashScopeApiKey = import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY?.trim()
343
+
344
+ const mcpServers: ChatMcpServers = [
345
+ {
346
+ id: 'amap-maps',
347
+ name: '高德地图',
348
+ baseUrl: 'https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/mcp',
349
+ headers: dashScopeApiKey ? { Authorization: `Bearer ${dashScopeApiKey}` } : undefined,
350
+ },
351
+ ]
352
+ ```
353
+
354
+ `headers` 和 `validate` 都是业务侧配置。Chat 默认 Adapter 不要求 API Key;未配置认证信息时,服务端或代理决定是否接受请求。`VITE_` 环境变量会进入浏览器构建产物,直传 Key 只适合本地调试,不适合作为生产接入方式。
355
+
356
+ ### 5.5 配置字段
357
+
358
+ | 字段 | 是否必填 | 说明 |
359
+ | ------------- | -------- | ------------------------------------------------- |
360
+ | `id` | 是 | 唯一标识;重复 ID 会在创建默认 Adapter 时同步报错 |
361
+ | `name` | 是 | 页面显示名称 |
362
+ | `baseUrl` | 是 | MCP Streamable HTTP 地址 |
363
+ | `installed` | 否 | 初始显示为已安装,但不自动启用 |
364
+ | `description` | 否 | 页面说明 |
365
+ | `icon` | 否 | 页面图标地址 |
366
+ | `headers` | 否 | MCP 请求头;仅建议用于非敏感 header 或本地调试 |
367
+ | `timeout` | 否 | 连接和请求超时,单位为毫秒 |
368
+ | `validate` | 否 | 创建 MCP 连接前执行的校验函数,参数为 Server ID |
369
+
370
+ ### 5.6 初始安装状态
371
+
372
+ ```ts
373
+ const mcpServers: ChatMcpServers = [
374
+ {
375
+ id: 'internal-tools',
376
+ name: 'Internal Tools',
377
+ baseUrl: '/mcp/internal-tools',
378
+ installed: true,
379
+ },
380
+ ]
381
+ ```
382
+
383
+ `installed: true` 的含义仅是 Runtime 初始化时该 Server 已安装:
384
+
385
+ - 不会自动调用 MCP 服务;
386
+ - 不会自动读取工具;
387
+ - 初始状态仍是未启用;
388
+ - 用户启用 Server 后才读取工具;
389
+ - Runtime 重建时会重新采用静态配置中的初始状态;
390
+ - 运行时删除 Server 不会修改原始 `mcpServers` 数组。
391
+
392
+ ### 5.7 地址与认证
393
+
394
+ 浏览器环境支持相对地址:
395
+
396
+ ```ts
397
+ const mcpServers: ChatMcpServers = [
398
+ {
399
+ id: 'local-tools',
400
+ name: 'Local Tools',
401
+ baseUrl: '/api/mcp/local-tools',
402
+ },
403
+ ]
404
+ ```
405
+
406
+ 浏览器中相对地址会依据当前页面的 origin 解析。非浏览器环境必须使用完整绝对地址,否则启用 Server 时会报错。
407
+
408
+ 带请求头和连接前校验的配置如下,适合本地调试或短期验证:
409
+
410
+ ```ts
411
+ const mcpApiKey = import.meta.env.VITE_MCP_API_KEY?.trim()
412
+
413
+ const mcpServers: ChatMcpServers = [
414
+ {
415
+ id: 'protected-tools',
416
+ name: 'Protected Tools',
417
+ baseUrl: 'https://mcp.example.com/tools',
418
+ headers: mcpApiKey ? { Authorization: `Bearer ${mcpApiKey}` } : undefined,
419
+ validate: (serverId) => {
420
+ if (!mcpApiKey) {
421
+ throw new Error(`Missing MCP API key for ${serverId}.`)
422
+ }
423
+ },
424
+ },
425
+ ]
426
+ ```
427
+
428
+ 浏览器中的 MCP 请求需要服务端处理 CORS。`VITE_` 变量会暴露给浏览器,不要在浏览器中放置生产环境长期密钥;生产环境应通过后端或 BFF 转发认证请求。
429
+
430
+ ### 5.8 用户实际操作后的行为
431
+
432
+ 1. 用户添加 Server,Runtime 将其标记为已安装并开始启用流程。
433
+ 2. 用户启用已安装的 Server,Runtime 读取该 Server 的工具列表。
434
+ 3. 读取成功后,页面显示可用工具。
435
+ 4. 用户可以选择或关闭单个工具。
436
+ 5. 发送消息时,只会把当前已安装、已启用 Server 中当前启用的工具加入本条消息的请求配置。
437
+ 6. 禁用 Server 会清空当前 Runtime 中该 Server 的工具状态和缓存;删除 Server 还会取消其已安装状态。
438
+ 7. 重新启用 Server 会重新读取工具。
439
+
440
+ 每次发送都会保存当时的模型、能力开关和 MCP 工具选择。发送后再切换模型、能力或工具,只影响下一条消息。已启用的 MCP Server 仍在读取工具时,发送不可用。
441
+
442
+ 如果工具读取失败,该 Server 仍保持已安装状态,但会自动禁用并记录错误;其他已启用的 Server 和普通聊天能力不受影响。
443
+
444
+ ### 5.9 默认 Adapter 边界
445
+
446
+ `mcpServers` 会自动创建默认 Adapter。默认 Adapter 负责:
447
+
448
+ - 使用 Streamable HTTP 连接 MCP Server;
449
+ - 在用户启用 Server 时执行 Tool discovery;
450
+ - 将 MCP Tool 转换为模型可见的 function tool;
451
+ - 根据本轮 `runConfig` 只暴露当前消息允许使用的 Tool;
452
+ - 调用 Tool 时使用 MCP 原始 Tool 名称;
453
+ - 对连接和请求设置超时;
454
+ - 对同一个 Server 的并发工具读取做去重;
455
+ - 禁用或删除 Server 时清空当前 Runtime 中的 Tool 状态和缓存;
456
+ - 工具读取失败时保留 installed 状态,自动禁用该 Server 并记录错误;
457
+ - 按需创建 Client,调用结束后关闭。
458
+
459
+ 默认 Adapter 不负责:
460
+
461
+ - 启动本地 stdio MCP 进程;
462
+ - 兼容旧 SSE 协议;
463
+ - 管理 OAuth 登录、授权码流程或 token refresh;
464
+ - 自动读取环境变量;
465
+ - 保存 API Key、Bearer Token 或长期凭证;
466
+ - 提供连接池或长会话复用;
467
+ - 实现企业权限、审计、限流或租户隔离;
468
+ - 处理模型平台内部托管的 MCP。
469
+
470
+ 这些场景应使用 BFF 或高级 `mcp` Adapter。
471
+
472
+ ### 5.10 高级 MCP 接入
473
+
474
+ 特殊协议或自定义运行逻辑可以传入高级 `mcp` Adapter。典型场景包括:
475
+
476
+ - 本地 stdio MCP;
477
+ - 旧 SSE MCP;
478
+ - OAuth 或企业统一登录;
479
+ - 后端网关、审计、限流、租户隔离;
480
+ - 自定义 Client 生命周期或连接复用;
481
+ - 平台已经托管 MCP,前端只需要消费宿主维护的工具状态。
482
+
483
+ 高级入口的类型是公开的 `UseLocalChatRuntimeMcpAdapter`,对象必须包含 `runtime`、`listTools` 和 `callTool`:
484
+
485
+ ```ts
486
+ import { shallowRef } from 'vue'
487
+ import { useLocalChatRuntime, type ChatMcpRuntime, type UseLocalChatRuntimeMcpAdapter } from '@opentiny/tiny-robot-chat'
488
+
489
+ const mcpRuntime: ChatMcpRuntime = {
490
+ servers: shallowRef([
491
+ {
492
+ id: 'custom-tools',
493
+ name: 'Custom Tools',
494
+ installed: true,
495
+ enabled: true,
496
+ },
497
+ ]),
498
+ tools: shallowRef({
499
+ 'custom-tools': [
500
+ {
501
+ id: 'search',
502
+ name: 'search',
503
+ description: 'Search internal documents',
504
+ enabled: true,
505
+ },
506
+ ],
507
+ }),
508
+ addServer: async () => {},
509
+ removeServer: async () => {},
510
+ setServerEnabled: async () => {},
511
+ setToolEnabled: async () => {},
512
+ }
513
+
514
+ const mcp: UseLocalChatRuntimeMcpAdapter = {
515
+ runtime: mcpRuntime,
516
+ async listTools(serverIds, toolIds) {
517
+ if (!serverIds.includes('custom-tools')) return []
518
+ if (!toolIds['custom-tools']?.includes('search')) return []
519
+
520
+ return [
521
+ {
522
+ serverId: 'custom-tools',
523
+ id: 'search',
524
+ name: 'custom-tools__search',
525
+ originalName: 'search',
526
+ description: 'Search internal documents',
527
+ inputSchema: {
528
+ type: 'object',
529
+ properties: {
530
+ query: { type: 'string' },
531
+ },
532
+ required: ['query'],
533
+ },
534
+ },
535
+ ]
536
+ },
537
+ async callTool(serverId, toolName, args) {
538
+ const response = await fetch('/api/tools/call', {
539
+ method: 'POST',
540
+ headers: { 'Content-Type': 'application/json' },
541
+ body: JSON.stringify({ serverId, toolName, args }),
542
+ })
543
+
544
+ if (!response.ok) {
545
+ throw new Error(`Tool call failed: HTTP ${response.status}`)
546
+ }
547
+
548
+ return response.json()
549
+ },
550
+ }
551
+
552
+ const runtime = useLocalChatRuntime({
553
+ modelProviders,
554
+ mcp,
555
+ })
556
+ ```
557
+
558
+ 三个字段的职责如下:
559
+
560
+ | 字段 | 职责 |
561
+ | ----------- | ------------------------------------------------------------- |
562
+ | `runtime` | 给页面展示 Server、Tool、启用状态、加载状态和错误 |
563
+ | `listTools` | 根据本轮消息的 Server/Tool 快照,返回要暴露给模型的 Tool 定义 |
564
+ | `callTool` | 执行模型发起的 Tool 调用,并返回调用结果 |
565
+
566
+ `listTools` 返回的 `name` 必须在所有模型工具中唯一。推荐格式是 `${serverId}__${toolId}`。`originalName` 用于保存 MCP Server 里的原始 Tool 名称,实际调用时会传给 `callTool`。
567
+
568
+ `mcp` 与 `mcpServers` 不能同时传入:
569
+
570
+ ```ts
571
+ useLocalChatRuntime({
572
+ modelProviders,
573
+ mcpServers,
574
+ mcp,
575
+ })
576
+ ```
577
+
578
+ 上面的写法会抛错。需要默认 Streamable HTTP 接入时使用 `mcpServers`;需要完全自定义 MCP 行为时使用 `mcp`。
579
+
580
+ ## 6. 配置聊天界面
581
+
582
+ `TrChat` 的 `ui` 属性接收 `ChatUIOptions`。未配置的字段使用默认值。
583
+
584
+ ### 6.1 常用完整示例
585
+
586
+ ```vue
587
+ <script setup lang="ts">
588
+ import { TrChat, type ChatUIOptions } from '@opentiny/tiny-robot-chat'
589
+
590
+ const prompts = [{ label: '总结这段内容' }, { label: '列出三个可执行步骤' }]
591
+
592
+ const ui: ChatUIOptions = {
593
+ layout: {
594
+ contentMaxWidth: 980,
595
+ panelPadding: 12,
596
+ panelGap: 12,
597
+ leftAside: {
598
+ mode: 'dock',
599
+ width: 280,
600
+ collapsedWidth: 56,
601
+ defaultOpen: true,
602
+ },
603
+ },
604
+ brand: {
605
+ name: '我的助手',
606
+ },
607
+ welcome: {
608
+ title: '欢迎使用',
609
+ description: '输入问题开始对话。',
610
+ },
611
+ prompts: {
612
+ items: prompts,
613
+ },
614
+ bubble: {
615
+ autoScroll: true,
616
+ bubbleList: {
617
+ roleConfigs: {
618
+ system: { hidden: true },
619
+ },
620
+ },
621
+ },
622
+ sender: {
623
+ maxLength: 4000,
624
+ placeholder: '输入消息',
625
+ },
626
+ labels: {
627
+ createConversation: '新建对话',
628
+ composerPlaceholder: '请输入消息',
629
+ },
630
+ }
631
+ </script>
632
+
633
+ <template>
634
+ <TrChat :runtime="runtime" :ui="ui" />
635
+ </template>
636
+ ```
637
+
638
+ 示例中的 `runtime` 应替换为已经创建好的 `ChatRuntime`。`brand.logo` 可以传入 Vue 组件;未配置时使用包内默认图标。若要自定义消息头像,可在业务项目中安装并导入自己的图标组件。
639
+
640
+ ### 6.2 常用配置说明
641
+
642
+ | 配置 | 说明 |
643
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
644
+ | `layout.contentMaxWidth` | 内容最大宽度,默认 `980` |
645
+ | `layout.panelPadding` | 面板内边距,默认 `12` |
646
+ | `layout.panelGap` | 面板间距,默认 `12` |
647
+ | `layout.leftAside` | 左侧会话栏;可设置 `mode`、`width`、`collapsedWidth`、`open`、`defaultOpen` |
648
+ | `layout.rightAside` | 右侧详情栏的唯一启用开关;未配置或设置为 `false` 时不创建右栏,设置为 `{}` 或具体配置时创建右栏,可配置侧栏模式、宽度、打开状态和关闭按钮 |
649
+ | `header` | 顶部栏配置为 `false` 时隐藏顶部栏 |
650
+ | `history` | 配置会话列表及菜单项,或设置为 `false` 隐藏会话列表 |
651
+ | `welcome` | 空会话欢迎区域,或设置为 `false` 隐藏 |
652
+ | `prompts` | 空会话提示项,或设置为 `false` 隐藏 |
653
+ | `bubble` | 消息气泡、自动滚动和角色配置 |
654
+ | `sender` | 输入框配置,或设置为 `false` 隐藏输入区 |
655
+ | `model` | 模型区域开关;当前支持默认配置对象或 `false` |
656
+ | `mcp` | MCP 区域开关;当前支持默认配置对象或 `false` |
657
+ | `labels` | 覆盖界面文案的部分字段 |
658
+
659
+ 默认值还包括:左侧栏宽度 `300`、折叠宽度 `56`、默认关闭;右侧栏默认关闭;输入区默认支持多行输入、清空、最大长度 `1000` 并显示字数限制;消息自动滚动默认开启。
660
+
661
+ ### 6.3 隐藏功能区
662
+
663
+ ```ts
664
+ const ui: ChatUIOptions = {
665
+ header: false,
666
+ history: false,
667
+ model: false,
668
+ mcp: false,
669
+ }
670
+ ```
671
+
672
+ 隐藏页面区域不等于禁用 Runtime 能力。Runtime 仍可以保存模型或 MCP 状态;这里只是不在 `TrChat` 中渲染对应的界面区域。
673
+
674
+ ### 6.4 响应式布局
675
+
676
+ 页面在宽度小于 `960px` 时进入移动端布局。移动端会强制使用抽屉形式的 Aside,并关闭打开状态;左侧栏展开宽度不会超过视口宽度的 `86%`。
677
+
678
+ - `dock`:桌面端的固定侧栏模式。
679
+ - `drawer`:抽屉模式。
680
+ - `width`:桌面端展开宽度。
681
+ - `collapsedWidth`:桌面端折叠后的宽度。
682
+ - `defaultOpen`:非受控模式的初始状态。
683
+ - `open`:受控模式的当前状态。
684
+
685
+ 当 `open` 存在时,组件不会用内部状态写回它;用户操作和移动端断点行为仍会通过 `left-aside-open-change` 或 `right-aside-open-change` 通知外部。
686
+
687
+ ## 7. 自定义内容:插槽
688
+
689
+ 只在需要替换或补充某个区域时使用插槽。常用插槽如下:
690
+
691
+ `layout-right-aside` 和 `layout-right-aside-title` 只提供右栏内容,不会启用右栏。需要先通过 `ui.layout.rightAside` 显式启用:
692
+
693
+ ```vue
694
+ <TrChat :runtime="runtime" :ui="{ layout: { rightAside: {} } }">
695
+ <template #layout-right-aside>
696
+ <DetailPanel />
697
+ </template>
698
+ </TrChat>
699
+ ```
700
+
701
+ | 插槽 | 用途 |
702
+ | -------------------------- | ---------------------------------- |
703
+ | `header-notice` | 顶部标题下方的提示区域 |
704
+ | `request-error` | 替换请求错误显示内容,提供 `error` |
705
+ | `layout-right-aside` | 右侧详情栏正文 |
706
+ | `layout-right-aside-title` | 右侧详情栏标题 |
707
+ | `sender-footer` | 输入区底部附加内容 |
708
+ | `sender-footer-right` | 输入区底部右侧附加内容 |
709
+ | `welcome-footer` | 欢迎区域底部附加内容 |
710
+ | `prompts-footer` | 提示项区域底部附加内容 |
711
+ | `bubble-prefix` | 消息列表前置内容 |
712
+ | `bubble-suffix` | 消息列表后置内容 |
713
+ | `bubble-after` | 消息列表之后的内容 |
714
+ | `bubble-content-footer` | 消息内容底部附加内容 |
715
+
716
+ 一个可运行的 `header-notice` 示例:
717
+
718
+ ```vue
719
+ <template>
720
+ <TrChat :runtime="runtime">
721
+ <template #header-notice>
722
+ <p class="notice">当前会话使用测试环境</p>
723
+ </template>
724
+ </TrChat>
725
+ </template>
726
+ ```
727
+
728
+ 需要完整替换布局时,可以使用以下插槽:
729
+
730
+ | 插槽 | 可替换区域 |
731
+ | ------------------- | ---------- |
732
+ | `layout-header` | 顶部栏 |
733
+ | `layout-left-aside` | 左侧会话栏 |
734
+ | `layout-main` | 消息主区域 |
735
+ | `layout-footer` | 输入区域 |
736
+
737
+ 完整插槽参数可以通过公开类型 `ChatUISlots` 查看。布局插槽会提供会话数据和对应操作函数;`layout-footer` 会提供输入值、输入状态、提交、取消和清空函数。
738
+
739
+ ## 8. 错误处理
740
+
741
+ `TrChat` 的 `runtime-action-error` 用于接收 Runtime 操作错误:
742
+
743
+ ```vue
744
+ <script setup lang="ts">
745
+ import { ref } from 'vue'
746
+ import { TrChat, type ChatRuntimeActionErrorPayload } from '@opentiny/tiny-robot-chat'
747
+
748
+ const lastActionError = ref<ChatRuntimeActionErrorPayload | null>(null)
749
+
750
+ function handleRuntimeActionError(payload: ChatRuntimeActionErrorPayload) {
751
+ lastActionError.value = payload
752
+ }
753
+ </script>
754
+
755
+ <template>
756
+ <div v-if="lastActionError" role="alert">{{ lastActionError.action }}: {{ String(lastActionError.error) }}</div>
757
+ <TrChat :runtime="runtime" @runtime-action-error="handleRuntimeActionError" />
758
+ </template>
759
+ ```
760
+
761
+ 该事件覆盖:
762
+
763
+ - 发送和取消;
764
+ - 创建、切换、重命名和删除会话;
765
+ - 模型切换和功能开关;
766
+ - MCP Server 添加、删除、启用,以及工具开关。
767
+
768
+ 发送错误会恢复发送前的草稿并继续向调用方抛出;其他由页面触发的 Runtime 操作会通过事件报告,不产生未处理的 Promise rejection。
769
+
770
+ 请求错误会在页面中显示。使用 `request-error` 插槽可以替换默认错误区域:
771
+
772
+ ```vue
773
+ <template>
774
+ <TrChat :runtime="runtime">
775
+ <template #request-error="{ error }"> <strong>请求失败:</strong> {{ String(error) }} </template>
776
+ </TrChat>
777
+ </template>
778
+ ```
779
+
780
+ 以下普通事件用于接收界面行为:
781
+
782
+ - `prompt-click`:用户点击提示项。
783
+ - `history-action`:用户触发非默认会话菜单动作。
784
+ - `bubble-state-change`:消息气泡状态变化。
785
+ - `bubble-event`:消息气泡产生事件。
786
+ - `left-aside-open-change`:左侧栏打开状态变化。
787
+ - `right-aside-open-change`:右侧栏打开状态变化。
788
+
789
+ 发送、取消、会话、模型和 MCP 操作已经由 `TrChat` 消费,不要在外部再次处理这些对应的 UI 事件。`history-action` 中 ID 为 `delete` 的默认删除行为也已经由 `TrChat` 处理;其他自定义菜单动作由外部处理。
790
+
791
+ 侧栏事件的 payload 为 `{ open, source }`,其中 `source` 是 `user` 或 `viewport`,分别表示用户操作或响应式断点导致的关闭。
792
+
793
+ ## 9. 接入已有 Kit 会话
794
+
795
+ 如果项目已经通过 Kit 创建了 `useConversation` 结果,使用 `useKitChatRuntime` 适配它。会话的具体创建参数由宿主按照 Kit 文档提供:
796
+
797
+ ```vue
798
+ <script setup lang="ts">
799
+ import { useConversation } from '@opentiny/tiny-robot-kit'
800
+ import { TrChat, useKitChatRuntime } from '@opentiny/tiny-robot-chat'
801
+
802
+ // 按 Kit 文档创建会话,并提供项目已有的消息响应逻辑。
803
+ const conversation = useConversation(/* existing Kit conversation options */)
804
+
805
+ const runtime = useKitChatRuntime({
806
+ conversation,
807
+ titleGenerator: (text) => text.trim().slice(0, 24) || '新对话',
808
+ composer: {
809
+ // 可选:传入宿主维护的 disabled、submitDisabled、model 或 mcp。
810
+ },
811
+ })
812
+ </script>
813
+
814
+ <template>
815
+ <TrChat :runtime="runtime" />
816
+ </template>
817
+ ```
818
+
819
+ 宿主负责创建和维护 `useConversation`。Chat Runtime 负责将它转换为 `TrChat` 使用的数据和操作。`composer` 是可选的,用于传入宿主已经维护的 Composer 状态;新项目使用 `useLocalChatRuntime` 时,模型 Provider 和声明式 MCP 应通过对应的 Local Runtime 配置提供。
820
+
821
+ ## 10. 仅使用界面层 TrChatUI
822
+
823
+ `TrChatUI` 是高级接入方式,不是新项目首选。它接收普通的 `data`、`ui` 和 `inputValue`,通过事件通知外部;它不会创建会话、发送请求或管理模型状态。
824
+
825
+ 下面是一个受控输入的最小示例:
826
+
827
+ ```vue
828
+ <script setup lang="ts">
829
+ import { ref } from 'vue'
830
+ import { TrChatUI, type ChatSendPayload, type ChatUIData } from '@opentiny/tiny-robot-chat'
831
+
832
+ const inputValue = ref('')
833
+ const data = ref<ChatUIData>({
834
+ conversation: {
835
+ items: [],
836
+ activeId: null,
837
+ title: '新对话',
838
+ },
839
+ bubble: {
840
+ messages: [],
841
+ },
842
+ sender: {
843
+ loading: false,
844
+ disabled: false,
845
+ submitDisabled: false,
846
+ },
847
+ request: {
848
+ state: 'idle',
849
+ },
850
+ })
851
+
852
+ function handleSubmit(payload: ChatSendPayload) {
853
+ // 在这里接入项目自己的发送逻辑,并按需更新 data。
854
+ console.log(payload.text)
855
+ }
856
+ </script>
857
+
858
+ <template>
859
+ <TrChatUI :data="data" :input-value="inputValue" @update:input-value="inputValue = $event" @submit="handleSubmit" />
860
+ </template>
861
+ ```
862
+
863
+ 受控模式下,外部必须响应 `update:input-value` 并更新 `inputValue`。外部还需要处理会话、请求状态、取消、模型和 MCP 事件,并将最新事实写回 `data`。
864
+
865
+ ## 11. 常见问题
866
+
867
+ ### 页面无法发送消息
868
+
869
+ 检查以下条件:
870
+
871
+ - 是否配置了 `modelProviders`,或提供了 `conversation.useMessageOptions.responseProvider`;
872
+ - `modelProviders` 与 `responseProvider` 是否被同时配置;
873
+ - `apiUrl` 是否指向可访问的模型服务或后端代理;
874
+ - 如果直连服务,Provider 是否配置了有效的 API Key;
875
+ - MCP Server 是否仍在读取工具;
876
+ - 是否通过 Runtime 的 `disabled` 或 `submitDisabled` 禁用了输入。
877
+
878
+ ### MCP 服务无法连接
879
+
880
+ 检查以下条件:
881
+
882
+ - `baseUrl` 是否正确;
883
+ - 浏览器是否允许跨域请求;
884
+ - 相对地址是否被用于非浏览器环境;
885
+ - `headers`、`validate` 或后端代理认证是否配置正确;
886
+ - 远程服务是否支持 Streamable HTTP。
887
+
888
+ ### 更换模型或工具后,当前请求没有变化
889
+
890
+ 模型、功能开关和 MCP 工具选择会在发送时保存为当前消息的请求配置。发送完成后再发生的变更只影响下一条消息,不会修改已经开始的请求。
891
+
892
+ ### 为什么浏览器中不应直接放生产密钥
893
+
894
+ 前端环境变量会进入浏览器构建产物,浏览器用户可以读取请求中的凭证。生产环境应使用后端或 BFF 保存密钥并代发模型和 MCP 请求。
895
+
896
+ ### 应该使用 `TrChat`、`useKitChatRuntime` 还是 `TrChatUI`
897
+
898
+ | 情况 | 选择 |
899
+ | ---------------------------------- | ------------------------------ |
900
+ | 从零开始创建聊天页面 | `useLocalChatRuntime + TrChat` |
901
+ | 已经有 Kit 的 `useConversation` | `useKitChatRuntime + TrChat` |
902
+ | 已有自己的数据层、请求层和状态管理 | `TrChatUI` |
903
+
904
+ ## 12. API 速查
905
+
906
+ | 导出 | 用途 |
907
+ | --------------------- | ------------------------------------------------- |
908
+ | `TrChat` | 完整聊天页面,连接 `ChatRuntime` 和 `TrChatUI` |
909
+ | `TrChatUI` | 纯界面层,接收 `ChatUIData` 和 UI 事件 |
910
+ | `useLocalChatRuntime` | 新项目默认 Runtime,组装会话、Provider 和可选 MCP |
911
+ | `useKitChatRuntime` | 适配已有 Kit 会话 |
912
+ | `ChatUIOptions` | `TrChat` 和 `TrChatUI` 的界面配置类型 |
913
+ | `ChatProviderConfig` | 模型服务配置类型 |
914
+ | `ChatMcpServers` | 声明式 MCP 服务配置类型 |
915
+ | `ChatRuntime` | 自有状态管理接入时实现的 Runtime 协议 |
916
+
917
+ 其他常用公开类型包括 `ChatMcpServerConfig`、`ChatRuntimeActionErrorPayload`、`ChatSendPayload`、`ChatUISlots` 和 `ChatUIData`。
package/package.json CHANGED
@@ -1,46 +1,55 @@
1
1
  {
2
2
  "name": "@opentiny/tiny-robot-chat",
3
- "version": "0.0.0",
3
+ "version": "0.4.1",
4
4
  "license": "MIT",
5
- "description": "TinyRobot 是一个 AI 对话组件库,提供了丰富的 AI 交互组件,助力开发者快速构建企业级 AI 应用;同时也是一个智能助手,支持普通 AI 问答、也支持集成 MCP Server,让 AI 真正帮人“干活”。",
5
+ "description": "TinyRobot Chat application assembly package",
6
6
  "homepage": "https://docs.opentiny.design/tiny-robot/",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/opentiny/tiny-robot.git",
10
- "directory": "packages/chat"
9
+ "url": "git+https://github.com/opentiny/tiny-robot.git"
11
10
  },
12
11
  "bugs": {
13
12
  "url": "https://github.com/opentiny/tiny-robot/issues"
14
13
  },
15
- "keywords": [
16
- "vue",
17
- "vue3",
18
- "vue-components",
19
- "component-library",
20
- "ai",
21
- "ai-components",
22
- "chat",
23
- "chat-ui",
24
- "chatbot",
25
- "llm",
26
- "openai",
27
- "assistant",
28
- "streaming",
29
- "conversation",
30
- "tiny-robot",
31
- "opentiny"
32
- ],
33
14
  "publishConfig": {
34
15
  "access": "public"
35
16
  },
36
17
  "type": "module",
37
- "main": "./index.js",
38
- "module": "./index.js",
39
- "exports": {
40
- ".": "./index.js"
41
- },
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "sideEffects": [
22
+ "**/*.css"
23
+ ],
42
24
  "files": [
43
- "index.js",
44
- "README.md"
45
- ]
25
+ "dist",
26
+ "README*"
27
+ ],
28
+ "scripts": {
29
+ "build": "pnpm type-check && vite build",
30
+ "dev": "vite --host 0.0.0.0",
31
+ "type-check": "vue-tsc --noEmit",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.30.0",
37
+ "@opentiny/tiny-robot": "0.5.1",
38
+ "@opentiny/tiny-robot-kit": "0.5.1",
39
+ "@opentiny/tiny-robot-svgs": "0.5.1",
40
+ "@vueuse/core": "^13.9.0"
41
+ },
42
+ "peerDependencies": {
43
+ "vue": "^3.3.11"
44
+ },
45
+ "devDependencies": {
46
+ "@vitejs/plugin-vue": "^4.5.2",
47
+ "typescript": "^5.2.2",
48
+ "vite": "^5.0.8",
49
+ "vite-plugin-dts": "^4.5.4",
50
+ "vitest": "^1.6.1",
51
+ "vue": "^3.3.11",
52
+ "vue-tsc": "^2.2.8"
53
+ },
54
+ "gitHead": "09317e7ea1f51d2b8b6a97ebb7b2ceee9be4a963"
46
55
  }
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- // Bootstrap package used to establish npm trusted publishing.
2
- export {}