以下是针对您提供的 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` 调用父类 `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` 用户点击点赞/点踩时触发,向服务端上报评分。 **请求参数:** ```json { "widgettype": "urlwidget", "options": { "url": this.estimate_url, "params": { "logid": this.logid, "value": val // 1 或 -1 } } } ``` > 执行后禁用评分按钮防止重复提交。 #### ✅ `async update_data(data)` → `Promise` 更新模型输出内容。 - 移除加载动画 `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` 主入口方法:接收用户输入并发起模型请求。 **行为分支:** | 模式 | 行为 | |------|------| | `'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` 显示用户输入内容在对话区域上方。 - 构建 `UserInputView` 显示输入数据 - 添加用户头像图标 - 插入到 `o_w` 容器中 #### ✅ `show_added_model(m)` → `void` 注册一个新的模型实例并将其标题添加到顶部工具栏。 - 若启用了 `textvoice`,自动注入 `tts_url` - 实例化 `LlmModel` - 调用 `.render_title()` 并加入 `title_w` #### ✅ `async open_search_models(event)` → `Promise` 打开“选择模型”弹窗,从远程获取模型列表。 使用 `PopupWindow + Cols` 组件展示表格形式的模型列表。 **表格列定义:** - 图标 + 名称(HBox) - 描述、启用时间(VBox 内嵌 Text) 点击某条记录会触发: - `add_new_model(event)` - 并关闭弹窗 #### ✅ `async add_new_model(event)` → `Promise` 将选中的模型加入 `this.models` 数组,并调用 `show_added_model` 显示。 #### ✅ `async open_input_widget(event)` → `Promise` 打开输入表单弹窗。 使用 `Form` 组件加载 `input_fields` 定义的字段。 提交后触发: - `handle_input(event)` - 并自动关闭弹窗 #### ✅ `async handle_input(event)` → `Promise` 处理用户提交的输入数据。 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