数字员工聊天组件
数字员工聊天组件概述
简介
数字员工聊天组件 是一个基于 React 开发的聊天组件,提供完整的对话交互能力。通过 OpenSDK 体系对外开放,第三方可以快速集成到自己的业务系统中。
核心特性
- ✅ 完整对话能力:支持文本对话、流式输出、打字机效果
- ✅ 会话管理:新建对话、历史会话、会话切换
- ✅ 知识库上传:支持对话携带知识库
- ✅ 富文本渲染:Markdown、代码高亮、数学公式、Mermaid 图表
- ✅ 国际化:支持中文简体、繁体、英文
- ✅ 事件监听:完整的生命周期事件通知
- ✅ 自定义扩展:支持自定义按钮、样式等
效果演示:
预览页地址:https://ark.wps.cn/demo/pages/web-sdk


开发必读
前置准备
-
准备好数字员工的
agentId(应用AK)去数字员工平台 创建一个应用

-
完成365API权限相关配置
详见:鉴权流程说明
在数字员工平台申请相关聊天对话组件需要的scope
1.agentspace_chat需要申请的权限:
功能名称 scope 业务域 数字员工智能问答组件 kso.component.agentspace_chat 开放组件 查询和管理知识库 kso.wiki.readwrite 知识库 
将开放能力打开
2、配置您的可信域名
-
去开放平台获取该应用的AK和SK
访问 https://365.kdocs.cn/3rd/open/developer/manager/{your-agentId}}/app-info

-
授权回调地址
需要
用户授权的开放组件,需要配置OAuth2回调地址- 对接用户授权对接流程:鉴权流程说明

-
发布版本
返回数字员工平台,在您创建的应用中发布一个版本

