跳转到内容

Web Component API

<rtc-agent> 是 RTC Agent 对外暴露的唯一组件。它基于 Lit 构建,内部包含 57 个子组件和 23 个 Controller,但对外只呈现一个简洁的 Web Component 接口——属性配置、事件监听、CSS 变量定制。

使用 createRtcAgent() 工厂函数创建组件实例。这是推荐的接入方式,提供完整的类型安全和生命周期管理:

import { createRtcAgent, z } from '@rtc-agent/component';
import type { RtcAgentConfig, RtcAgentWithLifecycle } from '@rtc-agent/component';
const config: RtcAgentConfig = {
// 基础配置
appLabel: '我的 AI 助手',
theme: 'system',
lang: 'zh-CN',
databaseName: 'my-app-rtc',
// 服务端配置
server: {
url: 'https://rtc-agent.cherish.chat',
redirectUri: '/auth/callback.html',
},
// SharedWorker URL(多标签页支持所需)
workerURL: '/rtc-agent/shared-worker.js',
// 认证配置(AuthProvider,见下方"认证配置"章节)
auth: {
getToken: async () => localStorage.getItem('token') || '',
getUserId: () => 'user-123',
deviceId: 'uuid-from-your-backend', // 必填,与服务端 JWT 中的 Device ID 一致
},
// 窗口配置
window: {
defaultMode: 'normal',
draggable: true,
resizable: true,
bubblePosition: {
corner: 'bottom-right',
offset: { x: -24, y: 24 },
},
},
// Activity Bar 配置(隐藏不需要的标签页)
activityBar: {
disabledActivities: ['settings'],
},
// 函数注册
agentName: 'MyApp',
agentDescription: '我的应用 AI 助手',
persona: '你是一个有帮助的助手...',
groups: [
{
name: 'editor',
description: '编辑器操作',
functions: [
{
name: 'getCode',
description: '获取当前代码',
handler: () => window.editorAPI.getCode(),
returns: { zodSchema: z.string().describe('当前编辑器中的代码') },
},
],
},
],
// 事件处理器
on: {
ready: () => {
console.log('RTC Agent 已就绪');
},
},
};
const agent: RtcAgentWithLifecycle = createRtcAgent(config);
// ⚠️ 必须手动添加到 DOM
document.body.appendChild(agent);
// 不再需要时销毁(清理所有事件监听器和 WebSocket 连接)
agent.destroy();

💡 重要:createRtcAgent() 返回组件实例,但不会自动添加到 DOM。你必须手动调用 document.body.appendChild(agent)。

对于简单场景或 CDN 快速预览,可以直接使用 HTML 属性:

<script type="module" src="https://cdn.jsdelivr.net/npm/@rtc-agent/component@0.3.0-rc.4/dist/index.js"></script>
<rtc-agent theme="dark" app-label="我的 AI 助手"></rtc-agent>

⚠️ HTML 属性方式无法配置 auth 等复杂对象选项。生产环境推荐使用工厂函数进行完整配置。

组件包内置完整的 .d.ts 类型声明,无需额外安装 @types 包。安装 @rtc-agent/component 后,TypeScript 自动识别类型。

import type {
// 工厂函数配置与返回值
RtcAgentConfig, // createRtcAgent() 的配置对象类型
RtcAgentWithLifecycle, // createRtcAgent() 的返回值类型(含 destroy())
// 认证配置
AuthConfig,
AuthProvider,
// 事件回调与事件类型映射
EventCallbacks,
RtcAgentEventDetailMap, // DOM 事件名 → detail 类型的映射表
// 窗口与 Activity Bar
WindowConfig,
ActivityBarConfig,
ResolvedActivityBarConfig,
Activity, // 'chat' | 'files' | 'functions' | 'settings'
// Agent 声明式配置
AgentConfig,
AgentFunctionGroup,
// 数据模型
Session,
Message,
ConnectionState, // 连接状态(用于 connectionStateChange 回调)
// 函数注册与校验
FunctionDef,
FunctionGroupDef,
ParameterDef,
ReturnDef,
OpenAPISchema,
VisualHooks,
ValidationError,
ValidationResult,
// 函数调试器
DebugHistoryItem,
FunctionDebugState,
LogEntry,
LogLevel,
ExecutionStatus,
HistoryPaginationState,
// 场景文档
ScenarioDef,
ScenarioManifest,
RegistryConfig,
} from '@rtc-agent/component';
import {
// 工厂函数
createRtcAgent,
// 就绪等待
whenReady,
// 函数注册(命令式)
defineRegistry,
FunctionRegistry,
FunctionGroup,
// Zod 校验工具
z, // Zod 库的 re-export
validateParams,
formatValidationError,
buildValidator,
withValidation,
zodToParams,
zodToOpenAPISchema,
openApiToZod,
withMeta, // 为 Zod schema 附加 example 等元数据
// 场景文档加载
loadScenariosFromURL,
loadScenariosContent,
parseFrontmatter,
// Markdown 文档生成
generateFunctionMd,
generateFunctionsIndex,
generateScenariosIndex,
generateAgentMd,
// EventBus(高级 — 监听内部工具调用事件)
EventBus,
eventBus,
createEventBus,
// Activity Bar 配置工具
DEFAULT_ACTIVITY_BAR_CONFIG,
resolveActivityBarConfig,
// 国际化
switchLocale,
initLocale,
getLocale,
persistLocale,
sourceLocale,
targetLocales,
localeContext,
isValidLocale,
// 主题
switchTheme,
initTheme,
getEffectiveTheme,
getStoredTheme,
persistTheme,
// 异常类
CancelledError, // 用户取消操作异常(hooks.onStart 可抛以此取消执行)
} from '@rtc-agent/component';

