414 lines
12 KiB
Markdown
414 lines
12 KiB
Markdown
以下是针对您提供的 JavaScript 代码编写的 **Markdown 格式技术文档**,结构清晰、注释完整,适用于前端开发团队或项目维护者使用。
|
||
|
||
---
|
||
|
||
# 📘 `bricks.LlmIO` 模块技术文档
|
||
|
||
> 基于 `bricks.js` 框架实现的多模型大语言模型(LLM)交互系统
|
||
> 支持流式响应、文本转语音(TTS)、用户反馈评分等功能
|
||
|
||
---
|
||
|
||
## 🔧 模块结构概览
|
||
|
||
```js
|
||
bricks = window.bricks || {}
|
||
bricks.LlmMsgAudio // 音频播放与流式消息处理
|
||
bricks.ModelOutput // 模型输出 UI 组件
|
||
bricks.LlmModel // 单个 LLM 模型控制器
|
||
bricks.LlmIO // 主控容器:管理输入、多个模型和输出展示
|
||
```
|
||
|
||
所有组件均继承自 `bricks.js` 提供的基础 UI 类(如 `VBox`, `HBox`, `JsWidget` 等),并支持动态构建、事件绑定和异步数据流处理。
|
||
|
||
---
|
||
|
||
## 1. `bricks.LlmMsgAudio` —— 流式音频消息处理器
|
||
|
||
### 功能说明
|
||
用于处理来自后端的流式文本响应,并根据标点符号分段发送至音频播放器。自动检测语言以适配中英文标点。
|
||
|
||
### 继承关系
|
||
```js
|
||
class LlmMsgAudio extends bricks.UpStreaming
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
| 参数 | 类型 | 描述 |
|
||
|------|------|------|
|
||
| `opts` | Object | 传递给父类的配置项 |
|
||
|
||
#### 初始化字段:
|
||
- `this.olddata`: 上一次接收到的数据(用于增量比较)
|
||
- `this.data`: 当前累积的完整文本
|
||
- `this.cn_p`: 中文标点符号数组 `[“。”,”,”,”!”,”?”,”\n”]`
|
||
- `this.other_p`: 英文标点符号数组 `[“.”,”,”,”!”, “?”, “\n”]`
|
||
- `this.audio`: 实例化的音频播放器(通过 `AudioPlayer({})` 创建)
|
||
|
||
### 方法列表
|
||
|
||
#### ✅ `detectLanguage(text)` → `String`
|
||
尝试使用浏览器内置的 `Intl.LocaleDetector` 推测文本语言。
|
||
|
||
- 返回示例:`'zh'`, `'en'`
|
||
- 若失败则返回 `'未知'`
|
||
|
||
> ⚠️ 注意:`Intl.LocaleDetector` 并非所有浏览器都支持,需注意兼容性降级处理。
|
||
|
||
#### ✅ `send(data)` → `void`
|
||
接收增量数据并进行分句处理,将已完成句子通过 `super.send()` 发送。
|
||
|
||
##### 工作流程:
|
||
1. 计算新增部分:`newdata = data.slice(this.olddata.length)`
|
||
2. 更新历史数据缓存
|
||
3. 调用 `detectLanguage(this.data)` 判断语言
|
||
4. 使用对应标点分割成句子数组(过滤空字符串)
|
||
5. 将除最后一句外的所有完整句子发送出去
|
||
6. 保留最后未完成句在 `this.data` 中等待下一批数据
|
||
|
||
> 💡 示例:
|
||
> 输入 `"你好!今天天气"` → 输出 `"你好!"`,缓存 `"今天天气"`
|
||
|
||
#### ✅ `async go()` → `Promise<Response>`
|
||
调用父类 `go()` 获取最终响应,并设置音频源为该响应内容。
|
||
|
||
```js
|
||
await super.go();
|
||
this.audio.set_source_from_response(resp);
|
||
```
|
||
|
||
通常用于非流式场景下的 TTS 音频播放。
|
||
|
||
---
|
||
|
||
## 2. `bricks.ModelOutput` —— 模型输出 UI 控件
|
||
|
||
### 功能说明
|
||
显示单个模型的输出结果,包含图标、加载动画、内容区域及满意度评价功能。
|
||
|
||
### 继承关系
|
||
```js
|
||
class ModelOutput extends bricks.VBox
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
|
||
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
||
|------|------|------|--------|------|
|
||
| `modelname` | String | 是 | - | 显示的模型名称 |
|
||
| `icon` | URL/String | 否 | `llm.svg` | 自定义图标路径 |
|
||
| `response_mode` | String | 否 | - | 可选 `'stream'`, `'sync'`, `'async'` |
|
||
| `estimate_url` | URL | 否 | - | 用户反馈提交地址 |
|
||
|
||
#### 内部组件初始化:
|
||
- `img`: 模型图标 SVG
|
||
- `content`: 主体 HBox 容器
|
||
- `run`: 加载动画组件(`BaseRunning`)
|
||
- `filler`: 实际内容显示区(`LlmOut` 组件)
|
||
- `estimate_w`: 满意度打分组件(点赞/踩)
|
||
|
||
### 核心方法
|
||
|
||
#### ✅ `build_estimate_widgets()` → `void`
|
||
创建“结果满意吗?”评分控件(仅当 `estimate_url` 存在时)。
|
||
|
||
包含:
|
||
- 文本提示:“结果满意吗?”
|
||
- 👍 点赞图标(绑定 `estimate_llm(1)`)
|
||
- 👎 点踩图标(绑定 `estimate_llm(-1)`)
|
||
|
||
初始状态为隐藏(`.hide()`),可在完成后显示。
|
||
|
||
#### ✅ `async estimate_llm(val, event)` → `Promise<void>`
|
||
用户点击点赞/点踩时触发,向服务端上报评分。
|
||
|
||
**请求参数:**
|
||
```json
|
||
{
|
||
"widgettype": "urlwidget",
|
||
"options": {
|
||
"url": this.estimate_url,
|
||
"params": {
|
||
"logid": this.logid,
|
||
"value": val // 1 或 -1
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> 执行后禁用评分按钮防止重复提交。
|
||
|
||
#### ✅ `async update_data(data)` → `Promise<void>`
|
||
更新模型输出内容。
|
||
|
||
- 移除加载动画 `run`
|
||
- 调用 `filler.update(data)` 渲染内容
|
||
- 支持流式逐步更新
|
||
|
||
#### ✅ `finish()` → `void`
|
||
生命周期钩子,当前无实际逻辑,可扩展。
|
||
|
||
---
|
||
|
||
## 3. `bricks.LlmModel` —— 单个 LLM 模型控制器
|
||
|
||
### 功能说明
|
||
封装一个 LLM 模型的行为逻辑,包括请求发送、格式化、响应处理等。
|
||
|
||
### 继承关系
|
||
```js
|
||
class LlmModel extends bricks.JsWidget
|
||
```
|
||
|
||
### 构造函数:`constructor(llmio, opts)`
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `llmio` | LlmIO 实例 | 上层控制容器引用 |
|
||
| `opts` | Object | 模型配置对象 |
|
||
|
||
#### 配置项 (`opts`) 支持字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `model` | String | 模型标识符(如 `gpt-3.5-turbo`) |
|
||
| `modelname` | String | 显示名 |
|
||
| `url` | URL | 请求接口地址 |
|
||
| `params` | Object | 固定请求参数 |
|
||
| `input_from` | String | 允许的数据来源标签 |
|
||
| `textvoice` | Boolean | 是否启用 TTS 语音播报 |
|
||
| `tts_url` | URL | TTS 接口地址 |
|
||
| `response_mode` | String | `'stream' \| 'sync' \| 'async'` |
|
||
| `icon` | URL | 图标路径 |
|
||
|
||
### 核心方法
|
||
|
||
#### ✅ `render_title()` → `HBox`
|
||
生成顶部标题栏(含图标),点击可打开设置面板(预留接口)。
|
||
|
||
#### ✅ `show_setup_panel(event)` → `void`
|
||
待实现:点击标题弹出模型配置面板。
|
||
|
||
#### ✅ `inputdata2uploaddata(data)` → `FormData \| Object`
|
||
将输入数据转换为适合上传的格式,并注入模型相关信息。
|
||
|
||
- 若是 `FormData`,使用 `.append()` 添加字段
|
||
- 否则直接赋值对象属性
|
||
- 注入 `model` 和 `llmid`
|
||
|
||
> 特殊逻辑:若 `llmio.model_inputed` 为真,则不添加 `model` 字段(避免重复)
|
||
|
||
#### ✅ `async model_inputed(data)` → `Promise<void>`
|
||
主入口方法:接收用户输入并发起模型请求。
|
||
|
||
**行为分支:**
|
||
|
||
| 模式 | 行为 |
|
||
|------|------|
|
||
| `'stream'` / `'async'` | 使用 `HttpResponseStream` 处理流式响应 |
|
||
| `'sync'` | 使用 `HttpJson` 发起同步 POST 请求 |
|
||
|
||
流程:
|
||
1. 创建 `ModelOutput` 显示组件并加入页面
|
||
2. 准备请求数据
|
||
3. 根据模式发起请求
|
||
4. 流式情况下逐块调用 `chunk_response(mout, line)`
|
||
|
||
#### ✅ `is_accept_source(source)` → `Boolean`
|
||
判断是否接受来自指定源的数据。
|
||
|
||
#### ✅ `llm_msg_format()` → `Object`
|
||
返回默认的消息结构模板:
|
||
|
||
```js
|
||
{ role: 'assistant', content: "${content}" }
|
||
```
|
||
|
||
可用于格式化聊天记录。
|
||
|
||
#### ✅ `chunk_response(mout, l)` → `void`
|
||
处理每一个流式响应片段(每行 JSON 字符串)。
|
||
|
||
- 去除空白字符
|
||
- 尝试解析 JSON
|
||
- 异步模式下只处理 `status === 'SUCCEEDED'` 的消息
|
||
- 调用 `mout.update_data(d)` 更新 UI
|
||
|
||
#### ✅ `chunk_ended()` → `void`
|
||
流结束回调(目前仅打印日志)
|
||
|
||
---
|
||
|
||
## 4. `bricks.LlmIO` —— 主控容器(LLM 输入输出管理器)
|
||
|
||
### 功能说明
|
||
集成式组件,提供以下能力:
|
||
- 多模型管理
|
||
- 输入表单弹窗
|
||
- 模型选择弹窗
|
||
- 自动渲染模型输出
|
||
- 支持 TTS 和用户反馈
|
||
|
||
### 继承关系
|
||
```js
|
||
class LlmIO extends bricks.VBox
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `user_icon` | URL | 用户头像图标 |
|
||
| `list_models_url` | URL | 获取可用模型列表的接口 |
|
||
| `input_fields` | Array | 表单字段定义(参考 `bricks.Form`) |
|
||
| `models` | Array | 初始已添加的模型列表 |
|
||
| `tts_url` | URL | TTS 接口地址 |
|
||
| `estimate_url` | URL | 用户反馈提交地址 |
|
||
|
||
#### 初始化行为:
|
||
- 创建标题栏 `title_w`
|
||
- 创建输出区域 `o_w`(滚动容器)
|
||
- 创建底部操作按钮:
|
||
- `i_w`: 输入按钮(打开输入表单)
|
||
- `nm_w`: 添加模型按钮(打开模型搜索窗口)
|
||
- 绑定点击事件
|
||
- 若模型少于 2 个且存在 `tts_url`,启用 `textvoice` 模式
|
||
- 遍历 `models` 初始化每个 `LlmModel`
|
||
|
||
### 核心方法
|
||
|
||
#### ✅ `async show_input(params)` → `Promise<void>`
|
||
显示用户输入内容在对话区域上方。
|
||
|
||
- 构建 `UserInputView` 显示输入数据
|
||
- 添加用户头像图标
|
||
- 插入到 `o_w` 容器中
|
||
|
||
#### ✅ `show_added_model(m)` → `void`
|
||
注册一个新的模型实例并将其标题添加到顶部工具栏。
|
||
|
||
- 若启用了 `textvoice`,自动注入 `tts_url`
|
||
- 实例化 `LlmModel`
|
||
- 调用 `.render_title()` 并加入 `title_w`
|
||
|
||
#### ✅ `async open_search_models(event)` → `Promise<void>`
|
||
打开“选择模型”弹窗,从远程获取模型列表。
|
||
|
||
使用 `PopupWindow + Cols` 组件展示表格形式的模型列表。
|
||
|
||
**表格列定义:**
|
||
- 图标 + 名称(HBox)
|
||
- 描述、启用时间(VBox 内嵌 Text)
|
||
|
||
点击某条记录会触发:
|
||
- `add_new_model(event)`
|
||
- 并关闭弹窗
|
||
|
||
#### ✅ `async add_new_model(event)` → `Promise<void>`
|
||
将选中的模型加入 `this.models` 数组,并调用 `show_added_model` 显示。
|
||
|
||
#### ✅ `async open_input_widget(event)` → `Promise<void>`
|
||
打开输入表单弹窗。
|
||
|
||
使用 `Form` 组件加载 `input_fields` 定义的字段。
|
||
|
||
提交后触发:
|
||
- `handle_input(event)`
|
||
- 并自动关闭弹窗
|
||
|
||
#### ✅ `async handle_input(event)` → `Promise<void>`
|
||
处理用户提交的输入数据。
|
||
|
||
1. 调用 `show_input(params)` 显示输入内容
|
||
2. 遍历所有 `llmmodels`,调度执行各自的 `model_inputed(params)`
|
||
- 使用 `schedule_once(fn, 0.01)` 实现微任务延迟执行
|
||
|
||
---
|
||
|
||
## 📦 注册组件
|
||
|
||
```js
|
||
bricks.Factory.register('LlmIO', bricks.LlmIO);
|
||
```
|
||
|
||
允许通过工厂方式动态创建此组件。
|
||
|
||
---
|
||
|
||
## 🎯 使用示例
|
||
|
||
```js
|
||
var llmio = new bricks.LlmIO({
|
||
user_icon: '/static/icons/user.png',
|
||
tts_url: '/api/tts',
|
||
estimate_url: '/api/feedback',
|
||
input_fields: [
|
||
{ name: 'prompt', label: '输入提示', type: 'textarea' }
|
||
],
|
||
models: [
|
||
{
|
||
model: 'qwen-plus',
|
||
modelname: '通义千问',
|
||
url: '/api/llm/stream',
|
||
response_mode: 'stream',
|
||
icon: '/icons/qwen.svg'
|
||
}
|
||
]
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ 注意事项 & 待优化点
|
||
|
||
| 问题 | 建议修复 |
|
||
|------|----------|
|
||
| `detectLaguage` 拼写错误 | 应为 `detectLanguage` |
|
||
| `this.oter_p` 拼写错误 | 应为 `this.other_p` |
|
||
| `lang='zh'` 错误地使用了赋值而非比较 | 应改为 `lang === 'zh'` |
|
||
| `objcopy` 函数未定义 | 应替换为 `JSON.parse(JSON.stringify(data))` 或引入深拷贝工具 |
|
||
| `Intl.LocaleDetector` 兼容性差 | 建议降级为基于关键词的语言识别算法 |
|
||
| `schedule_once(..., 0.01)` 不标准 | 建议改用 `setTimeout(fn, 0)` 或 `queueMicrotask` |
|
||
|
||
---
|
||
|
||
## 📚 附录:关键类图(UML 简化版)
|
||
|
||
```
|
||
+------------------+
|
||
| LlmIO |
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| LlmModel[x] |
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| ModelOutput |
|
||
+------------------+---> LlmMsgAudio (用于音频)
|
||
|
|
||
v
|
||
+------------------+
|
||
| UserInput |
|
||
+------------------+
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 总结
|
||
|
||
本模块实现了完整的 LLM 交互链路:
|
||
1. 用户输入 → 表单收集
|
||
2. 多模型并发调用 → 支持流式/同步
|
||
3. 分句播放音频 → 提升听觉体验
|
||
4. 用户反馈机制 → 支持效果评估闭环
|
||
|
||
适合集成于智能客服、AI 助手、教育平台等需要多模型对比或语音播报的应用场景。
|
||
|
||
---
|
||
|
||
✅ **文档版本:v1.0**
|
||
📅 最后更新:2025年4月5日
|
||
👨💻 编写:Bricks Framework Team |