快速开始
如果想要更加便捷的配置 直接查看完整示例
引入OpenSDK
您可以直接下载opensdK到您自己的项目或者直接引入在线CDN
OpenSDK地址:https://qn.cache.wpscdn.cn/open_static/libs/open-sdk/0.0.2/open-sdk.0.0.2.umd.js
在HTML页面中引入OpenSDK
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AgentSpace Chat SDK Demo</title>
<style>
body {
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #f7f8f9 !important;
}
#agentspace-container {
height: 100vh;
width: 100vw;
}
</style>
</head>
<body>
<div id="root" class="height-100vh width-100vw">
<div id="agentspace-container" class="height-100vh width-100vw"></div>
</div>
//引入opensdk
<script src="https://qn.cache.wpscdn.cn/open_static/libs/open-sdk/0.0.2/open-sdk.0.0.2.umd.js"></script>
<script>
....业务方需要实现的代码... (见下)
startApp();
</script>
</body>
</html>
引入数字员工聊天组件
您可以直接下载数字员工组件UMD包到您自己的项目或者直接引入在线CDN
agent_chat地址:https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.8/kso.component.agentspace_chat.1.0.8.umd.js
React项目导入
- 确保项目中依赖react>=18.0.0,react-dom>=18.0.0
- 将依赖挂载到指定的全局对象window.AgentSpaceWebSDKDeps中
- 依赖挂载后通过script加载SDK的UMD包
// 示例导入代码
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as ReactDOMClient from 'react-dom/client';
import * as ReactJSXRuntime from 'react/jsx-runtime';
// 单例加载 UMD 脚本:确保在使用 OpenSDK.create/mount 前,组件库已完成加载
let agentspaceChatUmdLoadPromise: Promise<void> | null = null;
// 加载web SDK的UMD包
function loadAgentspaceChatUmd() {
if (agentspaceChatUmdLoadPromise) return agentspaceChatUmdLoadPromise;
agentspaceChatUmdLoadPromise = new Promise<void>((resolve) => {
// UMD 包在浏览器环境下会从window读取这三个依赖,需要在script加载前挂载
(window as any).AgentSpaceWebSDKDeps = {
React:React,
ReactDOM: {
...ReactDOM,
createRoot:ReactDOMClient.createRoot, // React18的createRoot在react-dom/client上
},
ReactJSXRuntime:ReactJSXRuntime,
};
// 通过script加载组件库
const script = document.createElement('script');
// 通过script加载,从CDN导入SDK
script.src = 'https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.8/kso.component.agentspace_chat.1.0.8.umd.js';
script.onload = () => resolve();
document.head.appendChild(script);
});
return agentspaceChatUmdLoadPromise;
}
// 加载组件
const tryRenderWidget = async () => {
if (hasRendered) return;
try {
// 确保 UMD 组件库已加载完成,再创建实例/渲染
await loadAgentspaceChatUmd();
// 鉴权初始化流程
......
// 创建 agentspaceChat 实例
const agentspaceChatInstance = window.OpenSDK.create('kso-agentspace-chat', {
agentId: "your-agentId",
});
await agentspaceChatInstance.mount(containerRef.current);
console.log('[渲染组件] 组件渲染成功');
} catch (error) {
console.error('[渲染组件] 渲染失败:', error);
}
};
鉴权配置
在创建组件实例前,需要先完成授权配置:
// 定义所需的权限范围
const defaultScopes = [
'kso.component.agentspace_chat', // 数字员工聊天组件权限(必需)
'kso.wiki.readwrite' // 知识库管理和查询全选(必需)
];
// 从 localStorage 获取已保存的权限,或使用默认值
const savedScopes = localStorage.getItem('merged_scopes');
let scopes = savedScopes ? JSON.parse(savedScopes) : defaultScopes;
// OAuth2 授权函数
async function authorize(scopes) {
OpenSDK.OAuth2.authorize({
appId: 'YOUR_APP_ID', // 必填:您的应用 ID
redirect_uri: 'https://agentspace.wps.cn/sdk-callback', // 必填:回调地址(需要在需要在开放平台配置授权回调地址)
scope: scopes.join(','),
mode: OpenSDK.OAuth2.Mode.POPUP, // 必填:授权模式(POPUP 或 REDIRECT)
state: 'agent-space-chat', // 可选:自定义状态参数
});
}
初始化流程
// React,ReactDOM,ReactJSXRuntime挂载到AgentSpaceWebSDKDeps中
window.AgentSpaceWebSDKDeps = {
React: React,
ReactDOM: ReactDOM,
ReactJSXRuntime: jsxRuntime,
};
/**
* 主应用逻辑
*/
let agentspaceChatUmdLoadPromise = null;
// 加载第三方依赖
function loadScript(src) {
return new Promise((resolve, reject) => {
// 已存在则复用
const existing = document.querySelector(`script[src="${src}"]`);
if (existing) {
if (existing.dataset.loaded === "1") return resolve();
existing.addEventListener("load", resolve, { once: true });
existing.addEventListener("error", reject, { once: true });
return;
}
const s = document.createElement("script");
s.src = src;
s.async = true;
s.addEventListener("load", () => {
s.dataset.loaded = "1";
resolve();
});
s.addEventListener("error", reject);
document.head.appendChild(s);
});
}
/**
* 加载 agentspace_chat UMD
*/
function loadAgentspaceChatUmd() {
if (agentspaceChatUmdLoadPromise) return agentspaceChatUmdLoadPromise;
agentspaceChatUmdLoadPromise = (async () => {
await loadScript(
"https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.8/kso.component.agentspace_chat.1.0.8.umd.js"
);
})();
return agentspaceChatUmdLoadPromise;
}
let hasRender = false;
// localStorage.scopes || 使用默认值
const savedScopes = localStorage.getItem('merged_scopes');
let scopes = savedScopes ? JSON.parse(savedScopes) : defaultScopes;
async function startApp() {
if (hasRender) return;
OpenSDK.setDebug(true);
// 检查app_config是否过期
const app_config = localStorage.getItem('app_config');
if (app_config) {
const {
app_id,
signature,
noncestr,
timestamp,
tag,
url,
app_config_timestamp,
} = JSON.parse(app_config);
console.log('app_config_timestamp', Date.now() - app_config_timestamp);
if (Date.now() - app_config_timestamp < 7200000) {
await OpenSDK.config({
scopes,
signature, // 签名
appId: app_id, // 应用 appId
timestamp, // 时间戳(毫秒)
nonceStr: noncestr, // 随机字符串
tag,
url,
});
// 创建组件
renderWidget();
return;
} else {
localStorage.removeItem('app_config');
// 缓存过期,重新开始授权流程
// console.log('[授权流程] app_config 缓存已过期,重新授权')
window.location.reload();
}
}
// 无缓存或缓存过期,开始授权
console.log('[授权流程] 开始授权');
authorize(scopes);
OpenSDK.addEventListener(OpenSDK.Events.OAuth2Message, async (event) => {
// 安全起见,判断来源
if (event.origin !== 'https://agentspace.wps.cn') return;
const code = event.data.code;
console.log('OAuth2Message code', code);
// 步骤一:拿到临时授权码code,提交给服务端申请access_token
// 应用授权,无需走OAuth授权流程,直接申请access_token
// 用户授权,走OAuth授权流程,获取auth_code,然后申请access_token
if (!code) {
authorize(scopes);
return;
}
// 步骤二:获取biz-server 申请的js_ticket相关签名信息
const { app_id, signature, noncestr, timestamp, tag, url } = await fetch(
`${host}/api/get_app_config?code=${code}`
).then(async (res) => {
const resp = await res.json();
if (resp?.code === 0) {
return resp.data || {};
} else {
throw new Error('获取signature失败');
}
});
// 先删除缓存的 app_config
localStorage.removeItem('app_config');
// 将获取的信息缓存存储,两个小时后过期
localStorage.setItem(
'app_config',
JSON.stringify({
app_id,
signature,
noncestr,
timestamp,
tag,
url,
app_config_timestamp: Date.now(),
})
);
// 步骤三:OpenSDK设置签名信息
await OpenSDK.config({
scopes,
signature, // 签名
appId: app_id, // 应用 appId
timestamp, // 时间戳(毫秒)
nonceStr: noncestr, // 随机字符串
tag,
url,
});
// 步骤四:渲染组件
renderWidget();
});
}
创建组件实例
async function renderWidget() {
// 加载 agentspace_chat UMD
await loadAgentspaceChatUmd();
if (OpenSDK.components.size === 0) return;
const agentspaceChat = OpenSDK.create('kso-agentspace-chat', {
agentId: agentConfig.agentId,
});
agentspaceChat.mount(document.getElementById('agentspace-container'));
hasRender = true;
// 监听scopes范围不够,二次授权
agentspaceChat.addEventListener(
...... 见3.5 监听事件
);
}
监听事件
详见事件监听,此处展示必要的监听事件
agentspaceChat.addEventListener(
agentspaceChat.Events.OnAuthRequest,
async (e) => {
const detailScopeUser = e.detailScopeUser || [];
// detail接口返回的所有required scopes与本地配置的scopes进行对比,判断缺失的scopes
const missingScopes = detailScopeUser.filter(
(scope) => !scopes.includes(scope)
);
// 如果没有缺失的权限,无需重新授权
if (missingScopes.length === 0) {
console.log('[业务方 addEventListener] 权限验证通过,无需重新授权');
return;
}
// 合并 scopes
const mergedScopes = [...new Set([...scopes, ...missingScopes])];
scopes = mergedScopes;
// 保存合并后的scopes
localStorage.setItem('merged_scopes', JSON.stringify(mergedScopes));
// 清除缓存的 app_config,强制重新获取新的授权信息
localStorage.removeItem('app_config');
// 显示自定义授权提示 UI(不使用 window.confirm)实现见授权提示弹窗
showAuthPrompt(
missingScopes,
mergedScopes,
// 确认授权回调
(mergedScopes) => authorize(mergedScopes),
// 取消授权回调
() => {
console.log('[业务方 addEventListener] 用户取消授权');
}
);
}
);
自定义授权提示ui
showAuthPrompt( )
见 授权提示弹窗
动态更新配置
agentspaceChat.update({
theme: 'dark',
welcomeMessage: '配置已更新!我是您的智能助手,有什么可以帮助您的吗?',
locale: 'en',
placeholder: 'Please enter your question...',
});
发送消息
// 发送消息的参数类型
interface ISendMessageParams {
content: string; // 消息内容
knowledges?: string[]; // 知识库id数组
}
// 示例
agentspaceChat?.sendMessage({content:'你好'})
卸载组件
agentspaceChat.unmount();
后端api接口示例
步骤1:用户授权,code 换 access_token, access_token 禁止返回前端保存,
开发者自行实现OAuth2授权,获取并维护access_token(示例见app.js和auth.js)
步骤2:业务方接口,获取用户信息,app,signature等
const index: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
//步骤一 获取access_token
fastify.get('/get_app_config', async function (request, reply) {
// 此处简化获取access_token,js_ticket逻辑
const { code = '' } = (request.query as { code: string }) || {};
let user_access_token = '';
const resp = await getAccessToken({
code,
agent_ak: config.agent_ak,
agent_sk: config.agent_sk,
});
if (resp?.code === 0) {
user_access_token = resp?.data?.user_token || '';
if (!user_access_token) {
return reply
.status(400)
.send({ code: 400, message: '获取access_token失败' });
}
} else {
return reply
.status(400)
.send({ code: 400, message: '获取access_token失败' });
}
console.log('授权信息', {
user_access_token,
full_response: resp,
});
let jsapi_ticket = {
ticket: '',
tag: '',
};
//步骤二 获取jsapi_ticket
if (!jsapi_ticket.ticket) {
const ticketResp: any = await getJSAPITicket(user_access_token);
console.log('js_ticket', ticketResp);
if (ticketResp?.code === 0) {
jsapi_ticket = ticketResp.data;
} else {
return reply
.status(400)
.send({ code: 400, message: '获取jsapi_ticket失败' });
}
}
// 请求页面的url,不包含query,search
const url = `http://${request.headers.host}`;
console.log('url', url);
const noncestr = Math.random().toString(36).substring(2, 15);
const timestamp = (Date.now() / 1e3) | 0;
console.log('timestamp', timestamp);
const signature = genSignature(
jsapi_ticket.ticket,
noncestr,
timestamp,
url
);
return MakeSuccess({
app_id: config.agent_ak,
signature,
noncestr,
timestamp,
tag: jsapi_ticket?.tag,
url,
});
});
};
getAccessToken和getJSAPITicket函数
export async function getAccessToken(data: {
code: string;
agent_ak: string;
agent_sk: string;
}): Promise<IResponse<any>> {
if (!data.code) {
return {
code: 1,
message: 'code is required',
data: {},
};
}
const userResp = await openApi.post<any, any>('/oauth2/token',{
grant_type: 'authorization_code',
client_id: data.agent_ak,
client_secret: data.agent_sk,
code: data.code,
redirect_uri: 'https://agentspace.wps.cn/sdk-callback',
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
if (userResp?.access_token) {
return {
code: 0,
data: {
user_token: userResp.access_token,
user_auth: userResp,
},
};
}
return {
code: 1,
message: 'user authorization failed',
data: {},
};
}
export async function getJSAPITicket(
access_token: string
): Promise<IResponse<any>> {
const resp = await openApi.get('/oauth2/jsapi_ticket', {
headers: {
Authorization: `bearer ${access_token}`,
},
});
return {
code: 0,
data: {
...(resp || {}),
},
};
}
事件监听
支持的事件类型
| 事件名称 | 说明 | 事件数据 | 回调参数 |
|---|---|---|---|
| Updated | 组件配置更新时触发 | { info: string } | info: 更新信息描述,如 "数字员工组件刷新完成" |
| OnChatStart | 对话开始时触发 | { message: Message } | message: 用户发送的消息对象(包含 id、text、sender、timestamp 等字段) |
| OnChatEnd | 对话结束时触发 | {} | 无参数(空对象) |
| OnError | 发生错误时触发 | { error: Error | any } | error: 错误对象或错误信息 |
| OnMessageSend | 用户发送消息时触发 | { message: Message } | message: 发送的消息对象(包含消息内容、发送者等信息) |
| OnMessageReceive | 接收到 AI 回复时触发 | { message: Message } | message: 接收的消息对象(可能包含 content_blocks、wps_tools 等富内容) |
| OnSelected | 用户进行选择操作时触发 | any | 选择的内容(根据具体场景而定) |
| OnAuthRequest | 需要额外权限时触发(用于二次授权) | AuthRequestEvent | detailScopeUser: 所有必需权限数组 |
事件监听示例
// 1. Updated 事件
agentspaceChat.addEventListener(agentspaceChat.Events.Updated, (e) => {
console.log('[Updated]', e.info); // "数字员工组件刷新完成"
});
// 2. OnChatStart 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnChatStart, (e) => {
console.log('[OnChatStart]', e.message.text);
});
// 3. OnChatEnd 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnChatEnd, (e) => {
console.log('[OnChatEnd] 对话结束');
});
// 4. OnError 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnError, (e) => {
console.error('[OnError]', e.error);
});
// 5. OnMessageSend 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnMessageSend, (e) => {
console.log('[OnMessageSend]', e.message.text, e.message.sender);
});
// 6. OnMessageReceive 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnMessageReceive, (e) => {
console.log('[OnMessageReceive]', e.message.text);
// 处理富内容
if (e.message.content_blocks) {
e.message.content_blocks.forEach(block => {
console.log('内容块:', block.title);
});
}
});
// 7. OnSelected 事件
agentspaceChat.addEventListener(agentspaceChat.Events.OnSelected, (e) => {
console.log('[OnSelected]', e);
});
// 8. OnAuthRequest 事件(重要)
agentspaceChat.addEventListener(agentspaceChat.Events.OnAuthRequest, (e) => {
console.log('[业务方 addEventListener] detailScopeUser:',e.detailScopeUser );
});
移除事件监听
// 定义事件处理函数
const handleChatStart = (event) => {
console.log('对话开始:', event);
};
// 添加监听
agentspaceChat.addEventListener(agentspaceChat.Events.OnChatStart, handleChatStart);
// 移除监听
agentspaceChat.removeEventListener(agentspaceChat.Events.OnChatStart, handleChatStart);
完整使用示例
您需要在config.js中进行配置,然后使用这个示例(React项目),依赖挂载可以参考React项目导入
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AgentSpace Chat SDK Demo</title>
<style>
body {
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #f7f8f9 !important;
}
#agentspace-container{
height: 100vh;
width: 100vw;
}
</style>
</head>
<body>
<div id="root" class="height-100vh width-100vw">
<div id="agentspace-container" class="height-100vh width-100vw"></div>
</div>
<script src="https://qn.cache.wpscdn.cn/open_static/libs/open-sdk/0.0.2/open-sdk.0.0.2.umd.js"></script>
<!-- 授权弹框辅助函数【此处模拟业务方的实现】 -->
<script src="./demo/chat-demo/auth-popup-helpers.js"></script>
<script>
// 初始化弹窗检测器
initPopupBlockDetector();
</script>
<!-- 配置文件 -->
<script src="./demo/chat-demo/config.js"></script>
<!-- 授权逻辑 -->
<script src="./demo/chat-demo/auth.js"></script>
<!-- 主应用逻辑 -->
<script src="./demo/chat-demo/app.js"></script>
<!-- 启动应用 -->
<script>
startApp()
</script>
</body>
</html>
config.js
/**
* 配置文件
*/
// biz-server 第三方服务地址,您需要替换为您的服务地址
const host = 'http://127.0.0.1:3000';
// 默认权限范围,配置您需要的权限
const defaultScopes = [
'kso.component.agentspace_chat',
'kso.wiki.readwrite'
];
// Agent 配置,您需要配置您的数字员工的agentid
const agentConfig = {
agentId: '*********',
};
// OAuth2 配置,您需要配置您的state
const oauthConfig = {
appId: agentConfig.agentId,
redirect_uri: 'https://agentspace.wps.cn/sdk-callback',
state: 'agent-space-chat',
};
auth.js
/**
* 授权相关函数
*/
// 发起授权
async function authorize(scopes) {
// 用户授权演示
// 以下信息,建议从业务服务端返回给前端
OpenSDK.OAuth2.authorize({
appId: oauthConfig.appId,
redirect_uri: oauthConfig.redirect_uri,
scope: scopes.join(','),
mode: OpenSDK.OAuth2.Mode.POPUP,
state: oauthConfig.state,
});
}
// 暴露给全局
window.authorize = authorize;
app.js
/**
* 主应用逻辑
*/
let agentspaceChatUmdLoadPromise = null;
// 加载第三方依赖
function loadScript(src) {
return new Promise((resolve, reject) => {
// 已存在则复用
const existing = document.querySelector(`script[src="${src}"]`);
if (existing) {
if (existing.dataset.loaded === "1") return resolve();
existing.addEventListener("load", resolve, { once: true });
existing.addEventListener("error", reject, { once: true });
return;
}
const s = document.createElement("script");
s.src = src;
s.async = true;
s.addEventListener("load", () => {
s.dataset.loaded = "1";
resolve();
});
s.addEventListener("error", reject);
document.head.appendChild(s);
});
}
/**
* - 加载 agentspace_chat UMD
*/
function loadAgentspaceChatUmd() {
if (agentspaceChatUmdLoadPromise) return agentspaceChatUmdLoadPromise;
agentspaceChatUmdLoadPromise = (async () => {
// UMD 包在浏览器环境下会从window读取这三个依赖,需要在script加载前挂载
(window as any).AgentSpaceWebSDKDeps = {
React:React, // React依赖
ReactDOM: {
...ReactDOM,
createRoot:ReactDOMClient.createRoot, // React18的createRoot在react-dom/client上
}, // ReactDOM依赖
ReactJSXRuntime:ReactJSXRuntime, // ReactJSXRuntime
};
await loadScript(
"https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.8/kso.component.agentspace_chat.1.0.8.umd.js"
);
})();
return agentspaceChatUmdLoadPromise;
}
let hasRender = false;
// localStorage.scopes || 使用默认值
const savedScopes = localStorage.getItem('merged_scopes');
let scopes = savedScopes ? JSON.parse(savedScopes) : defaultScopes;
async function startApp() {
if (hasRender) return;
OpenSDK.setDebug(true);
// 检查app_config是否过期
const app_config = localStorage.getItem('app_config');
if (app_config) {
const {
app_id,
signature,
noncestr,
timestamp,
tag,
url,
app_config_timestamp,
} = JSON.parse(app_config);
console.log('app_config_timestamp', Date.now() - app_config_timestamp);
if (Date.now() - app_config_timestamp < 7200000) {
await OpenSDK.config({
scopes,
signature, // 签名
appId: app_id, // 应用 appId
timestamp, // 时间戳(毫秒)
nonceStr: noncestr, // 随机字符串
tag,
url,
});
// 创建组件
renderWidget();
return;
} else {
localStorage.removeItem('app_config');
// 缓存过期,重新开始授权流程
// console.log('[授权流程] app_config 缓存已过期,重新授权')
window.location.reload();
}
}
// 无缓存或缓存过期,开始授权
console.log('[授权流程] 开始授权');
authorize(scopes);
OpenSDK.addEventListener(OpenSDK.Events.OAuth2Message, async (event) => {
// 安全起见,判断来源
if (event.origin !== 'https://agentspace.wps.cn') return;
const code = event.data.code;
console.log('OAuth2Message code', code);
// 步骤一:拿到临时授权码code,提交给服务端申请access_token
// 应用授权,无需走OAuth授权流程,直接申请access_token
// 用户授权,走OAuth授权流程,获取auth_code,然后申请access_token
if (!code) {
authorize(scopes);
return;
}
// 步骤二:获取biz-server 申请的js_ticket相关签名信息
const { app_id, signature, noncestr, timestamp, tag, url } = await fetch(
`${host}/api/get_app_config?code=${code}`
).then(async (res) => {
const resp = await res.json();
if (resp?.code === 0) {
return resp.data || {};
} else {
throw new Error('获取signature失败');
}
});
// 先删除缓存的 app_config
localStorage.removeItem('app_config');
// 将获取的信息缓存存储,两个小时后过期
localStorage.setItem(
'app_config',
JSON.stringify({
app_id,
signature,
noncestr,
timestamp,
tag,
url,
app_config_timestamp: Date.now(),
})
);
// 步骤三:OpenSDK设置签名信息
await OpenSDK.config({
scopes,
signature, // 签名
appId: app_id, // 应用 appId
timestamp, // 时间戳(毫秒)
nonceStr: noncestr, // 随机字符串
tag,
url,
});
// 步骤四:渲染组件
renderWidget();
});
}
async function renderWidget() {
// 加载 agentspace_chat UMD
await loadAgentspaceChatUmd();
if (OpenSDK.components.size === 0) return;
const agentspaceChat = OpenSDK.create('kso-agentspace-chat', {
agentId: agentConfig.agentId,
initialExpanded: false,
});
agentspaceChat.mount(document.getElementById('agentspace-container'));
hasRender = true;
// 第三方开发者监听事件
agentspaceChat.addEventListener(agentspaceChat.Events.Updated, (e) => {
console.log('[3rd] Updated:', e);
});
agentspaceChat.addEventListener(agentspaceChat.Events.OnSelected, (e) => {
console.log('[3rd] OnSelected:', e);
});
// 监听scopes范围不够,二次授权
agentspaceChat.addEventListener(
agentspaceChat.Events.OnAuthRequest,
async (e) => {
const detailScopeUser = e.detailScopeUser || []; // detail接口返回的所有required scopes
console.log(
'[业务方 addEventListener] detailScopeUser:',
detailScopeUser
);
// 使用本地配置的scopes进行对比,判断缺失的scopes
const missingScopes = detailScopeUser.filter(
(scope) => !scopes.includes(scope)
);
// 如果没有缺失的权限,无需重新授权
if (missingScopes.length === 0) {
console.log('[业务方 addEventListener] 权限验证通过,无需重新授权');
return;
}
// 合并 scopes
const mergedScopes = [...new Set([...scopes, ...missingScopes])];
scopes = mergedScopes;
// 保存合并后的scopes
localStorage.setItem('merged_scopes', JSON.stringify(mergedScopes));
// 清除缓存的 app_config,强制重新获取新的授权信息
localStorage.removeItem('app_config');
console.log('[业务方 addEventListener] 合并后的scopes:', mergedScopes);
// 显示自定义授权提示 UI(不使用 window.confirm)
showAuthPrompt(
missingScopes,
mergedScopes,
// 确认授权回调
(mergedScopes) => authorize(mergedScopes),
// 取消授权回调
() => {
console.log('[业务方 addEventListener] 用户取消授权');
}
);
}
);
}
auth-popup-helpers.js
/**
* 授权弹框辅助函数 【此处模拟业务方的实现】
* 包含:弹窗被阻止检测、授权提示UI等
*/
// 显示弹窗被阻止的提示
function showPopupBlockedWarning() {
// 移除可能存在的旧提示
const existingWarning = document.getElementById('popup-blocked-warning');
if (existingWarning) {
existingWarning.remove();
}
const warningBox = document.createElement('div');
warningBox.id = 'popup-blocked-warning';
warningBox.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #fff3cd;
border: 1px solid #ffc107;
color: #856404;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 999999;
max-width: 500px;
font-size: 14px;
animation: slideDown 0.3s ease-out;
`;
warningBox.innerHTML = `
<style>
@keyframes slideDown {
from {
transform: translateX(-50%) translateY(-20px);
opacity: 0;
}
to {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
}
</style>
<div style="display: flex; align-items: flex-start; gap: 12px;">
<div style="font-size: 20px;">⚠️</div>
<div style="flex: 1;">
<div style="font-weight: bold; margin-bottom: 8px;">浏览器已阻止弹窗</div>
<div style="margin-bottom: 8px;">授权窗口被浏览器拦截,请按以下步骤操作:</div>
<ol style="margin: 8px 0 0 0; padding-left: 20px;">
<li>点击地址栏右侧的 <strong>弹窗被阻止</strong> 图标</li>
<li>选择 <strong>始终允许来自此网站的弹窗</strong></li>
<li>刷新页面重新授权</li>
</ol>
</div>
<button id="close-popup-warning" style="
background: none;
border: none;
font-size: 18px;
cursor: pointer;
padding: 0;
line-height: 1;
color: #856404;
">✕</button>
</div>
`;
document.body.appendChild(warningBox);
// 关闭按钮
document.getElementById('close-popup-warning').onclick = () => {
warningBox.style.animation = 'slideDown 0.3s ease-out reverse';
setTimeout(() => warningBox.remove(), 300);
};
// 10秒后自动关闭
setTimeout(() => {
if (document.getElementById('popup-blocked-warning')) {
warningBox.style.animation = 'slideDown 0.3s ease-out reverse';
setTimeout(() => warningBox.remove(), 300);
}
}, 10000);
}
// 显示授权提示 UI(不使用 window.confirm,浏览器会拦截,需要第三方实现一个弹框)
function showAuthPrompt(missingScopes, mergedScopes, onConfirm, onCancel) {
// 移除可能存在的旧提示
const existingPrompt = document.getElementById('auth-prompt-box');
if (existingPrompt) {
existingPrompt.remove();
}
// 提示框
const promptBox = document.createElement('div');
promptBox.id = 'auth-prompt-box';
promptBox.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
z-index: 99999;
min-width: 300px;
`;
// 内容
promptBox.innerHTML = `
<div style="margin-bottom: 15px;">
<strong>需要新增权限</strong>
</div>
<div style="margin-bottom: 15px; font-size: 14px; color: #666;">
缺失权限:${missingScopes.join(', ')}
</div>
<div style="text-align: right;">
<button id="auth-cancel-btn" style="margin-right: 10px; padding: 6px 15px; cursor: pointer;">取消</button>
<button id="auth-confirm-btn" style="padding: 6px 15px; background: #1890ff; color: white; border: none; cursor: pointer;">授权</button>
</div>
`;
document.body.appendChild(promptBox);
// 按钮事件
document.getElementById('auth-confirm-btn').onclick = () => {
console.log('[授权提示] 用户点击授权');
document.body.removeChild(promptBox);
if (typeof onConfirm === 'function') {
onConfirm(mergedScopes);
}
};
document.getElementById('auth-cancel-btn').onclick = () => {
console.log('[授权提示] 用户取消');
document.body.removeChild(promptBox);
if (typeof onCancel === 'function') {
onCancel();
}
};
}
// 初始化弹窗检测器(拦截 window.open)
function initPopupBlockDetector() {
const originalWindowOpen = window.open;
window.open = function (...args) {
const popup = originalWindowOpen.apply(this, args);
// 检测弹窗是否被阻止
if (!popup || popup.closed || typeof popup.closed === 'undefined') {
console.error('[弹窗检测] 浏览器阻止了弹窗');
showPopupBlockedWarning();
return null;
}
return popup;
};
}
// 导出到全局
window.showPopupBlockedWarning = showPopupBlockedWarning;
window.showAuthPrompt = showAuthPrompt;
window.initPopupBlockDetector = initPopupBlockDetector;
后端接口
见 后端接口实现
使用指南
传参说明
{
agentId:string; // 组件使用的智能体ID---必选参数
welcomeMessage:string; // 组件欢迎页显示文案
className:string; // 组件最外层自定义样式
agentChatClassName:IAgentChatClassNameType; // 组件定制区域自定义样式
initialExpanded:boolean; // 是否初始化展开组件,默认为false
floatingButtonTips:string; // 最小化状态下的tooltip文案
locale:ILocaleType; // 组件语言配置
presetQuestions:string[]; // 欢迎页默认问题配置
agentExtraButtons:IExtraButton[]; // AI消息自定义按钮配置
userExtraButtons:IExtraButton[]; // 用户消息自定义按钮配置
onChatMessageChannel:string; // 对话消息监听的唯一频道号
customAiMessageComponenent:ReactNode; // 自定义AI消息组件
onAgentChatClose:function(); // 组件关闭事件回调
hideTitleActions:boolean; // 是否隐藏标题栏右侧操作按钮,默认为false
hideMinimizeTooltip:boolean; // 是否隐藏最小化状态下的tooltip显示,默认为false
shouldShowUploadButton:boolean; // 是否显示知识库上传按钮,默认为true
}
interface IAgentChatClassNameType {
chatTitle?:string; // 自定义头部样式,选填
chatWelcome?:string; // 自定义欢迎页样式,选填
chatQuestion?:string; // 自定义默认问题样式,选填
chatAction?:string; // 自定义快捷指令样式,选填
chatActionMore?:string; // 自定义快捷指令“更多”,单个按钮样式,选填
chatInput?:string; // 自定义输入框样式,选填
}
enum ILocaleType {
// 中文简体变体
zh: 'zh',
'zh-cn': 'zh',
zh_cn: 'zh',
chinese: 'zh',
chs: 'zh',
'zh-hans': 'zh',
// 中文繁体变体
tw: 'tw',
'zh-tw': 'tw',
zh_tw: 'tw',
'zh-hk': 'tw',
'zh-mo': 'tw',
'zh-hant': 'tw',
taiwanese: 'tw',
cht: 'tw',
// 英文变体
en: 'en',
'en-us': 'en',
en_us: 'en',
'en-gb': 'en',
'en-au': 'en',
'en-ca': 'en',
english: 'en',
eng: 'en',
}
interface IExtraButton {
element:ReactNode; // 自定义按钮组件,必填参数
className?:string; // 自定义按钮包裹层自定义样式,选填
title?:string; // 自定义按钮标题,选填
text?:string; // 自定义按钮文本,选填
action?: (data: ImessageData) => void; // 自定义按钮回调事件,选填
}
interface ImessageData {
message_id:string; // 消息唯一ID
}
代码示例
修改欢迎页配置
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
welcomeMessage:"欢迎来到对话页欢迎页界面,请开始你的操作." // 修改欢迎页文案
presetQuestions:["默认问题1","自定义问题2","test3"] // 修改欢迎页默认问题
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染

修改组件语言配置
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
locale:"zh-tw" // 切换为中文繁体
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染
隐藏顶部操作按钮、最小化tooltip
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
hideTitleActions:true, // 隐藏顶部操作按钮
hideMinimizeTooltip:true, // 隐藏最小化tooltip显示
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染
添加关闭回调事件
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
onAgentChatClose:() => {
// 自定义关闭事件
console.log("agentspace-chat关闭!");
}
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染
自定义组件样式
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
className:"p-4 m-4", // 组件整体自定义样式
agentChatClassName:{
chatTitle:'bg-white',
chatWelcome:'mt-4',
chatQuestion:'border rounded-md p-2 bg-[#f5f5f5] border-green-200',
chatAction:'bg-whitet border rounded-md p-2 border-red-200 hover:bg-gray-200',
chatActionMore:'border rounded-md p-2 border-red-500',
chatInput:'text-red-500'
}, // 组件区域自定义样式
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染

添加自定义按钮及回调事件
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
agentExtraButtons: [
{
element: <TestCustomButtonAgent />, // 自定义AI消息按钮组件
className:'flex gap-[6px] rounded-md border-none px-1 py-1 text-xs font-normal text-[#000] hover:bg-[#f4f4f5]',
action:(data: { message_id: string }) => {
console.log('智能体测试按钮被点击', data);
},
},
], // 添加AI消息自定义按钮及回调事件
userExtraButtons: [
{
element: <TestCustomButtonUser />, // 自定义用户消息按钮组件
className:'flex gap-[6px] rounded-md border-none px-1 py-1 text-xs font-normal text-[#000] hover:bg-[#f4f4f5]',
action:(data: { message_id: string }) => {
console.log('用户测试按钮被点击', data);
},
},
],
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染

自定义AI消息组件渲染及对话数据获取
传入的自定义AI消息组件必须包含可接收message和onChatMessage的props,用于消息数据的获取和对话流式数据的监听
/**
* 自定义 AI 消息组件 props定义
*/
interface ICustomAiMessageProps {
message?: IMessage; // 会话消息数据
onChatMessage?: (callback: (data: IChatMessage) => void) => () => void; // 对话数据监听
// 其他props
......
}
/**
* 自定义 AI 消息组件示例
*/
import { useState, useEffect } from 'react';
import type { IMessage, IChatMessage } from '@/types';
const TestCustomAiMessageComponent: React.FC<ICustomAiMessageProps> = ({
message,
onChatMessage,
}) => {
const [streamContent, setStreamContent] = useState<string>('');
// 监听流式消息
useEffect(() => {
if (!onChatMessage || message?.category === 'message') return;
const unsubscribe = onChatMessage((data: IChatMessage) => {
if (data.type === 'answer') {
// 完整消息
setStreamContent(data.content || '');
} else if (data.type === 'answer_chunk') {
// 流式片段
setStreamContent((prev) => prev + (data.content_chunk || ''));
}
});
return unsubscribe;
}, [onChatMessage, message?.category]);
// 决定显示内容
const displayText = message?.category === 'message'
? message?.text || ''
: streamContent || message?.text || '';
return (
<div
style={{
borderRadius: '6px',
padding: '8px 12px',
maxWidth: '100%',
display: 'inline-block',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: '#2563eb',
backgroundColor: '#eff6ff',
border: '1px solid #bfdbfe',
fontSize: '14px',
lineHeight: '1.6',
}}
>
{displayText}
</div>
);
};
export default TestCustomAiMessageComponent;
关键类型定义
// 事件数据类型定义
interface EventMessage {
type: string;
content: string;
content_type: string;
seq_id: number;
step: number;
parent_step: number;
timestamp: string;
run_id: string;
extra_info?: Record<string, any>;
};
// 工具类型定义
interface Tools {
id: string;
name: string;
version: string;
display_name: string;
}
// 消息数据类型定义
interface IMessage {
chat_id?: string; // 聊天会话ID
flow_id: string;
text: string; // 消息文本内容
sender: string;
sender_name: string;
session_id: string; // 会话ID
timestamp: string;
files: Array<string>;
id: string;
frontend_id: string;
edit: boolean;
background_color: string;
text_color: string;
category?: string;
properties?: any;
content_blocks?: ContentBlock[]; // 内容块列表(结构化内容展示)
event_messages?: EventMessage[]; // 消息相关事件列表
wps_tools: Tools[]; // WPS 工具调用列表
role?: string;
knowledges?: string;
isError?: boolean;
};
// 对话数据类型定义
interface IChatMessage {
role: 'user' | 'assistant'; // 对话ID
type: string;
run_id: string;
content: string;
content_chunk: string;
content_type: string;
seq_id: number;
step: number;
parent_step: number;
chat_id: string;
session_id: string;
timestamp: string;
extra_info: Record<string, any>;
message_id: string;
}
自定义AI消息组件消息数据获取和渲染说明
const TestCustomAiMessageComponent: React.FC<ICustomAiMessageProps> = ({
message,
onChatMessage,
}) => {
// 其他状态处理和函数
// 监听流式消息
useEffect(() => {
const unsubscribe = onChatMessage((data: IChatMessage) => {
// 处理流式数据,固定判断为 message?.category ==='message',表达式为false时该组件在对话中
if(message?.category ==='message') return;
if (data.type === 'answer') {
// 完整消息
setStreamContent(data.content || '');
} else if (data.type === 'answer_chunk') {
// 流式片段
setStreamContent((prev) => prev + (data.content_chunk || ''));
}
});
return unsubscribe;
}, [onChatMessage, message?.category]);
// 根据固定判断 message?.category === 'message'和自定义状态进行选择,可以选择渲染消息历史数据,或者使用自定义数据进行渲染
// 示例:处于对话状态中时渲染自定义状态的数据,否则渲染历史消息数据
const displayText = message?.category === 'message'
? message?.text || ''
: streamContent || message?.text || '';
return (
<div
style={{
borderRadius: '6px',
padding: '8px 12px',
maxWidth: '100%',
display: 'inline-block',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: '#2563eb',
backgroundColor: '#eff6ff',
border: '1px solid #bfdbfe',
fontSize: '14px',
lineHeight: '1.6',
}}
>
{displayText}
</div>
);
};
初始化定义及组件渲染
const AgentChat = OpenSDK.create('kso-agentspace-chat', {
agentId: "×××××××××", // 填入需要使用的智能体ID
onChatMessageChannel: 'test-chat-message-channel', // 设置监听频道号,唯一字符串即可
customAiMessageComponent: <TestCustomAiMessageComponent />, // 传入自定义的AI消息组件
});
AgentChat.mount(document.getElementById('agentspace-container')); // 组件渲染