通过 querySelector 获取组件时,使用 RtcAgent 类型获得完整的属性提示:

import type { RtcAgent } from '@rtc-agent/component';
const agent = document.querySelector<RtcAgent>('rtc-agent');
// ✅ 完整的属性和事件类型提示
agent!.theme = 'dark';
agent!.windowConfig = { embedded: true };
agent!.addEventListener('rtc-agent-ready', () => {
// ...
});

组件扩展了 HTMLElementEventMap,addEventListener 的 detail 自动推导:

const agent = document.querySelector('rtc-agent')!;
agent.addEventListener('rtc-theme-change', (e) => {
// e.detail 自动推导为 { theme: 'light' | 'dark' | 'system' }
console.log(e.detail.theme);
});

destroy() 方法执行完整的、有序的组件拆解:

const agent = createRtcAgent(config);
document.body.appendChild(agent);
// 当组件不再需要时:
agent.destroy();

destroy() 按以下顺序执行:

  1. 从 DOM 中移除元素 — 在宿主元素上调用 this.remove(),触发 disconnectedCallback(清理 DOM 事件监听器、UIUpdateBus 订阅、RTC processor、auth 回调、auto-save 定时器等)
  2. 清除待处理的认证配置引用 — 释放工厂捕获的 _pendingAuthConfig / _pendingDynamicAuth / _pendingAuthProvider
  3. 调用所有 DOM 事件取消订阅函数 — 通过配置 on 字段注册的每个 addEventListener 都会被移除
  4. 调用所有 EventBus 取消订阅函数 — 内部订阅(工具调用事件桥接等)被拆除

💡 何时调用:在从页面移除组件之前,始终调用 destroy()(例如 SPA 路由切换、模态框关闭、或框架的 unmount 钩子)。跳过此步骤会导致事件监听器和 WebSocket 连接泄漏。

注意:destroy() 不会 清除 localStorage 中的认证 token(为了兼容 Vite HMR,避免开发时每次保存都导致登出)。如需主动登出,请调用 auth.logout()。

完整集成指南:集成教程

RtcAgentConfig 中的 auth 字段控制组件如何获取认证凭据。推荐使用 AuthProvider 模式,将认证关注点完全委托给你的 Provider。

💡 推荐方案:对于企业集成场景,强烈推荐使用 Token Exchange 模式。它集成简单、用户体验好,且无需实现完整的 OAuth2 流程。详见 认证与授权 - Token Exchange 完整指南。

完全控制认证生命周期。当宿主应用已经管理认证状态,且你希望组件与之集成时使用。

AuthProvider 支持两种认证模式:

模式type 值推荐度适用场景
Token Exchange(推荐)'token-exchange'⭐⭐⭐⭐⭐企业集成场景,已有认证系统
OAuth2 重定向'oauth2-redirect'⭐⭐⭐独立应用,需要完整登录流程

当宿主应用持有外部 JWT(如 admin-server 签发的管理员 JWT),组件可以通过 Token Exchange 将其换取 RTC 主服务器的 JWT。适用于 Admin UI 集成等场景。

const config: RtcAgentConfig = {
// ...其他配置
auth: {
type: 'token-exchange',
getExchangeToken: async () => adminAuthStore.getAdminToken(),
isLoggedIn: () => adminAuthStore.isAuthenticated(),
logout: async () => { await adminAuthStore.clearSession(); },
getUserId: () => adminAuthStore.getUserId(),
deviceId: 'uuid-from-your-backend',
},
};

💡 Token Exchange 模式下不需要 getToken() 和 refreshToken()——组件内部自动完成 JWT 交换和刷新。主服务器需要在 config.yaml 中配置 token_exchange.external_issuers 以信任对应的 JWT 签发方。

📚 完整集成指南:请参阅 认证与授权 - Token Exchange 完整指南,包含架构、配置、JWT 签发、JWKS 端点等详细说明。

const config: RtcAgentConfig = {
// ...其他配置
auth: {
// type: 'oauth2-redirect', // 默认值,可省略
getToken: async () => myAuthStore.getAccessToken(),
refreshToken: async () => myAuthStore.refreshAccessToken(),
isLoggedIn: () => myAuthStore.isAuthenticated,
logout: async () => { await myAuthStore.signOut(); },
getUserId: () => myAuthStore.getUserId(), // 返回当前用户唯一标识
deviceId: 'uuid-from-your-backend', // 必填,与服务端 JWT 中的 Device ID 一致
},
};

