嵌入到网页
为登录用户签发嵌入令牌,在网页中挂载 PraxisKit 并验证真实对话。
在你的网页中加入 PraxisKit,让登录用户与服务智能体对话。本页用本地订单助手跑通后端签发、前端挂载和身份隔离;其他形态见选择接入方式。
准备 Bun、一个可用的服务智能体及运行环境,以及同组织的 API Key。示例连接 Develop,API Key 仅存为后端环境变量 PRAXIS_API_KEY。浏览器登录宿主后,由宿主核验身份、向 Praxis 签发令牌,再把令牌交给 Kit。
1. 准备智能体
在 Develop 控制台进入服务智能体,选择已有智能体。在接入与配置 → 配置 → 指令保存指令;在配置 → 安全选择运行环境,打开允许 localhost,点保存安全设置。本页宿主页为 http://localhost:4242。请使用专用测试智能体,来源白名单暂留空,仅启用 localhost;令牌继承非空 HTTPS 来源列表时,本地请求会被拒绝。上线到 HTTPS 网站时,添加该网站的来源。
嵌入策略目前只能在控制台配置。复制公开智能体 ID(agent_…)备用。页面显示“已保存,不产生新版本”后,继续实际签发;保存提示本身不能证明对话可用。
2. 创建签发端点
新建目录,把代码保存为 server.ts。这是仅绑定本机的演示服务器,用 HTTP Basic 验证两个测试账户。DEMO_PASSWORD 是你自行设置的测试密码,与 Praxis API Key 无关。服务器从验证成功的账户取用户 ID,不读取浏览器提交的用户 ID。
import { timingSafeEqual } from "node:crypto";
const apiKey = process.env.PRAXIS_API_KEY;
const agentId = process.env.PRAXIS_AGENT_ID;
const password = process.env.DEMO_PASSWORD;
if (!apiKey || !agentId || !password) throw new Error("Set the three server variables");
const origin = "http://localhost:4242";
const noStore = { "Cache-Control": "no-store" };
function authenticate(request: Request): string | undefined {
const supplied = Buffer.from(request.headers.get("Authorization") ?? "");
for (const id of ["customer_123", "customer_456"]) {
const expected = Buffer.from("Basic " + Buffer.from(`${id}:${password}`).toString("base64"));
if (supplied.length === expected.length && timingSafeEqual(supplied, expected)) return id;
}
}
Bun.serve({
hostname: "127.0.0.1", port: 4242,
async fetch(request) {
const url = new URL(request.url);
if (url.origin !== origin) return new Response("Use localhost:4242", { status: 403 });
const userId = authenticate(request);
if (!userId) return new Response("Sign in", {
status: 401, headers: { ...noStore, "WWW-Authenticate": 'Basic realm="Local demo"' },
});
if (request.method === "GET" && url.pathname === "/") {
return new Response(Bun.file("index.html"), { headers: noStore });
}
if (request.method !== "POST" || url.pathname !== "/api/praxis-token") {
return new Response("Not found", { status: 404 });
}
if (request.headers.get("Origin") !== origin) {
return new Response("Origin rejected", { status: 403 });
}
try {
const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({ agent: agentId, external_user_id: userId }),
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) return new Response("Token issuance failed", {
status: response.status, headers: noStore,
});
const data = await response.json();
if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
throw new Error("Invalid token response");
}
return Response.json({ token: data.token, expires_at: data.expires_at }, { headers: noStore });
} catch {
return new Response("Token service unavailable", { status: 502, headers: noStore });
}
},
});
console.log(origin);将三个环境变量放入服务端进程;PRAXIS_AGENT_ID 替换为实际 ID。启动前还需在下一步保存 index.html。
export PRAXIS_AGENT_ID="agent_REPLACE_ME"
# Set PRAXIS_API_KEY and DEMO_PASSWORD in this server process, then run:
bun server.ts期望:终端输出本地网址。未认证请求被宿主拒绝;已认证且同源的请求才会调用 Praxis。Praxis 签发成功返回 HTTP 201;宿主端点只返回 token、expires_at,并禁止缓存。
接入已有应用时,将 authenticate 替换为应用的会话验证,把路由接在登录与 CSRF 保护之后,保留每次签发的身份核验。不要将本地测试账户机制用于线上登录。有效期和刷新见嵌入令牌与用户。
3. 挂载组件
选择一种前端实现。API 地址、CDN 和智能体必须属于同一环境;以下固定使用 Develop 的 Kit 0.2.8。
保存为同目录的 index.html,然后运行上一步启动命令。
<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Order assistant</title>
<script type="module" src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js" integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU" crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<p id="status" role="status"></p>
<script type="module">
await customElements.whenDefined("praxis-agent");
const agent = document.querySelector("praxis-agent");
agent.addEventListener("praxis.error", () => {
document.querySelector("#status").textContent = "Chat unavailable. Check your sign-in and configuration.";
});
agent.setOptions({
api: {
url: "https://api-develop.deepraxis.com",
getToken: async () => {
const response = await fetch("/api/praxis-token", {
method: "POST", credentials: "same-origin", cache: "no-store",
});
if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
const data = await response.json();
if (typeof data.token !== "string" || !data.token) throw new Error("Missing token");
return data.token;
},
},
agent: { name: "Order assistant" },
});
</script>
</html>期望:页面出现高度为 600 像素的对话区。api.url 指定 API 地址,getToken 返回令牌字符串,agent.name 是界面名称。等待元素注册后再调用 setOptions。praxis.ready 只表示组件挂载;必须收到真实回复才能证明接入成功。
4. 验证接入
- 打开
http://localhost:4242,用customer_123和所设测试密码登录。发送“你好,请介绍你能帮助处理的订单问题”,确认收到回复。 - 在网络面板检查宿主令牌请求成功,响应没有 API Key;会话请求使用嵌入令牌。不要把令牌复制到日志或截图。
- 用独立浏览器配置或无痕窗口登录
customer_456,期望看不到第一个账户的会话。不能用两个同登录态的标签页代替此检查。 - 不带凭据请求本地签发端点,验证宿主返回 HTTP 401。已有应用还应验证退出登录后不能再签发。
curl --silent --output /dev/null --write-out "%{http_code}\n" \
-X POST http://localhost:4242/api/praxis-token \
-H "Origin: http://localhost:4242"失败时检查
以下是接口定义的失败分支,按网络面板中的实际响应检查:
- 签发 HTTP 400:确认已保存嵌入策略,运行环境仍可用。
- 签发 HTTP 404:确认 API Key 与服务智能体属于同组织,ID 已替换正确。
- 对话返回
EMBED_ORIGIN_NOT_ALLOWED(HTTP 403):核对实际页面来源;本地应启用 localhost,上线应保存 HTTPS 来源。