Skip to content

Web 嵌入集成指南 ​

面向把 Servify 客服嵌入自有网站/应用的对接方。覆盖嵌入方式选型、会话互认、 工单上下文打通、事件回调、部署形态,以及组件与聊天窗的完整外观/主题配置面。

文中全部接口以仓库当前代码为准(路由注册见 apps/server/internal/app/server/), 示例可直接跟跑。

1. 嵌入方式选型 ​

方式适用接入成本定制深度
A. 组件直嵌(widget.js)官网/独立站加一个客服入口两个 <script> 标签外观与主题全参数化(见 §7),改样式不改代码
B. iframe 嵌入演示页宿主页面不允许注入第三方 JS一个 <iframe>同 A(iframe 内仍是 widget),但浮层定位由宿主控制
C. 自建界面走 SDK(servify-sdk.umd.js / React / Vue 包)需要自定义交互形态(如把会话嵌进应用侧边栏)自行实现 UI完全自控,Servify 只提供会话/语音客户端

择优口径:默认选 A;无 JS 注入权限选 B;要把会话能力融进自有产品界面选 C。 本指南主体围绕 A 展开,C 的 SDK 用法见 apps/demo-sdk/README.md 与 sdk/examples/{react,vue,vanilla}。

2. 快速开始 ​

sh
# 1) 起服务(本地;compose 部署见 §6)
cd apps/server && go run ./cmd/server
# 服务地址 http://localhost:8080
html
<!-- 2) 页面尾部嵌入 -->
<script src="http://localhost:8080/demo-sdk/servify-sdk.umd.js"></script>
<script src="http://localhost:8080/demo-sdk/widget.js"></script>
<script>
  ServifyWidget.create({ baseUrl: 'http://localhost:8080' });
</script>

打开页面,右下角出现悬浮按钮,点击展开聊天窗:AI 首答基于知识库,转人工后 坐席在管理端(/,admin SPA)接待(坐席侧接待与转派链路见 坐席工作台)。

不想手写页面?仓库自带两个可直接打开的示例页(由 Servify 服务端直接 提供,snippet 与本节一致、可逐行跟跑;两视口 375/桌面均已验证):

  • 浅色品牌站:http://localhost:8080/demo-sdk/examples/page-light.html
  • 深色站:http://localhost:8080/demo-sdk/examples/page-dark.html

零依赖体验形态(无 OpenAI key、无外部服务):config 中 ai/embedding/ knowledge 三 provider 均置 local(配 sqlite),AI 首答走本机抽取式问答—— 先 POST /api/v1/ai/knowledge/upload(admin 登录态)上传知识文档即索引, 再在组件里提问即可命中;注意示例页的 AI 回答内容取决于已上传的知识文档。

不带 sessionId 时,组件为每次页面加载生成新会话 id;跨页面保持会话见 §3。

零配置形态也支持:<script src="…/widget.js" data-servify-widget data-base-url="http://localhost:8080"></script>,常用参数都有对应 data-* 属性(全表见 §7.4)。

3. 会话互认与单点登录 ​

3.1 应用侧指定会话(登录用户绑定固定会话) ​

宿主应用在渲染嵌入代码时把自己的用户标识编进 sessionId(如 app_<uid>), 同一用户在任何页面/设备打开组件都会恢复同一会话:

html
<script>
  ServifyWidget.create({
    baseUrl: 'https://servify.example.com',
    sessionId: 'app_' + CURRENT_USER_ID,   // 宿主侧模板变量
  });
</script>

会话行在首条消息持久化时才建立,预先指定 id 不需要先建会话。

3.2 访客 token(反馈等认证面的入场券) ​

部分端点要求认证(如"这条回答有帮助吗"反馈 POST /api/v1/ai/feedback)。 访客 token 由宿主后端签发,浏览器不接触任何长期凭据:

sh
# 宿主后端 → servify(service API key 走 X-API-Key 头)
curl -X POST https://servify.example.com/api/v1/guest/session \
  -H 'X-API-Key: <SERVICE_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"session_id": "app_42"}'