💡 PKCE 自动处理:OAuth2 重定向模式下,组件内置的登录对话框会自动生成 PKCE code_verifier / code_challenge(RFC 7636),在授权请求中发送 code_challenge,在令牌交换时发送 code_verifier。开发者无需手动处理 PKCE 参数——如果使用内置登录流程,PKCE 默认启用。如果宿主应用自行管理 OAuth2 流程(不通过内置登录对话框),则需要自行实现 PKCE。

方法必填适用模式说明
type可选两者'oauth2-redirect'(默认)或 'token-exchange'
getToken()OAuth2 模式必填oauth2-redirect异步——返回当前认证令牌
refreshToken()OAuth2 模式必填oauth2-redirect异步——令牌过期时刷新
getExchangeToken()Token Exchange 模式必填token-exchange异步——返回外部 JWT,组件将其换取 RTC JWT
isLoggedIn()必填两者同步——返回 boolean,表示用户是否已认证
logout()可选两者异步——组件需要终止会话时调用
getUserId()可选两者同步——返回当前用户的唯一标识。强烈建议提供,否则所有用户将共享同一个 IndexedDB
deviceId必填两者设备唯一标识,必须与服务端嵌入 JWT 中的 Device ID 一致

详细指南,请参阅认证与授权。

属性类型默认值说明
🎨 theme"light" | "dark" | "system""system"主题模式。system 跟随操作系统设置。可通过 JS 动态设置:agent.theme = 'dark'
🌐 lang"zh-CN" | "en-US""zh-CN"界面语言。优先级:HTML attribute > localStorage > 浏览器语言 > 默认值 (zh-CN)。可通过 JS 动态设置:agent.lang = 'en-US'
💾 database-namestring"rtc-agent"IndexedDB 名称前缀。最终 DB 名为 {prefix}-{userId}。例如 <rtc-agent database-name="my-app-rtc"> 生成 my-app-rtc-{userId}
📛 app-labelstring"RTC Agent"标题栏文字 + 最小化气泡的 tooltip
🖼️ bubble-iconstring默认图标最小化气泡内显示的 SVG / HTML 内容
📄 scenarios-urlstring—场景文档的 URL,指向 manifest.json
🔗 server-urlstring""服务端地址。为空时使用当前页面域名
🔁 redirect-uristringwindow.location.origin + '/auth/callback.html'OAuth 回调地址。支持绝对路径和相对路径
👷 worker-urlstring""SharedWorker 文件 URL。用于多标签页共享 WebSocket 连接。工厂函数配置中使用 workerURL

JS 属性(仅通过 JavaScript 设置,非 HTML attribute):

属性类型默认值说明
⚙️ agentConfigobjectnull声明式函数注册(推荐方式)
📦 registryFunctionRegistrynull命令式函数注册(通过 defineRegistry 创建)
🪟 windowConfigWindowConfignull窗口行为配置(模式、尺寸、交互限制)
🎛️ activityBarConfigActivityBarConfignullActivity Bar 按钮显隐配置
🖼️ logo{ light?: string; dark?: string }null自定义 Logo,替换登录页、空状态、设置页的默认 Logo。支持亮/暗两套 SVG
<!-- 最简接入 -->
<rtc-agent></rtc-agent>
<!-- 自定义主题和标题 -->
<rtc-agent theme="dark" app-label="我的 AI 助手"></rtc-agent>
<!-- 自定义数据库名前缀和语言 -->
<rtc-agent database-name="my-app-rtc" lang="en-US"></rtc-agent>
<!-- 带场景文档和函数注册 -->
<rtc-agent
app-label="订单助手"
scenarios-url="https://example.com/scenarios"
.agentConfig=${{
name: 'OrderApp',
persona: '你是一个订单管理助手',
groups: [{ /* ... */ }]
}}
></rtc-agent>
// 窗口配置(JS 属性,在 rtc-agent-ready 后设置)
const agent = document.querySelector('rtc-agent');
agent.addEventListener('rtc-agent-ready', () => {
// 嵌入式面板:禁用拖拽/缩放/按钮,默认最大化
agent.windowConfig = { embedded: true };
// 只保留 chat 功能,隐藏 files/settings 按钮
agent.activityBarConfig = {
disabledActivities: ['files', 'settings'],
};
});

所有事件支持两种监听方式:DOM 事件(通过 addEventListener 监听)和配置回调(通过 RtcAgentConfig 的 on 字段)。

💡 事件命名规范:DOM 事件名统一使用 rtc- 前缀加 kebab-case 命名(如 rtc-theme-change)。配置回调名使用 camelCase(如 themeChange)。工厂函数内部自动将回调映射到对应的 DOM 事件。

DOM 事件配置回调触发时机用途
🟢 rtc-agent-readyon.ready组件完成初始化此时可安全访问组件实例、设置属性
🟡 rtc-before-destroyon.beforeDestroydisconnectedCallback 执行时,清理之前最后读取状态或取消拆解的机会
🎨 rtc-theme-changeon.themeChange主题变化(属性、JS 属性或系统偏好)将外部 UI 与组件主题同步。event.detail 为 { theme: 'light' | 'dark' | 'system' }
DOM 事件配置回调触发时机用途
📨 rtc-before-message-sendon.beforeMessageSend用户提交消息,发送之前检查或修改消息。返回 false 取消发送

