24 KiB
ChatbotSDK — 前端对接文档
P0 核心链路 ✅ | P1 体验增强 ✅ | P2 运营完善 ✅ | 版本:1.4.0 | 更新日期:2026-09-24
一、快速开始
最简接入(3 行代码)
<script src="https://your-domain.com/sdk/chatbot-sdk.min.js"></script>
<script>
ChatbotSDK.init({
integrateId: 1, // 必传:客服角色 ID(对应后端 roleId)
requestDomain: 'https://your-domain.com', // 必传:后端地址
});
</script>
引入后在页面右下角出现悬浮客服按钮,点击即可对话。
完整配置接入
<script src="/sdk/chatbot-sdk.min.js"></script>
<script>
ChatbotSDK.init({
// 必传
integrateId: 'hr-portal-v2', // 客服角色 ID(对应后端 roleId)
requestDomain: 'https://ai.example.com',
// 用户身份
userId: 'zhangsan',
// 鉴权(推荐 —— 详见上文「Token 鉴权与长会话」)
token: initialToken, // 首次从宿主后端换到的 SDK Token
expiresIn: 7200, // 该 Token 的有效期(秒)
tokenStrategy: 'expiry', // 'expiry'(默认)临期/401 才取;'always' 每个请求前都取
tokenUrl: '/my-backend/sdk-token', // SDK 直接 GET 它取 Token(与 getToken 二选一)
// RAG 知识库检索
enableRag: true, // 启用 RAG 增强对话(自动走 /ai/chat/stream,enableRag=true)
categoryId: 5, // 默认知识库分类
showCategorySwitch: true, // 显示知识库分类下拉
// UI 定制
title: 'HR 智能助手',
width: 420,
position: 'right-bottom',
primaryColor: '#2563EB',
showClear: true,
quickReplies: ['如何重置密码?', '报销流程是什么?', '查询我的年假余额'],
theme: 'light', // 主题:'light' | 'dark'
showTeaser: true, // 是否显示首访提示气泡
// 行为
streaming: true,
locale: 'zh-CN', // 多语言(P2):zh-CN / en
debug: false,
});
</script>
Token 鉴权与长会话(重要)
后端 /ai/**、/feedback、/attachment/upload 由 SDK JWT Token 保护,Token 默认有效期 2 小时。
第三方页面往往一挂就是一整天,因此 SDK 内置了自动刷新,接入方无需自己盯过期时间:
| 触发方式 | 时机 | 说明 |
|---|---|---|
| 临期主动刷新 | 剩余寿命不足「总寿命 10% 与 5 分钟中的较小值」 | 需传 expiresIn,用户完全无感 |
| 每请求前取 | 每个受保护请求发出前 | 需 tokenStrategy: 'always',完全不依赖 expiresIn |
| 401 兜底重试 | 请求收到 401 时 | 换一次 Token 后自动重试原请求,UI 上不会闪错误 |
| 手动推送 | 宿主自行换好后调 ChatbotSDK.setToken() |
不想暴露取 Token 逻辑时使用 |
| 手动拉取 | 宿主调 ChatbotSDK.refreshToken() |
SDK 去调 getToken / tokenUrl 立即换新 |
该选哪种:
| 场景 | 推荐 |
|---|---|
宿主能拿到可信的 expiresIn(如自己后端返回了 expiresIn) |
tokenStrategy: 'expiry'(默认)—— 只在临期与 401 时才取,往返最少 |
宿主后端已自行缓存 Token、SDK 拿不到可靠 expiresIn,或不想让 SDK 推算过期时间 |
tokenStrategy: 'always' —— 每次请求前都取一次,由宿主决定何时真的换 Token。代价是每个请求多一次宿主后端往返(含每次对话的首 token 之前),且不再使用 expiresIn |
| 什么都不配 | 只靠 401 兜底:每次失效都要先白跑一个 401 |
取 Token 的动作由宿主提供,推荐在宿主自己的服务端完成(服务端持 API Key 调
POST /open-api/auth/token),前端只把结果交给 SDK —— 这样 API Key 永远不会进入浏览器。
两种提供方式二选一(同时配置时 getToken 优先,tokenUrl 被忽略并告警):
// 方式 1:getToken 回调(最灵活,可复用宿主已有的请求封装 / 鉴权头 / 重试)
getToken: async () => {
const res = await fetch('/my-backend/sdk-token');
const data = await res.json();
return { token: data.token, expiresIn: data.expiresIn };
}
// 方式 2:tokenUrl 地址(零胶水代码,SDK 直接 GET 它)
tokenUrl: '/my-backend/sdk-token'
// 最简写法:只给 Token —— SDK 不会自动续期,令牌过期后需宿主自己调 setToken() 更新
ChatbotSDK.init({ integrateId: 1, requestDomain: 'https://ai.example.com', token: 'xxx' });
// 推荐写法:Token + 有效期 + 取 Token 方式 → SDK 自动续期
ChatbotSDK.init({
integrateId: 1,
requestDomain: 'https://ai.example.com',
token: initialToken,
expiresIn: 7200, // 单位「秒」,与后端 expiresIn 字段一致
tokenStrategy: 'expiry', // 默认值;想「每次请求前都取」就改 'always'
tokenUrl: '/my-backend/sdk-token', // 或改用 getToken 回调(二选一)
onError: (e) => {
// auth_expired = 刷新后仍鉴权失败(Token 彻底失效,需检查 API Key / 角色绑定)
// auth_refresh_failed = 取 Token 失败(网络或宿主后端异常)
console.warn('SDK 错误', e.code, e.message);
},
});
约定与边界:
- 取 Token 的两种形式:
getToken回调(优先)、tokenUrl地址。tokenUrl会兼容{ token, expiresIn }与{ success, token, expiresIn, roles }两种响应。 tokenUrl的 Cookie 行为:请求使用credentials: 'same-origin'—— 同源(宿主页面调自己后端, 最常见)自动带 Cookie;跨域则不带,避免把宿主站点的 Cookie 泄露给别的域。需要跨域带 Cookie 的场景请改用getToken自己发请求。- 「返回对象
{ token, expiresIn }」可让 SDK 提前主动刷新;只返回字符串则只能靠 401 驱动重试 (功能正常,只是首个失效请求会多一次往返)。tokenStrategy: 'always'下expiresIn不参与判断。 expiresIn单位是秒,只做 24 小时上界钳制(防误传毫秒),不做下界 —— 传expiresIn: 20会真的在 18 秒后刷新。- 取 Token 失败不会打断对话:沿用现有 Token 继续发请求(由 401 兜底),并进入 60 秒熔断窗口,
窗口内不再重复取(避免宿主端点挂死时每个请求都多等一次超时)。
refreshToken()可强制绕过该窗口。 - Token 只存内存,不写
localStorage/sessionStorage。页面刷新后由宿主重新init()传入。 - 刷新失败不会吞掉错误:
onError会带auth_refresh_failed/auth_expired上报,同一错误码 30 秒内只报一次。
集成到现有项目
SDK 产物位于 client/dist/ 目录:
| 文件 | 大小 | 说明 |
|---|---|---|
chatbot-sdk.js |
~5.6MB | 未压缩开发版(含 sourcemap) |
chatbot-sdk.min.js |
~4.0MB | 压缩生产版(gzip ~1.1MB) |
部署路径: 将 .min.js 文件上传到后端 static/sdk/ 目录或任意 CDN。SDK 内置 TDesign Web Components 组件库(t-chat-* + 通用组件),按需 tree-shaking 后打包进 IIFE,无需额外加载组件库 CDN。
二、SDKConfig 完整参数
| 参数 | 类型 | 必传 | 默认值 | 阶段 | 说明 |
|---|---|---|---|---|---|
integrateId |
string | number |
✅ | — | P0 | 集成标识 → 后端 roleId(客服角色 ID,决定 AI 人设和知识库范围) |
requestDomain |
string |
✅ | — | P0 | 后端 API 域名 |
userId |
string |
❌ | — | P0 | 宿主用户标识 → 后端 accountId |
token |
string |
❌ | — | P0 | SDK JWT Token(从 /open-api/auth/token 换取)。不传则受保护的 /ai/** 接口会 401 |
expiresIn |
number |
❌ | — | P0 | token 的有效期(秒,与后端 expiresIn 同单位)。传入后 SDK 会在临期前主动刷新 |
getToken |
function |
❌ | — | P0 | 取 Token 回调:SDK 在临期、每请求前(always 模式)或收到 401 时调用它。推荐返回 { token, expiresIn }。详见上文「Token 鉴权与长会话」 |
tokenStrategy |
'expiry' | 'always' |
❌ | 'expiry' |
P0 | 取 Token 的时机:'expiry' = 临期 / 401 才取;'always' = 每个受保护请求前都取一次(此时 expiresIn 不参与判断,代价是每请求多一次宿主后端往返) |
tokenUrl |
string |
❌ | — | P0 | 取 Token 的地址(与 getToken 二选一,同时配置时 getToken 优先)。SDK 会 GET 它并解析 {token,expiresIn} 或 {success,token,expiresIn,roles};请求用 credentials: 'same-origin' |
roles |
Array<{id,key?,name?}> |
❌ | — | P0 | 可用客服角色列表(与 token 配套),决定角色选择器可选项 |
enableRag |
boolean |
❌ | true |
P1 | 启用 RAG 知识库检索对话(走 /ai/chat/stream 接口,enableRag=true) |
categoryId |
number |
❌ | — | P1 | 默认知识库分类 |
showCategorySwitch |
boolean |
❌ | false |
P1 | 是否显示知识库下拉切换 |
title |
string |
❌ | "AI 智能助手" |
P0 | 弹窗标题 |
width |
number |
❌ | 500 |
P0 | 弹窗宽度(px) |
position |
string |
❌ | "right-bottom" |
P0 | 悬浮按钮位置 |
primaryColor |
string |
❌ | "#4F46E5" |
P0 | 主色调 |
launcherIcon |
string |
❌ | 极光粒子图标 | P0 | 悬浮按钮图标(可传 URL 或 SVG 字符串),默认使用极光粒子动画图标 |
launcherSize |
number | string |
❌ | 80 |
P0 | 悬浮按钮尺寸:数字=px(范围 36~96),或 CSS 长度字符串如 "4rem"/"5vw"/"clamp(48px,3vw,80px)" 用于响应式适配 |
showClear |
boolean |
❌ | true |
P0 | 是否显示清空按钮 |
quickReplies |
string[] |
❌ | [] |
P1 | 欢迎态快捷问题芯片,点击即自动发送 |
theme |
string |
❌ | "light" |
P2 | 主题模式:"light" / "dark" |
showTeaser |
boolean |
❌ | true |
P1 | 首访提示气泡(延迟 1.5s 弹出) |
teaserText |
string |
❌ | i18n 默认 | P1 | 提示气泡文字,留空使用语言包默认值 |
streaming |
boolean |
❌ | true |
P0 | 是否启用 SSE 流式输出 |
locale |
string |
❌ | "zh-CN" |
P2 | 界面语言:zh-CN / en |
debug |
boolean |
❌ | true |
P0 | 是否输出调试日志 |
三、公开 API
| 方法 | 阶段 | 说明 |
|---|---|---|
ChatbotSDK.init(config) |
P0 | 初始化 SDK |
ChatbotSDK.destroy() |
P0 | 销毁实例(清理 DOM + 样式 + 事件) |
ChatbotSDK.open() |
P0 | 打开聊天窗口 |
ChatbotSDK.close() |
P0 | 关闭聊天窗口 |
ChatbotSDK.toggle() |
P0 | 切换窗口显示/隐藏 |
ChatbotSDK.clearHistory() |
P0 | 清空当前会话 |
ChatbotSDK.setToken(token, expiresIn?) |
P0 | 运行时更新 Token(宿主自行换好后推送),无需 destroy + init 重建 DOM |
ChatbotSDK.refreshToken() |
P0 | 让 SDK 调 getToken 回调立即换新 Token,返回是否换到了不同的 Token |
四、P1 体验增强功能
4.1 SSE 流式打字机效果
默认开启(streaming: true),AI 回复逐字输出,支持:
- 流式追加到气泡,实时滚动到底部
- 流中断兜底:保留已接收内容 + 灰色提示
- 无流内容时自动降级为同步请求
4.2 Markdown 渲染
AI 回复支持 Markdown 格式渲染,包括:
- 代码块(深色背景
```语法) - 行内代码(
`code`) - 标题(
#####等) - 粗体(
**text**)、斜体(*text*)、删除线(~~text~~) - 有序/无序列表
- 链接(
[text](url),仅允许 http/https 协议) - 引用块(
>) - 水平线(
---)
XSS 安全:Markdown 渲染由 TDesign Chat 组件内置 cherry-markdown 处理,HTML 标签默认被转义,仅保留安全的 Markdown 语法输出。
实现说明:渲染由 TDesign Chat 组件(
<t-chat-item>)内置 cherry-markdown 承担,无需额外加载marked.min.js。SDK 不再依赖自研 Markdown 渲染器,列表、代码块、表格等样式由 TDesign 统一保证。
4.3 知识库联动
方式一:直接启用 RAG(无需分类选择)
ChatbotSDK.init({
integrateId: 'my-app',
requestDomain: 'https://ai.example.com',
enableRag: true, // 所有对话自动走 RAG 增强接口
});
方式二:配合知识库分类下拉框
开启 showCategorySwitch: true 后,输入区上方出现知识库分类下拉框:
ChatbotSDK.init({
integrateId: 'my-app',
requestDomain: 'https://ai.example.com',
enableRag: true, // 启用 RAG
showCategorySwitch: true, // 显示分类下拉框
categoryId: 5, // 默认选中分类
});
enableRag: true时,所有对话自动走 RAG 增强(/ai/chat/stream接口,enableRag=true)- 选择分类后,后续对话也走 RAG 增强
- 选择「全部分类」则走普通流式(
enableRag=false) - 分类数据从
/category/tree接口动态加载,支持树形缩进显示
4.4 RAG 引用来源展示
使用 RAG 对话后,AI 气泡底部自动展示引用来源卡片:
┌─────────────────────────────────┐
│ 📚 3 条参考来源 ▼ │
├─────────────────────────────────┤
│ 员工手册.pdf │
│ 员工可以享受年假... │
│ 员工手册.pdf · 分块 #3 · 85% │
│ │
│ HR制度.md │
│ 年假计算规则如下... │
│ HR制度.md · 分块 #1 · 72% │
└─────────────────────────────────┘
- 默认折叠,只显示标题行,点击展开/折叠
- 显示文档名称、摘要、来源文件、分块编号、相关度
- 来源数据从
/ai/chat/sources接口获取
五、P2 运营完善功能
5.1 多语言国际化
支持 zh-CN(中文)和 en(英文)两种语言:
ChatbotSDK.init({
integrateId: 'my-app',
requestDomain: 'https://ai.example.com',
locale: 'en', // 英文界面
});
国际化覆盖范围:
- 弹窗标题、输入框占位符、发送按钮
- 最小化/关闭按钮提示
- 清空对话按钮及确认弹窗
- Loading 状态文字
- 所有错误提示(网络/超时/服务器/跨域等)
- 知识库下拉框默认选项
- RAG 引用来源标题
- 历史会话面板文本
5.2 会话管理面板
弹窗头部新增时钟图标按钮,点击展开历史会话面板:
- 展示当前用户的历史会话列表(从
/conversation/list接口获取) - 支持导出会话(下载文本文件)
- 支持删除会话(二次确认)
- 空状态提示
5.3 控制台日志体系
SDK 全流程结构化日志,带 [ChatbotSDK] 前缀:
| 生命周期 | 日志内容 |
|---|---|
init() |
初始化完成 integrateId={} requestDomain={} |
sendMessage() |
发送消息 integrateId={} length={} |
收到回复 |
AI 回复 integrateId={} length={} duration={}ms |
流式完成 |
流式回复完成 integrateId={} length={} duration={}ms |
请求失败 |
请求失败 integrateId={} status={} message={} |
clearHistory() |
清空会话 integrateId={} |
destroy() |
销毁实例 integrateId={} |
分类切换 |
切换知识库分类 categoryId={} |
config.debug = false关闭 info/warn 日志,error 始终输出- 内置性能计时器,自动记录 AI 回复耗时
六、对接的后端接口
P0 — 基础对话
GET /ai/chat # 同步对话
GET /ai/chat/stream # SSE 流式对话
P1 — 知识库联动
GET /ai/chat/stream # RAG 增强流式对话(enableRag=true)
GET /ai/chat/sources # RAG 引用来源
GET /category/tree # 分类树(下拉框数据源)
GET /category/list # 分类列表
P2 — 会话管理
GET /conversation/list # 会话列表
GET /conversation/{id}/messages # 会话消息
DELETE /conversation/{id} # 删除会话
GET /conversation/{id}/export # 导出会话
七、参数映射关系
核心映射(SDK → 后端):
| SDK 入参 | 后端参数 | 说明 |
|---|---|---|
integrateId |
roleId |
客服角色 ID(决定 AI 人设和知识库检索范围) |
userId |
accountId |
客户账号 ID(账号可绑定角色,绑定后服务端覆盖 roleId) |
| (自动管理) | chatId |
对话 ID(自动从 /conversation/list 获取或生成,格式 sdk_时间戳_随机串) |
chatId 自动管理逻辑:
- SDK 初始化时,查询
/conversation/list?accountId={userId}&roleId={integrateId} - 有匹配会话 → 使用最新会话的
conversationId作为chatId - 无匹配会话 → 自动生成新
chatId(格式:sdk_时间戳_随机串) chatId缓存在 localStorage(key:csk_chatId_{integrateId}_{userId})clearHistory()会生成新的chatId,开始全新对话
八、CSS 命名空间隔离
所有 DOM 元素的 class/id 均使用 csk- 前缀,不会与宿主页面样式冲突:
| 元素 | ID/Class |
|---|---|
| 悬浮按钮 | #csk-launcher .csk-launcher |
| 聊天弹窗 | #csk-window .csk-window |
| 消息区 | #csk-messages .csk-messages |
| 输入框 | #csk-input .csk-input |
| 发送按钮 | #csk-send-btn .csk-send-btn |
| 知识库下拉 | #csk-category-select .csk-category-select |
| 历史面板 | .csk-history-panel |
| 样式标签 | style[data-csk-sdk] |
z-index 分层:悬浮按钮 9998,弹窗 9999。
九、localStorage 缓存
缓存 key 格式:csk_history_{integrateId}
数据结构:
{
"messages": [
{
"id": "uuid",
"role": "user|ai",
"content": "消息内容",
"timestamp": 1700000000000,
"sources": []
}
],
"updatedAt": 1700000000000
}
- 消息上限 200 条,超出自动裁剪最早 50 条
- 按
integrateId隔离,不同接入方互不影响 init()时自动恢复历史消息- RAG 引用来源同步缓存到
sources字段
注意:SDK Token 不在此列 —— Token 只存内存,不写 localStorage/sessionStorage,避免被 XSS
或同域其它页面读取。页面刷新后请由宿主重新 init() 传入(或依赖 getToken 回调自动重取)。
十、错误处理
所有错误不抛异常、不阻塞宿主页面。控制台日志已关闭,错误统一通过 init({ onError }) 回调上报:
ChatbotSDK.init({
// ...
onError: (e) => console.warn(e.code, e.message, e.detail),
});
| 错误场景 | 中文提示 | 英文提示 |
|---|---|---|
| 网络断开 | 网络连接失败,请检查网络 | Network connection failed |
| 请求超时 | 请求超时,请稍后重试 | Request timed out |
| HTTP 500 | 服务器异常,请稍后重试 | Server error |
| 跨域阻断 | 跨域请求被拦截... | CORS request blocked... |
| HTTP 401 | 鉴权失败,请联系管理员 | Authentication failed |
| HTTP 403 | 无访问权限 | Access denied |
| 流中断 | 回复被中断 | Response interrupted |
onError 的 code 字段(除下表外,其余为通用错误码):
| code | 含义 | 建议处置 |
|---|---|---|
auth_expired |
刷新后仍返回 401,Token 彻底失效 | 检查 API Key 是否被吊销/过期、是否绑定了启用中的客服角色;修好后调 setToken() |
auth_refresh_failed |
换 Token 的 getToken 回调失败(宿主后端异常或网络问题) |
检查宿主换 Token 接口;同一 code 30 秒内只上报一次,不会刷屏 |
config_invalid |
init() 传入的 token / expiresIn 等参数非法,该参数被忽略 |
按提示修正配置 |
十一、技术栈
- 语言:TypeScript → ES2017
- 打包:Rollup → IIFE 格式
- 压缩:Terser
- UI 组件:TDesign Web Components 1.2.10(
t-chat-*聊天组件 +t-select/t-tag/t-button/t-alert等通用组件) - Markdown:由 TDesign Chat 组件内置 cherry-markdown 渲染
- 国际化:内置 i18n 字典
- 浏览器兼容:Chrome 70+, Firefox 70+, Safari 13+, Edge 79+
- 产物大小:
.min.js~4.0MB(gzip ~1.1MB),体积主要由内置的 TDesign Web Components 组件库贡献(按需 tree-shaking 后)
十二、开发与构建
# 进入 SDK 工程目录
cd client/
# 安装依赖
npm install
# 构建(输出到 dist/)
npm run build
# 开发模式(watch)
npm run dev
源码结构:
client/
├── src/
│ ├── index.ts # 入口:window.ChatbotSDK 挂载
│ ├── types.ts # TypeScript 类型定义
│ ├── config.ts # 配置解析 + 参数校验
│ ├── logger.ts # 日志模块(P2 增强:计时+生命周期)
│ ├── storage.ts # localStorage 封装
│ ├── api.ts # HTTP 封装 + SSE 流式 + P1 RAG + P2 会话
│ ├── token.ts # Token 管理:临期主动刷新 + 401 兜底重试 + setToken/refreshToken
│ ├── dom.ts # DOM 构建 + 拖拽 + P1 来源/分类 + P2 面板
│ ├── styles.ts # CSS 注入 + 主题(--csk-* 与 --td-* 变量透传)
│ ├── chat.ts # 对话核心 + P1 RAG + P2 i18n(Markdown 由 t-chat-item 内置 cherry-markdown 处理)
│ ├── i18n.ts # P2 多语言国际化
│ └── utils.ts # UUID、防抖、XSS 转义
├── dist/ # 构建产物
├── package.json
├── tsconfig.json
├── rollup.config.js
└── README.md
十三、功能路线图
| 阶段 | 状态 | 内容 |
|---|---|---|
| P0 核心链路 | ✅ 已完成 | 项目骨架、配置校验、样式注入、悬浮按钮+弹窗、HTTP 通信、对话核心、本地缓存、打包构建 |
| P1 体验增强 | ✅ 已完成 | SSE 流式打字机、Markdown 渲染、知识库下拉切换、RAG 引用来源展示、UI 全配置化、弹窗拖拽 |
| P2 运营完善 | ✅ 已完成 | 多语言国际化(zh-CN/en)、控制台日志体系、会话管理面板(列表/导出/删除) |
| Token 自动刷新 | ✅ 已完成 | 临期主动刷新 + 401 兜底重试、setToken / refreshToken 手动入口(v1.3.0);tokenStrategy: 'always' 每请求前取 Token + tokenUrl 地址取 Token(v1.4.0) |
| 可选扩展 | 🔜 待定 | 知识库管理嵌入、更多语言、主题皮肤 |
十四、验证测试
单元测试(vitest) —— 覆盖配置解析与 Token 管理(含刷新竞态、自激循环等难以手工复现的场景):
cd client/
npm test # 等价于 npx vitest run
浏览器端验证 —— 访问 http://localhost:9090/sdk/test.html 运行核心用例:
测试覆盖(10 个核心用例):
| 编号 | 阶段 | 功能 |
|---|---|---|
| T1-T6 | P0 | 全局加载、参数校验、DOM 创建、open/close、destroy 清理、实际对话 |
| T7-T9 | P1 | SSE 流式接口、RAG 引用来源、知识库分类切换 |
| T10 | P2 | 多语言国际化 |
Token 自动刷新的手工验证步骤(setToken 生效、401 自动重试、临期主动刷新、destroy 清理)见
根目录 SDK-INTEGRATION.md 的验证章节。