@daocube/joy-chat
v0.3.0
Published
Embeddable React AI chat SDK with SaaS user tokens, conversation history and SSE streaming
Maintainers
Readme
@daocube/joy-chat
可嵌入 React 18/19 项目的 Joy Chat 组件与 TypeScript 客户端。支持 SSE 流式回答、内置会话列表/切换/清空/删除、分页历史消息、图片、主题与自定义头像。
安装
pnpm add @daocube/joy-chat生产 API:https://sdk.joyclaw.net
交互式接口文档:https://sdk.joyclaw.net/docs
5 分钟接入
1. 在管理平台创建应用密钥
打开 JoyClaw Agent Console,进入应用的 API Keys 区域并创建应用密钥。完整密钥只显示一次,以 joy_app_sk_ 开头,请立即复制并保存在应用后端的安全环境变量中。
安全接入(推荐)
joy_app_sk_... 是应用后端密钥,绝不能放进 React、Vite 环境变量或其它浏览器代码。正确流程是:
- 在管理平台创建应用密钥。
- 应用自己的后端用该 Key 创建/更新终端用户。
- 应用后端调用令牌接口,得到短期 Bearer Token。
- React 仅把该 Bearer Token 传给
JoyChat。
2. 应用后端创建用户并签发 Token
应用自己的登录系统负责确认当前用户身份。下面的代码必须运行在 Node.js 服务端、API Route、Server Action 或其它可信后端,不能打包进浏览器。
应用后端示例:
const sdkOrigin = "https://sdk.joyclaw.net";
const appKey = process.env.JOY_CHAT_APP_KEY!;
const externalUserId = "your-user-42";
const headers = {
Authorization: `Bearer ${appKey}`,
"Content-Type": "application/json",
};
const upsertResponse = await fetch(`${sdkOrigin}/v1/users/${encodeURIComponent(externalUserId)}`, {
method: "PUT",
headers,
body: JSON.stringify({ display_name: "Joy", profile: { plan: "pro" } }),
});
if (!upsertResponse.ok) throw new Error(`Joy Chat user upsert failed: ${upsertResponse.status}`);
const tokenResponse = await fetch(
`${sdkOrigin}/v1/users/${encodeURIComponent(externalUserId)}/tokens`,
{ method: "POST", headers: { Authorization: `Bearer ${appKey}` } },
);
if (!tokenResponse.ok) throw new Error(`Joy Chat token issue failed: ${tokenResponse.status}`);
const { access_token } = await tokenResponse.json();
// 把 access_token 返回给当前已登录的网页用户。externalUserId 应使用你系统内稳定且不可变的用户 ID。同一个应用内重复 PUT 会更新该用户;不同应用即使 ID 相同,数据也完全隔离。
3. React 引用组件
import { JoyChat } from "@daocube/joy-chat";
import "@daocube/joy-chat/style.css";
export default function SupportChat({ accessToken }: { accessToken: string }) {
return (
<div style={{ width: 420, height: 680 }}>
<JoyChat
accessToken={accessToken}
serverUrl="https://sdk.joyclaw.net"
locale="en-US"
title="Joy 学习助手"
AgentAvatar="https://example.com/agent-avatar.png"
UserAvatar="https://example.com/user-avatar.png"
loadingText="AI is thinking..."
emptyText="No messages yet"
theme={{
primary: "#6755e7",
background: "#f4f1ff",
fontSize: 15,
messageFontSize: 16,
imageRadius: 12,
radius: 20,
}}
/>
</div>
);
}传入 accessToken 时默认使用 /v1。令牌过期后,宿主应用应从自己的后端获取新令牌并重新渲染组件。
组件右上角自带:
- 会话列表、历史会话切换与删除;
- 清空当前会话消息但保留会话;
- 新建会话;
- 图片上传、停止生成和 SSE 流式回复。
如宿主应用自己实现会话 UI,可传 enableConversationList={false},并使用下面的 JoyChatClient。
国际化
组件内置简体中文和英文。不传 locale 时默认使用 zh-CN,以保持已有项目的界面不变:
<JoyChat
accessToken={accessToken}
serverUrl="https://sdk.joyclaw.net"
locale="en-US"
/>支持的语言:
zh-CN:简体中文;en-US:英文。
使用 messages 可以覆盖所选语言中的任意文案,未覆盖的字段继续使用内置翻译:
<JoyChat
accessToken={accessToken}
locale="en-US"
messages={{
emptyText: "Welcome to Acme Support",
sendMessage: "Send",
defaultConversationTitle: "New support request",
}}
/>title、subtitle、placeholder、emptyText 和 loadingText 等原有属性仍可使用,且优先级高于 messages。组件还会根据 locale 设置 lang、格式化会话时间,并切换按钮提示和无障碍标签。
单独使用客户端时也可设置语言;它会影响默认会话标题和无响应正文时的错误信息:
const client = new JoyChatClient({
serverUrl: "https://sdk.joyclaw.net",
accessToken,
locale: "en-US",
});会话客户端 API
import { JoyChatClient } from "@daocube/joy-chat";
const client = new JoyChatClient({
serverUrl: "https://sdk.joyclaw.net",
accessToken,
});
const page = await client.listConversations({ limit: 30 });
const conversation = await client.createConversation("新的问题");
const history = await client.listMessages(conversation.id, { limit: 50 });
await client.updateConversation(conversation.id, { title: "学习计划" });
await client.clearConversationHistory(conversation.id);
await client.deleteConversation(conversation.id);listConversations 返回 { items, next_cursor },下一页把 next_cursor 作为 cursor 传回;listMessages 返回 { items, next_before }。
流式调用
await client.streamMessage(
conversation.id,
"帮我制定学习计划",
[],
(message) => {
if (message.event === "text_delta") {
console.log(message.data.delta);
}
if (message.event === "error") {
console.error(message.data.message);
}
},
);主要事件包括 message_created、text_delta、tool_start、tool_done、image、message_done、error 和 done。
后端 SaaS API 速查
应用后端(使用 joy_app_sk_...)
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| PUT | /v1/users/{external_user_id} | 幂等创建或更新用户 |
| GET | /v1/users/{external_user_id} | 查询当前应用的用户 |
| GET | /v1/users | 分页列出应用用户 |
| POST | /v1/users/{external_user_id}/tokens | 签发短期终端用户 Token |
| POST | /v1/users/{external_user_id}/revoke-tokens | 立即撤销该用户的旧 Token |
| DELETE | /v1/users/{external_user_id} | 删除用户及其会话和附件 |
浏览器/终端用户(使用短期 Bearer Token)
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET, POST | /v1/conversations | 分页会话列表/新建会话 |
| GET, PATCH, DELETE | /v1/conversations/{id} | 详情/改名/删除会话 |
| GET | /v1/conversations/{id}/messages | 分页读取消息历史 |
| DELETE | /v1/conversations/{id}/messages | 清空历史但保留会话 |
| POST | /v1/conversations/{id}/stream | SSE 流式发送文本和图片 |
所有终端用户资源同时按 Application 和 ApplicationUser 过滤;访问其它用户或其它应用的资源返回 404。
常用属性
| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| accessToken | string | - | 推荐,终端用户短期 Bearer Token |
| apiVersion | "v1" \| "legacy" | 自动 | accessToken 默认 v1,apiKey 默认 legacy |
| apiKey | string | - | 旧版 joy_sk_ 兼容参数,不是 joy_app_sk_ |
| serverUrl | string | http://localhost:8000 | API origin,不含 /v1 |
| locale | "zh-CN" \| "en-US" | zh-CN | 内置界面语言 |
| messages | Partial<JoyChatMessages> | - | 覆盖所选语言的内置文案 |
| conversationId | string \| null | - | 打开指定会话 |
| storageKey | string \| null | joy-chat:conversation | 保存最近会话;传 null 关闭 |
| enableImages | boolean | true | 是否允许图片上传 |
| enableConversationList | boolean | true | 是否显示内置会话列表和管理按钮 |
| AgentAvatar / agentAvatar | ReactNode \| ComponentType \| string | 内置头像 | Agent 头像;字符串按图片 URL 渲染 |
| UserAvatar / userAvatar | ReactNode \| ComponentType | 内置头像 | 用户头像,兼容直接传组件类型 |
| loadingText | string | 思考中 | 流式回答等待文案 |
| emptyText | ReactNode | 内置欢迎语 | 空会话内容 |
| tradeApiKey | string | - | 第三方兼容头;浏览器可见,绝不能传服务端密钥 |
| theme | JoyChatTheme | - | 主题配置 |
| onConversationChange | (conversation) => void | - | 会话变化回调 |
| onMessage | (message) => void | - | 消息回调 |
| onStreamEvent | (event) => void | - | 原始 SSE 事件回调 |
组件填满父容器,宿主需给外层明确宽高。
theme 支持 background、surface、primary、text、fontFamily、fontSize、messageFontSize、imageRadius、imageMaxHeight 等外观配置。图片可由用户在输入框中选择,也可由 Agent/MCP 工具作为消息附件返回;组件会在消息气泡区域内显示图片,并携带鉴权请求附件内容。
旧版兼容
已有 joy_sk_... 可继续使用:
<JoyChat apiKey="joy_sk_xxx" apiVersion="legacy" serverUrl="https://sdk.joyclaw.net" />旧 Key 直接代表平台用户,适合迁移兼容;多应用生产接入应使用上述 Application + end-user token 模型。
常见问题
Failed to execute 'fetch' ... Illegal invocation
该浏览器兼容问题已在 0.2.0 修复。SDK 会正确绑定原生 fetch;升级后无需在宿主项目包装 window.fetch。
返回 401
joy_app_sk_只能用于应用后端的/v1/users接口;- React 组件必须传用户 Token,而不是应用 Key;
- 用户 Token 默认 60 分钟过期,宿主后端应重新签发;
- 撤销应用 Key、停用用户或调用
revoke-tokens后,旧凭据立即失效。
浏览器提示 CORS
后端必须把宿主 origin 加入 CORS_ORIGINS 或可信的 CORS_ORIGIN_REGEX。serverUrl 只填写 origin,例如 https://sdk.joyclaw.net,不要追加 /v1。
完整示例
源码仓库中的 joy-chat-demo 是一个独立 Vite + React 项目,从 npm 安装本包;其中的 server-example.mjs 展示应用后端签发 Token 的完整逻辑。
开发
pnpm install
pnpm typecheck
pnpm test
pnpm pack构建产物包含 ESM、UMD、TypeScript declarations 和独立 CSS。