beforeMessageSend 回调接收 { message: { content: string, metadata?: Record<string, unknown> } },必须返回 boolean | Promise<boolean>。返回 false 取消发送。你也可以就地修改 message.content 来在消息发出前重写内容。

DOM 事件配置回调触发时机event.detail
🔌 rtc-connection-retryon.connectionRetry用户点击重连按钮void
🔐 rtc-auth-login-requestedon.authLoginRequested用户请求登录void
❌ rtc-auth-refresh-failedon.authError认证刷新失败void
🚪 rtc-auth-logouton.authLogout用户登出void
📡 rtc-connection-state-changeon.connectionStateChange连接状态变化{ state: ConnectionState }
✅ rtc-auth-loginon.authLogin登录成功(含初始加载、刷新、手动登录){ userId: string }
DOM 事件配置回调触发时机event.detail
📝 rtc-session-createdon.sessionCreated新会话创建{ session: Session }
🔀 rtc-session-switchedon.sessionSwitched切换会话{ id: string }
✏️ rtc-session-renamedon.sessionRenamed会话重命名{ id: string, title: string }
🗑️ rtc-session-deletedon.sessionDeleted会话删除{ id: string }
📥 rtc-message-receivedon.messageReceived收到 AI 消息{ message: Message }
📤 rtc-message-senton.messageSent用户消息已发送{ message: Message }

工具调用事件通过内部 EventBus 桥接,仅支持 on.* 配置回调方式,不作为 DOM 事件派发。这避免了大量工具调用事件对 DOM 事件系统的压力。

配置回调触发时机回调参数EventBus 来源
on.toolCallStart工具调用开始{ path, params }function:start
on.toolCallSuccess工具调用成功完成{ path, result }function:success
on.toolCallError工具调用失败{ path, error }function:error
on.toolCallProgress工具调用报告中间进度{ path, progress }function:progress

path 为函数完整路径(如 "myGroup.myFunction"),params 为校验后的入参。

const agent = createRtcAgent({
// ...其他配置
on: {
// 生命周期
ready: () => {
console.log('RTC Agent 已就绪');
},
beforeDestroy: () => {
console.log('组件即将销毁');
},
themeChange: (detail) => {
console.log('主题已切换为:', detail.theme);
document.body.dataset.theme = detail.theme;
},
// 消息拦截 — 发送前验证
beforeMessageSend: async ({ message }) => {
// 阻止空消息
if (!message.content.trim()) {
return false;
}
// 追加签名
message.content += '\n\n— 来自我的应用';
return true;
},
// 工具调用追踪(仅通过 on 回调,非 DOM 事件)
toolCallStart: ({ path, params }) => {
console.log(`工具调用开始: ${path}`, params);
},
toolCallSuccess: ({ path, result }) => {
console.log(`工具调用成功: ${path}`, result);
},
toolCallError: ({ path, error }) => {
console.error(`工具调用失败: ${path}`, error);
},
toolCallProgress: ({ path, progress }) => {
updateProgressBar(path, progress);
},
},
});

同样的生命周期事件也可以通过 DOM addEventListener 监听(适用于无法设置配置回调的场景):

const agent = document.querySelector('rtc-agent');
agent.addEventListener('rtc-agent-ready', () => {
// 组件已就绪,可以安全操作
agent.theme = 'dark';
agent.lang = 'en-US';
agent.appLabel = '自定义标题';
});
agent.addEventListener('rtc-theme-change', (e) => {
console.log('当前主题:', e.detail.theme);
});
agent.addEventListener('rtc-before-message-send', (e) => {
const { message } = e.detail;
if (containsSensitiveWords(message.content)) {
e.preventDefault(); // 取消发送
}
});

💡 最佳实践:始终在 rtc-agent-ready 事件触发后再操作组件,避免组件尚未初始化导致的错误。

<rtc-agent> 内置三种窗口模式,支持浮动、全屏和最小化气泡:

交互说明
🖱️ 拖拽标题栏(title-bar)作为拖拽手柄
↔️ 缩放支持 8 个方向的 resize 操作
⌨️ 键盘方向键移动窗口,Shift 加速
📏 视口约束窗口始终保持在可视区域内,不会被拖出屏幕

通过 windowConfig 属性可以控制窗口的默认行为和交互限制:

agent.windowConfig = {
// 默认窗口模式
defaultMode: 'maximized', // 'normal' | 'maximized' | 'minimized'
// 嵌入式模式(快捷方式)
// 等同于: defaultMode: 'maximized' + draggable: false + resizable: false
// + showMinimize: false + showMaximize: false
// CSS 使用 position: relative 填充父容器(而非 position: fixed 填充视口)
embedded: true,
// 细粒度控制
draggable: false, // 是否可拖拽
resizable: false, // 是否可缩放
showMinimize: false, // 是否显示最小化按钮
showMaximize: false, // 是否显示最大化按钮
showClose: false, // 是否显示关闭按钮
// 尺寸和位置
initialSize: { width: 420, height: 640 },
initialPosition: { x: 100, y: 100 },
minWidth: 350,
minHeight: 520,
maxWidth: Infinity,
maxHeight: Infinity,
// 最小化气泡位置
bubblePosition: {
corner: 'bottom-right', // 坐标系原点所在角
offset: { x: -20, y: 20 }, // 数学笛卡尔坐标偏移
},
};

