12 KiB
圆桌规划抽屉 · wujie 嵌入使用说明
把 DeerFlow 的「圆桌规划」页用无界(wujie)嵌进本 Vue3 工程的一个右侧抽屉里:宿主传入 任务信息与业务链条,抽屉打开即自动开跑,第二步达成共识后把各智能体交付结果回传给宿主。
- 抽屉组件:
src/roundtable-embed/RoundtableDrawer.vue - 演示入口:
src/App.vue顶部页签「圆桌规划抽屉(wujie)」 - 子应用(DeerFlow)侧入口:
/#/embed/roundtable(只渲染圆桌主体、无侧边栏、用户名登录)
1. 快速开始
1.1 安装 wujie 依赖(内网离线)
见 offline-deps/wujie/README.md。一键:
./offline-deps/wujie/install-offline.ps1 # Windows
bash ./offline-deps/wujie/install-offline.sh # Linux/macOS
1.2 启动联调
- 启 DeerFlow 前端(被嵌的子应用):
cd frontend-web && pnpm dev(默认端口 5174)。 - 启本工程:
pnpm dev,打开后切到「圆桌规划抽屉(wujie)」页签。 - 填写:DeerFlow 前端站点源(如
http://localhost:5174)、用户名、密码/口令、任务名称/描述、 业务链条 chainId、任务 id、主题(默认深蓝)→ 点「打开圆桌规划抽屉」。
2. 组件用法
<script setup lang="ts">
import { ref } from "vue";
import { RoundtableDrawer } from "@/roundtable-embed";
import type { RoundtableConsensusPayload } from "@/roundtable-embed";
const open = ref(false);
function onConsensus(p: RoundtableConsensusPayload) {
// 第二步达成共识 → 拿到各智能体交付
console.log(p.consensusText, p.deliverables);
}
</script>
<template>
<RoundtableDrawer
v-model:open="open"
base-url="http://localhost:5174"
username="zhangsan"
password="123ewq"
task-title="XX 产品出海方案规划"
task-goal="背景/目标/约束……"
chain-id="chain_xxx"
task-id="task-001"
theme="dark"
@consensus="onConsensus"
/>
</template>
Props
| Prop | 类型 | 必填 | 说明 |
|---|---|---|---|
open (v-model:open) |
boolean |
✓ | 抽屉开关 |
baseUrl |
string |
✓ | DeerFlow 前端站点源(如 http://localhost:5174),不是后端地址 |
username |
string |
登录用户名(决定登录哪个账号) | |
password |
string |
用户名+密码登录的共享口令;后端开启 username_login.require_password 时必填 |
|
taskTitle |
string |
任务名称(注入子应用第一步首条消息) | |
taskGoal |
string |
任务描述(注入子应用第一步首条消息) | |
chainId |
string |
指定业务链条 id,子应用据此自动选链进第二步 | |
taskId |
string |
任务 id,聊天记录按此存取;该任务有记录则打开时回显最新一条 | |
theme |
string |
主题色,默认 dark(深蓝);传 light 走浅色 |
|
degrade |
boolean | 'auto' |
无界 iframe 降级,默认 auto:跨域时自动降级(Nginx 不能配 CORS 时靠这个) |
事件
| 事件 | 载荷 | 说明 |
|---|---|---|
update:open |
boolean |
抽屉关闭时触发 |
consensus |
RoundtableConsensusPayload |
第二步达成共识时触发,携带各智能体交付 |
RoundtableConsensusPayload:
interface RoundtableConsensusPayload {
taskTitle: string | null;
chainId: string | null;
chainTitle: string | null;
coordinatorName: string | null; // 总控名
consensusText: string; // 总控最终共识正文
deliverables: Array<{ // 各智能体交付(剔除总控)
agentName: string;
content: string;
threadId: string | null;
}>;
}
3. 父 ↔ 子 通信契约
底层是无界事件总线 bus(wujie),抽屉组件已封装好,宿主一般无需直接接触。
- 父 → 子(props 注入):
window.$wujie.props = { taskTitle, taskGoal, chainId, taskId, username, theme }。 子应用首帧从readWujieProps()读取(DeerFlowcore/embed/wujie.ts)。 - 子 → 父(共识回传):子应用
bus.$emit('roundtable:consensus', payload),抽屉bus.$on监听后 转成@consensus事件给宿主。 - 父 → 子(重置,保活复用时用):
bus.$emit('roundtable:reset')→ 子应用回到第一步。 本抽屉默认alive=false(关闭即卸载、下次打开是干净新实例),通常用不到。
4. 行为说明
4.1 自动化流程
- 抽屉打开 → 子应用
/#/embed/roundtable用username+password登录、应用主题。 - 带
taskId时先按任务取最新草稿:- 有历史 → 回显最新一条聊天记录(跳到对应步骤),不再自动发首条消息。
- 无历史 → 自动把「任务名称/描述」作为第一步首条消息发出,触发任务理解分析。
- 用户点「进入多智能体研讨」→ 若带了
chainId,跳过选链弹窗、按该业务链条进第二步。 - 第二步总控自然收敛达成共识 → 回传
roundtable:consensus。
4.2 聊天记录持久化(按任务 id,不分权)
- 传了
taskId后,子应用产生的草稿(第一步意图 + 第二步圆桌)都绑定该 task_id。 - 这类草稿按 task_id 共享、不按登录用户分权:同一
taskId不论谁打开,都能读到/续写同一份; 打开时默认回显该任务最新一条记录。 - 未传
taskId的草稿仍按登录用户隔离(原有行为不变)。 - 后端:
roundtable_drafts表新增task_id列;接口GET /api/roundtable-drafts/by-task/{task_id}/latest。 老库需跑offline-backend-20260512/backend/scripts/sql/column_additions_mysql.sql补列(或新库create_all自动加列),并重启 Gateway。
4.3 主题色
默认 深蓝(dark);其主色为蓝(子应用 dark 主题)。宿主传 theme="light" 可切浅色。
子应用侧的主题映射在
EmbedRoundtablePage:dark/dark-blue/空 → 深蓝,light/红白→ 浅色。
5. 联调注意
baseUrl是前端站点源(如http://localhost:5174),不要填后端 8001。- 登录(用户名+密码):内网走
POST /api/v1/auth/login/username——username决定登录哪个账号 (不存在则自动注册),password是后端配置的单一共享口令(仅auth_login.username_login.require_password开启时校验,默认口令123ewq),它只是门禁、不区分账号。另一种 token 登录(login/token)本嵌入不使用。 - chainId / taskId 要用后端真实存在的业务链 id / 业务方自有的任务 id。
- 高度自适应:抽屉已把无界容器逐层撑满(
WujieVue width/height=100%+<wujie-app>/iframe铺满),内容超出时由圆桌页内部各列滚动。
5.1 跨域(wujie 三层 origin)
宿主(本工程,默认 5183)嵌 DeerFlow 前端(5174),子应用再调 Gateway(8001), 三层 origin 不同,跨域要分别处理:
| 现象 | 失败 URL 特征 | 处理 |
|---|---|---|
| 抽屉空白 / 子应用 JS 拉不下来 | 5174 的 html、.js |
子应用 Vite 开 cors: true(已配);宿主 WujieVue 用 :fetch="wujieCredentialsFetch"(已配,见 wujie-fetch.ts) |
| 一直「正在进入圆桌…」/ 登录失败 | 8001 的 /api/... |
Gateway GATEWAY_CORS_ORIGINS 加入宿主页 origin(含 localhost 与 127.0.0.1 两种写法),改后重启 Gateway |
本地 Gateway 示例(offline-backend-20260512/.env):
GATEWAY_CORS_ORIGINS=http://localhost:5174,http://127.0.0.1:5174,\
http://localhost:5183,http://127.0.0.1:5183
排查:DevTools → Network,看第一个红字请求的 URL 与 Request Headers 里的 Origin,
把该 origin 原样加入 GATEWAY_CORS_ORIGINS。
常见坑:
localhost与127.0.0.1算不同 origin,白名单要各写一条。baseUrl端口须与子应用 Vite 实际端口一致(默认 5174;仅pnpm dev时看终端Local:行)。- 浏览器地址栏用哪种 hostname 打开宿主,Gateway 就要允许哪种(例如用
127.0.0.1:5183打开就要加http://127.0.0.1:5183)。
生产推荐:Nginx 同源反代,把宿主、DeerFlow 前端、Gateway API 挂到同一域名下
(如 /、/deerflow/、/deerflow/api/),baseUrl 改为反代后的子应用路径,可免大部分 CORS。
子应用静态站点勿设 X-Frame-Options: DENY;若用 credentials,勿对子应用资源回 Access-Control-Allow-Origin: *。
5.2 Nginx 不能改 CORS 时(推荐)
运维无法给 DeerFlow 静态站加 Access-Control-Allow-Origin 时,不要依赖无界默认的
「父页面 fetch 子应用 JS」模式——一定会跨域失败。改用下面两种方案之一:
方案 A:无界 iframe 降级(默认已开启,零 Nginx 改动)
RoundtableDrawer 默认 degrade="auto":检测到 baseUrl 与宿主不同源时自动走 iframe 降级。
| 对比 | 默认模式(fetch 注入) | iframe 降级(degrade: true) |
|---|---|---|
| 子应用 HTML/JS 谁加载 | 宿主跨域 fetch | 浏览器 iframe 直接导航 |
| 是否需要子站 Nginx CORS | 需要 | 不需要 |
| props / bus 通信 | 支持 | 支持 |
| 子应用调 API 的 Origin | 往往是宿主域名 | 子应用域名 |
因此生产跨域嵌入时,Gateway 只需允许 DeerFlow 前端域名(子应用 iframe 的 origin), 不必再为宿主域名加 CORS——前提是 DeerFlow 打包时 API 与子应用同源,例如:
# frontend-web/.env.production / .env.sit
VITE_BACKEND_BASE_URL=https://ch1.b.sit.4.cn/deerflow
VITE_LANGGRAPH_BASE_URL=https://ch1.b.sit.4.cn/deerflow/api
显式强制降级(跨域场景一般不必写,auto 即可):
<RoundtableDrawer degrade :base-url="https://ch1.b.sit.4.cn/deerflow" ... />
注意:子站若配置了 X-Frame-Options: DENY 或 CSP frame-ancestors 'none',
iframe 会被浏览器拦截——这与 CORS 无关,需运维放开「允许被 iframe 嵌入」(或去掉该响应头)。
方案 B:同源静态目录(彻底无跨域)
若能在同一域名下多挂一个静态目录(只加 location 指到 dist,不必配 CORS 头),
把 DeerFlow 前端 dist 部署到宿主站点下,例如:
https://portal.example.com/ → 宿主 Vue
https://portal.example.com/deerflow/ → DeerFlow dist(复制或 CI 同步)
宿主 baseUrl 填相对路径:
<RoundtableDrawer base-url="/deerflow" ... />
同源时 degrade 自动为 false,走无界常规模式,无任何跨域问题。
6. 目录
parallel-agents-vue/
├── src/roundtable-embed/
│ ├── RoundtableDrawer.vue # 抽屉组件(wujie 嵌入 + props/bus 通信)
│ ├── wujie-fetch.ts # 跨域 fetch(credentials: include,非降级时用)
│ ├── wujie-origin.ts # 同源/跨域判断、相对 baseUrl、降级 query 兜底
│ ├── resolve-wujie-degrade.ts
│ └── index.ts # 导出组件 + 类型
├── src/App.vue # Demo 页签入口
├── offline-deps/wujie/ # wujie 离线依赖包(内网安装)
│ ├── *.tgz # wujie-vue3 / wujie / @babel/runtime
│ ├── install-offline.ps1/.sh
│ └── README.md
└── docs/圆桌规划抽屉-wujie-嵌入使用说明.md # 本文