Files
deerflow-code/parallel-agents-vue/docs/圆桌规划抽屉-wujie-嵌入使用说明.md
T
2026-06-11 17:11:28 +08:00

12 KiB
Raw Blame History

圆桌规划抽屉 · 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 启动联调

  1. 启 DeerFlow 前端(被嵌的子应用):cd frontend-web && pnpm dev(默认端口 5174)。
  2. 启本工程:pnpm dev,打开后切到「圆桌规划抽屉(wujie)」页签。
  3. 填写: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() 读取(DeerFlow core/embed/wujie.ts)。
  • 子 → 父(共识回传):子应用 bus.$emit('roundtable:consensus', payload),抽屉 bus.$on 监听后 转成 @consensus 事件给宿主。
  • 父 → 子(重置,保活复用时用):bus.$emit('roundtable:reset') → 子应用回到第一步。 本抽屉默认 alive=false(关闭即卸载、下次打开是干净新实例),通常用不到。

4. 行为说明

4.1 自动化流程

  1. 抽屉打开 → 子应用 /#/embed/roundtable 用 username + password 登录、应用主题。
  2. 带 taskId 时先按任务取最新草稿:
    • 有历史 → 回显最新一条聊天记录(跳到对应步骤),不再自动发首条消息。
    • 无历史 → 自动把「任务名称/描述」作为第一步首条消息发出,触发任务理解分析。
  3. 用户点「进入多智能体研讨」→ 若带了 chainId,跳过选链弹窗、按该业务链条进第二步。
  4. 第二步总控自然收敛达成共识 → 回传 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. 联调注意

  1. baseUrl 是前端站点源(如 http://localhost:5174),不要填后端 8001。
  2. 登录(用户名+密码):内网走 POST /api/v1/auth/login/username——username 决定登录哪个账号 (不存在则自动注册),password 是后端配置的单一共享口令(仅 auth_login.username_login.require_password 开启时校验,默认口令 123ewq),它只是门禁、不区分账号。另一种 token 登录(login/token)本嵌入不使用。
  3. chainId / taskId 要用后端真实存在的业务链 id / 业务方自有的任务 id。
  4. 高度自适应:抽屉已把无界容器逐层撑满(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   # 本文