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 # 本文
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
# wujie 离线依赖包(内网无网安装)
|
||||
|
||||
圆桌规划抽屉(`src/roundtable-embed/`)用无界(wujie)把 DeerFlow 圆桌页嵌进抽屉,需要以下
|
||||
依赖。内网环境无法联网拉包,故把它们及全部传递依赖预先打成 `.tgz` 放在本目录。
|
||||
|
||||
## 依赖清单(共 3 个,已全部含在本目录)
|
||||
|
||||
| 包 | 版本 | 说明 |
|
||||
|----|------|------|
|
||||
| `wujie-vue3` | 2.0.2 | 无界 Vue3 封装组件(`<WujieVue>` + `bus`) |
|
||||
| `wujie` | 2.0.2 | 无界内核(`wujie-vue3` 的依赖) |
|
||||
| `@babel/runtime` | 7.29.7 | `wujie` 的唯一依赖(自身无下游依赖) |
|
||||
|
||||
> `vue@^3` 是 `wujie-vue3` 的 peer 依赖,项目本就有,无需打包。
|
||||
> 经核对,上述 3 个包之外**没有任何其他传递依赖**。
|
||||
|
||||
## 安装(任选一种)
|
||||
|
||||
> 全程不联网。命令里**依赖在前、消费方在后**,让本地 tarball 互相满足 semver 范围。
|
||||
|
||||
### 方式 A:脚本一键装(推荐)
|
||||
```powershell
|
||||
# Windows PowerShell
|
||||
./install-offline.ps1
|
||||
```
|
||||
```bash
|
||||
# Linux / macOS / Git-Bash
|
||||
bash ./install-offline.sh
|
||||
```
|
||||
脚本会自动 `cd` 到项目根、优先用 pnpm(无则 npm)离线安装。
|
||||
|
||||
### 方式 B:手动命令
|
||||
在**项目根目录**(`parallel-agents-vue/`)执行:
|
||||
```bash
|
||||
# pnpm
|
||||
pnpm add --offline \
|
||||
./offline-deps/wujie/babel-runtime-7.29.7.tgz \
|
||||
./offline-deps/wujie/wujie-2.0.2.tgz \
|
||||
./offline-deps/wujie/wujie-vue3-2.0.2.tgz
|
||||
|
||||
# 或 npm
|
||||
npm install --offline \
|
||||
./offline-deps/wujie/babel-runtime-7.29.7.tgz \
|
||||
./offline-deps/wujie/wujie-2.0.2.tgz \
|
||||
./offline-deps/wujie/wujie-vue3-2.0.2.tgz
|
||||
```
|
||||
|
||||
### 方式 C:纯解压兜底(连包管理器都不想用时)
|
||||
npm pack 的 tarball 内层是 `package/` 目录,直接解压改名进 `node_modules`:
|
||||
```bash
|
||||
cd parallel-agents-vue
|
||||
mkdir -p node_modules/@babel
|
||||
tar -xzf offline-deps/wujie/wujie-2.0.2.tgz && rm -rf node_modules/wujie && mv package node_modules/wujie
|
||||
tar -xzf offline-deps/wujie/wujie-vue3-2.0.2.tgz && rm -rf node_modules/wujie-vue3 && mv package node_modules/wujie-vue3
|
||||
tar -xzf offline-deps/wujie/babel-runtime-7.29.7.tgz && rm -rf node_modules/@babel/runtime && mv package node_modules/@babel/runtime
|
||||
```
|
||||
(Win10+/Linux/macOS 均自带 `tar`。这三个包是纯 JS、无原生编译,复制即可用。)
|
||||
|
||||
## 验证
|
||||
```bash
|
||||
node -e "console.log(require('wujie-vue3/package.json').version, require('wujie/package.json').version, require('@babel/runtime/package.json').version)"
|
||||
# 期望输出: 2.0.2 2.0.2 7.29.7
|
||||
```
|
||||
之后 `pnpm dev` / `npm run dev` 打开「圆桌规划抽屉(wujie)」页签即可。
|
||||
|
||||
## 重新生成这些 tarball(有网机器上)
|
||||
```bash
|
||||
cd offline-deps/wujie
|
||||
npm pack wujie@2.0.2 wujie-vue3@2.0.2 @babel/runtime@7.29.7
|
||||
```
|
||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
# 离线安装 wujie 相关依赖(内网无网环境)。
|
||||
# 用法:在本文件所在目录右键「使用 PowerShell 运行」,或:
|
||||
# pwsh ./install-offline.ps1
|
||||
#
|
||||
# 依赖集(已全部打进同目录 .tgz,无其他传递依赖;vue 为已存在的 peer):
|
||||
# wujie-vue3@2.0.2 -> wujie@2.0.2 -> @babel/runtime@7.29.7
|
||||
$ErrorActionPreference = "Stop"
|
||||
$dir = $PSScriptRoot
|
||||
$proj = (Resolve-Path (Join-Path $dir "..\..")).Path
|
||||
|
||||
# 依赖在前、消费方在后,便于本地 tarball 互相满足 semver 范围(不触网)。
|
||||
$tgz = @(
|
||||
(Join-Path $dir "babel-runtime-7.29.7.tgz"),
|
||||
(Join-Path $dir "wujie-2.0.2.tgz"),
|
||||
(Join-Path $dir "wujie-vue3-2.0.2.tgz")
|
||||
)
|
||||
|
||||
Push-Location $proj
|
||||
try {
|
||||
$hasPnpm = $null -ne (Get-Command pnpm -ErrorAction SilentlyContinue)
|
||||
if ($hasPnpm) {
|
||||
Write-Host "[offline] 使用 pnpm 离线安装 wujie 依赖 ..." -ForegroundColor Cyan
|
||||
& pnpm add --offline @tgz
|
||||
} else {
|
||||
Write-Host "[offline] 未找到 pnpm,改用 npm 离线安装 ..." -ForegroundColor Cyan
|
||||
& npm install --offline @tgz
|
||||
}
|
||||
Write-Host "[offline] 完成。可运行 'pnpm dev' / 'npm run dev' 验证。" -ForegroundColor Green
|
||||
}
|
||||
finally { Pop-Location }
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# 离线安装 wujie 相关依赖(内网无网环境)。
|
||||
# bash ./install-offline.sh
|
||||
#
|
||||
# 依赖集(已全部打进同目录 .tgz,无其他传递依赖;vue 为已存在的 peer):
|
||||
# wujie-vue3@2.0.2 -> wujie@2.0.2 -> @babel/runtime@7.29.7
|
||||
set -euo pipefail
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJ="$(cd "$DIR/../.." && pwd)"
|
||||
|
||||
# 依赖在前、消费方在后,便于本地 tarball 互相满足 semver 范围(不触网)。
|
||||
TGZ=(
|
||||
"$DIR/babel-runtime-7.29.7.tgz"
|
||||
"$DIR/wujie-2.0.2.tgz"
|
||||
"$DIR/wujie-vue3-2.0.2.tgz"
|
||||
)
|
||||
|
||||
cd "$PROJ"
|
||||
if command -v pnpm >/dev/null 2>&1; then
|
||||
echo "[offline] 使用 pnpm 离线安装 wujie 依赖 ..."
|
||||
pnpm add --offline "${TGZ[@]}"
|
||||
else
|
||||
echo "[offline] 未找到 pnpm,改用 npm 离线安装 ..."
|
||||
npm install --offline "${TGZ[@]}"
|
||||
fi
|
||||
echo "[offline] 完成。可运行 'pnpm dev' / 'npm run dev' 验证。"
|
||||
Binary file not shown.
Binary file not shown.
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"./style.css": "./dist/parallel-agents-vue.css"
|
||||
},
|
||||
"files": ["dist"],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
@@ -26,7 +28,9 @@
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"markdown-it": "^14.1.0"
|
||||
"markdown-it": "^14.1.0",
|
||||
"wujie": "^2.0.2",
|
||||
"wujie-vue3": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
|
||||
Generated
+34
-225
@@ -11,19 +11,19 @@ importers:
|
||||
markdown-it:
|
||||
specifier: ^14.1.0
|
||||
version: 14.2.0
|
||||
wujie:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
wujie-vue3:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(vue@3.5.35(typescript@5.9.3))
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.0
|
||||
version: 4.3.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0))
|
||||
'@types/markdown-it':
|
||||
specifier: ^14.1.2
|
||||
version: 14.1.2
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.4(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.35(typescript@5.9.3))
|
||||
tailwindcss:
|
||||
specifier: ^4.1.0
|
||||
version: 4.3.0
|
||||
typescript:
|
||||
specifier: ^5.6.0
|
||||
version: 5.9.3
|
||||
@@ -52,6 +52,10 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/runtime@7.29.7':
|
||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.29.7':
|
||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -212,22 +216,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==}
|
||||
cpu: [arm]
|
||||
@@ -366,100 +357,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/node@4.3.0':
|
||||
resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.3.0':
|
||||
resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.3.0':
|
||||
resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.3.0':
|
||||
resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.3.0':
|
||||
resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
|
||||
resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
|
||||
resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.3.0':
|
||||
resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.3.0':
|
||||
resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.3.0':
|
||||
resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.3.0':
|
||||
resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
|
||||
resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.3.0':
|
||||
resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide@4.3.0':
|
||||
resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@tailwindcss/vite@4.3.0':
|
||||
resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==}
|
||||
peerDependencies:
|
||||
vite: ^5.2.0 || ^6 || ^7 || ^8
|
||||
|
||||
'@types/estree@1.0.9':
|
||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||
|
||||
@@ -550,10 +447,6 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
enhanced-resolve@5.23.0:
|
||||
resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
@@ -584,9 +477,6 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
@@ -721,13 +611,6 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tailwindcss@4.3.0:
|
||||
resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==}
|
||||
|
||||
tapable@2.3.3:
|
||||
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -797,6 +680,14 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
wujie-vue3@2.0.2:
|
||||
resolution: {integrity: sha512-RypNFueT1z7Db/f44gfTrRAVJNj5Brq3httVgKpsEZ7UpCrNhjNZkBd+RX+XT4mNMB1L9t3fsO/DuqfhViT7YA==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
wujie@2.0.2:
|
||||
resolution: {integrity: sha512-K4SyJ1/frFOFJK4plE41UJ9Fe26pYGizemz2kXv3A0w+AcEoEVVU4mamHxXU1r/3jq3UXCragOacvPbg5XT9yg==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.29.7': {}
|
||||
@@ -807,6 +698,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.29.7
|
||||
|
||||
'@babel/runtime@7.29.7': {}
|
||||
|
||||
'@babel/types@7.29.7':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.29.7
|
||||
@@ -890,25 +783,8 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
optional: true
|
||||
|
||||
@@ -984,74 +860,6 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/node@4.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.23.0
|
||||
jiti: 2.7.0
|
||||
lightningcss: 1.32.0
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.3.0
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.3.0':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.3.0':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.3.0
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.3.0
|
||||
'@tailwindcss/oxide-darwin-x64': 4.3.0
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.3.0
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.3.0
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.3.0
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.3.0
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.3.0
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.3.0
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.3.0
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.3.0
|
||||
|
||||
'@tailwindcss/vite@4.3.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.3.0
|
||||
'@tailwindcss/oxide': 4.3.0
|
||||
tailwindcss: 4.3.0
|
||||
vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
@@ -1166,12 +974,8 @@ snapshots:
|
||||
|
||||
de-indent@1.0.2: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
enhanced-resolve@5.23.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.3
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
@@ -1215,11 +1019,10 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
jiti@2.7.0: {}
|
||||
jiti@2.7.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
@@ -1269,6 +1072,7 @@ snapshots:
|
||||
lightningcss-linux-x64-musl: 1.32.0
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
optional: true
|
||||
|
||||
linkify-it@5.0.1:
|
||||
dependencies:
|
||||
@@ -1344,10 +1148,6 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
tailwindcss@4.3.0: {}
|
||||
|
||||
tapable@2.3.3: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@@ -1387,3 +1187,12 @@ snapshots:
|
||||
'@vue/shared': 3.5.35
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
wujie-vue3@2.0.2(vue@3.5.35(typescript@5.9.3)):
|
||||
dependencies:
|
||||
vue: 3.5.35(typescript@5.9.3)
|
||||
wujie: 2.0.2
|
||||
|
||||
wujie@2.0.2:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
@@ -10,6 +10,48 @@ import { ParallelAgentsPanel } from "./parallel-agents";
|
||||
import type { AgentInput, AgentReply } from "./parallel-agents";
|
||||
|
||||
import { DEFAULT_BASE_URL } from "./parallel-agents/config";
|
||||
import { RoundtableDrawer } from "./roundtable-embed";
|
||||
import type { RoundtableConsensusPayload } from "./roundtable-embed";
|
||||
|
||||
// ── 顶部页签:在「并行面板 Demo」与「圆桌规划抽屉」之间切换 ──────────────
|
||||
const view = ref<"panel" | "roundtable">("panel");
|
||||
|
||||
// ── 圆桌规划抽屉(wujie 嵌入 DeerFlow)Demo 状态 ───────────────────────────
|
||||
const rt = reactive({
|
||||
// DeerFlow 前端站点源(无界子应用 URL 的源,非后端地址)。
|
||||
baseUrl: localStorage.getItem("rt.baseUrl") || "http://localhost:5174",
|
||||
username: localStorage.getItem("rt.username") || "demo",
|
||||
// 用户名+密码登录的共享口令(后端开启 require_password 时必填)。
|
||||
password: localStorage.getItem("rt.password") || "",
|
||||
taskTitle: localStorage.getItem("rt.taskTitle") || "",
|
||||
taskGoal: localStorage.getItem("rt.taskGoal") || "",
|
||||
chainId: localStorage.getItem("rt.chainId") || "",
|
||||
// 任务 id:聊天记录按此存取、打开时回显最新一条。
|
||||
taskId: localStorage.getItem("rt.taskId") || "",
|
||||
// 主题色:默认深蓝(dark)。
|
||||
theme: localStorage.getItem("rt.theme") || "dark",
|
||||
});
|
||||
const rtOpen = ref(false);
|
||||
const rtConsensus = ref<RoundtableConsensusPayload | null>(null);
|
||||
|
||||
function openRoundtable() {
|
||||
localStorage.setItem("rt.baseUrl", rt.baseUrl);
|
||||
localStorage.setItem("rt.username", rt.username);
|
||||
localStorage.setItem("rt.password", rt.password);
|
||||
localStorage.setItem("rt.taskTitle", rt.taskTitle);
|
||||
localStorage.setItem("rt.taskGoal", rt.taskGoal);
|
||||
localStorage.setItem("rt.chainId", rt.chainId);
|
||||
localStorage.setItem("rt.taskId", rt.taskId);
|
||||
localStorage.setItem("rt.theme", rt.theme);
|
||||
rtConsensus.value = null;
|
||||
rtOpen.value = true;
|
||||
}
|
||||
|
||||
function onRoundtableConsensus(payload: RoundtableConsensusPayload) {
|
||||
// 子应用第二步达成共识 → 收到各智能体交付。这里 demo 仅打印 + 展示。
|
||||
console.log("[demo] roundtable:consensus → 共识达成,各智能体交付:", payload);
|
||||
rtConsensus.value = payload;
|
||||
}
|
||||
|
||||
const cfg = reactive({
|
||||
baseUrl: localStorage.getItem("pa.baseUrl") || DEFAULT_BASE_URL,
|
||||
@@ -65,6 +107,24 @@ function launch() {
|
||||
|
||||
<template>
|
||||
<div style="min-height: 100vh; background: #0a1020; color: #cbd5e1; padding: 24px; font-family: system-ui">
|
||||
<!-- 顶部页签:并行面板 Demo / 圆桌规划抽屉 -->
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px">
|
||||
<button
|
||||
@click="view = 'panel'"
|
||||
:style="{ padding: '6px 14px', borderRadius: '6px', border: 0, cursor: 'pointer', background: view === 'panel' ? '#1677ff' : '#1a2742', color: '#e5edf7' }"
|
||||
>
|
||||
并行多智能体面板
|
||||
</button>
|
||||
<button
|
||||
@click="view = 'roundtable'"
|
||||
:style="{ padding: '6px 14px', borderRadius: '6px', border: 0, cursor: 'pointer', background: view === 'roundtable' ? '#1677ff' : '#1a2742', color: '#e5edf7' }"
|
||||
>
|
||||
圆桌规划抽屉(wujie)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── 页面一:并行多智能体面板 Demo ───────────────────────────────── -->
|
||||
<template v-if="view === 'panel'">
|
||||
<h1 style="font-size: 18px; font-weight: 700; margin-bottom: 12px">并行多智能体面板 · Dev Demo</h1>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px; max-width: 640px">
|
||||
<label>后端 baseUrl
|
||||
@@ -154,5 +214,70 @@ function launch() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ── 页面二:圆桌规划抽屉(wujie 嵌入 DeerFlow) ──────────────────── -->
|
||||
<template v-else>
|
||||
<h1 style="font-size: 18px; font-weight: 700; margin-bottom: 12px">圆桌规划抽屉 · wujie 嵌入 Demo</h1>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px; max-width: 640px">
|
||||
<label>DeerFlow 前端站点源(无界子应用 URL 的源)
|
||||
<input v-model="rt.baseUrl" placeholder="http://localhost:5174" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>用户名(决定登录哪个账号)
|
||||
<input v-model="rt.username" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>密码 / 口令(后端开启 require_password 时必填,单一共享口令)
|
||||
<input v-model="rt.password" type="password" placeholder="未开启门禁可留空" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>任务名称
|
||||
<input v-model="rt.taskTitle" placeholder="例如:XX 产品出海方案规划" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>任务描述
|
||||
<textarea v-model="rt.taskGoal" rows="3" placeholder="把背景、目标、约束写清楚" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px; resize: vertical" />
|
||||
</label>
|
||||
<label>指定业务链条 chainId(直传,子应用据此自动选链进第二步)
|
||||
<input v-model="rt.chainId" placeholder="例如 chain_xxx" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>任务 id(聊天记录按此存取;该任务有记录则打开时回显最新一条)
|
||||
<input v-model="rt.taskId" placeholder="例如 task-001" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px" />
|
||||
</label>
|
||||
<label>主题色(默认深蓝 dark)
|
||||
<select v-model="rt.theme" style="width: 100%; padding: 6px; background: #111c33; color: #e5edf7; border: 1px solid #2d4473; border-radius: 6px">
|
||||
<option value="dark">深蓝(dark)</option>
|
||||
<option value="light">浅色(light)</option>
|
||||
</select>
|
||||
</label>
|
||||
<button @click="openRoundtable" style="align-self: flex-start; padding: 8px 16px; background: #1677ff; color: #fff; border: 0; border-radius: 6px; cursor: pointer">
|
||||
打开圆桌规划抽屉
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 共识回传结果(子应用第二步达成共识后回传各智能体交付) -->
|
||||
<div v-if="rtConsensus" style="margin-top: 16px; max-width: 880px; background: #0d1526; border: 1px solid #1e2d4d; border-radius: 8px; padding: 12px">
|
||||
<div style="font-weight: 700; color: #7ee787; margin-bottom: 8px">✅ 已收到共识回传(roundtable:consensus)</div>
|
||||
<div style="font-size: 13px; color: #9fb0c8; margin-bottom: 6px">
|
||||
链条:{{ rtConsensus.chainTitle || rtConsensus.chainId || "—" }}|总控:{{ rtConsensus.coordinatorName || "—" }}
|
||||
</div>
|
||||
<div style="font-size: 13px; color: #cbd5e1; white-space: pre-wrap; margin-bottom: 8px">{{ rtConsensus.consensusText }}</div>
|
||||
<div v-for="d in rtConsensus.deliverables" :key="d.agentName" style="border-top: 1px solid #1e2d4d; padding-top: 8px; margin-top: 8px">
|
||||
<div style="font-weight: 600; color: #e5edf7">{{ d.agentName }}</div>
|
||||
<div style="font-size: 12px; color: #9fb0c8; white-space: pre-wrap">{{ d.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 抽屉本体:wujie 嵌入 DeerFlow 圆桌规划页 -->
|
||||
<RoundtableDrawer
|
||||
v-model:open="rtOpen"
|
||||
:base-url="rt.baseUrl"
|
||||
:username="rt.username || undefined"
|
||||
:password="rt.password || undefined"
|
||||
:task-title="rt.taskTitle || undefined"
|
||||
:task-goal="rt.taskGoal || undefined"
|
||||
:chain-id="rt.chainId || undefined"
|
||||
:task-id="rt.taskId || undefined"
|
||||
:theme="rt.theme"
|
||||
@consensus="onRoundtableConsensus"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 圆桌规划嵌入抽屉(宿主侧 / 父页面)。
|
||||
*
|
||||
* 用无界(wujie)把 DeerFlow 的「圆桌规划」页塞进一个右侧抽屉:
|
||||
* - 父 → 子:通过 `:props` 注入「任务名称 / 任务描述 / 指定业务链条 chainId / 用户名 / 主题」,
|
||||
* 子应用首帧从 `window.$wujie.props` 读取(见 DeerFlow `core/embed/wujie.ts`)。
|
||||
* 抽屉打开即自动跑第一步分析,点「进入多智能体研讨」按指定链条进第二步。
|
||||
* - 子 → 父:子应用第二步达成共识后 `bus.$emit('roundtable:consensus', payload)`,
|
||||
* 本组件监听后向宿主 `emit('consensus', payload)`,把各智能体交付交还父页面。
|
||||
*
|
||||
* 抽屉关闭即卸载子应用(`alive=false`),下次打开是干净的新实例、读最新 props。
|
||||
*/
|
||||
import { computed, onBeforeUnmount } from "vue";
|
||||
import WujieVue from "wujie-vue3";
|
||||
import { bus } from "wujie";
|
||||
|
||||
/** 单个智能体交付(与 DeerFlow `collectRoundtableDeliverables` 对齐)。 */
|
||||
export interface RoundtableDeliverable {
|
||||
agentName: string;
|
||||
content: string;
|
||||
threadId: string | null;
|
||||
}
|
||||
|
||||
/** 共识回传载荷(与 DeerFlow `emitWujieEvent("roundtable:consensus", …)` 对齐)。 */
|
||||
export interface RoundtableConsensusPayload {
|
||||
taskTitle: string | null;
|
||||
chainId: string | null;
|
||||
chainTitle: string | null;
|
||||
coordinatorName: string | null;
|
||||
consensusText: string;
|
||||
deliverables: RoundtableDeliverable[];
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 抽屉开关(v-model:open)。 */
|
||||
open: boolean;
|
||||
/** DeerFlow 前端站点源,如 http://localhost:5174。 */
|
||||
baseUrl: string;
|
||||
/** 任务名称(注入子应用 Step 1 首条消息)。 */
|
||||
taskTitle?: string;
|
||||
/** 任务描述(注入子应用 Step 1 首条消息)。 */
|
||||
taskGoal?: string;
|
||||
/** 指定业务链条 id(直传,子应用据此自动选链进第二步)。 */
|
||||
chainId?: string;
|
||||
/** 外部任务 id:聊天记录按此存取,打开时回显该任务最新一条记录。 */
|
||||
taskId?: string;
|
||||
/** 登录用户名(决定登录哪个账号)。 */
|
||||
username?: string;
|
||||
/** 用户名+密码登录的共享口令(后端开启 require_password 时必填)。 */
|
||||
password?: string;
|
||||
/** 主题色:默认 dark(深蓝)。 */
|
||||
theme?: string;
|
||||
}>(),
|
||||
{ theme: "dark" },
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:open", v: boolean): void;
|
||||
(e: "consensus", payload: RoundtableConsensusPayload): void;
|
||||
}>();
|
||||
|
||||
/** 无界子应用实例名(同名复用同一沙箱,这里抽屉级唯一即可)。 */
|
||||
const APP_NAME = "deerflow-roundtable";
|
||||
|
||||
/**
|
||||
* 子应用 URL:DeerFlow 前端站点 + 圆桌**嵌入**路由(只渲染主体、无侧边栏,用户名登录)。
|
||||
* 注意走 `/#/embed/roundtable`,而非带 workspace 侧边栏的 `/#/page/workspace/roundtable`。
|
||||
*/
|
||||
const subUrl = computed(() => {
|
||||
const origin = props.baseUrl.replace(/\/+$/, "");
|
||||
return `${origin}/#/embed/roundtable`;
|
||||
});
|
||||
|
||||
/** 注入子应用的 props(DeerFlow `readWujiePrefill()` 读取)。 */
|
||||
const wujieProps = computed(() => ({
|
||||
taskTitle: props.taskTitle,
|
||||
taskGoal: props.taskGoal,
|
||||
chainId: props.chainId,
|
||||
taskId: props.taskId,
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
theme: props.theme,
|
||||
}));
|
||||
|
||||
function close() {
|
||||
emit("update:open", false);
|
||||
}
|
||||
|
||||
function onConsensus(payload: RoundtableConsensusPayload) {
|
||||
emit("consensus", payload);
|
||||
}
|
||||
|
||||
// 订阅子应用共识回传;组件卸载时退订(全局总线,避免重复监听)。
|
||||
bus.$on("roundtable:consensus", onConsensus);
|
||||
onBeforeUnmount(() => bus.$off("roundtable:consensus", onConsensus));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="open" class="rt-drawer-mask" @click.self="close">
|
||||
<aside class="rt-drawer" @click.stop>
|
||||
<header class="rt-drawer__head">
|
||||
<span class="rt-drawer__title">圆桌规划 · {{ taskTitle || "新任务" }}</span>
|
||||
<button class="rt-drawer__close" type="button" @click="close">✕</button>
|
||||
</header>
|
||||
<div class="rt-drawer__body">
|
||||
<WujieVue
|
||||
:name="APP_NAME"
|
||||
:url="subUrl"
|
||||
:props="wujieProps"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:sync="false"
|
||||
:alive="false"
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rt-drawer-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.rt-drawer {
|
||||
width: 92vw;
|
||||
max-width: 1280px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.25);
|
||||
animation: rt-slide-in 0.22s ease-out;
|
||||
}
|
||||
@keyframes rt-slide-in {
|
||||
from {
|
||||
transform: translateX(24px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.rt-drawer__head {
|
||||
flex: 0 0 auto;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.rt-drawer__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
.rt-drawer__close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: #888;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rt-drawer__close:hover {
|
||||
background: #f2f2f2;
|
||||
color: #333;
|
||||
}
|
||||
.rt-drawer__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
/* 让无界容器链逐层铺满抽屉主体,子应用 #root 的 height:100% 才有确定父高度可解析。
|
||||
WujieVue 根 div 已由 width/height props 撑成 100%;这里再兜底注入的 web component
|
||||
主机 <wujie-app> 与退化 iframe,让链路不断。 */
|
||||
.rt-drawer__body :deep(wujie-app),
|
||||
.rt-drawer__body :deep(iframe) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as RoundtableDrawer } from "./RoundtableDrawer.vue";
|
||||
export type {
|
||||
RoundtableConsensusPayload,
|
||||
RoundtableDeliverable,
|
||||
} from "./RoundtableDrawer.vue";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { isCrossOriginBaseUrl } from "./wujie-origin";
|
||||
|
||||
export type WujieDegradeMode = boolean | "auto";
|
||||
|
||||
/** 解析无界是否走 iframe 降级(跨域且 Nginx 未配 CORS 时必须降级)。 */
|
||||
export function resolveWujieDegrade(
|
||||
baseUrl: string,
|
||||
degrade: WujieDegradeMode,
|
||||
): boolean {
|
||||
if (degrade === true) return true;
|
||||
if (degrade === false) return false;
|
||||
return isCrossOriginBaseUrl(baseUrl);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 无界跨域加载子应用 HTML/JS/CSS 时的 fetch 封装。
|
||||
* 宿主与子应用不同源时必须携带 credentials,否则子应用资源或 Cookie 可能拉不下来。
|
||||
* @see https://wujie-micro.github.io/doc/question/
|
||||
*/
|
||||
export function wujieCredentialsFetch(
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> {
|
||||
return window.fetch(input, { ...init, credentials: "include" });
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/** 解析圆桌嵌入完整 URL(支持同源相对路径,如 `/deerflow`)。 */
|
||||
export function resolveRoundtableEmbedUrl(baseUrl: string): string {
|
||||
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
||||
if (!trimmed) return "/#/embed/roundtable";
|
||||
if (trimmed.startsWith("/")) {
|
||||
return `${window.location.origin}${trimmed}/#/embed/roundtable`;
|
||||
}
|
||||
return `${trimmed}/#/embed/roundtable`;
|
||||
}
|
||||
|
||||
/** baseUrl 是否与当前宿主页面不同源(跨域嵌入)。相对路径视为同源。 */
|
||||
export function isCrossOriginBaseUrl(baseUrl: string): boolean {
|
||||
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
||||
if (!trimmed || trimmed.startsWith("/")) return false;
|
||||
try {
|
||||
const target = new URL(trimmed, window.location.href);
|
||||
return target.origin !== window.location.origin;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RoundtableEmbedQuery {
|
||||
taskTitle?: string;
|
||||
taskGoal?: string;
|
||||
chainId?: string;
|
||||
taskId?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
/** iframe 降级模式下把关键参数挂到 hash 路由 query,作 props 注入的兜底。 */
|
||||
export function appendEmbedQueryToUrl(
|
||||
embedUrl: string,
|
||||
fields: RoundtableEmbedQuery,
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(fields)) {
|
||||
if (value != null && String(value).length > 0) {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
const qs = params.toString();
|
||||
if (!qs) return embedUrl;
|
||||
const sep = embedUrl.includes("?") ? "&" : "?";
|
||||
return `${embedUrl}${sep}${qs}`;
|
||||
}
|
||||
Reference in new issue
Block a user