feat: 接收到webaddress时自动展开
This commit is contained in:
125
src/views/home/agent/AGENTS.md
Normal file
125
src/views/home/agent/AGENTS.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Agent 页面 — 开发者指南
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
agent/
|
||||||
|
├── index.vue # 主入口:布局 + 路由 + 事件注册
|
||||||
|
├── AGENTS.md
|
||||||
|
└── components/
|
||||||
|
├── Agent.vue # AI 对话核心组件
|
||||||
|
├── List.vue # 消息列表容器(自动滚动)
|
||||||
|
├── Item.vue # 单条消息渲染(用户/AI)
|
||||||
|
├── Preview.vue # 右侧预览面板(sketch/report/url)
|
||||||
|
├── Menu.vue # 三点菜单(仅 UI,暂无逻辑)
|
||||||
|
├── Pause.vue # 生成暂停提示条
|
||||||
|
├── ReportCard.vue # 报告/URL/草图的卡片插槽组件
|
||||||
|
├── UrlCard.vue # URL 卡片(包裹 ReportCard)
|
||||||
|
├── SketchCard.vue # 草图卡片(包裹 ReportCard)
|
||||||
|
└── versionTree/ # 版本树抽屉面板
|
||||||
|
├── index.vue # 版本树入口(el-drawer)
|
||||||
|
├── detail/ # 版本详情/聊天详情
|
||||||
|
├── tree/ # 树形视图组件
|
||||||
|
├── components/ # 版本树内部子组件
|
||||||
|
└── tools/ # 版本数据工具
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
|
||||||
|
### 1. 项目加载流程
|
||||||
|
```
|
||||||
|
路由参数 id → index.vue:handleGetProjectInfoAndHistory()
|
||||||
|
→ getProjectInfo API → Agent.vue:setChatInfo(conversation, project)
|
||||||
|
→ messageList + sketchList 填充 → Preview 渲染
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 对话发送流程
|
||||||
|
```
|
||||||
|
用户输入 → Input.vue @send
|
||||||
|
→ Agent.vue:handleSendMessage()
|
||||||
|
→ SSE GET 请求(/api/ai-design/chat)
|
||||||
|
→ 逐行解析 event/data → 实时更新 messageList
|
||||||
|
→ 草图/报告/URL 事件 → Preview 切换模式
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 跨组件通信(MyEvent 事件总线)
|
||||||
|
|
||||||
|
| 事件名 | 触发方 | 监听方 | 说明 |
|
||||||
|
|--------|--------|--------|------|
|
||||||
|
| `openReport` | Item.vue | index.vue | 点击报告卡片,Preview 切 report 模式 |
|
||||||
|
| `openUrls` | Item.vue | index.vue | 点击 URL 卡片,Preview 切 url 模式 |
|
||||||
|
| `openSketch` | Item.vue | index.vue | 点击草图卡片,Preview 切 sketch 模式 |
|
||||||
|
| `quote` | Preview.vue | Input 组件 | 引用草图图片到输入框 |
|
||||||
|
| `openFlowCanvas` | Preview.vue | Flow Canvas | 编辑草图 |
|
||||||
|
| `resetAgent` | 外部 (Flow Canvas) | Agent.vue | 重置对话状态 |
|
||||||
|
| `closeFlowCanvas` | 外部 | index.vue | 关闭 Flow Canvas 后刷新 |
|
||||||
|
| `stopChat` | Agent.vue | Item.vue | 停止生成时显示暂停提示 |
|
||||||
|
| `newTitle` | Agent.vue | 对话列表 | 更新项目标题 |
|
||||||
|
| `projectChange` | index.vue | 外部 | 项目切换通知 |
|
||||||
|
| `renameConversation` | 外部 | index.vue | 重命名项目时更新标题 |
|
||||||
|
|
||||||
|
### 4. Store 使用
|
||||||
|
|
||||||
|
| Store | 用途 |
|
||||||
|
|-------|------|
|
||||||
|
| `projectStore` | 当前项目 ID、nodeId、type/region/style/temperature |
|
||||||
|
| `userStore` | 用户 token、头像 |
|
||||||
|
| `agentStore` | 初始项目数据(跨页面传递首次对话参数) |
|
||||||
|
|
||||||
|
## SSE 协议
|
||||||
|
|
||||||
|
请求 URL: `/api/ai-design/chat` (GET)
|
||||||
|
参数: `AgentParamsType` (message, projectID, versionID, configParams, token 等)
|
||||||
|
|
||||||
|
响应格式: Server-Sent Events (SSE)
|
||||||
|
|
||||||
|
### 事件类型
|
||||||
|
|
||||||
|
| event 名称 | data 格式 | 作用 |
|
||||||
|
|-----------|-----------|------|
|
||||||
|
| `message` (默认) | `{"content": "..."}` | AI 回复文本流 |
|
||||||
|
| `reasoning` | `{"reasoning": "..."}` | AI 推理过程 |
|
||||||
|
| `nodeId` | 纯文本 `versionId` | 记录当前对话节点 ID |
|
||||||
|
| `sketchIDAndUrl` | `{"key": "url", ...}` | 生成的草图键值对 |
|
||||||
|
| `report` | `{"report": "..."}` | 报告内容流 |
|
||||||
|
| `reportName` / `reportTitle` | `{"reportName": "..."}` | 报告名称 |
|
||||||
|
| `tool` | 无 | 草图工具调用中 |
|
||||||
|
| `title` | `{"title": "..."}` | 自动生成的对话标题 |
|
||||||
|
| `webAddress` | `{"webAddress": "[...]"}` | 引用的网页来源 |
|
||||||
|
| `error` | 无 | 服务端错误 |
|
||||||
|
| `todo` | 无 | 忽略事件 |
|
||||||
|
| `end` | `{"type": "end"}` 或 `[DONE]` | 流结束 |
|
||||||
|
|
||||||
|
## 关键组件说明
|
||||||
|
|
||||||
|
### Agent.vue
|
||||||
|
- **`setChatInfo(info)`**: 加载历史对话,解析 `ancestors` + `current` 结构,合并连续 assistant 消息
|
||||||
|
- **`handleSendMessage()`**: 核心方法,处理发消息、SSE 流式接收、解析各种事件
|
||||||
|
- **`handlePause()`**: 通过 `AbortController.abort()` 中止请求
|
||||||
|
- **`processDialogue()`**: 将原始对话数组中的连续 assistant 消息合并为一条,追加插槽标记
|
||||||
|
- **`mergeUniqueKeys()`**: 防止草图片列表插入重复 key
|
||||||
|
|
||||||
|
### Item.vue
|
||||||
|
- 使用 `VueMarkdown` + `rehype-raw` 渲染 Markdown
|
||||||
|
- 自定义插槽: `s-card`(报告), `s-url`(网页), `s-sketch`(草图)
|
||||||
|
- 用户消息显示参数标签(type/area/style)
|
||||||
|
- 仅最后一条 AI 消息显示操作按钮(点赞/点踩/重新生成/复制)
|
||||||
|
|
||||||
|
### Preview.vue
|
||||||
|
- **sketch 模式**: 4列网格展示草图,每个图片支持引用/编辑(Flow Canvas)/删除
|
||||||
|
- **report 模式**: 渲染 Markdown 报告,支持下载 `.md` 文件
|
||||||
|
- **url 模式**: 网页来源卡片,点击跳转
|
||||||
|
- 使用自定义指令 `v-img-loading` 实现图片预加载占位
|
||||||
|
|
||||||
|
### VersionTree (versionTree/index.vue)
|
||||||
|
- `el-drawer` 抽屉面板,73.5rem 宽度
|
||||||
|
- 加载版本树数据 → 递归遍历添加 `versionId` 路径标识
|
||||||
|
- 选中节点可恢复对话 (Restore) 或导出图片 (Export)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **AbortController** 每次请求前重新创建,旧的未完成请求会被中止
|
||||||
|
2. **KeepAlive** 包裹 Agent 组件,切换项目时通过 `:key="proJectId"` 强制重建
|
||||||
|
3. `onActivated` 处理缓存激活时的新队列数据(后台对话完成的节点/标题/草图)
|
||||||
|
4. SSE 解析先用 `\n\n` 拆分事件块,再按 `\n` 解析 event/data 行,不完整的 JSON 放回 buffer
|
||||||
|
5. 草图删除调用 `deleteSketchFlowCanvas` API 后需在父组件同步清理 sketchList
|
||||||
@@ -454,11 +454,12 @@
|
|||||||
const jsonData = JSON.parse(jsonText)
|
const jsonData = JSON.parse(jsonText)
|
||||||
// console.log('jsonData', jsonData)
|
// console.log('jsonData', jsonData)
|
||||||
if (jsonData.webAddress) {
|
if (jsonData.webAddress) {
|
||||||
|
const parsed = JSON.parse(jsonData.webAddress)
|
||||||
aiMessage.webAddress = JSON.parse(jsonData.webAddress)
|
aiMessage.webAddress = parsed
|
||||||
// contentBody += `<slot slot-name="url"></slot>`
|
|
||||||
// aiMessage.loading = false
|
|
||||||
hasUrlEvent = true
|
hasUrlEvent = true
|
||||||
|
if (String(aiMessage.sessionId) === String(projectStore.state.id)) {
|
||||||
|
MyEvent.emit('openUrls', parsed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (jsonData.title) {
|
if (jsonData.title) {
|
||||||
if (aiMessage.sessionId === projectStore.state.id) {
|
if (aiMessage.sessionId === projectStore.state.id) {
|
||||||
|
|||||||
@@ -129,6 +129,16 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
(val) => {
|
||||||
|
if (val === 'sketch') {
|
||||||
|
fetchedUrlSet.value = new Set()
|
||||||
|
urlList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.sketchList,
|
() => props.sketchList,
|
||||||
() => {
|
() => {
|
||||||
@@ -199,13 +209,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const urlLoading = ref(false)
|
const urlLoading = ref(false)
|
||||||
|
const fetchedUrlSet = ref<Set<string>>(new Set())
|
||||||
const setUrls = async (list: string[]) => {
|
const setUrls = async (list: string[]) => {
|
||||||
reportType.value = 'urls'
|
reportType.value = 'urls'
|
||||||
urlList.value = []
|
const newUrls = [...new Set(list.filter((url) => !fetchedUrlSet.value.has(url)))]
|
||||||
|
if (newUrls.length === 0) return
|
||||||
urlLoading.value = true
|
urlLoading.value = true
|
||||||
const res = await fetchUrlTitle(list)
|
const res = await fetchUrlTitle(newUrls)
|
||||||
urlLoading.value = false
|
urlLoading.value = false
|
||||||
urlList.value = res
|
newUrls.forEach((url) => fetchedUrlSet.value.add(url))
|
||||||
|
urlList.value = [...urlList.value, ...res]
|
||||||
}
|
}
|
||||||
// watch(
|
// watch(
|
||||||
// () => sessionId.value,
|
// () => sessionId.value,
|
||||||
|
|||||||
Reference in New Issue
Block a user