跳到主要内容

示例:3 分钟给你的网站装一个客服组件

目标: 在你自己的网站右下角挂一个聊天气泡,背后是你在 OpenHex 发布的 Agent。访客不用登录、不用注册就能聊,费用默认记在 Agent 所有者头上,访客零门槛。

整套东西就三块:

  1. 一个已发布的 Agent(拿到它的 agentId
  2. 一个工作区 API Keysk_…)——你的后端用它签发访客令牌,密钥永远不进浏览器
  3. 前端挂上 SDK 的 <ChatWidget>

前提

  • app.openhex.tech 发布一个 Agent,拿到它的 Agent ID:在首页点开它,浏览器地址栏里 /console/ 后面那段 UUID 就是(其他方式见获取 Agent ID)。前端组件要用它,放进 NEXT_PUBLIC_OPENHEX_AGENT_ID(Agent ID 不是密钥)。
  • 准备好工作区 API Key 和工作区 slug,签发方法见工作区 API 概览。把两者存进后端环境变量 OPENHEX_WORKSPACE_KEYOPENHEX_WORKSPACE_SLUG(在 Next.js 里不要NEXT_PUBLIC_ 前缀)。
  • 安装 SDK:
npm install @openhex-ai/agent-sdk react react-dom

第 1 步:后端签发访客令牌

浏览器不能碰 sk_…,所以由你的后端换一个短时的访客会话令牌交给前端。startVisitorSession 把「开通访客成员 + 签发令牌」合成一次调用:

app/api/openhex/chat-token/route.ts(Next.js App Router)
import { OpenhexClient } from '@openhex-ai/agent-sdk';
import { cookies } from 'next/headers';
import { randomBytes } from 'node:crypto';

const ws = new OpenhexClient({ apiKey: process.env.OPENHEX_WORKSPACE_KEY! })
.workspace(process.env.OPENHEX_WORKSPACE_SLUG!);

export async function POST() {
// 给每个访客一个稳定的引用(放 httpOnly cookie),下次来还是同一个人 → 续上同一段对话
const jar = await cookies();
let ref = jar.get('ohx_ref')?.value;
if (!ref) {
ref = `web_${randomBytes(12).toString('hex')}`;
jar.set('ohx_ref', ref, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 24 * 180 });
}

const { token, expires_at } = await ws.startVisitorSession({
sp_user_ref: ref, // 稳定引用 → 同一访客始终是同一个成员
display_name: '网站访客',
ttl_seconds: 1800, // 30 分钟过期,泄漏了也很快失效
});

return Response.json({ token, expiresAt: expires_at });
}

不知道 slug? 用这把 Key 调一次 client.workspaces.whoami(),返回里就有 slug

第 2 步:前端挂上组件

<ChatWidget> 是一个开箱即用的浮动气泡:桌面端是角落面板,手机端全屏。给它 agentId 和一个 getToken,组件在每次请求前调用它:

components/SupportWidget.tsx
'use client';
import { useCallback, useRef } from 'react';
import { ChatWidget } from '@openhex-ai/agent-sdk/react';

export default function SupportWidget() {
// 缓存令牌到过期前一分钟,避免每个请求都打一次签发接口
const cache = useRef<{ token: string; exp: number } | null>(null);

const getToken = useCallback(async () => {
if (cache.current && cache.current.exp - Date.now() > 60_000) return cache.current.token;
const r = await fetch('/api/openhex/chat-token', { method: 'POST' });
if (!r.ok) throw new Error(`chat-token ${r.status}`);
const d = (await r.json()) as { token: string; expiresAt: string };
cache.current = { token: d.token, exp: new Date(d.expiresAt).getTime() };
return d.token;
}, []);

return (
<ChatWidget
agentId={process.env.NEXT_PUBLIC_OPENHEX_AGENT_ID!}
getToken={getToken}
persist // 刷新页面也续上同一段对话
title="在线客服"
greeting="你好 👋 有什么可以帮你的?"
placeholder="输入你的问题…"
accentColor="#0f766e"
launcherLabel="联系客服"
newConversationLabel="新对话"
/>
);
}

<SupportWidget /> 放进根布局(app/layout.tsx),全站都会出现这个气泡。

就这些

访客打开网页 → 组件调用 /api/openhex/chat-token → 后端 startVisitorSession 换到令牌 → 组件拿着令牌直接和你的 Agent 流式对话。

几个要点:

  • 费用记在 Agent 所有者头上。 只要 Agent 所有者没有把计费方设成「由使用者付费」,匿名访客余额为 0 也能聊。
  • 同一访客续同一段对话。 sp_user_ref 稳定(上面用了 httpOnly cookie)→ 始终是同一个成员;再加上 persist,刷新、重开页面都能接着聊,历史一起恢复。
  • 保留「新对话」按钮。 访客身份绑在浏览器上,下一个用这台电脑的人点它就能开始一段干净的对话,不会看到上一个人的内容。
  • 密钥安全。 sk_… 只在后端;浏览器只拿到 30 分钟过期的令牌。给签发接口加上按 IP / cookie 的限流更稳妥。

下一步