# snail-ai-chat **Repository Path**: tongjuncode/snail-ai-chat ## Basic Information - **Project Name**: snail-ai-chat - **Description**: snail ai 内置对话页面 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 23 - **Created**: 2026-09-17 - **Last Updated**: 2026-09-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Snail AI Chat `snail-ai-chat` 是 Snail AI 的独立嵌入式聊天前端,默认通过 `/api/snail/chat` 网关访问后端。 ## 本地运行 ```bash pnpm install pnpm dev ``` 默认访问地址: ```text http://localhost:9728/ ``` ## 构建 ```bash pnpm build ``` `build` 会先执行类型检查和 Vite 构建,然后通过 `scripts/embed.mjs` 将 `dist` 复制到: ```text ../snail-ai/snail-ai-agent/snail-ai-agent-chat/snail-ai-agent-chat-starter/src/main/resources/META-INF/chat ``` 如果只需要生成 `dist`,使用: ```bash pnpm build:only ``` ## 配置来源 前端启动时会按下面顺序合并配置,后面的配置覆盖前面的配置: 1. 内置默认配置。 2. 当前站点下的 `config.json`,开发时对应 `public/config.json`。 3. 网关接口 `${gatewayPath}/config` 返回的后端 yml 配置。 生产环境建议把业务配置写在后端 yml 中,前端只保留默认兜底配置。 ## 后端 yml 配置 配置路径为 `snail-ai.chat`。 ```yaml snail-ai: chat: enabled: true ui: page-title: SnailAIChat logo: https://snailjob.opensnail.com/logo.svg theme-scheme: auto locale: zh-CN embed: enabled: false show-header: false show-sidebar-user: false show-agent-market: false compact-input: true lock-agent: false lifecycle: enabled: true allowed-parent-origins: - self - https://admin.example.com before-submit-timeout-ms: 3000 max-runtime-context-bytes: 4096 timeout-policy: CONTINUE session: token-ttl-seconds: 3600 ``` 后端会通过 `/api/snail/chat/config` 返回前端可识别的配置: ```json { "gatewayPath": "/api/snail/chat", "pageTitle": "SnailAIChat", "logo": "https://snailjob.opensnail.com/logo.svg", "themeScheme": "auto", "locale": "zh-CN", "embed": { "enabled": false, "showHeader": false, "showSidebarUser": false, "showAgentMarket": false, "compactInput": true, "lockAgent": false, "lifecycle": { "enabled": true, "allowedParentOrigins": ["self", "https://admin.example.com"], "beforeSubmitTimeoutMs": 3000, "maxRuntimeContextBytes": 4096, "timeoutPolicy": "CONTINUE" } } } ``` ### 配置字段 | yml 字段 | 前端字段 | 说明 | 默认行为 | | --- | --- | --- | --- | | `ui.page-title` | `pageTitle` | 浏览器标题和顶部标题。 | `Snail AI Chat` | | `ui.logo` | `logo` | 顶部 logo 和 favicon 地址。 | 空时使用默认机器人图标 | | `ui.theme-scheme` | `themeScheme` | 主题模式,支持 `light`、`dark`、`auto`,也兼容 `system`。 | 未配置时使用本地缓存,默认浅色 | | `ui.locale` | `locale` | 界面语言,支持 `zh-CN`、`en-US`,也兼容 `zh`、`en`、`zh_CN`、`en_US`。 | 未配置时跟随本地缓存或浏览器语言 | | `ui.embed.enabled` | `embed.enabled` | 是否默认使用嵌入模式。 | `false` | | `ui.embed.show-header` | `embed.showHeader` | 嵌入模式是否显示顶部栏。 | 嵌入模式默认隐藏 | | `ui.embed.show-sidebar-user` | `embed.showSidebarUser` | 嵌入模式是否显示侧栏底部用户入口。 | 嵌入模式默认隐藏 | | `ui.embed.show-agent-market` | `embed.showAgentMarket` | 嵌入模式是否显示智能体市场入口。 | 嵌入模式默认隐藏 | | `ui.embed.compact-input` | `embed.compactInput` | 嵌入模式是否启用紧凑输入框。 | 嵌入模式默认启用 | | `ui.embed.lock-agent` | `embed.lockAgent` | 是否默认锁定 `agentId` 指定的智能体。 | `false` | | `ui.embed.lifecycle.enabled` | `embed.lifecycle.enabled` | 是否启用 iframe 生命周期协议。 | `false` | | `ui.embed.lifecycle.allowed-parent-origins` | `embed.lifecycle.allowedParentOrigins` | 允许通信的父页面精确 Origin;`self` 表示 Chat 自身 Origin。 | 空列表 | | `ui.embed.lifecycle.before-submit-timeout-ms` | `embed.lifecycle.beforeSubmitTimeoutMs` | 等待宿主发送前响应的毫秒数,范围 100 至 10000。 | `3000` | | `ui.embed.lifecycle.max-runtime-context-bytes` | `embed.lifecycle.maxRuntimeContextBytes` | 单次 Runtime Context 的 UTF-8 字节上限,范围 256 至 16384。 | `4096` | | `ui.embed.lifecycle.timeout-policy` | `embed.lifecycle.timeoutPolicy` | 宿主未响应时继续发送或取消发送,支持 `CONTINUE`、`CANCEL`。 | `CONTINUE` | | `session.token-ttl-seconds` | - | 嵌入式会话 token 有效期,单位秒。 | `3600` | 如果某个 `embed` 字段没有配置,前端会使用内置嵌入默认值。 ## 前端静态配置 `public/config.json` 可作为本地开发或纯静态部署的兜底配置: ```json { "gatewayPath": "/api/snail/chat", "pageTitle": "Snail AI Chat", "logo": "" } ``` 也支持下划线和短横线字段名: ```json { "gateway_path": "/api/snail/chat", "page_title": "Snail AI Chat" } ``` `pageTitle`、`logo`、`embed` 也可以放在 `ui` 节点中: ```json { "gatewayPath": "/api/snail/chat", "ui": { "pageTitle": "Snail AI Chat", "logo": "/favicon.svg", "theme": "auto", "lang": "zh-CN", "embed": { "enabled": true, "showHeader": false } } } ``` 当 `gatewayPath` 以 `/` 开头时,前端会根据 `VITE_APP_BASE_API` 或当前路径中的 `/snail-chat` 前缀自动补齐部署前缀。 ## 环境变量 开发环境使用 `.env.test`: ```ini VITE_HTTP_PROXY=Y VITE_CHAT_CLIENT_BASE_URL=http://localhost:8081 VITE_CHAT_BASE_URL=/ VITE_APP_BASE_API= ``` 生产构建使用 `.env.prod`: ```ini VITE_HTTP_PROXY=N VITE_CHAT_BASE_URL=./ VITE_APP_BASE_API= ``` | 变量 | 说明 | | --- | --- | | `VITE_HTTP_PROXY` | 是否启用 Vite 本地代理。`Y` 时代理 `/api/snail/chat`。 | | `VITE_CHAT_CLIENT_BASE_URL` | 本地代理目标后端地址。 | | `VITE_CHAT_BASE_URL` | Vite `base`,控制静态资源基础路径。 | | `VITE_APP_BASE_API` | 部署在后端 context path 下时使用的 API 前缀。 | ## URL 参数 URL 参数主要用于传递用户态信息,或者临时覆盖 yml 默认配置。 ### 会话参数 | 参数 | 说明 | | --- | --- | | `openId` | 宿主系统传入的平台用户标识,用于创建嵌入式会话。 | | `trustedCredential` | 可选的可信凭据,后端如配置校验器会使用它校验会话创建请求。 | | `agentId` | 指定默认选中的智能体。 | | `parentOrigin` | Lifecycle 通信目标 Origin,必须已经存在于后端白名单中,不能通过 URL 扩大权限。 | | `theme` / `themeScheme` | 指定主题模式,支持 `light`、`dark`、`auto`、`system`。 | | `lang` / `locale` | 指定界面语言,支持 `zh-CN`、`en-US` 及 `zh`、`en` 别名。 | 示例: ```text http://localhost:9728/?openId=user-001&agentId=1&theme=dark&lang=en-US ``` ### 临时覆盖参数 布尔参数支持 `1/0`、`true/false`、`yes/no`、`on/off`。这些参数优先级高于后端 yml。 | 参数 | 说明 | | --- | --- | | `embed` | 覆盖 `snail-ai.chat.ui.embed.enabled`。 | | `showHeader` | 覆盖 `snail-ai.chat.ui.embed.show-header`。 | | `showSidebarUser` | 覆盖 `snail-ai.chat.ui.embed.show-sidebar-user`。 | | `showAgentMarket` | 覆盖 `snail-ai.chat.ui.embed.show-agent-market`。 | | `compactInput` | 覆盖 `snail-ai.chat.ui.embed.compact-input`。 | | `lockAgent` | 覆盖 `snail-ai.chat.ui.embed.lock-agent`。 | 如果后端 yml 已经配置了嵌入模式,iframe 地址可以只保留用户和智能体信息: ```text http://localhost:9728/?openId=user-001&agentId=1 ``` 临时覆盖示例: ```text http://localhost:9728/?openId=user-001&agentId=1&showHeader=1&compactInput=0 ``` ## 宿主动态切换主题和语言 嵌入到 iframe 后,宿主系统可以通过 `postMessage` 动态同步主题和语言: ```js iframe.contentWindow?.postMessage( { type: 'snail-ai-chat:ui', payload: { theme: 'dark', locale: 'en-US' } }, 'https://your-domain' ); ``` 也支持命名空间写法: ```js iframe.contentWindow?.postMessage( { snailAiChat: { themeScheme: 'auto', lang: 'zh-CN' } }, 'https://your-domain' ); ``` 生命周期启用后,主题和语言消息也会校验 `event.origin` 和 `event.source`。生命周期关闭时保留旧宿主的兼容行为,建议新接入方始终配置精确 Origin。 ## iframe 生命周期与运行时上下文 Lifecycle 默认关闭。启用后,Chat 会在用户发送消息前向父页面发出 `before-submit`,宿主可以附加当前客户、工单、报表或页面引用,也可以取消本次发送。宿主不能替换用户消息,也不能通过响应事件主动发起一条新消息。 iframe URL 使用 `parentOrigin` 选择实际通信目标: ```text https://chat.example.com/snail-chat/?openId=user-001&parentOrigin=https%3A%2F%2Fadmin.example.com ``` 该参数只会从 `allowed-parent-origins` 中选择目标,不会新增或扩大白名单。Origin 必须包含协议和可选端口,不允许路径、查询、通配符或凭据。 ### 宿主 Bridge 示例 ```ts const iframe = document.querySelector('#snail-ai-chat'); const iframeOrigin = new URL(iframe!.src).origin; let channelId = ''; window.addEventListener('message', async event => { if (event.source !== iframe?.contentWindow) return; if (event.origin !== iframeOrigin) return; const message = event.data; if (message?.namespace !== 'snail-ai-chat.lifecycle' || message.version !== 1) return; if (message.event === 'ready') { channelId = message.channelId; return; } if (message.channelId !== channelId || message.event !== 'before-submit' || !message.requestId) return; const runtimeContexts = await provideCurrentPageContext(message.payload); iframe.contentWindow?.postMessage( { namespace: 'snail-ai-chat.lifecycle', version: 1, channelId, event: 'before-submit-result', requestId: message.requestId, payload: { action: 'continue', runtimeContexts: [{ namespace: 'example.crm.current-customer', contentType: 'application/json', data: { referenceToken: runtimeContexts.referenceToken } }] } }, iframeOrigin ); }); ``` 宿主必须同时校验 `event.source`、`event.origin`、协议版本、`channelId` 和 `requestId`。`before-submit-result` 只能响应当前待处理的 `before-submit`;重复、晚到或无法关联的响应会被忽略。 取消发送时只返回 `action` 和可选原因,不能携带 Runtime Context: ```ts iframe.contentWindow?.postMessage({ namespace: 'snail-ai-chat.lifecycle', version: 1, channelId, event: 'before-submit-result', requestId: message.requestId, payload: { action: 'cancel', reason: '发送前策略检查未通过' } }, iframeOrigin); ``` `timeout-policy: CONTINUE` 表示宿主超时后按原消息继续发送;`CANCEL` 表示保留输入并取消本次发送。Runtime Context 只接受 `application/json` 和 `text/plain`,并受条目数、JSON 深度和总字节数限制。 V1 不修改 `/completions` DTO,因此 Runtime Context 会编码到发送正文尾部,可能进入后端会话历史。Chat UI 会在展示用户历史消息时隐藏合法上下文块,但 UI 隐藏不等于数据不落库;敏感数据应使用短期引用 Token,不应直接放入上下文。 ## 宿主页面 iframe 示例 ```html ``` ## 认证说明 页面会先调用 `${gatewayPath}/session` 获取嵌入式 token,后续请求通过后端返回的认证头发送 token。默认认证头为: ```text Snail-Ai-Auth ``` 不要把 token 拼到 URL query 中。