bubblePosition 使用数学笛卡尔坐标系控制最小化气泡的位置:

字段类型说明
corner'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'坐标系原点在宿主应用的哪个角
offset.xnumber水平偏移(右正左负)
offset.ynumber垂直偏移(上正下负,数学坐标系)

示例:

// 右下角,向内偏移 20px(默认)
bubblePosition: { corner: 'bottom-right', offset: { x: -20, y: 20 } }
// 左上角,向右下偏移 20px
bubblePosition: { corner: 'top-left', offset: { x: 20, y: -20 } }
// 左下角,向右上偏移 30px
bubblePosition: { corner: 'bottom-left', offset: { x: 30, y: 30 } }

💡 展开方向:首次从最小化恢复时,窗口会根据 corner 配置决定展开方向。例如 corner: 'bottom-right' 时,窗口的右下角对齐气泡位置,向左上方向展开。后续的最小化/恢复循环使用记忆的位置。

典型场景:

场景配置
嵌入式面板{ embedded: true }
固定位置窗口{ draggable: false, resizable: false }
无最小化按钮{ showMinimize: false, defaultMode: 'maximized' }
浮动聊天窗null(使用默认值)

通过 activityBarConfig 属性可以控制 Activity Bar 中各按钮的显隐:

agent.activityBarConfig = {
// 要隐藏的活动按钮(chat 始终显示,不可隐藏)
disabledActivities: ['files', 'settings'],
// 默认激活的活动
defaultActivity: 'chat',
};
活动说明可隐藏
💬 chat聊天界面❌ 始终显示
📁 files文件管理✅ 通过 disabledActivities
🛠️ functions函数调试器面板✅ 通过 disabledActivities
⚙️ settings设置面板✅ 通过 disabledActivities

函数调试器是 Activity Bar 中的一个可选面板,提供完整的函数调试界面:

  • 函数树:按分组展示所有已注册的函数,支持展开/折叠,状态持久化到 localStorage
  • 参数编辑器:JSON 语法高亮 + 校验,支持 Zod schema 自动推导参数模板
  • 执行控制台:彩色日志输出(info/warn/error/debug),实时显示执行结果
  • 执行历史:底部抽屉式面板,支持分页、按函数名过滤,每条记录包含完整的参数、日志、结果详情
  • 参数文档:递归展开的参数表格,复用 schemaToTypeString 生成类型字符串

在 disabledActivities 中添加 'functions' 可以隐藏函数调试器按钮(适用于不需要调试功能的最终用户场景)。

组件内部使用 23 个 Controller 管理状态。其中 11 个核心状态 Controller 负责业务逻辑,12 个 UI 辅助 Controller 负责界面交互。Controller 之间不直接引用,由根组件 <rtc-agent> 作为中枢编排跨 Controller 通信:

💡 设计原则:Controller 之间解耦,所有跨 Controller 通信都经过根组件中转。子组件通过 @lit/context 获取状态,不直接持有 Controller 引用。

RTC Agent 内置完整的国际化支持,基于 @lit/localize 实现运行时语言切换。

语言代码语言说明
zh-CN简体中文默认语言(源语言)
en-USEnglish目标语言

通过 switchLocale() API 切换界面语言:

import { switchLocale } from '@rtc-agent/component';
// 切换到英文
await switchLocale('en-US');
// 切换到中文
await switchLocale('zh-CN');

语言选择自动保存到 localStorage(键:rtc-agent-locale),刷新页面后保持不变。

组件中使用 @localized() 装饰器和 msg() 函数:

import { localized, msg } from '@lit/localize';
import { consume } from '@lit/context';
import { localeContext } from '@rtc-agent/component';
@localized()
@customElement('my-component')
export class MyComponent extends LitElement {
@consume({ context: localeContext, subscribe: true })
private _localeCtx!: LocaleContextValue;
render() {
// 响应 locale 变化
void this._localeCtx.locale;
return html`
<div>${msg('欢迎使用 RTC Agent')}</div>
`;
}
}
  1. 标记文本:在组件中使用 msg() 包裹需要翻译的文本
  2. 提取翻译:运行 npm run localize:extract 生成 XLIFF 文件
  3. 翻译文本:编辑 xliff/en-US.xlf 文件
  4. 构建翻译:运行 npm run localize:build 生成语言包

详细指南见 国际化集成指南。

RTC Agent 支持亮色、暗色和跟随系统三种主题模式,通过 switchTheme() API 运行时切换。

import { switchTheme } from '@rtc-agent/component';
// 切换到暗色主题
switchTheme('dark');
// 切换到亮色主题
switchTheme('light');
// 跟随系统
switchTheme('system');

