Appearance
AI 对话界面(5 分钟接入)
SDK 内置了一个零依赖的 AI 对话组件:消息列表、流式逐字渲染、停止/重试、 代码块/加粗/链接渲染、深浅色自适应——一行代码挂到页面里,你的应用立刻有 一个能用的 AI 聊天窗。
安全模型不变:页面拿不到 app_secret,所以组件打的是你自己的后端中继, 中继再凭 app token 调平台的 /api/open/ai/completions(消耗应用所属组织的 钱包,需 ai 能力)。
1. 后端中继(Express,约 15 行)
js
// 中继契约:POST {messages, stream} → 原样转发平台响应(SSE 透传)
app.post("/api/chat", async (req, res) => {
const upstream = await fetch(`${JLT_BASE}/ai/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${await appToken()}`, // 见「完整示例」的 token 缓存
},
body: JSON.stringify({ messages: req.body.messages, stream: req.body.stream !== false }),
});
if (!upstream.ok) return res.status(502).json({ detail: (await upstream.text()).slice(0, 300) });
if (req.body.stream !== false) {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
for await (const chunk of upstream.body) res.write(chunk);
return res.end();
}
res.json(await upstream.json());
});中继是无状态的:组件每次带全量 messages,你不需要存对话。
2. 页面挂载(一行)
html
<script src="https://cos-pub.smabbit.com/open-platform/sdk/1.3.0/jlt-sdk.min.js"></script>
<div id="chat" style="height: 560px"></div>
<script>
const chat = jltSdk.mountChat(document.getElementById("chat"), {
endpoint: "/api/chat",
});
</script>npm 引入则是 import { mountChat } from "@ai-im/org-app-sdk"。完成——输入、 流式渲染、停止(发送中再点一次/按停止)、失败重试、清空都已内置。
3. 常用配置
ts
mountChat(el, {
endpoint: "/api/chat",
title: "报销助手", // 顶部标题
welcome: "把报销单贴给我,我帮你核对", // 欢迎语(传 "" 关闭)
placeholder: "输入问题…",
systemPrompt: "你是报销审核助手,回答简洁。", // 每轮动态前置,不进 UI 历史
headers: { "x-session-id": sid }, // 额外请求头(比如带上你自己的会话)
stream: true, // 后端不支持 SSE 时设 false
maxHistory: 40, // 每次请求最多携带的历史条数
buildBody: (messages) => ({ messages, stream: true, temperature: 0.3 }), // 完全自定义请求体
onError: (msg) => console.warn(msg),
});返回句柄:chat.stop()(中止当前生成)、chat.retry()(重发失败轮)、 chat.clear()(清空)、chat.send(text)(编程式发送)、chat.destroy()。
3.5 会话持久化:存哪里?
initialHistory 恢复 + onHistoryChange 存档,存储位置你自己挑,三档:
① IndexedDB(内置助手,推荐起步)——配额通常数百 MB 起,长会话/多会话 都放得下;SDK 自带零依赖的 createChatStore(自带预算裁剪、异步写入、 错误只警告不炸聊天、隐私模式等不可用环境自动降级为空):
ts
const store = jltSdk.createChatStore({ key: "main" }); // 多会话各用不同 key
jltSdk.mountChat(el, {
endpoint: "/api/chat",
initialHistory: await store.load(),
onHistoryChange: store.save,
persistMaxChars: 4 * 1024 * 1024, // 放开组件侧载荷上限,匹配存储预算(默认 5MB)
});
// 组件「清空」会以空数组回调 → 存档自动清空;也可 await store.clear()② 你自己的后端(跨设备必选)——历史本是业务数据:不限量、多设备漫游、 可审计:
ts
mountChat(el, {
endpoint: "/api/chat",
initialHistory: await fetch("/api/chat-history").then((r) => r.json()).catch(() => []),
onHistoryChange: (msgs) =>
fetch("/api/chat-history", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(msgs) }),
});③ localStorage(仅轻量场景)——单源配额一般只有 ~5MB(Safari 还会更 激进地清理),长会话很容易写满。组件做了三层兜底,naive 用法不至于炸:
- 回调里的存储异常被吞掉(只
console.warn),写满不打断聊天; - 存档载荷有尺寸上限——
persistMaxChars(默认 256KB),超限自动从最旧 一轮裁起,至少保留最后一轮; - 内存历史也有上限——
historyLimit(默认 200 条,恢复存档时同样生效), 超长会话不会把页面内存拖爆。
ts
mountChat(el, {
endpoint: "/api/chat",
initialHistory: JSON.parse(localStorage.getItem("chat") || "[]"),
onHistoryChange: (msgs) => localStorage.setItem("chat", JSON.stringify(msgs)),
persistMaxChars: 128 * 1024, // localStorage 场景建议调小
});onHistoryChange 在每轮对话完成和清空后触发,systemPrompt 不在内(它本来 就不进历史)。createChatStore 的预算参数:maxChars(默认 5MB)、 maxMessages(默认 2000),超出从最旧一轮裁起。
4. 组合 JSAPI:让对话知道「谁在用」
ts
if (jltSdk.isInJltClient()) {
const ctx = await jltSdk.ready();
mountChat(el, {
endpoint: "/api/chat",
headers: { "x-user-id": ctx.user.user_id }, // 中继侧自行验真(不能只信头部!)
systemPrompt: `当前用户:${ctx.user.nickname}。`,
});
}WARNING
headers 里的用户标识只是提示——中继必须用可信方式识别用户(例如先走 免登给自己发会话 cookie,再带 cookie 调中继),否则任何人都能 伪造别人的身份跟你聊天。
5. 想改样式?
组件的类名都以 jltc- 开头(jltc/jltc-list/jltc-bubble user|assistant/ jltc-input…),直接在页面里覆盖即可:
css
.jltc { border-radius: 16px; }
.jltc-bubble.user { background: #10b981; }