Files
deerflow-code/parallel-agents-vue/docs/07-使用说明.md
T
2026-06-10 12:51:13 +08:00

24 KiB
Raw Blame History

使用说明 · 在其它 Vue 项目中引入

本组件库只对外暴露一个组件 ParallelAgentsPanel(其余 MessageBubble / MessageStepsCard / ParallelSeatStrip / Avatar / MarkdownContent 都是它的内部子组件,无需也不要单独引入)。 宿主只管把它放进自己的页面 / 抽屉 / 弹窗,并传几个 prop。

⚠️ 契约变更(纯展示版):面板不再自带顶部表单与按钮。

  • 任务名称 / 简介 / 模型 全部由宿主通过 props 传入(taskName / brief 支持 v-model);
  • 「重新分析 / 保存 / 停止」按钮由宿主自己渲染,通过模板 ref 调用面板 defineExpose 出的 reanalyze() / save() / cancel() 等方法(见 §六·二);
  • 面板本体只展示「席位状态条 + 并行会话 + 错误提示」。

完整的宿主参考实现见仓库 src/App.vue(弹窗里宿主自渲染工具条 + 调用 ref 方法)。


一、要引入的东西(就这两样)

  1. 组件:ParallelAgentsPanel
  2. 样式:parallel-agents-vue/style.css(一次性引入即可,样式作用域全在 .parallel-agents 下)

依赖说明:vue ^3.4(宿主提供,库不打包 vue);markdown-it 已打进库内,无需宿主安装。 无需 Tailwind 或任何 CSS 框架:布局样式已内置为 parallel-agents.css 末尾的「最小工具类子集」 (全部作用域在 .parallel-agents 下,不污染宿主)。宿主项目不用装 / 配 Tailwind。


二、两种引入方式

方式 A:构建成库后按包引入(推荐)

  1. 在本项目构建:
    pnpm install
    pnpm build:lib      # 产出 dist/parallel-agents-vue.{js,umd.cjs,css}
    
  2. 宿主项目里安装(任选其一):
    • 把本目录作为本地依赖:"parallel-agents-vue": "file:../parallel-agents-vue";
    • 或发到你们的私有 npm 后正常 pnpm add parallel-agents-vue。
  3. 代码里引入:
    import { ParallelAgentsPanel } from "parallel-agents-vue";
    import "parallel-agents-vue/style.css";
    

方式 B:直接拷贝源码(无需发包,推荐给需要改源码的人)

所有实现已收进一个自包含文件夹 src/parallel-agents/。把整个文件夹拷进宿主项目的 src/ 下即可,内部全是相对路径引用、不依赖任何构建别名:

src/parallel-agents/          ← 整个文件夹拷走即用
  index.ts                    入口:自动引样式 + 导出组件/类型
  ParallelAgentsPanel.vue     主组件
  ParallelSeatStrip.vue · MessageBubble.vue · MessageStepsCard.vue · StepIcon.vue
  ClarificationCard.vue · MarkdownContent.vue · Avatar.vue   (子组件)
  useParallelRun.ts           并行运行编排
  api.ts · types.ts · config.ts · avatar-constants.ts · step-display.ts
  parallel-agents.css         作用域样式(深色 token + 语义类 + 工具类子集,无 Tailwind)
  README.md                   本文件夹的速用说明

引入(样式由 index.ts 自动引入,无需手动 import css):

import { ParallelAgentsPanel } from "@/parallel-agents";
// 或相对路径: import { ParallelAgentsPanel } from "./parallel-agents";

宿主需有 markdown-it 依赖(pnpm add markdown-it),并保证构建支持 .vue(Vite/@vitejs/plugin-vue)。 不需要 Tailwind。


三、注册与使用

局部引入(推荐)

宿主持有任务名 / 简介 / 模型,并自渲染按钮调用面板暴露的方法:

<script setup lang="ts">
import { ref } from "vue";
import { ParallelAgentsPanel } from "parallel-agents-vue";
import "parallel-agents-vue/style.css";

const agents = [
  { agent_id: "roundtable-solution-design", name: "方案设计" },
  { agent_id: "roundtable-risk-review", name: "风险审查" },
];

// 宿主持有这三项(面板不再渲染输入框)
const taskName = ref("");
const brief = ref("");
const model = ref("");

// 模板 ref:拿到面板暴露的方法 / 状态
const panel = ref<InstanceType<typeof ParallelAgentsPanel> | null>(null);
</script>