switchTheme() 会同时:

  1. 更新所有 <rtc-agent> 元素的 theme 属性
  2. 持久化到 localStorage(键:rtc-agent-theme)
  3. 设置 document.documentElement 的 data-theme 属性(解析后的实际主题)
import { getStoredTheme, getEffectiveTheme } from '@rtc-agent/component';
// 用户设置的偏好(可能是 'system')
const preference = getStoredTheme(); // 'light' | 'dark' | 'system'
// 解析后的实际主题(始终是 'light' 或 'dark')
const effective = getEffectiveTheme(); // 'light' | 'dark'

在应用启动时调用 initTheme() 从 localStorage 恢复主题偏好:

import { initTheme } from '@rtc-agent/component';
initTheme();

监听 rtc-theme-change 事件同步外部 UI:

agent.addEventListener('rtc-theme-change', (e) => {
console.log('主题已切换为:', e.detail.theme);
});

详细指南见 全局设置系统。

通过 CSS 变量可以定制组件的外观尺寸,无需修改源码:

rtc-agent {
/* 窗口默认尺寸 */
--rtc-window-default-width: 420px;
--rtc-window-default-height: 640px;
/* 最小化气泡大小 */
--rtc-bubble-size: 40px;
/* 用户自定义字号(影响所有文本) */
--rtc-font-size-user: 16px;
/* 品牌色 */
--rtc-color-primary-rgb: 39 65 254; /* Light: #2741FE */
--rtc-color-accent: #2741FE;
}
变量默认值说明
--rtc-window-default-width420px浮动窗口默认宽度
--rtc-window-default-height640px浮动窗口默认高度
--rtc-bubble-size40px最小化气泡的直径
--rtc-font-size-user14px用户自定义基础字号(12-24px)
--rtc-color-primary-rgbLight: 39 65 254
Dark: 26 122 176
主色 RGB 值(用于透明度计算)
--rtc-color-accentLight: #2741FE
Dark: #1A7AB0
强调色(链接、按钮等)

设置 --rtc-font-size-user 后,所有字号按等比缩放:

Token计算公式示例(base=14px)
--rtc-font-size-xsbase * 0.85712px
--rtc-font-size-smbase * 0.92913px
--rtc-font-size-basebase14px
--rtc-font-size-mdbase * 1.14316px
--rtc-font-size-lgbase * 1.28618px
--rtc-font-size-xlbase * 1.42920px
--rtc-font-size-2xlbase * 1.71424px

通过 logo 属性可以替换登录页、空状态、设置页的默认品牌 Logo,支持亮色和暗色两套主题自动切换。

const agent = createRtcAgent({
// ...其他配置
logo: {
light: '<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg"><circle cx="40" cy="40" r="36" fill="#2741FE"/><text x="40" y="52" text-anchor="middle" fill="white" font-size="32" font-weight="bold">窥</text></svg>',
dark: '<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg"><circle cx="40" cy="40" r="36" fill="#1A7AB0"/><text x="40" y="52" text-anchor="middle" fill="white" font-size="32" font-weight="bold">窥</text></svg>',
},
});

也可以只设置其中一个,缺失的版本会回退到默认 Logo:

agent.logo = {
light: '<svg>...</svg>', // 仅替换亮色主题下的 Logo
// dark 未设置,暗色主题使用默认 Logo
};

组件内部通过 <rtc-logo> 子组件 + Lit Context API 实现主题自动切换:

  • <rtc-logo> 消费 LogoContext,根据当前主题(light / dark)渲染对应版本的 Logo
  • 支持 theme 属性:'light' | 'dark' | 'system',其中 system 会监听 prefers-color-scheme 媒体查询
  • 如果未提供自定义 Logo,则渲染默认的 RTC Agent Logo

bubbleIcon 与 logo 是独立的两个概念:

属性作用位置用途
bubbleIcon最小化气泡窗口最小化后,右下角圆形气泡内的图标
logo登录页、空状态、设置页品牌标识,用于展示公司/产品品牌

两者都可以自定义 SVG,但应用场景不同。

如需修改默认 Logo,替换以下 SVG 文件:

文件说明使用场景
logo.svg蓝天版 Logo亮色主题
logo-dark.svg夜空版 Logo暗色主题

RTC Agent 在组件初始化时自动尝试建立连接。连接状态通过内部状态机管理,前端实时反映连接进展。

💡 设计原则:连接失败不会自动重试,而是将控制权交给用户。用户可以检查网络状况后手动触发重连,避免在网络故障时重复尝试消耗资源。

连接失败时,用户可以通过以下方式手动重连:

连接失败后,标题栏会显示红色重试按钮,点击即可触发重连。

const agent = document.querySelector('rtc-agent');
// 手动触发重连
await agent.reconnect();

通过以下只读属性可以获取连接状态信息:

属性类型说明
connectionFailedboolean连接是否失败
connectionErrorstring连接失败时的错误信息
// 检查连接状态
agent.addEventListener('rtc-agent-ready', () => {
console.log('Connection failed:', agent.connectionFailed);
console.log('Connection error:', agent.connectionError);
if (agent.connectionFailed) {
console.error('连接失败:', agent.connectionError);
}
});

