代码提交
This commit is contained in:
1 parent
ace30af202
commit
64afa9a1ed
5 files changed
+409
-172
No files matched your search
@@ -4,6 +4,14 @@
|
||||
`ParallelSeatStrip` / `Avatar` / `MarkdownContent` 都是它的内部子组件,**无需也不要单独引入**)。
|
||||
宿主只管把它放进自己的页面 / 抽屉 / 弹窗,并传几个 prop。
|
||||
|
||||
> **⚠️ 契约变更(纯展示版)**:面板**不再自带顶部表单与按钮**。
|
||||
> - **任务名称 / 简介 / 模型** 全部由宿主通过 props 传入(`taskName` / `brief` 支持 `v-model`);
|
||||
> - **「重新分析 / 保存 / 停止」按钮由宿主自己渲染**,通过模板 ref 调用面板 `defineExpose` 出的
|
||||
> `reanalyze()` / `save()` / `cancel()` 等方法(见 [§六·二](#六二暴露的方法与状态模板-ref));
|
||||
> - 面板本体只展示「席位状态条 + 并行会话 + 错误提示」。
|
||||
>
|
||||
> 完整的宿主参考实现见仓库 `src/App.vue`(弹窗里宿主自渲染工具条 + 调用 ref 方法)。
|
||||
|
||||
---
|
||||
|
||||
## 一、要引入的东西(就这两样)
|
||||
@@ -55,8 +63,11 @@ src/
|
||||
|
||||
### 局部引入(推荐)
|
||||
|
||||
宿主持有任务名 / 简介 / 模型,并自渲染按钮调用面板暴露的方法:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ParallelAgentsPanel } from "parallel-agents-vue";
|
||||
import "parallel-agents-vue/style.css";
|
||||
|
||||
@@ -64,15 +75,43 @@ 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="height: 80vh">
|
||||
<ParallelAgentsPanel :username="'demo'" :agents="agents" />
|
||||
<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`**。
|
||||
|
||||
### 全局注册(可选)
|
||||
|
||||
```ts
|
||||
@@ -90,8 +129,20 @@ app.use(ParallelAgentsVue); // 之后任意模板可直接用 <ParallelAgentsPan
|
||||
|
||||
```vue
|
||||
<YourDrawer v-model="open" size="large">
|
||||
<div style="height: 86vh">
|
||||
<ParallelAgentsPanel :username="currentUsername" :agents="agents" @saved="onSaved" />
|
||||
<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>
|
||||
```
|
||||
@@ -122,14 +173,66 @@ app.use(ParallelAgentsVue); // 之后任意模板可直接用 <ParallelAgentsPan
|
||||
| `getToken` | `() => string \| Promise<string>` | | 动态取 token |
|
||||
| `loginPassword` | `string` | | 仅当后端开启口令门时需要 |
|
||||
| `taskId` | `string` | | 传入则挂载时自动加载并回显该任务 |
|
||||
| `model` | `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` | 最近一次错误(面板内也会展示一条) |
|
||||
|
||||
```vue
|
||||
<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`(必填)——「智能体参数」怎么传
|
||||
@@ -171,11 +274,24 @@ const agents = [
|
||||
<ParallelAgentsPanel :get-token="() => authStore.getAccessToken()" :agents="agents" />
|
||||
```
|
||||
|
||||
### `model`——指定模型(不传走默认/后端回退;也可在面板右上角下拉切)
|
||||
### `model`——指定模型(宿主持有;不传走默认/后端回退)
|
||||
|
||||
```vue
|
||||
<ParallelAgentsPanel :model="'deepseek-chat'" :username="'demo'" :agents="agents" />
|
||||
<ParallelAgentsPanel :model="model" :username="'demo'" :agents="agents" />
|
||||
```
|
||||
> 模型下拉已移到宿主侧:宿主自己渲染下拉(可调后端 `/api/models`),把选中的模型名用 `:model` 传进来。
|
||||
|
||||
### `taskName` / `brief`——任务名 / 简介(宿主持有,支持 v-model)
|
||||
|
||||
```vue
|
||||
<ParallelAgentsPanel
|
||||
v-model:task-name="taskName"
|
||||
v-model:brief="brief"
|
||||
:username="'demo'"
|
||||
:agents="agents"
|
||||
/>
|
||||
```
|
||||
> 用 `v-model` 是为了**回显**:加载已保存任务时面板会把存档标题 / 简介回传给宿主。
|
||||
|
||||
### `dark`——深色(默认 true,自带成色)
|
||||
|
||||
@@ -208,6 +324,11 @@ const agents: AgentInput[] = [
|
||||
{ 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);
|
||||
}
|
||||
@@ -217,13 +338,26 @@ function onCompleted(results: AgentResult[]) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 86vh">
|
||||
<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"
|
||||
:model="'deepseek-chat'"
|
||||
v-model:task-name="taskName"
|
||||
v-model:brief="brief"
|
||||
:model="model"
|
||||
:dark="true"
|
||||
@saved="onSaved"
|
||||
@update:taskId="(id) => (taskId = id)"
|
||||
@@ -237,9 +371,12 @@ function onCompleted(results: AgentResult[]) {
|
||||
|
||||
| 事件 | 载荷 | 触发时机 |
|
||||
| ----------------- | ------------------ | --------------------------------------- |
|
||||
| `saved` | `SavedTask` | 保存成功(新建或更新)后 |
|
||||
| `saved` | `SavedTask` | 保存成功(新建或更新)后;**含自动保存** |
|
||||
| `update:taskId` | `string` | 当前任务 id 变化(保存 / 加载历史时) |
|
||||
| `completed` | `AgentResult[]` | 一轮并行分析结束时,回传各智能体返回内容 |
|
||||
| `update:taskName` | `string` | 加载已保存任务回显标题(配合 `v-model:task-name`)|
|
||||
| `update:brief` | `string` | 加载已保存任务回显简介(配合 `v-model:brief`)|
|
||||
| `agent-completed` | `AgentResult` | **每个**智能体一分析完就回传它单个的结果(各席位各触发一次)|
|
||||
| `completed` | `AgentResult[]` | 一轮并行分析**全部**结束时,回传所有智能体返回内容 |
|
||||
|
||||
```vue
|
||||
<ParallelAgentsPanel
|
||||
@@ -254,9 +391,22 @@ function onCompleted(results: AgentResult[]) {
|
||||
|
||||
## 七·一、如何获取模型返回的内容
|
||||
|
||||
每个智能体(模型)返回的正文是 `AgentResult.content`。两种方式,任选其一:
|
||||
每个智能体(模型)返回的正文是 `AgentResult.content`。组件会**自动把结果推给宿主**,无需轮询:
|
||||
|
||||
**A. 事件**:监听 `@completed`,一轮分析结束时自动回传 `AgentResult[]`:
|
||||
**A·1. 单个完成就推**:监听 `@agent-completed`,**哪个智能体先分析完就先回传哪个**(各触发一次):
|
||||
|
||||
```vue
|
||||
<ParallelAgentsPanel
|
||||
:username="'demo'"
|
||||
:agents="agents"
|
||||
@agent-completed="(r) => {
|
||||
// r: { agent_id, name, content, status } —— 单个智能体
|
||||
console.log('完成一个:', r.name, '→', r.content);
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
**A·2. 全部完成再推一次**:监听 `@completed`,一轮分析**全部**结束时回传 `AgentResult[]`:
|
||||
|
||||
```vue
|
||||
<ParallelAgentsPanel
|
||||
@@ -269,6 +419,8 @@ function onCompleted(results: AgentResult[]) {
|
||||
/>
|
||||
```
|
||||
|
||||
> 两个事件可同时用:`agent-completed` 做「来一个渲染一个」的增量更新,`completed` 做「全部到齐」的汇总。
|
||||
|
||||
**B. 方法(模板 ref)**:随时主动取(不必等事件):
|
||||
|
||||
```vue
|
||||
@@ -294,14 +446,22 @@ function readOne(id: string) {
|
||||
```
|
||||
|
||||
`AgentResult = { agent_id, name, content, status }`,`content` 即该智能体模型返回的正文(Markdown)。
|
||||
`defineExpose` 还暴露了 `start()` / `cancel()`,可由宿主代替按钮触发开始 / 停止。
|
||||
`defineExpose` 还暴露了 `reanalyze()` / `start()` / `save()` / `cancel()` / `loadTask(id)` 等方法及
|
||||
`running` / `saving` / `hasResults` / `canStart` / `history` 等状态,完整清单见 [§六·二](#六二暴露的方法与状态模板-ref)。
|
||||
|
||||
## 七·二、如何保存 + 回显聊天记录
|
||||
|
||||
**保存**:面板里点「保存」即把当前整段聊天记录(各智能体气泡 + 步骤条等)存到后端,按用户隔离。
|
||||
**保存**:宿主调用 `panel.save()` 即把当前整段聊天记录(各智能体气泡 + 步骤条等)存到后端,按用户隔离。
|
||||
传 `:task-id` 时**以它为存储主键**做 upsert(已存在→更新;不存在→用该 id 新建);不传则首次保存由后端
|
||||
生成 id(通过 `@update:taskId` 回传,下次再保存即更新这条)。
|
||||
|
||||
> **自动保存**:每轮分析(含单席位重跑)**结束后会自动保存一次**,宿主无需手动点保存也不丢记录;
|
||||
> 自动保存同样走 upsert,并照常触发 `saved` / `update:taskId` 事件(首轮会回传新生成的 id)。
|
||||
> `panel.save()` 仍保留,供宿主想随时手动存档时调用。
|
||||
|
||||
> **进入默认显示最新**:组件挂载时若**没传 `taskId`**,会自动加载并回显**该用户最新的一条**聊天记录
|
||||
> (历史按更新时间倒序,取第一条);传了 `taskId` 则优先回显指定那条。
|
||||
|
||||
**回显(两种方式)**:
|
||||
|
||||
```vue
|
||||
@@ -316,7 +476,20 @@ const taskId = ref<string>(""); // 留空=新任务
|
||||
taskId.value = "task-001"; // 改了之后面板自动回显 task-001
|
||||
```
|
||||
|
||||
> 方式 2:面板**右上角内置「历史」下拉**——点开选一条历史任务即回显,**无需写任何代码**。
|
||||
> 方式 2:**历史下拉已移到宿主侧**(面板不再内置)。宿主读 `panel.history`(`SavedTask[]`)自建下拉,
|
||||
> 选中某条时调 `panel.loadTask(id)` 回显;面板会通过 `update:taskName` / `update:brief` 把存档标题 / 简介
|
||||
> 回传给宿主,配合 `v-model` 同步宿主自己的输入框。需要刷新列表调 `panel.refreshHistory()`。
|
||||
|
||||
```vue
|
||||
<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,属正常,面板静默处理)。
|
||||
@@ -325,14 +498,17 @@ taskId.value = "task-001"; // 改了之后面板自动回显 task-
|
||||
|
||||
## 八、交互要点(与圆桌 Step2 一致)
|
||||
|
||||
- 输入「任务名称 + 简介」→「开始分析」→ 各智能体**并行**输出。运行期间禁用**写文件**(`write_file`/
|
||||
`str_replace`)与**派活/脚本**(`agent_orchestration`/`bash`,防子智能体模仿总控调度);检索、查看、
|
||||
`ask_clarification` 等照常 —— 所以**步骤条 / 协作卡片正常显示**,每个智能体只做自己那份分析。
|
||||
- 宿主备好「任务名称 + 简介(+ 模型)」props → 调 `panel.reanalyze()` → 各智能体**并行**输出。
|
||||
运行期间禁用**写文件**(`write_file`/`str_replace`)与**派活/脚本**(`agent_orchestration`/`bash`,
|
||||
防子智能体模仿总控调度);检索、查看、`ask_clarification` 等照常 —— 所以**步骤条 / 协作卡片正常
|
||||
显示**,每个智能体只做自己那份分析。
|
||||
- 主区每次只显示**一个**席位的消息列表;**点席位头像切换**要看哪个(单选)。
|
||||
- 顶部席位状态条显示每个智能体的进行中 / 完成 / 出错。
|
||||
- 操作区右侧有**模型切换**下拉(拉取后端 `/api/models`);选「默认」则用 `model` prop / 配置 / 后端回退。
|
||||
切换作用于**下一次**「开始分析 / 重新分析」,运行中禁用。
|
||||
- 「保存」存档;「历史」里选回**回显**;「重新分析」清空重跑。
|
||||
- **任务名 / 简介 / 模型 / 按钮 / 历史下拉全部在宿主侧**:模型空 = `model` prop / 配置 / 后端回退;
|
||||
模型作用于**下一次**`reanalyze()`。
|
||||
- `panel.save()` 存档;`panel.loadTask(id)` 回显;`panel.reanalyze()` 清空重跑;`panel.cancel()` 停止。
|
||||
- **每轮分析结束自动保存一次**;挂载时没传 `taskId` 会**默认回显最新一条**记录。
|
||||
- 结果自动推给宿主:`@agent-completed`(来一个推一个)+ `@completed`(全部到齐再推一次)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user