示例:3 分钟给你的网站装一个客服组件
目标: 在你自己的网站右下角挂一个聊天气泡,背后是你在 OpenHex 发布的 Agent。访客不用登录、不用注册就能聊,费用默认记在 Agent 所有者头上,访客零门槛。
整套东西就三块:
- 一个已发布的 Agent(拿到它的
agentId) - 一个工作区 API Key(
sk_…)——你的后端用它签发访客令牌,密钥永远不进浏览器 - 前端挂上 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_KEY、OPENHEX_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 的限流更稳妥。