接入与开发
用用户身份嵌入对话和工作区
以当前用户的 AskTable 身份,把对话或项目工作区嵌入你的网站。
用户身份嵌入把 AskTable 页面放进你的网站,并使用当前用户自己的 AskTable 身份:组织、项目和数据权限都与他直接登录 AskTable 时相同。适合内部平台和企业门户。
访客不需要登录、所有人共用一个数据范围时,改用匿名嵌入。
入口说明:项目侧栏 →「Agent 集成」→「在业务系统中集成 AskTable」→「iframe」。
选择方式
| 方式 | 适合 | 页面范围 |
|---|---|---|
| Chat | 在已有业务页面中加入对话,由你的网站管理对话列表 | 当前用户本人的一段对话 |
| Workspace | 把 AskTable 工作区放进内部平台或门户 | 指定组织内的项目工作区,可以切换项目 |
两种方式都需要当前用户的 AskTable 用户凭证(user_token)。你的平台要先通过已有的登录或单点登录流程换取该用户的 AskTable 凭证。不能用 API Key 或共享的管理员凭证代表所有用户。
Chat
-
准备 AskTable 网站地址、组织 ID、项目 ID、智能体 ID 和当前用户的凭证。
-
调用接口列出本人的对话,或指定智能体创建一段对话,取得对话
id:GET /api/v1/conversations?created_by_me=true&types=chat&page=1&size=20&sort=modified_desc Authorization: Bearer <当前用户的 AskTable 凭证> X-Project-Id: <项目 ID>POST /api/v1/conversations Authorization: Bearer <当前用户的 AskTable 凭证> X-Project-Id: <项目 ID> Content-Type: application/json {"type":"chat","data_agent_id":"<智能体 ID>"}示例使用一体化部署的
/api前缀,Cloud 和其他部署按实际 API 地址替换。 -
用对话 ID 构造地址,按下文的握手协议传入凭证:
<网站地址>/user-embed/p/<项目 ID>/conv/<对话 ID>?org_id=<组织 ID>&parent_origin=<编码后的父页面来源>
创建对话不会自动发送问题。切换对话时更新 iframe 地址并重新握手。此入口只能打开本人的对话,管理员也不能借此查看他人的对话。
Workspace
<网站地址>/workspace-embed?org_id=<组织 ID>&parent_origin=<编码后的父页面来源>&project_id=<初始项目 ID>project_id可选,不传时显示用户可访问的项目列表。- 用户按现有权限使用对话、智能体、数据源、技能、项目设置等功能,可以在组织内切换项目;不提供组织切换和系统管理入口。
- 在地址的
#之前加上&layout=content,会隐藏 AskTable 的项目侧栏和移动端菜单按钮,适合由你的网站菜单直接打开某个项目页面,例如...&layout=content#/u/p/<项目 ID>/data/data-source。私有部署需 4.2.76 及以上。
握手协议
两种方式都必须传 org_id 和 parent_origin。parent_origin 取父页面的 location.origin(协议、主机和非默认端口,不带路径和末尾斜杠),用 URLSearchParams 编码。
先注册消息监听,再设置 iframe 地址。 以下是 Workspace 的示例;Chat 使用 Chat 地址,把 mode 换成 chat,并同时核对消息中的项目 ID 和对话 ID。
<iframe id="asktable" title="AskTable" style="width:100%;height:700px;border:0"></iframe>const webBase = 'https://asktable.example.com'; // 可以包含部署子路径,不带末尾斜杠
const askTableOrigin = new URL(webBase).origin;
const orgId = '你的组织 ID';
const projectId = '你的项目 ID';
const frame = document.getElementById('asktable');
let channelId;
let generation = 0;
let closing = false;
window.addEventListener('message', async (event) => {
if (event.origin !== askTableOrigin || event.source !== frame.contentWindow) return;
const m = event.data;
if (!m || m.mode !== 'workspace' || m.org_id !== orgId) return;
if (m.type === 'asktable.ready' && !closing) {
channelId = m.channel_id;
const current = ++generation;
try {
// 由你的应用实现:取得当前用户的 AskTable 用户凭证。
const token = await getCurrentAskTableUserToken();
if (closing || current !== generation) return;
frame.contentWindow.postMessage({
type: 'asktable.authenticate', channel_id: channelId, token
}, askTableOrigin);
} catch (error) {
if (!closing && current === generation) showIntegrationError(error);
}
return;
}
if (!channelId || m.channel_id !== channelId) return;
if (m.type === 'asktable.loaded') hideLoading();
if (m.type === 'asktable.error') showIntegrationError(m.code);
if (m.type === 'asktable.auth_required') {
// 交回登录入口;登录成功后重新加载 iframe,限制重试次数。
++generation;
showSignIn();
}
if (m.type === 'asktable.logged_out') {
++generation;
closing = true;
frame.src = 'about:blank'; // 收到退出确认后才卸载。
}
});
const params = new URLSearchParams({
org_id: orgId, parent_origin: location.origin, project_id: projectId
});
frame.src = `${webBase}/workspace-embed?${params}`;
function logoutAskTable() {
closing = true;
++generation; // 丢弃尚未完成的凭证请求。
if (channelId) frame.contentWindow.postMessage({
type: 'asktable.logout', channel_id: channelId
}, askTableOrigin);
}getCurrentAskTableUserToken、hideLoading、showIntegrationError、showSignIn 由你的应用实现。正式接入时,还应为加载、取凭证和退出设置超时提示;重新加载、换账号或切换对话前,让旧的凭证请求失效。多个 iframe 分别维护各自的握手状态。
| 消息 | 方向 | 含义与处理 |
|---|---|---|
asktable.ready | iframe → 父页面 | 记录新的 channel_id,发送 authenticate |
asktable.authenticate | 父页面 → iframe | 携带当前 channel 和用户凭证;目标来源必须精确匹配,不能用 * |
asktable.authenticated | iframe → 父页面 | 身份确认成功,不代表有权访问目标页面 |
asktable.loaded | iframe → 父页面 | 目标页面的必要数据已就绪,可以移除加载遮罩 |
asktable.auth_required | iframe → 父页面 | 取得新凭证后重新认证;限制重试次数,失败后让用户重新登录 |
asktable.error | iframe → 父页面 | 显示错误或重试入口;权限错误不能靠刷新凭证解决 |
asktable.logout | 父页面 → iframe | 请求退出,携带当前 channel_id |
asktable.logged_out | iframe → 父页面 | 已清除凭证,此后才能销毁 iframe;用户在 iframe 内主动退出时也会发送 |
AskTable 发出的消息都带 mode、org_id 和 channel_id;Chat 还带 project_id、conversation_id。父页面必须核对这些字段、消息来源和发送窗口,只处理当前 channel 的消息。同一用户续期凭证时可能只再次收到 authenticated,输入框中的草稿会保留。
登录与退出
- 验证身份后,凭证保存在 AskTable 自己域名的浏览器存储中。同一浏览器里,iframe 和直接打开的 AskTable 页面共用同一个身份:换账号会覆盖原账号,退出会同时退出两边。不能用来让多个账号并行使用。
- 关闭 iframe 不等于退出。先发送
logout,收到logged_out后再卸载。退出只清除浏览器中的凭证,不会撤销服务端的凭证。 - 凭证不要放进 URL、不要写入日志,也不要保存到你网站的 localStorage。
- 部分浏览器会限制第三方存储,请在实际域名和目标浏览器中验证登录、刷新、续期、换账号和退出。
部署检查
- AskTable Web 容器的
EMBED_ORIGINS默认为*,允许所有 HTTP/HTTPS 父页面。需要限制时改为逗号分隔的精确来源列表;设为空字符串表示禁止握手。放行来源不授予任何用户权限。 - 反向代理或 CDN 要允许
/workspace-embed和/user-embed/被嵌入,检查X-Frame-Options和 CSPframe-ancestors;你的网站的 CSPframe-src也要允许 AskTable。 - HTTPS 页面不能嵌入 HTTP 页面。如果给 iframe 设置了
sandbox,至少需要allow-scripts allow-same-origin,下载按需放行。 - 只通过 postMessage 传凭证时,不需要为你的网站开放 API 跨域。你的网站直接调用 AskTable API 时,另行配置跨域。代理流式回复时需关闭缓冲并允许长连接。
从旧版 Chat 嵌入升级
在 4.2.66 之前接入 Chat 嵌入的平台,升级到 4.2.66 及以上后需要:在地址中补充 org_id;在消息校验中加入 mode=chat 和 org_id;退出时等待 logged_out 再关闭窗口。AskTable 前端和服务端需要同时升级。
验收
在实际域名中验证:无权限的访问会被拒绝;只能打开本人的对话;能切换项目;上传、下载和流式回复正常;登录、刷新、续期、换账号和退出都符合预期。