<template>
  <div style="display: flex; flex-direction: column; height: 80vh">
    <!-- 宿主自己的工具条 -->
    <input v-model="taskName" placeholder="任务名称" />
    <textarea v-model="brief" placeholder="任务简介" />
    <button :disabled="!panel?.canStart" @click="panel?.reanalyze()">
      {{ panel?.hasResults ? "重新分析" : "开始分析" }}
    </button>
    <button :disabled="panel?.saving || !panel?.hasResults" @click="panel?.save()">保存</button>

    <!-- 面板:纯展示,需父容器给高度 -->
    <ParallelAgentsPanel
      ref="panel"
      style="flex: 1; min-height: 0"
      :username="'demo'"
      :agents="agents"
      v-model:task-name="taskName"
      v-model:brief="brief"
      :model="model"
    />
  </div>
</template>

面板暴露出的 running / saving / hasResults / canStart 等是 ref,在组件实例上会自动解包, 模板里直接 panel?.canStart 读,无需 .value。

全局注册(可选)

import ParallelAgentsVue from "parallel-agents-vue";
import "parallel-agents-vue/style.css";
app.use(ParallelAgentsVue); // 之后任意模板可直接用 <ParallelAgentsPanel/>

四、放进抽屉 / 弹窗

组件根元素是 h-full w-full flex flex-col min-h-0,会充满父容器。所以父容器要给一个高度 (弹窗/抽屉本身有高度即可):

<YourDrawer v-model="open" size="large">
  <div style="display: flex; flex-direction: column; height: 86vh">
    <!-- 宿主工具条:任务名/简介/模型 + 按钮,由你自己渲染 -->
    <MyToolbar v-model:task-name="taskName" v-model:brief="brief"
               @start="panel?.reanalyze()" @save="panel?.save()" />
    <ParallelAgentsPanel
      ref="panel"
      style="flex: 1; min-height: 0"
      :username="currentUsername"
      :agents="agents"
      v-model:task-name="taskName"
      v-model:brief="brief"
      :model="model"
      @saved="onSaved"
    />
  </div>
</YourDrawer>

五、接口地址与登录

  • 接口地址:默认读 src/config.ts 的 DEFAULT_BASE_URL(可改源码,或 dev 设 VITE_PA_BASE_URL); 也可用 :base-url prop 临时覆盖。
  • 登录:把宿主的用户名用 :username 传进来即可——组件自动走本面板专用的「绕过常规登录」 公共端点 POST /api/parallel-agents/auth/token 换 token、缓存、401 自动重登。宿主不用管 token。 若已有 token,可改传 :token / :get-token(优先级更高)。
    • 为什么专用端点:内网部署对标准 /api/v1/auth/login/username 有额外限制(口令门/网关裁剪等),拿不到 token;该端点传用户名直接签发(账号不存在自动注册),已在 auth/csrf 中间件白名单里放行。
  • token 缓存按 baseUrl::username 维度,多个实例/多用户互不串。

六、Props

prop 类型 必填 说明
agents { agent_id, name, avatarType? }[] ✔ 要并行调用的智能体(agent_id 必须是真实 id)
username string 免密自动登录用户名(推荐方式)
baseUrl string 后端地址;不传用 config.ts 默认
token string 已有 Bearer token(优先于 username)
getToken () => string | Promise<string> 动态取 token
loginPassword string 仅当后端开启口令门时需要
taskId string 传入则挂载时自动加载并回显该任务
taskName string 任务名称(宿主持有),支持 v-model:task-name
brief string 任务简介(宿主持有),支持 v-model:brief
model string 模型名(宿主持有;空 = 默认 / 后端回退)
dark boolean(默认 true) 深色面板(自带成色,不依赖宿主主题)

taskName / brief 用 v-model 是为了回显:加载已保存任务时,面板会通过 update:taskName / update:brief 把存档里的标题 / 简介回传给宿主,宿主据此同步自己的输入框。

AgentInput/SavedTask/AgentResult 等类型也从包里导出: import type { AgentInput, SavedTask, AgentResult } from "parallel-agents-vue"。


六·二、暴露的方法与状态(模板 ref)

面板通过 defineExpose 暴露下列方法与状态,供宿主用模板 ref 调用 / 读取。 拿 ref:const panel = ref<InstanceType<typeof ParallelAgentsPanel> | null>(null),模板 <ParallelAgentsPanel ref="panel" .../>。

方法

