Skip to content

Agentspace 聊天组件

一、AgentSpace 聊天组件概述

简介:

AgentSpace 聊天组件 是一个基于 React 开发的智能助手聊天组件,提供完整的对话交互能力。通过 OpenSDK 体系对外开放,第三方可以快速集成到自己的业务系统中。

核心特性

  • 完整对话能力:支持文本对话、流式输出、打字机效果
  • 会话管理:新建对话、历史会话、会话切换
  • 知识库上传:支持对话携带知识库
  • 富文本渲染:Markdown、代码高亮、数学公式、Mermaid 图表
  • 主题切换:支持明暗主题(light/dark)
  • 国际化:支持中文简体、繁体、英文
  • 事件监听:完整的生命周期事件通知
  • 自定义扩展:支持自定义按钮、样式等

效果演示:

image-20251124105951453

1762341415248

二、开发必读

前置准备

  • 准备好 AgentSpace 的 agentId(应用AK)

    AgentSpace 创建一个应用

  • 完成 365API权限相关配置

    详见 鉴权流程说明

    在agentspace平台申请相关聊天对话组件需要的scope

    1.agentspace_chat需要申请的权限:

    功能名称scope业务域:
    Agentspace智能问答组件kso.component.agentspace_chat开放组件
    智能体会话管理kso.devhub_session.readwrite智能体管理
    智能体对话管理kso.devhub_chat.readwrite智能体管理
    智能体应用管理kso.devhub_app.readwrite智能体管理
    查询和管理知识库kso.wiki.readwrite知识库

    image-20251121182459195

    image-20251121182511131

    2、配置您的可信域名

    1762325488352


  • 发布版本

    返回agentspace 你创建的应用 发布一个版本

    image-20251118110104674

三、快速开始

如果想要更加便捷的配置 直接查看 完整示例

3.1 引入 OpenSDK

3.1.1引入SDK介绍

您可以直接下载opensdK到您自己的项目 或者直接引入在线CDN

OpenSDK地址:https://qn.cache.wpscdn.cn/open_static/libs/open-sdk/0.0.1/open-sdk.0.0.1.umd.js

agent_chat地址:https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.2/kso.component.agentspace_chat.1.0.2.umd.js

3.1.2在 HTML 页面中引入 OpenSDK以及agentspace聊天组件:

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>
    
    //引入opensdk以及 agentspace_chat 
    <script src="https://qn.cache.wpscdn.cn/open_static/libs/open-sdk/0.0.1/open-sdk.0.0.1.umd.js"></script>
   <script src="https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.2/kso.component.agentspace_chat.1.0.2.umd.js"></script>
  
    <script>
      // 业务方逻辑
  </script>
</body>
</html>

3.2 鉴权配置

在创建组件实例前,需要先完成 授权配置:

javascript
// 定义所需的权限范围
const defaultScopes = [
  'kso.devhub_app.readwrite',		 //智能体应用查询和创建
  'kso.wiki.readwrite',				 //知识库获取权限
  'kso.component.agentspace_chat',   // AgentSpace 聊天组件权限(必需)
  'kso.devhub_chat.readwrite',       // DevHub 聊天读写权限
  'kso.devhub_session.readwrite'     // DevHub 会话读写权限
];

// 从 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',                       // 可选:自定义状态参数
  });
}

3.3 完整的初始化流程

javascript
let hasRender = false;
const host = 'http://127.0.0.1:3000';            // 您的后端服务地址

async function startApp() {
  if (hasRender) return;
  
  OpenSDK.setDebug(true);                        // 开启调试模式(生产环境请关闭)
  
  // 检查缓存的配置是否过期(2小时有效期)
  const app_config = localStorage.getItem('app_config');
  if (app_config) {
    const { 
      app_id, 
      signature, 
      noncestr, 
      timestamp, 
      tag, 
      url, 
      app_access_token, 
      app_config_timestamp 
    } = JSON.parse(app_config);
    
    // 检查是否在有效期内(2小时 = 7200000 毫秒)
    if (Date.now() - app_config_timestamp < 7200000) {
      // 使用缓存的配置
      await OpenSDK.config({
        scopes,
        signature,
        appId: app_id,
        timestamp,
        nonceStr: noncestr,
        tag,
        url,
      });
      
      // 等待组件加载完成后渲染
      const timer = setInterval(() => {
        renderWidget(app_access_token);
        if (hasRender) {
          clearInterval(timer);
        }
      }, 1000);
      return;
    } else {
      // 配置已过期,清除缓存
      localStorage.removeItem('app_config');
    }
  }
  
  // 无缓存或缓存过期,开始 OAuth2 授权流程
  console.log('[授权流程] 开始授权');
  authorize(scopes);
  
  // 监听 OAuth2 授权回调消息
  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);
    
    if (!code) {
      authorize(scopes);
      return;
    }
    
    // 步骤一:使用 auth_code 从您的后端获取 app_access_token 和签名信息
    const { 
      app_id, 
      signature, 
      noncestr, 
      timestamp, 
      tag, 
      url, 
      app_access_token 
    } = 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失败');
        }
      });
    
    // 步骤二:缓存配置信息(2小时有效期)
    localStorage.setItem('app_config', JSON.stringify({
      app_id,
      signature,
      noncestr,
      timestamp,
      tag,
      url,
      app_access_token,
      app_config_timestamp: Date.now()
    }));
    
    // 步骤三:配置 OpenSDK  
    await OpenSDK.config({
      scopes,
      signature,      // 签名(来自您的后端)
      appId: app_id,  // 应用 ID
      timestamp,      // 时间戳(秒)
      nonceStr: noncestr, // 随机字符串
      tag,            // ticket 标签
      url,            // 当前页面 URL(不含 query 和 hash)
    });
    
    // 步骤四:等待组件注册完成后渲染
    function tryRenderWidget() {
      if (hasRender) {
        console.log('[渲染组件] 组件已渲染,停止尝试');
        return;
      }
      if (OpenSDK && OpenSDK.components && OpenSDK.components.size > 0) {
        console.log('[渲染组件] OpenSDK 组件已加载,开始渲染');
        renderWidget(app_access_token);
      } else {
        console.log('[渲染组件] 等待 OpenSDK 组件加载...');
        setTimeout(tryRenderWidget, 100); // 100ms 后重试
      }
    }
    
    tryRenderWidget();
  });
}

