wujie应用联调
This commit is contained in:
1 parent
0784f30e23
commit
f3ca996116
35 files changed
+1590
-334
No files matched your search
@@ -0,0 +1,250 @@
|
||||
# 圆桌规划抽屉 · 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/README.md)。一键:
|
||||
```powershell
|
||||
./offline-deps/wujie/install-offline.ps1 # Windows
|
||||
```
|
||||
```bash
|
||||
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. 组件用法
|
||||
|
||||
```vue
|
||||
<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`:
|
||||
```ts
|
||||
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`):
|
||||
|
||||
```bash
|
||||
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 与子应用同源,例如:
|
||||
|
||||
```bash
|
||||
# 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` 即可):
|
||||
|
||||
```vue
|
||||
<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` 部署到宿主站点下,例如:
|
||||
|
||||
```text
|
||||
https://portal.example.com/ → 宿主 Vue
|
||||
https://portal.example.com/deerflow/ → DeerFlow dist(复制或 CI 同步)
|
||||
```
|
||||
|
||||
宿主 `baseUrl` 填**相对路径**:
|
||||
|
||||
```vue
|
||||
<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 # 本文
|
||||
```
|
||||
Reference in new issue
Block a user