方法 等价于 说明
reanalyze() 旧「重新分析」按钮 用当前 taskName/brief/model 开始 / 重跑一轮
start() 同 reanalyze() 别名,语义同上
save() 旧「保存」按钮 保存 / 更新当前聊天记录(按 taskId upsert)
cancel() 旧「停止」按钮 中止本轮并行运行
loadTask(id) 旧「历史」选择 加载并回显某条任务(同时回传标题/简介)
refreshHistory() — 重新拉取历史列表(结果在 history 里)
getResults() — 取所有智能体返回内容 AgentResult[]
getResult(id) — 取某个智能体返回内容 AgentResult | null

状态(ref,组件实例上自动解包,模板里直接读无需 .value)

状态 类型 说明
running boolean 是否正在并行运行(用于切「停止」按钮)
saving boolean 是否正在保存
hasResults boolean 是否已有结果(「开始」/「重新」文案 & 保存可用)
canStart boolean 是否满足开始条件(有 agents + 任务名或简介)
history SavedTask[] 已保存任务列表(宿主可自建历史下拉)
errorMsg string | null 最近一次错误(面板内也会展示一条)
<script setup lang="ts">
const panel = ref<InstanceType<typeof ParallelAgentsPanel> | null>(null);
// JS 里读 ref 状态要带 .value:
function onClickStart() {
  if (panel.value?.canStart) panel.value.reanalyze();
}
</script>
<template>
  <!-- 模板里自动解包,直接读 -->
  <button :disabled="!panel?.canStart" @click="panel?.reanalyze()">
    {{ panel?.hasResults ? "重新分析" : "开始分析" }}
  </button>
</template>

六·一、每个参数怎么传(逐个举例)

agents(必填)——「智能体参数」怎么传

一个数组,每项 { agent_id, name?, avatarType? }:

const agents = [
  { agent_id: "roundtable-solution-design", name: "方案设计" },                 // 最常用:id + 显示名
  { agent_id: "roundtable-risk-review", name: "风险审查", avatarType: "amber" }, // 顺带指定头像配色
  { agent_id: "demo-expert" },                                                  // 只给 id(显示名回退成 id)
];
<ParallelAgentsPanel :agents="agents" :username="'demo'" />
  • agent_id必须是后端真实注册的 id(只写名字无效);可用 id 见 06-可用智能体清单.md。
  • name:席位/气泡显示名,可省(省了用 id)。
  • avatarType:可选 blue|purple|emerald|amber|pink|human;不传按顺序自动轮转配色。

username——免密登录(推荐)

<ParallelAgentsPanel :username="currentUser.name" :agents="agents" />

baseUrl——后端地址(不传用 config 默认)

<ParallelAgentsPanel :base-url="'http://10.0.0.5:8001'" :username="'demo'" :agents="agents" />

token / getToken——已有登录态时(优先于 username)

<!-- 直接给字符串 token -->
<ParallelAgentsPanel :token="hostToken" :agents="agents" />
<!-- 或给一个(异步)取 token 的函数 -->
<ParallelAgentsPanel :get-token="() => authStore.getAccessToken()" :agents="agents" />

model——指定模型(宿主持有;不传走默认/后端回退)

<ParallelAgentsPanel :model="model" :username="'demo'" :agents="agents" />

模型下拉已移到宿主侧:宿主自己渲染下拉(可调后端 /api/models),把选中的模型名用 :model 传进来。

taskName / brief——任务名 / 简介(宿主持有,支持 v-model)

<ParallelAgentsPanel
  v-model:task-name="taskName"
  v-model:brief="brief"
  :username="'demo'"
  :agents="agents"
/>

用 v-model 是为了回显:加载已保存任务时面板会把存档标题 / 简介回传给宿主。

dark——深色(默认 true,自带成色)

<ParallelAgentsPanel :dark="true" :username="'demo'" :agents="agents" />

taskId——存储主键 + 回显(见 §七·二)

<ParallelAgentsPanel
  :task-id="taskId"
  :username="'demo'"
  :agents="agents"
  @update:taskId="(id) => (taskId = id)"
/>

全量示例(所有参数一起)

<script setup lang="ts">
import { ref } from "vue";
import { ParallelAgentsPanel } from "parallel-agents-vue";
import "parallel-agents-vue/style.css";
import type { AgentInput, AgentResult, SavedTask } from "parallel-agents-vue";

const agents: AgentInput[] = [
  { agent_id: "roundtable-solution-design", name: "方案设计" },
  { agent_id: "roundtable-risk-review", name: "风险审查" },
];
const taskId = ref("task-001");
const taskName = ref("");
const brief = ref("");
const model = ref("deepseek-chat");
const panel = ref<InstanceType<typeof ParallelAgentsPanel> | null>(null);