# 201 {"access_token":"<HS256>","token_type":"bearer","expires_at":1767225600}
  • token 绑定 session_id,默认有效期 24h(security.guest_token.ttl 可调); WS 握手 /api/v1/ws?access_token= 时服务端校验签名+时效+会话绑定三层, 拿 A 会话的 token 握 B 会话会被拒。
  • 浏览器侧把 token 交给组件:
js
ServifyWidget.create({
  baseUrl: 'https://servify.example.com',
  sessionId: 'app_42',
  accessToken: '<上一步的 access_token>',
});

凭据纪律:X-API-Key(service principal)只在宿主后端使用;浏览器侧只持有 绑定单一会话、短期有效的访客 token。

4. 工单上下文打通 ​

4.1 会话转工单(免认证面) ​

访客从聊天窗发起工单,按 session 归属租户 scope;分类/优先级/来源由服务端 固定默认值(general/normal/chat),自定义字段与标签不对访客开放:

sh
curl -X POST https://servify.example.com/api/v1/tickets \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "app_42",
    "title": "流量扣费异常",
    "description": "10 月账单多扣了一次流量包",
    "ai_summary": "用户反馈 10 月账单重复扣流量包费用,AI 已引导提供订单号"
  }'

ai_summary 是宿主/AI 侧整理的会话摘要,随工单落库,坐席在工单页直接看到 上下文,不用回翻聊天记录。

4.2 客户业务上下文(坐席侧只读展示) ​

把宿主侧的业务数据(套餐/流量/订单)以客户资料形式同步进 Servify,坐席在 管理端查看,全程走服务端接口、凭据不进浏览器:

sh
# 宿主后端定时/事件驱动同步(均为管理面接口,需 agent/admin/service 主体)
curl -X POST https://servify.example.com/api/customers \
  -H 'X-API-Key: <SERVICE_API_KEY>' -H 'Content-Type: application/json' \
  -d '{"name": "用户 42", "email": "u42@example.com"}'

curl -X POST https://servify.example.com/api/customers/<id>/notes \
  -H 'X-API-Key: <SERVICE_API_KEY>' -H 'Content-Type: application/json' \
  -d '{"content": "套餐:Pro 年付;本月流量 82%;最近订单 #10023(待发货)"}'

curl -X PUT https://servify.example.com/api/customers/<id>/tags \
  -H 'X-API-Key: <SERVICE_API_KEY>' -H 'Content-Type: application/json' \
  -d '{"tags": "pro,年付,高价值"}'

客户面接口一览:POST/GET /api/customers、GET/PUT /api/customers/:id、 GET /api/customers/:id/activity、POST /api/customers/:id/notes、 PUT /api/customers/:id/tags(权限面 customers;另有 stats/export/erase-data 等运营与合规端点)。当前是"宿主推、坐席读"的单向同步;聊天会话与客户档案 的自动关联(按邮箱/手机号命中)在后续批次。

5. 事件回调:工单状态回流应用 ​

Servify 内置出站 webhook:内部事件总线上的白名单事件按端点配置投递到宿主 地址,后台 worker 异步投递,HMAC 签名防篡改。

sh
# 注册端点(管理面,权限面 webhooks)
curl -X POST https://servify.example.com/api/webhooks \
  -H 'X-API-Key: <SERVICE_API_KEY>' -H 'Content-Type: application/json' \
  -d '{
          "url": "https://app.example.com/hooks/servify",
          "events": [
            "ticket.created",
            "ticket.assigned",
            "ticket.closed"
          ]
        }'
  • 可订阅事件以 GET /api/webhooks/events 返回的白名单为准;当前收录 12 类: ticket.created / ticket.assigned / ticket.closed、 conversation.created / conversation.message_received、 routing.agent_assigned / routing.transfer_completed、 call.started / call.held / call.resumed / call.transferred / call.ended。
  • 每次投递带 X-Servify-Signature: t=<unix秒>,v1=<hex> 头,v1 = HMAC-SHA256(secret, "<t>.<body>");宿主侧先验签再处理,拒绝重放 (时间窗校验)。
  • 投递记录与补偿:GET /api/webhooks/deliveries 查询、 POST /api/webhooks/deliveries/:id/redeliver 手工补投、 POST /api/webhooks/:id/test 连通性自测、POST /api/webhooks/:id/secret 轮换签名密钥。

宿主侧收到 ticket.closed 后即可在自己的系统里关掉对应服务请求,形成 "聊天 → 工单 → 回流"闭环。

