状态:设计初稿,2026-10-24。新助手以独立的大页面展示(菜单路由),不放在顶栏右上角。需求见 requirements.md,前后端结构见 technical-design.md。对话组件来自
@agent-server/chat-ui(AgentServerpackages/chat-ui)。
在 src/router/record.ts 增加一级菜单:
{
path: 'ai-assistant',
name: 'aiAssistant',
icon: 'i-ph:sparkle', // 与旧助手的 i-ph:chat-circle-dots 区分
label: 'AI 助手',
meta: {
componentName: 'AiAssistant',
showTab: true,
},
component: 'ai-assistant/index',
},
enableMultiTab):一个页面同时只打开一个会话,避免多个标签各自维持 SSE 连接。?session=ses_xxx(切换会话时 router.replace):刷新页面、从浏览器历史返回都能回到同一个会话;链接发给别人时,对方打开会因归属校验失败而回到新会话。AiAssistant,进入标签页的 KeepAlive:切换到其他标签时组件不销毁,进行中的回答继续接收;关闭标签时 dispose 订阅(运行本身不中断,下次打开从断点继续显示)。AGENT_ENABLED=false 时不注册该菜单。宽屏(≥ 1280px)三栏,中间为对话主区:
┌───────────────┬──────────────────────────────────────────────┬─────────────────┐
│ + 新对话 │ 站点 XXXX 告警排查 ✎ [Sonnet 5.5 ▾] ⋯ │ 本会话文件 ⇥ │
│ 🔍 搜索会话 ├──────────────────────────────────────────────┤ ─────────────── │
│ │ │ 上传 │
│ 今天 │ (对话时间线,最大宽度 880px 居中) │ 📄 sales.xlsx │
│ ● 站点XXXX告警 │ 用户:…… │ 🖼 截图1.png │
│ SIM 用量统计 │ 助手:…… │ 产出 │
│ 昨天 │ ▸ 已调用 查询站点概况(0.8s) │ 📊 report.docx │
│ TOU 模版对比 │ ▸ 已调用 查询站点告警/事件记录(1.2s) │ 📈 trend.png │
│ 最近 7 天 │ ┌ 审批:保存 TOU 模版(测试服)… ┐ │ │
│ … │ └ [允许一次][本会话允许][拒绝] ┘ │ ┌─────────────┐ │
│ │ 📊 report.docx 2.1 MB [下载] │ │ 文件预览 │ │
│ │ │ │ (图片/PDF/ │ │
│ ├──────────────────────────────────────────────┤ │ Markdown/ │ │
│ ⇤ 收起 │ [📎] 输入问题,Enter 发送,Shift+Enter 换行 ▶ │ │ CSV 前 200 行)│ │
│ │ 本轮 12.3k / 1.8k tokens · 排队中(第 2 位) │ └─────────────┘ │
└───────────────┴──────────────────────────────────────────────┴─────────────────┘
240px 自适应 320px
| 区域 | 内容 | 实现 |
|---|---|---|
| 左栏:会话 | 新对话按钮;按标题搜索;按“今天 / 昨天 / 最近 7 天 / 更早”分组;悬停显示改名、删除;当前会话高亮;可收起 | NaiveUI(NInput、NScrollbar、NDropdown、NPopconfirm),数据来自 /agent/sessions |
| 顶部栏 | 会话标题(点击改名);模型选择(Profile 允许的模型);更多菜单:删除会话、复制会话链接 | NaiveUI;模型切换调用 PATCH /agent/v1/sessions/:id { model } |
| 中间:对话 | 时间线、工具卡片、审批与提问卡片、文件卡片、输入框(附件、拖拽、粘贴截图)、中断、断线续传 | <AgentChat :client :session @open-file>;#toolbar 插槽放用量与排队信息,#empty 插槽放欢迎页(第 3 节) |
| 右栏:文件 | 本会话的上传与产出文件(按来源分组,最新在前);点击在下方预览;下载 | client.sessions.files(id),收到 file.changed 事件时刷新;预览用 chat-ui 的 useFileObjectUrl、MarkdownView |
对话主区的时间线限制最大宽度(880px)居中,宽屏下不至于一行过长;表格类回复允许横向滚动。
| 宽度 | 布局 |
|---|---|
| 1024~1279px | 右栏默认收起,顶部栏出现“文件”按钮,以右侧 NDrawer 打开 |
| < 1024px | 左栏也收起为顶部栏的“会话”按钮,以左侧 NDrawer 打开 |
左右栏的展开/收起状态记在 localStorage(读写包在 try/catch 中,失败时用默认值)。
新对话或空会话时,主区显示:
session.send(text)):
示例中的 SN、告警码等用占位符,点击后填入输入框而不直接发送,让用户补全(带占位符的卡片显示“填写后发送”)。
| 场景 | 行为 |
|---|---|
首次进入(无 ?session=) |
打开最近活跃的会话;没有会话时显示欢迎页,发送第一条消息时才创建会话(避免产生空会话) |
| 新对话 | 主区回到欢迎页,地址去掉 ?session=;左栏不出现新条目,直到发送第一条消息 |
| 发送第一条消息 | POST /agent/sessions → 发送消息 → 用前 30 字改名 → 左栏插入并高亮 |
| 切换会话 | 当前会话 dispose,打开目标会话(拉取历史事件 + 订阅),右栏刷新文件列表 |
| 会话不存在 / 不属于本人 | 提示“会话不存在或无权访问”,回到欢迎页 |
| 运行中切换会话或离开页面 | 不中断运行;左栏该会话显示“进行中”小圆点(依据最近一次打开时的运行状态,回到该会话时以实际状态为准) |
| 运行中再次发送 | 输入框保持可编辑,发送按钮置灰并提示“上一轮尚未结束,可先中断” |
| 删除当前会话 | 二次确认 → 删除 → 回到欢迎页 |
| 审批 / 提问卡片待处理 | 页面标签显示提醒标记;离开页面期间到达的审批,回到页面时卡片仍在(来自 GET /v1/interactions) |
| AgentServer 或 fwh-agent-mcp 不可用 | 顶部栏下方显示横幅“AI 服务暂不可用”,输入框禁用;工具级别的不可用由工具卡片显示错误,不影响对话 |
@agent-server/chat-ui/style.css;组件的原子类已在包内编译隔离,ToolBox 的 UnoCSS 不需要扫描 SDK。data-theme(组件读取 <html> 上的属性,需验证不影响 NaiveUI 的主题)。--app-* 变量,使按钮、链接、选中色与 ToolBox 主色一致;完整变量见 @agent-server/ui-tokens 的 variables.css。src/views/ai-assistant/
index.vue 页面:三栏布局、路由参数、窄屏抽屉
components/
SessionSidebar.vue 左栏:会话列表、搜索、分组、改名、删除
ChatHeader.vue 顶部栏:标题、模型选择、更多菜单
WelcomePanel.vue 欢迎页与示例问题
FilePanel.vue 右栏:文件列表与预览
composables/
useAgentClient.ts AgentClient 单例(baseUrl = /agent,令牌 = ToolBox 登录令牌)
useAgentSessions.ts 会话列表、创建、改名、删除、当前会话与路由同步
useSessionFiles.ts 文件列表与 file.changed 刷新
src/service/agent/index.ts /agent/sessions/* 接口
不修改 src/layout/header/Actions/AiChat/、src/service/aiChat/。