366 lines
12 KiB
Markdown
366 lines
12 KiB
Markdown
# `bricks.js` 技术文档:LlmDialog 模块
|
||
|
||
> **版本**:1.0
|
||
> **模块名称**:`LlmDialog`, `LlmMsgBox`, `UserMsgBox`, 工具函数 `escapeSpecialChars`
|
||
> **依赖框架**:`bricks.js` 前端组件库(基于类的 UI 架构)
|
||
|
||
---
|
||
|
||
## 📚 概述
|
||
|
||
该模块提供了一套用于构建与 LLM(大语言模型)交互对话界面的组件系统,支持多模型并行响应、流式/同步响应模式、Markdown 渲染以及消息内容转义。主要包含以下核心类:
|
||
|
||
- `bricks.escapeSpecialChars`:字符串特殊字符转义工具。
|
||
- `bricks.UserMsgBox`:用户消息展示组件。
|
||
- `bricks.LlmMsgBox`:LLM 回应消息展示及请求处理组件。
|
||
- `bricks.LlmDialog`:主对话容器,管理整个对话流程。
|
||
|
||
此外,通过 `bricks.Factory.register` 注册了 `LlmDialog` 以便动态创建。
|
||
|
||
---
|
||
|
||
## 🔧 工具函数
|
||
|
||
### `bricks.escapeSpecialChars(s)`
|
||
|
||
对输入字符串中的特殊字符进行反斜杠转义,常用于防止 JSON 注入或确保文本安全输出。
|
||
|
||
#### 参数
|
||
| 参数 | 类型 | 描述 |
|
||
|------|--------|--------------|
|
||
| `s` | string | 待转义字符串 |
|
||
|
||
#### 返回值
|
||
- **类型**:`string`
|
||
- 转义后的字符串,适用于嵌入 JSON 或 HTML 环境。
|
||
|
||
#### 支持转义的字符
|
||
| 字符 | 原始表示 | 转义后表示 | 说明 |
|
||
|------|----------|----------------|--------------------|
|
||
| `\` | `\` | `\\` | 反斜杠 |
|
||
| `"` | `"` | `\"` | 双引号 |
|
||
| `\n` | 换行 | `\n` | 换行符 |
|
||
| `\r` | 回车 | `\r` | 回车符 |
|
||
| `\t` | 制表符 | `\t` | Tab |
|
||
| `\f` | 换页 | `\f` | Form feed |
|
||
| `\v` | 垂直制表 | `\v` | Vertical tab |
|
||
| `\0` | 空字符 | `\0` | Null byte |
|
||
|
||
> ⚠️ 注意:单引号 `'` 的转义代码被注释,当前未启用。
|
||
|
||
#### 示例
|
||
```js
|
||
bricks.escapeSpecialChars('Hello "world"\n');
|
||
// 输出: 'Hello \\"world\\"\\n'
|
||
```
|
||
|
||
---
|
||
|
||
## 💬 用户消息组件:`UserMsgBox`
|
||
|
||
继承自 `bricks.HBox`,用于显示用户发送的消息。
|
||
|
||
### 继承关系
|
||
```js
|
||
class UserMsgBox extends bricks.HBox
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
|
||
#### 参数 `opts`
|
||
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|
||
|------------|--------|------|----------------------------|------------------------------|
|
||
| `icon` | string | 否 | `imgs/chat-user.svg` | 用户头像 SVG 图标 URL |
|
||
| `prompt` | string | 是 | - | 用户输入的消息内容 |
|
||
| `msg_css` | string | 否 | `'user_msg'` | 应用到消息体的 CSS 类名 |
|
||
|
||
#### 内部结构
|
||
1. 创建一个 SVG 头像图标(使用 `bricks.Svg`)。
|
||
2. 使用 `MdWidget` 显示 Markdown 格式消息。
|
||
3. 添加左侧空白图标占位符(`BlankIcon`),保持布局对齐。
|
||
4. 消息排列顺序:`[BlankIcon] ← [消息内容] ← [用户头像]`
|
||
|
||
#### 示例实例化
|
||
```js
|
||
new bricks.UserMsgBox({
|
||
prompt: "你好,请介绍一下你自己。",
|
||
icon: "/icons/user.svg",
|
||
msg_css: "custom-user-style"
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 🤖 LLM 消息组件:`LlmMsgBox`
|
||
|
||
继承自 `bricks.HBox`,用于展示 LLM 的回复,并支持流式和同步两种响应方式。
|
||
|
||
### 继承关系
|
||
```js
|
||
class LlmMsgBox extends bricks.HBox
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
|
||
#### 参数 `opts`
|
||
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|
||
|------------------|----------|------|-------------------------|----------------------------------------------------------------------|
|
||
| `model` | string | 是 | - | 使用的模型名称(如 `gpt-3.5-turbo`) |
|
||
| `mapi` | string | 是 | - | API 类型标识(可用于后端路由判断) |
|
||
| `url` | string | 是 | - | 请求后端接口地址 |
|
||
| `icon` | string | 否 | `imgs/user.png` | LLM 头像图标 URL |
|
||
| `msg_css` | string | 否 | `'llm_msg'` | 消息区域应用的 CSS 类 |
|
||
| `response_mode` | string | 否 | `'stream'` | 响应模式:`'stream'`, `'sync'`, `'async'` |
|
||
| `user_msg` | object | 否 | `{role:'user', content:"${prompt}"}` | 用户消息模板,支持 `${prompt}` 插值 |
|
||
| `llm_msg` | object | 否 | `{role:'assistant', content:"${content}"}` | LLM 消息模板,支持 `${content}` 插值 |
|
||
|
||
#### 内部初始化逻辑
|
||
- 创建头像图标(SVG)
|
||
- 初始化 `MdWidget` 显示消息内容
|
||
- 加载 `BaseRunning` 动画组件(在响应中显示加载状态)
|
||
- 初始化消息历史数组 `this.messages = []`
|
||
|
||
#### 消息排列顺序
|
||
`[LLM头像] → [运行动画] → [消息内容] → [空白占位符]`
|
||
|
||
---
|
||
|
||
### 方法列表
|
||
|
||
#### `responsed()`
|
||
标记响应已开始,停止加载动画,移除运行指示器。
|
||
|
||
```js
|
||
this.run.stop_timepass();
|
||
this.remove_widget(this.run);
|
||
```
|
||
|
||
#### `user_msg_format()`
|
||
生成用户消息对象格式。
|
||
|
||
- 若设置了 `this.user_msg`,则返回其值;
|
||
- 否则返回默认模板:`{role: 'user', content: "${prompt}"}`
|
||
|
||
> 实际内容将在调用时通过 `bricks.apply_data()` 替换 `${prompt}`。
|
||
|
||
#### `llm_msg_format()`
|
||
生成 LLM 消息对象格式。
|
||
|
||
- 若设置了 `this.llm_msg`,返回其值;
|
||
- 否则返回:`{role: 'assistant', content: "${content}"}`
|
||
|
||
#### `chunk_response(l)`
|
||
处理流式响应中的每一个数据块(chunk)。
|
||
|
||
##### 参数
|
||
- `l`: JSON 字符串片段(通常来自 ReadableStream)
|
||
|
||
##### 行为
|
||
1. 解析 JSON 数据。
|
||
2. 若无 `content` 字段或为空,忽略。
|
||
3. 累加内容至当前消息。
|
||
4. 更新 `MdWidget` 内容。
|
||
5. 触发 `updated` 事件。
|
||
|
||
#### `chunk_ended()`
|
||
流式响应结束后,将最终内容保存为结构化消息并推入 `messages` 数组。
|
||
|
||
- 调用 `escapeSpecialChars` 对内容转义
|
||
- 使用 `apply_data` 填充模板
|
||
- 推入 `this.messages`
|
||
|
||
#### `async set_prompt(prompt)`
|
||
发起对 LLM 的请求。
|
||
|
||
##### 参数
|
||
- `prompt`: 用户输入文本(自动转义)
|
||
|
||
##### 流程
|
||
1. 将用户消息按模板格式化并加入 `messages`。
|
||
2. 准备请求参数:
|
||
```js
|
||
{
|
||
messages: this.messages,
|
||
mapi: this.mapi,
|
||
model: this.model
|
||
}
|
||
```
|
||
3. 根据 `response_mode` 执行不同请求策略:
|
||
|
||
| 模式 | 行为说明 |
|
||
|-----------|---------|
|
||
| `stream` | 使用 `HttpResponseStream` 发起 POST 请求,逐块接收并渲染 |
|
||
| `sync` | 使用 `HttpJson` 同步获取完整响应,一次性渲染结果 |
|
||
| 其他 | 不做任何操作(预留扩展) |
|
||
|
||
##### 示例调用
|
||
```js
|
||
await llmMsgBox.set_prompt("请写一首关于春天的诗");
|
||
```
|
||
|
||
---
|
||
|
||
## 🧩 主对话框组件:`LlmDialog`
|
||
|
||
继承自 `bricks.VBox`,是完整的聊天对话界面容器,支持多模型同时响应。
|
||
|
||
### 继承关系
|
||
```js
|
||
class LlmDialog extends bricks.VBox
|
||
```
|
||
|
||
### 构造函数:`constructor(opts)`
|
||
|
||
#### 参数 `opts`
|
||
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|
||
|------------------|----------|------|----------------|----------------------------------------------------------------------|
|
||
| `models` | array | 是 | - | 模型配置数组,每个元素定义一个 LLM 模型 |
|
||
| `response_mode` | string | 否 | `'stream'` | 全局响应模式:`'stream'`, `'sync'`, `'async'` |
|
||
| `user_msg_css` | string | 否 | `'user_msg'` | 用户消息使用的 CSS 类 |
|
||
| `user_icon` | string | 否 | - | 用户头像图标路径 |
|
||
| `title_ccs` | string | 否 | `'llm_title'` | 标题栏 CSS 类(拼写疑似错误,应为 `title_css`) |
|
||
| `height` | string | 否 | `'100%'` | 容器高度 |
|
||
|
||
#### `models` 数组项结构
|
||
```js
|
||
{
|
||
model: "gpt-3.5-turbo",
|
||
mapi: "openai",
|
||
url: "/api/openai/chat",
|
||
icon: "/icons/gpt.svg",
|
||
css: "gpt-response",
|
||
user_msg: { role: "user", content: "${prompt}" },
|
||
llm_msg: { role: "assistant", content: "${content}" }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 方法列表
|
||
|
||
#### `show_models_info()`
|
||
遍历所有模型,调用 `show_model_info(model)` 在标题栏显示模型信息。
|
||
|
||
#### `show_model_info(model)`
|
||
在顶部标题区添加一个 HBox,包含模型图标和名称。
|
||
|
||
- 使用 `Svg` 显示图标
|
||
- 使用 `Text` 显示模型名
|
||
- 存储引用到 `this.model_info_ws[model.model]`
|
||
|
||
#### `add_model(model)`
|
||
动态添加新模型配置,并立即显示在标题栏。
|
||
|
||
#### `delete_model(model)`
|
||
根据 `model.model` 名称删除模型及其视图。
|
||
|
||
#### `async set_prompt(prompt)`
|
||
主入口方法:发送用户消息并触发所有模型响应。
|
||
|
||
##### 步骤
|
||
1. 转义用户输入。
|
||
2. 创建并添加 `UserMsgBox` 到滚动面板。
|
||
3. 调用 `llm_request(prompt)` 并等待完成。
|
||
4. 自动滚动到底部。
|
||
|
||
#### `async llm_request(prompt)`
|
||
为每个注册的模型创建一个 `LlmMsgBox`,并异步启动请求。
|
||
|
||
- 使用 `schedule_once(fn, 0.1)` 延迟执行以避免阻塞 UI
|
||
- 所有模型并行响应
|
||
|
||
---
|
||
|
||
### 事件机制
|
||
|
||
#### `llm_answer` 事件
|
||
当某个模型完成响应后,可通过监听此事件获取回答内容。
|
||
|
||
> ❗ 当前代码中未显式触发 `llm_answer` 事件,需补充如下代码才能生效:
|
||
```js
|
||
// 在 chunk_ended 或 sync 响应结束处添加:
|
||
this.fire('llm_answer', { content: txt });
|
||
```
|
||
|
||
建议增强事件通知能力。
|
||
|
||
---
|
||
|
||
## 🏗️ 工厂注册
|
||
|
||
```js
|
||
bricks.Factory.register('LlmDialog', bricks.LlmDialog);
|
||
```
|
||
|
||
允许通过字符串标识动态创建组件:
|
||
```js
|
||
var dialog = bricks.Factory.create('LlmDialog', options);
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 使用示例
|
||
|
||
```js
|
||
var dialog = new bricks.LlmDialog({
|
||
height: '600px',
|
||
user_icon: '/icons/me.svg',
|
||
title_ccs: 'chat-title',
|
||
response_mode: 'stream',
|
||
models: [
|
||
{
|
||
model: 'gpt-3.5-turbo',
|
||
mapi: 'openai',
|
||
url: '/api/chat',
|
||
icon: '/icons/gpt.svg',
|
||
css: 'gpt-msg',
|
||
user_msg: { role: 'user', content: '${prompt}' },
|
||
llm_msg: { role: 'assistant', content: '${content}' }
|
||
},
|
||
{
|
||
model: 'claude-2',
|
||
mapi: 'anthropic',
|
||
url: '/api/anthropic',
|
||
icon: '/icons/claude.svg',
|
||
css: 'claude-msg'
|
||
}
|
||
]
|
||
});
|
||
|
||
document.body.appendChild(dialog.dom_element);
|
||
|
||
// 发送消息
|
||
dialog.set_prompt("解释什么是机器学习");
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 注意事项与改进建议
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 🔐 安全性 | `escapeSpecialChars` 有助于防止注入,但建议结合 DOMPurify 或其他 sanitizer 进一步防护 XSS |
|
||
| 🧩 单引号转义 | 被注释掉,若需兼容 SQL 或属性字符串场景,建议启用 |
|
||
| 🎯 事件缺失 | `llm_answer` 事件未实际触发,建议在 `chunk_ended()` 中补全 |
|
||
| 🖼️ 图标资源 | 依赖 `bricks_resource()` 函数解析静态资源路径,需确保其存在 |
|
||
| ⏳ 异步控制 | `schedule_once(..., 0.1)` 是一种 Hack,可考虑改为 `queueMicrotask` 或 `Promise.resolve().then()` |
|
||
| 📏 布局兼容 | 使用 Flexbox(HBox/VBox),确保父级容器设置正确尺寸 |
|
||
|
||
---
|
||
|
||
## 📚 总结
|
||
|
||
本模块提供了完整的前端 LLM 聊天对话解决方案,具备以下特性:
|
||
|
||
✅ 多模型支持
|
||
✅ 流式 & 同步响应
|
||
✅ Markdown 渲染
|
||
✅ 自动滚动与加载动画
|
||
✅ 可扩展模板机制
|
||
|
||
适合集成进低代码平台、AI 助手界面、多模型对比测试工具等场景。
|
||
|
||
---
|
||
|
||
> 文档版本:1.0
|
||
> 最后更新:2025年4月5日 |