// 页面加载完成后初始化
startApp();

3.4 创建组件实例

javascript
async function renderWidget(app_access_token) {
  // 检查组件是否已注册
  if (OpenSDK.components.size === 0) return;
  
  // 创建 AgentSpace 聊天组件实例
  const agentspaceChat = OpenSDK.create('kso-agentspace-chat', {
    agentId: 'YOUR_AGENT_ID',           // 必填:智能助手 ID
    scopes: scopes,                     // 必填:权限范围数组
    theme: 'light',                     // 可选:主题(light/dark)
    locale: 'zh-CN',                    // 可选:语言(zh-CN/zh-TW/en)
    welcomeMessage: '您好!我是您的智能助手,有什么可以帮助您的吗?',
    placeholder: '请输入您的问题...',
  });
  
  // 将组件挂载到 DOM 节点
  agentspaceChat.mount(document.getElementById('agentspace-container'));
  
  hasRender = true;
  
  // 设置事件监听(见下一节)
  setupEventListeners(agentspaceChat);
}

3.5 监听事件

javascript
function setupEventListeners(agentspaceChat) {
  // 监听权限不足,需要二次授权的事件(重要)
  agentspaceChat.addEventListener(agentspaceChat.Events.OnAuthRequest, async (e) => {
    console.log('[第三方监听] OnAuthRequest: 需要额外权限', e);
    
    // 合并新的权限范围
    const missingScopes = e.scopes || [];
    const mergedScopes = [...new Set([...scopes, ...missingScopes])];
    scopes = mergedScopes;
    
    // 保存合并后的权限
    localStorage.setItem('merged_scopes', JSON.stringify(mergedScopes));
    
    // 清除缓存的配置,强制重新授权
    localStorage.removeItem('app_config');
      
    // 显示授权提示 UI(自定义实现)
    showAuthPrompt(missingScopes, mergedScopes);
  });
}

3.6 自定义授权提示ui

js
// 显示授权提示 UI(不使用 window.confirm,避免被浏览器拦截)
function showAuthPrompt(missingScopes, mergedScopes) {
  // 创建提示框
  const promptBox = document.createElement('div');
  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);
    authorize(mergedScopes); // 重新发起授权
  };
  
  // 取消按钮事件
  document.getElementById('auth-cancel-btn').onclick = () => {
    console.log('[授权提示] 用户取消');
    document.body.removeChild(promptBox);
  };
}

3.7 动态更新配置

javascript
agentspaceChat.update({
  theme: 'dark',
  welcomeMessage: '配置已更新!我是您的智能助手,有什么可以帮助您的吗?',
  locale: 'en',
  placeholder: 'Please enter your question...',
});

3.8 卸载组件

javascript
agentspaceChat.unmount();

3.9后端api 接口示例

js
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',
      },
    }
  );
  console.log('用户授权结果:', userResp);

  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 || {}),
    },
  };
}
js
const index: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
  // 步骤1:用户授权,code 换 access_token, access_token 禁止返回前端保存
  // 开发者自行实现OAuth2授权,获取并维护access_token
  // 开发文档:https://365.kdocs.cn/3rd/open/documents/app-integration-dev/wps365/server/certification-authorization/user-authorization/flow.html

  // 步骤2:业务方接口,获取用户信息,app,signature等
  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: '',
    };

    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,
    });
  });
};

四、事件监听

4.1 支持的事件类型

事件名称说明事件数据回调参数
Updated组件配置更新时触发info: 更新信息描述,如 "AgentSpace组件刷新完成"
OnChatStart对话开始时触发message: 用户发送的消息对象(包含 id、text、sender、timestamp 等字段)
OnChatEnd对话结束时触发{}无参数(空对象)
OnError发生错误时触发{ error: Error | any }error: 错误对象或错误信息
OnMessageSend用户发送消息时触发message: 发送的消息对象(包含消息内容、发送者等信息)
OnMessageReceive接收到 AI 回复时触发message: 接收的消息对象(可能包含 content_blocks、wps_tools 等富内容)
OnSelected用户进行选择操作时触发any选择的内容(根据具体场景而定)
OnAuthRequest需要额外权限时触发(用于二次授权)AuthRequestEventscopes: 缺失的权限数组
allRequiredScopes: 所有必需权限数组
mergedScopes: 合并后的权限数组
missingScopes: 明确缺失的权限数组