6. 部署形态 ​

6.1 独立部署(docker compose) ​

sh
cd infra/compose
JWT_SECRET=<强随机值> SERVIFY_PUBLISH_PORT=8080 docker compose \
  -f docker-compose.yml up -d
# 需要外部知识库(weknora)时叠加:
JWT_SECRET=<…> docker compose -f docker-compose.yml -f docker-compose.weknora.yml up -d
  • 栈内端口可用环境变量外移(宿主机端口冲突时不改容器): SERVIFY_PUBLISH_PORT / POSTGRES_PUBLISH_PORT / REDIS_PUBLISH_PORT / WEKNORA_API_PUBLISH_PORT / WEKNORA_WEB_PUBLISH_PORT / EMBEDDING_PUBLISH_PORT / ELASTICSEARCH_PUBLISH_PORT。
  • 生产部署必须显式设置 JWT_SECRET(config gate 拦默认值)。

6.2 对接地址与静态资源 ​

  • 组件与窗体的所有请求都指向 baseUrl(默认同源)。跨域部署时把 baseUrl 指到 servify 地址即可,无需代理。
  • /demo-sdk/* 静态资源(SDK 产物 + widget)在非 production 环境由服务端 直接提供;production 环境不再默认暴露(P3-3 起走 SPA 兜底),生产嵌入 请自行托管三个文件(servify-sdk.umd.js / widget.js / widget.css) 到宿主 CDN,并把 snippet 里的 src 指过去。
  • 语音翻译通道 /api/v1/ws/voice 需要服务端装配 ai.asr;未装配时组件 头部不出现麦克风按钮(能力探测),聊天主链路不受影响。

7. 组件与聊天窗主题 ​

全部外观/主题走初始化参数,改样式不改代码。亮暗两套独立 token, theme: 'auto' 跟随宿主站深浅(prefers-color-scheme)。

7.1 组件外观(悬浮按钮) ​

参数类型默认说明
iconstring | object'chat'三档:预设名 chat/smile/headset/lifebuoy/dots/bolt;{image: '<svg…|data:|https://…>'} 自定义上传(内联 SVG/data URL/图片直链);{url: 'https://…'} URL 引用
colorstring'#667eea'主色调(primaryColor 为兼容别名);同时参与主题 token 自动生成
borderRadiusnumber | string'50%'触发按钮圆角;数字按 px,字符串原样(如 '12px'、'8px 50%')
positionstring'bottom-right'bottom-right/bottom-left/top-right/top-left
sizestring | number'medium'small(44px)/medium(56px)/large(64px) 档位,或按钮直径 px 数字

7.2 聊天窗主题 token 全表 ​

token作用亮套默认暗套默认
primary主色(发送钮/链接/焦点)colorcolor 提亮一档
primaryContrast主色上的文字按亮度自动按亮度自动
panelBg / panelText窗体底/文字#ffffff / #333333#1f2430 / #e5e7eb
msgsBg消息区底#fafafa#171a23
bubbleSelfBg / bubbleSelfText访客气泡主色 / 自动对比色主色 / 自动对比色
bubbleAgentBg / bubbleAgentText坐席/AI 气泡#f0f0f0 / #333333#2a3040 / #e5e7eb
systemText系统提示条#888888#9ca3af
metaText时间/引用等次级文字#999999#8b93a3
inputBg / inputBorder / inputText输入框白 / #dddddd / #333333#262c3a / #3a4152 / #e5e7eb
chipBg / chipBorder建议问题 chips白 / #e0e0e0#262c3a / #3a4152
headerBg窗头背景(可传渐变)主色渐变主色渐变(提亮)
font字体栈系统栈同左
shadowPanel / shadowTrigger窗体/按钮阴影各自默认各自默认
radiusPanel / radiusBubble / radiusInput三处圆角16/12/20px同左

品牌主色自动生成整套:只给 themeFromColor(或 color),其余 token 按亮度 推导(气泡自色随主色、对比文字色按 WCAG 亮度切换深白)。

7.3 主题装载方式 ​

参数说明
theme'light' | 'dark' | 'auto';auto 监听 prefers-color-scheme 实时切换
themeLight / themeDark亮/暗套独立覆盖(对象,键为 §7.2 token 名),未覆盖字段保持默认
themeTokens当前主题覆盖,同名字段同时盖掉亮暗两套
themeUrl远程主题 JSON({"light":{…},"dark":{…},"brand":{…}}),配置放服务端多站点统一改;拉取失败不阻塞,保持本地配置
brand{ logo, name, welcome } 品牌位:窗头 logo、名称、开场欢迎语;远程主题的 brand 可二次覆盖

7.4 data-* 自动初始化全表 ​

<script src="…/widget.js" data-servify-widget …> 的 data-* 配置全表(19 项,JSON 类属性值为 JSON 字符串)见 Web SDK 文档,该处为唯一权威。

7.5 两风格示例 ​

起服务后由 Servify 直接提供(无需另起静态服务器):

两页均自带窄视口响应式(≤480px 组件自动切近全高 sheet,页面卡片单列), 嵌入块源码处标有「与 §2 一致」的 snippet 注释,浏览器查看源码即可逐行跟跑。 源文件:apps/demo-sdk/examples/page-light.html / page-dark.html。

  • 远程主题样例:apps/demo-sdk/examples/theme.json(亮暗两套 + 品牌位, 经 themeUrl 下发的完整格式)

本地跟跑:仓库根目录起任意静态服务器指到仓库根,访问 /apps/demo-sdk/examples/page-light.html;baseUrl 里的 :8080 按部署地址改。

7.6 交互行为(内置,无需配置) ​

  • 悬浮球多状态:收起显示图标,有未读时红色徽标计数(封顶 99+,弹出动画); 展开即清零并换关闭图标。
  • 展开/收起动画:面板从悬浮球角落以缩放+位移入场(0.18s),切换图标旋入; prefers-reduced-motion 用户自动降级为无动画。
  • 移动端:视口 ≤480px 时面板拉成近全高 sheet,不遮死整屏。
  • 层级:组件根 z-index: 99999,窗体/按钮阴影走 token(§7.2)可整体调。

8. 体验基准对照(Crisp livechat) ​

体验基准参考 Crisp livechat(商业产品,仅借鉴交互模式与信息架构,不取其 代码与美术资产)。逐项对照:

能力Crisp 表现Servify widget 现状差距处置
悬浮球多状态收起图标/未读数徽标/hover 反馈全有:图标切换、未读计数封顶 99+(弹出动画)、hover 缩放提亮—
展开动画面板从悬浮球角落平滑生长(约 0.15–0.2s,transform+opacity)0.18s scale+translate 入场,transform-origin 随四角自动—
图标切换形态打开时图标旋转变关闭切换图标旋入动画(0.2s)—
窗体层级与阴影高层级、深层柔和阴影z-index: 99999 + 阴影 token 可配—
移动端适配小屏近全屏 sheet≤480px 近全高 sheet(dvh,旧内核回退 vh)—
深浅模式跟随产品亮暗自动切换theme: 'auto' 监听 prefers-color-scheme—
品牌主题注入色板/文案/位置可配外观五参数 + 23 token 亮暗两套 + 远程主题 + 品牌位—
前摄消息惰性弹出气泡引导开口开场欢迎语气泡(brand.welcome)定时/行为触发的主动弹出未做,后续批
打字指示独立"正在输入"指示流式打字机(AI 逐字上屏 + ▌光标)形态不同:这里是真实流式内容
消息类型音频/文件/GIF/CSAT 卡片文本 + 知识库引用行 + 帮助度反馈 + 语音字幕文件/图片上传 UI 未入组件(服务端 /api/v1/upload 已有),后续批
帮助中心入口窗内文章 tab建议问题 chips(/public/suggestions/*)形态不同:入口收敛为可点的问题
多语言locale 可配界面文案当前为中文多语言未做,后续批
已读回执Read/Unread 标记未做后续批
访客身份邮件识别后跨设备续聊sessionId 绑定 + 访客 token(§3)自动身份合并(按邮箱命中客户档案)未做,后续批

结论:主链路(悬浮球状态、展开动画、层级阴影、移动端、深浅跟随、品牌注入) 已对齐基准;差距集中在内容形态面(前摄消息、文件上传、多语言、已读回执、 身份合并),已列后续批次。