function onSaved(t: SavedTask) {
  console.log("已保存", t.id);
}
function onCompleted(results: AgentResult[]) {
  console.log("各智能体返回内容", results);
}
</script>

<template>
  <div style="display: flex; flex-direction: column; height: 86vh">
    <!-- 宿主工具条 -->
    <input v-model="taskName" placeholder="任务名称" />
    <textarea v-model="brief" placeholder="任务简介" />
    <button v-if="!panel?.running" :disabled="!panel?.canStart" @click="panel?.reanalyze()">
      {{ panel?.hasResults ? "重新分析" : "开始分析" }}
    </button>
    <button v-else @click="panel?.cancel()">停止</button>
    <button :disabled="panel?.saving || !panel?.hasResults" @click="panel?.save()">保存</button>

    <ParallelAgentsPanel
      ref="panel"
      style="flex: 1; min-height: 0"
      :base-url="'http://localhost:8001'"
      :username="'demo'"
      :agents="agents"
      :task-id="taskId"
      v-model:task-name="taskName"
      v-model:brief="brief"
      :model="model"
      :dark="true"
      @saved="onSaved"
      @update:taskId="(id) => (taskId = id)"
      @completed="onCompleted"
    />
  </div>
</template>

七、事件

事件 载荷 触发时机
saved SavedTask 保存成功(新建或更新)后;含自动保存
update:taskId string 当前任务 id 变化(保存 / 加载历史时)
update:taskName string 加载已保存任务回显标题(配合 v-model:task-name)
update:brief string 加载已保存任务回显简介(配合 v-model:brief)
agent-completed AgentResult 每个智能体一分析完就回传它单个的结果(各席位各触发一次)
completed AgentResult[] 一轮并行分析全部结束时,回传所有智能体返回内容
<ParallelAgentsPanel
  :username="user"
  :agents="agents"
  :task-id="taskId"
  @saved="(t) => console.log('saved', t.id)"
  @update:taskId="(id) => (taskId = id)"
  @completed="(results) => console.log('各智能体返回:', results)"
/>

七·一、如何获取模型返回的内容

每个智能体(模型)返回的正文是 AgentResult.content。组件会自动把结果推给宿主,无需轮询:

A·1. 单个完成就推:监听 @agent-completed,哪个智能体先分析完就先回传哪个(各触发一次):

<ParallelAgentsPanel
  :username="'demo'"
  :agents="agents"
  @agent-completed="(r) => {
    // r: { agent_id, name, content, status } —— 单个智能体
    console.log('完成一个:', r.name, '→', r.content);
  }"
/>

A·2. 全部完成再推一次:监听 @completed,一轮分析全部结束时回传 AgentResult[]:

<ParallelAgentsPanel
  :username="'demo'"
  :agents="agents"
  @completed="(results) => {
    // results: [{ agent_id, name, content, status }]
    for (const r of results) console.log(r.name, '→', r.content);
  }"
/>

两个事件可同时用:agent-completed 做「来一个渲染一个」的增量更新,completed 做「全部到齐」的汇总。

B. 方法(模板 ref):随时主动取(不必等事件):

<script setup lang="ts">
import { ref } from "vue";
import { ParallelAgentsPanel } from "parallel-agents-vue";
import type { AgentResult } from "parallel-agents-vue";

const panelRef = ref<InstanceType<typeof ParallelAgentsPanel> | null>(null);

function readAll() {
  const all: AgentResult[] = panelRef.value!.getResults();
  // [{ agent_id, name, content, status }]
}
function readOne(id: string) {
  const one = panelRef.value!.getResult(id); // AgentResult | null
}
</script>

<template>
  <ParallelAgentsPanel ref="panelRef" :username="user" :agents="agents" />
</template>

AgentResult = { agent_id, name, content, status },content 即该智能体模型返回的正文(Markdown)。 status 取值:pending | running | done | clarify | error。clarify = 该智能体在等用户协助 (待确认),不是已完成——宿主判断「是否全部完成」时应排除 clarify(例如 results.every(r => r.status === "done")),不要把待协助当成 done。 defineExpose 还暴露了 reanalyze() / start() / save() / cancel() / loadTask(id) 等方法及 running / saving / hasResults / canStart / history 等状态,完整清单见 §六·二。

七·二、如何保存 + 回显聊天记录