注意:连接状态的详细信息(如 disconnected、connecting、connected、reconnecting)由组件内部管理,不对外暴露。外部只能通过 connectionFailed 和 connectionError 判断连接是否成功。

标题栏根据连接状态显示不同的视觉反馈:

状态状态点颜色文字重试按钮
connected🟢 绿色“已连接”❌ 隐藏
connecting🟡 黄色脉冲“连接中”❌ 隐藏
reconnecting🟡 黄色脉冲“重新连接中”❌ 隐藏
disconnected🔴 红色“未连接”❌ 隐藏
连接失败🔴 红色闪烁“连接失败: …”✅ 显示

组件采用三层样式架构,从底层 Token 到顶层变量层层递进:

层级内容说明
🏗️ Design Tokens间距、字体、圆角、阴影、过渡、z-index基础设计常量,保证视觉一致性
🌓 颜色主题light / dark(VS Code 风格)两套完整色彩方案,自动适配
🔧 CSS 变量组件级自定义(窗口尺寸、气泡大小)宿主应用可覆盖,实现个性化

<rtc-agent> 在所有构建(dev + prod)中暴露 window.rtcAgentDebug 对象,提供 40+ 个方法用于 E2E 测试、调试和自动化操作。

const debug = window.rtcAgentDebug;
域方法说明
StategetState(), clearData(), seedData()状态访问和数据管理
AuthloginAs(userId, tokens?), logout()认证模拟
VirtualFSlistFiles(path?), readFile(path), writeFile(path, content), deleteFile(path)虚拟文件系统操作
SessioncreateSession(), switchSession(id), deleteSession(id), renameSession(id, title), getSessions(), getCurrentSessionId()会话管理
MessagesendMessage(content), getMessages(), addDemoMessage(content, role?), clearMessages()消息操作
Tool CallgetToolCalls(), addPendingToolCall(call), approveToolCall(id), denyToolCall(id), approveAllToolCalls(toolName)工具调用模拟
UI Controlclick(selector), scrollIntoView(selector), typeText(selector, text)UI 交互模拟
ToastshowToast(message, type?), getToasts()Toast 通知
SettingsgetSettings(), updateSettings(section, patch)设置管理
ActivitysetActivity(activity), getActivity()Activity Bar 控制
NetworksimulateOffline(), restoreNetwork(), isOffline网络状态模拟
EventtriggerEvent(name, detail?)自定义事件模拟
MetricsgetMetrics()性能指标采集
Componentelement, waitForReady(timeout?), waitForConnected(timeout?)组件引用和等待
Logslogs, clearLogs()日志访问
const debug = window.rtcAgentDebug;
// 等待组件就绪
const agent = await debug.waitForReady(5000);
// 等待连接建立
await debug.waitForConnected(10000);
// 创建会话并发送消息
const sessionId = debug.createSession();
debug.switchSession(sessionId);
await debug.sendMessage('你好,帮我写一个 Hello World');
// 模拟离线状态
debug.simulateOffline();
console.log('Is offline:', debug.isOffline);
// 恢复网络
debug.restoreNetwork();
// 获取所有消息
const messages = debug.getMessages();
console.log('Message count:', messages.length);
// 获取当前状态
const state = debug.getState();
console.log('Current session:', debug.getCurrentSessionId());
// Playwright 示例
test('send message and get response', async ({ page }) => {
await page.goto('/');
// 等待组件就绪
await page.waitForFunction(() => window.rtcAgentDebug?.waitForReady);
await page.evaluate(() => window.rtcAgentDebug.waitForReady(5000));
// 发送消息
await page.evaluate(() => window.rtcAgentDebug.sendMessage('Hello'));
// 等待 AI 响应
await page.waitForFunction(() => {
const msgs = window.rtcAgentDebug.getMessages();
return msgs.some(m => m.role === 'assistant');
}, { timeout: 30000 });
// 验证响应
const messages = await page.evaluate(() => window.rtcAgentDebug.getMessages());
expect(messages.length).toBeGreaterThan(1);
});

💡 Debug API 在生产环境中同样可用,方便线上问题排查。但建议仅在开发和测试环境中使用 seedData() 和 clearData() 等数据修改方法。

