Web Component API
<rtc-agent> 是 RTC Agent 对外暴露的唯一组件。它基于 Lit 构建,内部包含 57 个子组件和 23 个 Controller,但对外只呈现一个简洁的 Web Component 接口——属性配置、事件监听、CSS 变量定制。
工厂函数(推荐)
Section titled “工厂函数(推荐)”使用 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);
// ⚠️ 必须手动添加到 DOMdocument.body.appendChild(agent);
// 不再需要时销毁(清理所有事件监听器和 WebSocket 连接)agent.destroy();💡 重要:
createRtcAgent()返回组件实例,但不会自动添加到 DOM。你必须手动调用document.body.appendChild(agent)。
HTML 属性(简单场景)
Section titled “HTML 属性(简单场景)”对于简单场景或 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等复杂对象选项。生产环境推荐使用工厂函数进行完整配置。
TypeScript 支持
Section titled “TypeScript 支持”组件包内置完整的 .d.ts 类型声明,无需额外安装 @types 包。安装 @rtc-agent/component 后,TypeScript 自动识别类型。
主要导出类型
Section titled “主要导出类型”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';主要导出值与运行时 API
Section titled “主要导出值与运行时 API”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';类型安全的组件访问
Section titled “类型安全的组件访问”通过 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', () => { // ...});事件类型安全
Section titled “事件类型安全”组件扩展了 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);});生命周期与清理
Section titled “生命周期与清理”destroy() 方法执行完整的、有序的组件拆解:
const agent = createRtcAgent(config);document.body.appendChild(agent);
// 当组件不再需要时:agent.destroy();destroy() 按以下顺序执行:
- 从 DOM 中移除元素 — 在宿主元素上调用
this.remove(),触发disconnectedCallback(清理 DOM 事件监听器、UIUpdateBus 订阅、RTC processor、auth 回调、auto-save 定时器等) - 清除待处理的认证配置引用 — 释放工厂捕获的
_pendingAuthConfig/_pendingDynamicAuth/_pendingAuthProvider - 调用所有 DOM 事件取消订阅函数 — 通过配置
on字段注册的每个addEventListener都会被移除 - 调用所有 EventBus 取消订阅函数 — 内部订阅(工具调用事件桥接等)被拆除
💡 何时调用:在从页面移除组件之前,始终调用
destroy()(例如 SPA 路由切换、模态框关闭、或框架的unmount钩子)。跳过此步骤会导致事件监听器和 WebSocket 连接泄漏。注意:
destroy()不会 清除 localStorage 中的认证 token(为了兼容 Vite HMR,避免开发时每次保存都导致登出)。如需主动登出,请调用auth.logout()。完整集成指南:集成教程
RtcAgentConfig 中的 auth 字段控制组件如何获取认证凭据。推荐使用 AuthProvider 模式,将认证关注点完全委托给你的 Provider。
💡 推荐方案:对于企业集成场景,强烈推荐使用 Token Exchange 模式。它集成简单、用户体验好,且无需实现完整的 OAuth2 流程。详见 认证与授权 - Token Exchange 完整指南。
AuthProvider(推荐)
Section titled “AuthProvider(推荐)”完全控制认证生命周期。当宿主应用已经管理认证状态,且你希望组件与之集成时使用。
AuthProvider 支持两种认证模式:
| 模式 | type 值 | 推荐度 | 适用场景 |
|---|---|---|---|
| Token Exchange(推荐) | 'token-exchange' | ⭐⭐⭐⭐⭐ | 企业集成场景,已有认证系统 |
| OAuth2 重定向 | 'oauth2-redirect' | ⭐⭐⭐ | 独立应用,需要完整登录流程 |
Token Exchange(推荐)
Section titled “Token Exchange(推荐)”当宿主应用持有外部 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 端点等详细说明。
OAuth2 重定向(备选方案)
Section titled “OAuth2 重定向(备选方案)”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。
AuthProvider 方法参考
Section titled “AuthProvider 方法参考”| 方法 | 必填 | 适用模式 | 说明 |
|---|---|---|---|
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-name | string | "rtc-agent" | IndexedDB 名称前缀。最终 DB 名为 {prefix}-{userId}。例如 <rtc-agent database-name="my-app-rtc"> 生成 my-app-rtc-{userId} |
📛 app-label | string | "RTC Agent" | 标题栏文字 + 最小化气泡的 tooltip |
🖼️ bubble-icon | string | 默认图标 | 最小化气泡内显示的 SVG / HTML 内容 |
📄 scenarios-url | string | — | 场景文档的 URL,指向 manifest.json |
🔗 server-url | string | "" | 服务端地址。为空时使用当前页面域名 |
🔁 redirect-uri | string | window.location.origin + '/auth/callback.html' | OAuth 回调地址。支持绝对路径和相对路径 |
👷 worker-url | string | "" | SharedWorker 文件 URL。用于多标签页共享 WebSocket 连接。工厂函数配置中使用 workerURL |
JS 属性(仅通过 JavaScript 设置,非 HTML attribute):
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
⚙️ agentConfig | object | null | 声明式函数注册(推荐方式) |
📦 registry | FunctionRegistry | null | 命令式函数注册(通过 defineRegistry 创建) |
🪟 windowConfig | WindowConfig | null | 窗口行为配置(模式、尺寸、交互限制) |
🎛️ activityBarConfig | ActivityBarConfig | null | Activity 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 事件。
生命周期事件
Section titled “生命周期事件”| DOM 事件 | 配置回调 | 触发时机 | 用途 |
|---|---|---|---|
🟢 rtc-agent-ready | on.ready | 组件完成初始化 | 此时可安全访问组件实例、设置属性 |
🟡 rtc-before-destroy | on.beforeDestroy | disconnectedCallback 执行时,清理之前 | 最后读取状态或取消拆解的机会 |
🎨 rtc-theme-change | on.themeChange | 主题变化(属性、JS 属性或系统偏好) | 将外部 UI 与组件主题同步。event.detail 为 { theme: 'light' | 'dark' | 'system' } |
消息拦截事件
Section titled “消息拦截事件”| DOM 事件 | 配置回调 | 触发时机 | 用途 |
|---|---|---|---|
📨 rtc-before-message-send | on.beforeMessageSend | 用户提交消息,发送之前 | 检查或修改消息。返回 false 取消发送 |
beforeMessageSend 回调接收 { message: { content: string, metadata?: Record<string, unknown> } },必须返回 boolean | Promise<boolean>。返回 false 取消发送。你也可以就地修改 message.content 来在消息发出前重写内容。
连接与认证事件
Section titled “连接与认证事件”| DOM 事件 | 配置回调 | 触发时机 | event.detail |
|---|---|---|---|
🔌 rtc-connection-retry | on.connectionRetry | 用户点击重连按钮 | void |
🔐 rtc-auth-login-requested | on.authLoginRequested | 用户请求登录 | void |
❌ rtc-auth-refresh-failed | on.authError | 认证刷新失败 | void |
🚪 rtc-auth-logout | on.authLogout | 用户登出 | void |
📡 rtc-connection-state-change | on.connectionStateChange | 连接状态变化 | { state: ConnectionState } |
✅ rtc-auth-login | on.authLogin | 登录成功(含初始加载、刷新、手动登录) | { userId: string } |
会话与消息事件
Section titled “会话与消息事件”| DOM 事件 | 配置回调 | 触发时机 | event.detail |
|---|---|---|---|
📝 rtc-session-created | on.sessionCreated | 新会话创建 | { session: Session } |
🔀 rtc-session-switched | on.sessionSwitched | 切换会话 | { id: string } |
✏️ rtc-session-renamed | on.sessionRenamed | 会话重命名 | { id: string, title: string } |
🗑️ rtc-session-deleted | on.sessionDeleted | 会话删除 | { id: string } |
📥 rtc-message-received | on.messageReceived | 收到 AI 消息 | { message: Message } |
📤 rtc-message-sent | on.messageSent | 用户消息已发送 | { message: Message } |
工具调用事件(仅配置回调)
Section titled “工具调用事件(仅配置回调)”工具调用事件通过内部 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 }, // 数学笛卡尔坐标偏移 },};Bubble 位置配置
Section titled “Bubble 位置配置”bubblePosition 使用数学笛卡尔坐标系控制最小化气泡的位置:
| 字段 | 类型 | 说明 |
|---|---|---|
corner | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 坐标系原点在宿主应用的哪个角 |
offset.x | number | 水平偏移(右正左负) |
offset.y | number | 垂直偏移(上正下负,数学坐标系) |
示例:
// 右下角,向内偏移 20px(默认)bubblePosition: { corner: 'bottom-right', offset: { x: -20, y: 20 } }
// 左上角,向右下偏移 20pxbubblePosition: { corner: 'top-left', offset: { x: 20, y: -20 } }
// 左下角,向右上偏移 30pxbubblePosition: { corner: 'bottom-left', offset: { x: 30, y: 30 } }💡 展开方向:首次从最小化恢复时,窗口会根据
corner配置决定展开方向。例如corner: 'bottom-right'时,窗口的右下角对齐气泡位置,向左上方向展开。后续的最小化/恢复循环使用记忆的位置。
典型场景:
| 场景 | 配置 |
|---|---|
| 嵌入式面板 | { embedded: true } |
| 固定位置窗口 | { draggable: false, resizable: false } |
| 无最小化按钮 | { showMinimize: false, defaultMode: 'maximized' } |
| 浮动聊天窗 | null(使用默认值) |
Activity Bar 配置
Section titled “Activity Bar 配置”通过 activityBarConfig 属性可以控制 Activity Bar 中各按钮的显隐:
agent.activityBarConfig = { // 要隐藏的活动按钮(chat 始终显示,不可隐藏) disabledActivities: ['files', 'settings'],
// 默认激活的活动 defaultActivity: 'chat',};| 活动 | 说明 | 可隐藏 |
|---|---|---|
💬 chat | 聊天界面 | ❌ 始终显示 |
📁 files | 文件管理 | ✅ 通过 disabledActivities |
🛠️ functions | 函数调试器面板 | ✅ 通过 disabledActivities |
⚙️ settings | 设置面板 | ✅ 通过 disabledActivities |
函数调试器(Function Debugger)
Section titled “函数调试器(Function Debugger)”函数调试器是 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 引用。
国际化(i18n)
Section titled “国际化(i18n)”RTC Agent 内置完整的国际化支持,基于 @lit/localize 实现运行时语言切换。
| 语言代码 | 语言 | 说明 |
|---|---|---|
zh-CN | 简体中文 | 默认语言(源语言) |
en-US | English | 目标语言 |
通过 switchLocale() API 切换界面语言:
import { switchLocale } from '@rtc-agent/component';
// 切换到英文await switchLocale('en-US');
// 切换到中文await switchLocale('zh-CN');语言选择自动保存到 localStorage(键:rtc-agent-locale),刷新页面后保持不变。
在组件中使用
Section titled “在组件中使用”组件中使用 @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> `; }}- 标记文本:在组件中使用
msg()包裹需要翻译的文本 - 提取翻译:运行
npm run localize:extract生成 XLIFF 文件 - 翻译文本:编辑
xliff/en-US.xlf文件 - 构建翻译:运行
npm run localize:build生成语言包
详细指南见 国际化集成指南。
主题(Theme)
Section titled “主题(Theme)”RTC Agent 支持亮色、暗色和跟随系统三种主题模式,通过 switchTheme() API 运行时切换。
import { switchTheme } from '@rtc-agent/component';
// 切换到暗色主题switchTheme('dark');
// 切换到亮色主题switchTheme('light');
// 跟随系统switchTheme('system');switchTheme() 会同时:
- 更新所有
<rtc-agent>元素的theme属性 - 持久化到
localStorage(键:rtc-agent-theme) - 设置
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 变量
Section titled “CSS 变量”通过 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-width | 420px | 浮动窗口默认宽度 |
--rtc-window-default-height | 640px | 浮动窗口默认高度 |
--rtc-bubble-size | 40px | 最小化气泡的直径 |
--rtc-font-size-user | 14px | 用户自定义基础字号(12-24px) |
--rtc-color-primary-rgb | Light: 39 65 254Dark: 26 122 176 | 主色 RGB 值(用于透明度计算) |
--rtc-color-accent | Light: #2741FEDark: #1A7AB0 | 强调色(链接、按钮等) |
设置 --rtc-font-size-user 后,所有字号按等比缩放:
| Token | 计算公式 | 示例(base=14px) |
|---|---|---|
--rtc-font-size-xs | base * 0.857 | 12px |
--rtc-font-size-sm | base * 0.929 | 13px |
--rtc-font-size-base | base | 14px |
--rtc-font-size-md | base * 1.143 | 16px |
--rtc-font-size-lg | base * 1.286 | 18px |
--rtc-font-size-xl | base * 1.429 | 20px |
--rtc-font-size-2xl | base * 1.714 | 24px |
Logo 定制
Section titled “Logo 定制”通过 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)
Section titled “气泡图标(bubbleIcon)”bubbleIcon 与 logo 是独立的两个概念:
| 属性 | 作用位置 | 用途 |
|---|---|---|
bubbleIcon | 最小化气泡 | 窗口最小化后,右下角圆形气泡内的图标 |
logo | 登录页、空状态、设置页 | 品牌标识,用于展示公司/产品品牌 |
两者都可以自定义 SVG,但应用场景不同。
默认 Logo 文件
Section titled “默认 Logo 文件”如需修改默认 Logo,替换以下 SVG 文件:
| 文件 | 说明 | 使用场景 |
|---|---|---|
logo.svg | 蓝天版 Logo | 亮色主题 |
logo-dark.svg | 夜空版 Logo | 暗色主题 |
RTC Agent 在组件初始化时自动尝试建立连接。连接状态通过内部状态机管理,前端实时反映连接进展。
💡 设计原则:连接失败不会自动重试,而是将控制权交给用户。用户可以检查网络状况后手动触发重连,避免在网络故障时重复尝试消耗资源。
连接失败时,用户可以通过以下方式手动重连:
方式一:点击标题栏重试按钮
Section titled “方式一:点击标题栏重试按钮”连接失败后,标题栏会显示红色重试按钮,点击即可触发重连。
方式二:调用 reconnect() 方法
Section titled “方式二:调用 reconnect() 方法”const agent = document.querySelector('rtc-agent');
// 手动触发重连await agent.reconnect();连接状态查询
Section titled “连接状态查询”通过以下只读属性可以获取连接状态信息:
| 属性 | 类型 | 说明 |
|---|---|---|
connectionFailed | boolean | 连接是否失败 |
connectionError | string | 连接失败时的错误信息 |
// 检查连接状态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判断连接是否成功。
标题栏状态显示
Section titled “标题栏状态显示”标题栏根据连接状态显示不同的视觉反馈:
| 状态 | 状态点颜色 | 文字 | 重试按钮 |
|---|---|---|---|
connected | 🟢 绿色 | “已连接” | ❌ 隐藏 |
connecting | 🟡 黄色脉冲 | “连接中” | ❌ 隐藏 |
reconnecting | 🟡 黄色脉冲 | “重新连接中” | ❌ 隐藏 |
disconnected | 🔴 红色 | “未连接” | ❌ 隐藏 |
| 连接失败 | 🔴 红色闪烁 | “连接失败: …” | ✅ 显示 |
组件采用三层样式架构,从底层 Token 到顶层变量层层递进:
| 层级 | 内容 | 说明 |
|---|---|---|
| 🏗️ Design Tokens | 间距、字体、圆角、阴影、过渡、z-index | 基础设计常量,保证视觉一致性 |
| 🌓 颜色主题 | light / dark(VS Code 风格) | 两套完整色彩方案,自动适配 |
| 🔧 CSS 变量 | 组件级自定义(窗口尺寸、气泡大小) | 宿主应用可覆盖,实现个性化 |
Debug API
Section titled “Debug API”<rtc-agent> 在所有构建(dev + prod)中暴露 window.rtcAgentDebug 对象,提供 40+ 个方法用于 E2E 测试、调试和自动化操作。
const debug = window.rtcAgentDebug;| 域 | 方法 | 说明 |
|---|---|---|
| State | getState(), clearData(), seedData() | 状态访问和数据管理 |
| Auth | loginAs(userId, tokens?), logout() | 认证模拟 |
| VirtualFS | listFiles(path?), readFile(path), writeFile(path, content), deleteFile(path) | 虚拟文件系统操作 |
| Session | createSession(), switchSession(id), deleteSession(id), renameSession(id, title), getSessions(), getCurrentSessionId() | 会话管理 |
| Message | sendMessage(content), getMessages(), addDemoMessage(content, role?), clearMessages() | 消息操作 |
| Tool Call | getToolCalls(), addPendingToolCall(call), approveToolCall(id), denyToolCall(id), approveAllToolCalls(toolName) | 工具调用模拟 |
| UI Control | click(selector), scrollIntoView(selector), typeText(selector, text) | UI 交互模拟 |
| Toast | showToast(message, type?), getToasts() | Toast 通知 |
| Settings | getSettings(), updateSettings(section, patch) | 设置管理 |
| Activity | setActivity(activity), getActivity() | Activity Bar 控制 |
| Network | simulateOffline(), restoreNetwork(), isOffline | 网络状态模拟 |
| Event | triggerEvent(name, detail?) | 自定义事件模拟 |
| Metrics | getMetrics() | 性能指标采集 |
| Component | element, waitForReady(timeout?), waitForConnected(timeout?) | 组件引用和等待 |
| Logs | logs, 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());E2E 测试最佳实践
Section titled “E2E 测试最佳实践”// 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 |
文件上传与预览
Section titled “文件上传与预览”文件上传流程
Section titled “文件上传流程”每个文件在上传过程中有三种状态,通过 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状态时,发送按钮禁用
文件预览组件
Section titled “文件预览组件”文件预览相关的子组件:
| 组件 | 用途 | 关键特性 |
|---|---|---|
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 公共 API
Section titled “FileStorage 公共 API”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 天)。
- 认证与授权 — 了解登录流程和令牌机制
- Function 注册指南 — 通过
agentConfig注册自定义函数 - Scenario 编写指南 — 编写场景文档引导 AI 行为