4.2 事件监听示例

javascript
// 1. Updated 事件
agentspaceChat.addEventListener(agentspaceChat.Events.Updated, (e) => {
  console.log('[Updated]', e.info); // "AgentSpace组件刷新完成"
});

// 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('[OnAuthRequest] 缺失权限:', e.missingScopes);
  console.log('[OnAuthRequest] 所有必需权限:', e.allRequiredScopes);
  console.log('[OnAuthRequest] 合并后权限:', e.mergedScopes);
  
  // 保存合并后的权限
  localStorage.setItem('merged_scopes', JSON.stringify(e.mergedScopes));
  localStorage.removeItem('app_config');
  
  // 显示授权提示并重新授权
  showAuthPrompt(e.missingScopes, e.mergedScopes);
});

4.3 移除事件监听

javascript
// 定义事件处理函数
const handleChatStart = (event) => {
  console.log('对话开始:', event);
};

// 添加监听
agentspaceChat.addEventListener(agentspaceChat.Events.OnChatStart, handleChatStart);

// 移除监听
agentspaceChat.removeEventListener(agentspaceChat.Events.OnChatStart, handleChatStart);

五、完整使用示例

1.index.html

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.1/open-sdk.0.0.1.umd.js"></script>
    <script src="https://qn.cache.wpscdn.cn/open_static/libs/widgets/agentspace_chat/1.0.2/kso.component.agentspace_chat.1.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>

2.config.js

js
/**
 * 配置文件
 */

// biz-server 第三方服务地址 ,您需要替换为您的服务地址
const host = 'http://127.0.0.1:3000'; 

// 默认权限范围,您需要配置您需要的权限
const defaultScopes = [
  // 'kso.file.readwrite',
  // 'kso.file_search.readwrite',
  // 'kso.file_permission.readwrite',
  'kso.component.agentspace_chat',
  'kso.devhub_chat.readwrite',
  'kso.devhub_session.readwrite',
  'kso.devhub_app.readwrite',
  'kso.wiki.readwrite',
];

// OAuth2 配置,您需要配置您的appid 以及state 		
const oauthConfig = {
  appId: 'AK20251111RJYVZA',
  redirect_uri: 'https://agentspace.wps.cn/sdk-callback',
  state: 'agent-space-chat',
};

// Agent 配置,您需要配置您的agentspace的agentid                
const agentConfig = {
  agentId: 'AK20251110EGNSHO',
};

3.auth.js

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;

4.app.js

js
/**
 * 主应用逻辑
 */

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_access_token,
      app_config_timestamp,
    } = JSON.parse(app_config);
    console.log('app_config_timestamp', Date.now() - app_config_timestamp);
    if (Date.now() - app_config_timestamp < 7200000) {
      OpenSDK.config({
        scopes,
        signature,
        appId: app_id,
        timestamp,
        nonceStr: noncestr,
        tag,
        url,
      });
      // 创建定时器不断尝试加载组件
      const timer = setInterval(() => {
        renderWidget(app_access_token);
        if (hasRender) {
          clearInterval(timer);
        }
      }, 1000);
      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,
      app_access_token,
    } = 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失败');
        }
      }
    );
    // 将获取的信息缓存存储,两个小时后过期
    localStorage.setItem(
      'app_config',
      JSON.stringify({
        app_id,
        signature,
        noncestr,
        timestamp,
        tag,
        url,
        app_access_token,
        app_config_timestamp: Date.now(),
      })
    );
    // 步骤三:OpenSDK设置签名信息
    await OpenSDK.config({
      scopes,
      signature, // 签名
      appId: app_id, // 应用 appId
      timestamp, // 时间戳(毫秒)
      nonceStr: noncestr, // 随机字符串
      tag,
      url,
    });
    // 步骤四:渲染组件

    // 使用递归 setTimeout 代替 setInterval
    function tryRenderWidget() {
      if (hasRender) {
        console.log('[渲染组件] 组件已渲染,停止尝试');
        return;
      }
      if (OpenSDK && OpenSDK.components && OpenSDK.components.size > 0) {
        console.log('[渲染组件] OpenSDK 组件已加载,开始渲染');
        renderWidget(app_access_token);
      } else {
        console.log('[渲染组件] 等待 OpenSDK 组件加载...');
        setTimeout(tryRenderWidget, 100); // 100ms 后重试
      }
    }

    tryRenderWidget();
  });
}

async function renderWidget(app_access_token) {
  if (OpenSDK.components.size === 0) return;
  const agentspaceChat = OpenSDK.create('kso-agentspace-chat', {
    agentId: agentConfig.agentId,
    appAccessToken: app_access_token,
  });
  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] 用户取消授权');
        }
      );
    }
  );
}