区域行为
⌨️ 输入区textarea + 底部工具栏;Enter 提交,Shift+Enter 换行;工具栏包含附件、命令、场景选择、模式切换、发送/停止;支持文件上传(点击附件按钮或 Ctrl/Cmd+V 粘贴),文件先缓存到本地 IndexedDB 再后台上传 S3,所有文件上传完成后才能发送;当配置了 scenarios-url 时,工具栏显示 scenario 选择按钮,点击后弹出 rtc-scenario-panel 组件供用户选择场景
📎 文件上传点击附件按钮打开文件选择器(支持 image/* 和 text/*),或使用 Ctrl/Cmd+V 粘贴剪贴板文件;文件先本地缓存(FileStorage.cacheFileForUpload()),缩略图立即显示;后台上传 S3,实时显示进度;上传失败可点击重试;发送条件:所有文件状态为 loaded
🖼️ 文件预览点击缩略图打开全屏预览弹窗(rtc-file-preview-modal);支持图片预览(object-fit: contain)和文本预览(等宽字体,大文件截断到 1000 行);三种关闭方式:ESC 键、点击背景、关闭按钮;下载失败时降级到 S3 预签名 URL
📨 消息列表自动滚动到底部;用户滚动离开时显示”新消息”按钮;支持 Markdown 渲染和代码高亮
⚡ 工具确认弹窗显示工具名和参数;Yes / No 按钮;点击背景等同于拒绝
🔄 连接失败重试连接断开时显示重试按钮,也可通过 JS 调用 agent.reconnect() 方法手动重连
🗂️ 工具调用卡片每个工具调用显示为带时间线的卡片。点击时间线圆点可将当前内容复制到剪贴板(输入卡片复制参数,回复卡片复制输出)。对于 todoWrite 工具,卡片会渲染为带状态图标的任务列表(✓ 完成 / ● 进行中 / ○ 未开始),而非原始 JSON

每个文件在上传过程中有三种状态,通过 uploadStates Map 管理:

状态图标说明
loading🔄 进度环正在上传到 S3,显示 0-100% 进度
loaded✅ 完成上传成功,缩略图正常显示
error❌ 错误上传失败,显示重试按钮

发送按钮的可用性由 _canSubmit 计算属性控制:

// 简化的逻辑
get _canSubmit(): boolean {
if (!this._hasContent) return false;
// 检查是否有任何文件正在上传或失败
const hasUploadingOrFailedFiles = Array.from(this._uploadStates.values())
.some(state => state === 'loading' || state === 'error');
return !hasUploadingOrFailedFiles;
}
  • 有文本或文件附件时,_hasContent 为 true
  • 所有文件状态为 loaded 时,发送按钮可用
  • 任何文件处于 loading 或 error 状态时,发送按钮禁用

文件预览相关的子组件:

组件用途关键特性
rtc-file-preview-area附件预览区容器水平滚动列表,显示所有附件缩略图;支持编辑模式(显示删除按钮)和只读模式(消息展示)
rtc-file-thumbnail单个文件缩略图图片文件显示缩略图(object-fit: cover),文本文件显示文件图标;显示上传进度环、同步状态徽章、文件名
rtc-file-preview-modal全屏预览弹窗图片预览(object-fit: contain)、文本预览(等宽字体,大文件截断);三种关闭方式:ESC、点击背景、关闭按钮

FileStorage 上下文:

文件预览组件通过 FileStorageContext 获取 FileStorage 实例,用于:

  • 加载缩略图:fileStorage.getThumbnailUrl({md5, ext})
  • 下载文件:fileStorage.download(md5, ext, {signal})
  • 获取预签名 URL:fileStorage.getPresignedUrl({md5, ext}, expires)(降级方案)

FileStorage 是组件暴露的高级文件存储抽象,通过 FileStorageContext 获取实例。支持离线优先的上传/下载生命周期、批量操作和缓存管理。

方法签名说明
cacheFileForUpload(file: File) => Promise<FileInfo>离线优先:立即将文件写入本地 IndexedDB 缓存(计算 MD5),返回 FileInfo(syncStatus: 'pending')。缩略图可立即显示,实际 S3 上传由后续 upload() 完成
upload(opts: UploadOptions) => Promise<FileInfo>将文件上传到 S3。支持 onProgress 回调和 signal 中止。上传完成后 syncStatus 变为 'synced'
download(md5: string, ext: string, opts?: DownloadOptions) => Promise<Blob>下载文件。优先从本地缓存读取,缓存未命中时从 S3 下载。支持 forceRefresh 强制刷新缓存
getThumbnailUrl(opts: {md5, ext}) => Promise<string>获取缩略图的 Blob URL。内置共享缓存和引用计数,避免重复创建
releaseThumbnailUrl(url: string) => void释放缩略图 Blob URL 引用。引用计数归零时自动 revoke
getPresignedUrl(opts: {md5, ext}, expires?: number) => Promise<string>获取 S3 预签名 URL(降级方案)
方法签名说明
uploadBatch(files: File[], opts?: BatchOptions) => Promise<FileInfo[]>批量上传文件,支持并发控制和错误处理策略('continue' 跳过失败 / 'cancel' 全部取消)
downloadBatch(items: {md5, ext}[], opts?: BatchOptions) => Promise<Blob[]>批量下载文件
deleteBatch(items: {md5, ext}[]) => Promise<void>批量删除文件(本地缓存 + S3)
方法签名说明
getCacheStats() => Promise<CacheStats>获取缓存统计信息(总文件数、总大小等)
evictCache(opts?: {maxAge?: number}) => Promise<number>清理过期缓存文件,返回清理数量
resumeInterruptedUploads() => Promise<void>恢复中断的上传(网络恢复后调用)

💡 离线优先:cacheFileForUpload() 将文件立即写入 IndexedDB,即使无网络也能显示缩略图。后续 upload() 负责将文件同步到 S3,syncStatus 从 'pending' 变为 'syncing' 再到 'synced'(或 'failed')。UploadOptions.cacheTtlMs 控制本地缓存过期时间(默认 7 天)。