保存:宿主调用 panel.save() 即把当前整段聊天记录(各智能体气泡 + 步骤条等)存到后端,按用户隔离。 传 :task-id 时以它为存储主键做 upsert(已存在→更新;不存在→用该 id 新建);不传则首次保存由后端 生成 id(通过 @update:taskId 回传,下次再保存即更新这条)。

自动保存:每轮分析(含单席位重跑)结束后会自动保存一次,宿主无需手动点保存也不丢记录; 自动保存同样走 upsert,并照常触发 saved / update:taskId 事件(首轮会回传新生成的 id)。 panel.save() 仍保留,供宿主想随时手动存档时调用。

进入默认显示最新:组件挂载时若没传 taskId,会自动加载并回显该用户最新的一条聊天记录 (历史按更新时间倒序,取第一条);传了 taskId 则优先回显指定那条。

回显(两种方式):

<!-- 方式 1:挂载时传一个已存在的 taskId → 自动加载并回显该任务的聊天记录 -->
<ParallelAgentsPanel :task-id="'task-001'" :username="'demo'" :agents="agents" />
// 方式 1 变体:运行中改 taskId(双向绑定),面板会自动切到并回显那条任务
const taskId = ref<string>("");          // 留空=新任务
// <ParallelAgentsPanel :task-id="taskId" @update:taskId="id => taskId = id" ... />
taskId.value = "task-001";               // 改了之后面板自动回显 task-001

方式 2:历史下拉已移到宿主侧(面板不再内置)。宿主读 panel.history(SavedTask[])自建下拉, 选中某条时调 panel.loadTask(id) 回显;面板会通过 update:taskName / update:brief 把存档标题 / 简介 回传给宿主,配合 v-model 同步宿主自己的输入框。需要刷新列表调 panel.refreshHistory()。

<template>
  <!-- 宿主自建历史下拉 -->
  <select @change="(e) => panel?.loadTask((e.target as HTMLSelectElement).value)">
    <option v-for="t in panel?.history || []" :key="t.id" :value="t.id">
      {{ t.title || "未命名任务" }}
    </option>
  </select>
</template>

要点:先保存过、再用同一个 taskId(或从历史里选)才会有内容回显;没存过的新 id 第一次进去是空白 (后台会探测一次 GET /…/{id} 返回 404,属正常,面板静默处理)。


八、交互要点(与圆桌 Step2 一致)

  • 宿主备好「任务名称 + 简介(+ 模型)」props → 调 panel.reanalyze() → 各智能体并行输出。 运行期间禁用写文件(write_file/str_replace)与派活/脚本(agent_orchestration/bash, 防子智能体模仿总控调度);检索、查看、ask_clarification 等照常 —— 所以步骤条 / 协作卡片正常 显示,每个智能体只做自己那份分析。
  • 主区每次只显示一个席位的消息列表;点席位头像切换要看哪个(单选)。
  • 顶部席位状态条显示每个智能体的进行中 / 完成 / 待协助 / 出错;待协助数量会单独标「N 待协助」。
  • 用户协助(澄清):智能体要澄清时该席位变「待协助」(不是完成),气泡里弹协作卡片。多个智能体 同时待协助时可分别点协助、互不阻塞(协助 A 不会禁用 B/C 的卡片);提交回答后该席位自动带上 补充重跑。
  • 任务名 / 简介 / 模型 / 按钮 / 历史下拉全部在宿主侧:模型空 = model prop / 配置 / 后端回退; 模型作用于下一次reanalyze()。
  • panel.save() 存档;panel.loadTask(id) 回显;panel.reanalyze() 清空重跑;panel.cancel() 停止。
  • 每轮分析结束自动保存一次;挂载时没传 taskId 会默认回显最新一条记录。
  • 结果自动推给宿主:@agent-completed(来一个推一个)+ @completed(全部到齐再推一次)。

九、注意事项

  • 不会污染宿主全局样式:不依赖 Tailwind,布局类已内置为作用域工具类子集,所有规则都在 .parallel-agents 下,绝不外泄到宿主页面。
  • 深色自带成色:面板始终深蓝深色,不随宿主明暗切换(与圆桌 Step2 观感一致)。
  • 跨域:若宿主与后端不同源,后端需允许该来源的 CORS + 带 credentials;登录端点公共且 CSRF 豁免, 业务请求用 Bearer 头已绕过 CSRF。
  • agent_id 必须真实:见 06-可用智能体清单.md 获取可用 id 与自查方法。