# 用用户身份嵌入对话和工作区

以当前用户的 AskTable 身份，把对话或项目工作区嵌入你的网站。

使用指南 · 模块: integrations · 应用开发者 · 部署形态：Cloud / 私有部署 · 已确认产品版本：资料未说明 · 来源：编辑整理 · 按产品 4.2.76-1110（CLI 0.0.18）核对；私有部署的旧版本可能不包含近期变化。

用户身份嵌入把 AskTable 页面放进你的网站，并使用当前用户自己的 AskTable 身份：组织、项目和数据权限都与他直接登录 AskTable 时相同。适合内部平台和企业门户。

访客不需要登录、所有人共用一个数据范围时，改用[匿名嵌入](https://docs.asktable.com/integrations/embed)。

入口说明：项目侧栏 →「Agent 集成」→「在业务系统中集成 AskTable」→「iframe」。

## 选择方式

| 方式        | 适合                       | 页面范围               |
| --------- | ------------------------ | ------------------ |
| Chat      | 在已有业务页面中加入对话，由你的网站管理对话列表 | 当前用户本人的一段对话        |
| Workspace | 把 AskTable 工作区放进内部平台或门户  | 指定组织内的项目工作区，可以切换项目 |

两种方式都需要当前用户的 AskTable 用户凭证（`user_token`）。你的平台要先通过已有的登录或单点登录流程换取该用户的 AskTable 凭证。不能用 API Key 或共享的管理员凭证代表所有用户。

## Chat

1. 准备 AskTable 网站地址、组织 ID、项目 ID、智能体 ID 和当前用户的凭证。

2. 调用接口列出本人的对话，或指定智能体创建一段对话，取得对话 `id`：

   ```http
   GET /api/v1/conversations?created_by_me=true&types=chat&page=1&size=20&sort=modified_desc
   Authorization: Bearer <当前用户的 AskTable 凭证>
   X-Project-Id: <项目 ID>
   ```

   ```http
   POST /api/v1/conversations
   Authorization: Bearer <当前用户的 AskTable 凭证>
   X-Project-Id: <项目 ID>
   Content-Type: application/json

   {"type":"chat","data_agent_id":"<智能体 ID>"}
   ```

   示例使用一体化部署的 `/api` 前缀，Cloud 和其他部署按[实际 API 地址](https://docs.asktable.com/integrations/openapi#%E6%8E%A5%E5%8F%A3%E5%9C%B0%E5%9D%80)替换。

3. 用对话 ID 构造地址，按下文的握手协议传入凭证：

   ```text
   <网站地址>/user-embed/p/<项目 ID>/conv/<对话 ID>?org_id=<组织 ID>&parent_origin=<编码后的父页面来源>
   ```

创建对话不会自动发送问题。切换对话时更新 iframe 地址并重新握手。此入口只能打开本人的对话，管理员也不能借此查看他人的对话。

## Workspace

```text
<网站地址>/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。

```html
<iframe id="asktable" title="AskTable" style="width:100%;height:700px;border:0"></iframe>
```

```js
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` 和 CSP `frame-ancestors`；你的网站的 CSP `frame-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 前端和服务端需要同时升级。

## 验收

在实际域名中验证：无权限的访问会被拒绝；只能打开本人的对话；能切换项目；上传、下载和流式回复正常；登录、刷新、续期、换账号和退出都符合预期。
