# 首页对外 Agent 设计 ## 概述 将首页从空白欢迎页改造为完整的对外 agent 对话界面,支持 session 管理、工具调用、推理展示等功能。采用混合模式:未登录用户可体验(受限功能),登录后解锁完整功能并持久化历史。 ## 用户分层 | 维度 | 未登录用户 | 已登录用户 | |------|-----------|-----------| | Session 存储 | 服务端临时 session(HttpOnly cookie token 关联),2h TTL | 持久化 session,软删除永久保留 | | 模型 | 管理员配置的系统级默认模型(`agentDefaultModelId`,不可切换) | 用户偏好模型(`preferredLlmModelId`,session 内可切换) | | 推理 | 固定开启(不可切换) | session 级别开关,默认开启 | | 工具 | 白名单工具自动启用(不可切换) | session 级别开关,默认开启 | | 消息上限 | 单 session 20 条 assistant 回复 + IP 日 50 条 assistant 回复 | 无限制 | | 反馈 | 不可用 | 可用 | | System prompt 查看 | 不可见 | 工具栏按钮查看 | | 工具栏 | 仅显示模型名 | 模型选择 + 推理开关 + 工具开关 + system prompt 按钮 | ## 整体架构 ### 布局 新建 `agent` layout(无 TopNav、无 BoContainer),全屏占满,侧边栏(260px 固定)+ 主对话区。对话区消息限宽 800px 居中。移动端(< 768px)侧边栏抽屉化。首页 `app/pages/index.vue` 使用 `definePageMeta({ layout: 'agent' })`。 ### 核心数据流 ``` 用户输入 → POST /api/agent/chat (sessionId, content) → 服务端校验身份(cookie token 或登录态) → 限流检查(未登录:session 计数 + IP 计数) → 保存 user message → 从 DB 加载 session 历史 messages → 注入 system prompt + 工具(按用户角色过滤) → streamText (ai-sdk) → 流式响应 → onFinish: 持久化 assistant message(含 parts JSON、modelId、token 用量) → 更新 session lastActiveAt → 首轮对话后异步生成标题 ``` ### 新增 API 路由 ``` /api/agent/sessions GET — 分页列表(15/页) POST — 新建 session /api/agent/sessions/:id GET — session 详情 + 首页消息(30条) PUT — 重命名 DELETE — 软删除 /api/agent/sessions/:id/config PUT — 更新 session 配置(modelId/enableThinking/enableTools) /api/agent/sessions/:id/messages GET — 消息分页加载 /api/agent/chat POST — 发送消息(流式响应) /api/agent/chat/tool-approve POST — 工具审批(仅已登录用户) /api/agent/feedback POST — 提交/修改反馈 /api/agent/migrate POST — 登录后迁移临时 session /api/admin/agent-config GET — 读取所有配置 /api/admin/agent-config/system-prompt PUT — 更新 system prompt /api/admin/agent-config/default-model PUT — 更新默认模型 + 标题模型 /api/admin/agent-config/public-tools PUT — 更新公开工具白名单 /api/admin/llm/providers GET/POST — 系统级 provider 管理 /api/admin/llm/models GET/POST — 系统级 model 管理 ``` ## 数据库 Schema ### 现有表修改:`llm_providers` 允许系统级 provider 修改 `llm_providers.userId` 为可空(`NULL` 表示系统级 provider,管理员创建,供未登录用户使用): 对应 schema 修改(`packages/drizzle-pkg/lib/schema/llm.ts`),去掉 `.notNull()`: ```typescript userId: integer("user_id") // 去掉 .notNull(),允许 NULL = 系统级 .references(() => users.id, { onDelete: "cascade" }), ``` LLM service 新增函数: - `getSystemProviderById(id)` — 查询 userId IS NULL 的 provider - `getSystemModelById(id)` — 查询关联 provider.userId IS NULL 的 model - `listSystemModels()` — 列出所有系统级模型(供管理员后台选择默认模型用) ### 现有表修改:`agent_tools` AgentToolTypes 枚举修正 在 `AgentToolTypes` 数组中补充 `"user-info"`(service 中已注册但 schema enum 缺失)。 ### `agent_sessions` 表 ```typescript export const agentSessions = sqliteTable("agent_sessions", { id: text("id").primaryKey(), // UUID userId: integer("user_id"), // null = 临时 session tempToken: text("temp_token", { length: 64 }), // 临时 session 的 cookie token(正式 session 为 null) title: text("title", { length: 100 }).notNull().default("新对话"), modelId: integer("model_id"), // session 使用的模型(null = 用默认/偏好模型) enableThinking: integer("enable_thinking").default(1).notNull(), // 0/1 enableTools: integer("enable_tools").default(1).notNull(), // 0/1 lastActiveAt: integer("last_active_at", { mode: "timestamp_ms" }).defaultNow().notNull(), expiresAt: integer("expires_at", { mode: "timestamp_ms" }), // 临时 session 的过期时间 deletedAt: integer("deleted_at", { mode: "timestamp_ms" }), // 软删除时间 createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().$onUpdate(() => new Date()).notNull(), }) ``` ### `agent_messages` 表 ```typescript export const agentMessages = sqliteTable("agent_messages", { id: text("id").primaryKey(), // UUID sessionId: text("session_id").notNull(), role: text("role", { length: 20 }).notNull(), // "user" | "assistant" content: text("content").notNull().default(""), // 纯文本内容 parts: text("parts"), // JSON string,存 MessagePart[] modelId: integer("model_id"), // assistant 消息使用的模型 inputTokens: integer("input_tokens"), // assistant 消息的输入 token outputTokens: integer("output_tokens"), // assistant 消息的输出 token sortOrder: integer("sort_order").notNull(), // 同 session 内顺序 createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }) ``` ### `agent_message_feedback` 表 ```typescript export const agentMessageFeedback = sqliteTable("agent_message_feedback", { id: integer("id").primaryKey({ autoIncrement: true }), messageId: text("message_id").notNull(), userId: integer("user_id").notNull(), feedback: text("feedback", { length: 10 }).notNull(), // "like" | "dislike" comment: text("comment"), // 可选文字说明 createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().$onUpdate(() => new Date()).notNull(), }) ``` ### 配置存储:复用 `app_configs` + `CONFIG_REGISTRY` **不新建独立表。** 复用现有 `app_configs` 表 + `CONFIG_REGISTRY` 强类型注册表体系。在 `CONFIG_REGISTRY` 中新增以下配置项: | key | scope | valueType | defaultValue | userOverridable | 说明 | |-----|-------|-----------|-------------|-----------------|------| | `agentSystemPrompt` | global | string | `""` | false | 管理员配置的 system prompt | | `agentDefaultModelId` | global | number | `0` | false | 未登录用户使用的默认模型 ID(指向系统级 model) | | `agentTitleModelId` | global | number | `0` | false | 生成 session 标题的低成本模型 ID | | `agentPublicToolSlugs` | global | json | `[]` | false | 公开工具白名单(slug 数组) | | `preferredLlmModelId` | user | number | `0` | true | 已登录用户的偏好 LLM 模型 ID | **预设配置项无默认值语义:** `agentDefaultModelId` / `agentTitleModelId` 默认值 `0` 表示"未配置"。管理员首次使用前需在后台配置: - 未配置 `agentDefaultModelId` → 未登录用户新建 session 报错"默认模型未配置" - 未配置 `agentTitleModelId` → 标题生成跳过,保持"新对话"标题 - 未配置 `agentPublicToolSlugs` → 未登录用户工具为空列表 - 未配置 `agentSystemPrompt` → 使用空字符串作为 system prompt ### 索引 - `agent_sessions`: `userId` + `deletedAt`(列表查询)、`tempToken`(临时 session 查找) - `agent_messages`: `sessionId` + `sortOrder`(消息分页) - `agent_message_feedback`: `messageId` + `userId`(唯一约束) ## 后端 API 设计 ### Session 管理 #### `GET /api/agent/sessions` 分页列表,按 `lastActiveAt` 倒序,每页 15 条。 ``` 请求: ?page=1 响应: { code: 0, data: { list: Session[], // id, title, lastActiveAt total: number, page: number, pageSize: 15, } } ``` **身份识别逻辑:** - 已登录:`WHERE userId = ? AND deletedAt IS NULL` - 未登录:从 cookie 读 `tempToken`,`WHERE tempToken = ? AND deletedAt IS NULL AND expiresAt > now` - 未登录且无 token:返回空列表 #### `POST /api/agent/sessions` 新建 session。 ``` 请求: { modelId?: number } 响应: { code: 0, data: Session } ``` **逻辑:** - 已登录:`userId` = 当前用户,`modelId` = 传入值 || 用户偏好模型(`preferredLlmModelId` user config)|| 管理员默认模型(`agentDefaultModelId` global config) - 未登录:生成 `tempToken`(64 位随机 hex),写入 HttpOnly cookie(`path=/; httpOnly; secure; sameSite=strict`),`modelId` = 管理员默认模型(`agentDefaultModelId`,指向系统级 model),`expiresAt` = now + 2h。若 `agentDefaultModelId` 未配置(=0)→ 报错"默认模型未配置" #### `GET /api/agent/sessions/:id` Session 详情 + 首页消息(最近 30 条)。 ``` 响应: { code: 0, data: { session: Session, messages: AgentMessage[], // 最近 30 条,按 sortOrder 正序 hasMore: boolean, } } ``` **权限校验:** session 的 `userId` 或 `tempToken` 必须匹配当前身份。 #### `PUT /api/agent/sessions/:id` 重命名。 ``` 请求: { title: string } 响应: { code: 0 } ``` #### `DELETE /api/agent/sessions/:id` 软删除。 ``` 逻辑: deletedAt = now ``` #### `GET /api/agent/sessions/:id/messages` 消息分页加载(向上加载更早消息)。 ``` 请求: ?before=&limit=30 响应: { code: 0, data: { messages: AgentMessage[], hasMore: boolean } } ``` ### 对话 #### `POST /api/agent/chat` 发送消息,流式响应。 ``` 请求: { sessionId: string, content: string, // regenerate 时也必填,默认取最后一条 user message 内容(前端传入) editMessageId?: string, // 编辑消息时传 regenerate?: boolean, // 重新生成时传 } 响应: SSE 流(ai-sdk toUIMessageStreamResponse) 响应头(未登录用户): X-RateLimit-Session-Remaining: number — 当前 session 剩余 assistant 回复次数 X-RateLimit-Ip-Remaining: number — 当前 IP 今日剩余 assistant 回复次数 ``` **核心流程:** 1. 身份校验 + session 权限校验 2. 限流检查(未登录:session 计数 ≥ 20 或 IP 计数 ≥ 50 → 403) 3. 处理编辑/重新生成(截断 messages) 4. 保存 user message(`sortOrder` = 当前最大 + 1) 5. 读取 session 历史 messages 6. 读取 `agent_config` 的 system prompt 7. 工具注入: - 已登录 + session.enableTools:全部 enabled 工具(需用户审批后执行) - 已登录 + !session.enableTools:无工具 - 未登录:`agentPublicToolSlugs` 白名单工具(强制启用,自动执行,无需审批) 8. `streamText` 调用(复用现有 `resolveModel` 逻辑;未登录用户使用系统级 provider/model,已登录用户使用自己或系统级的 model) 9. `onFinish` 回调:保存 assistant message(含 parts、modelId、token 用量),更新 session `lastActiveAt`,限流计数 +1 10. 第一条 assistant 回复完成后,异步调用 `titleModelId` 生成标题 **编辑消息(截断 + 重新生成):** - 前端传 `editMessageId` + `content` - 服务端删除该 message 之后的所有 messages(硬删除,含 feedback) - 用编辑后的内容更新该 message - 继续正常对话流程 **重新生成:** - 前端传 `regenerate: true` + `content`(取最后一条 user message 内容) - 服务端删除最后一条 assistant message(含 feedback) - 用传入的 content 重新生成 ### Session 配置更新 #### `PUT /api/agent/sessions/:id/config` 更新 session 级别配置(仅已登录用户)。 ``` 请求: { modelId?: number, enableThinking?: boolean, enableTools?: boolean } 响应: { code: 0 } 逻辑: 仅更新传入的字段,未登录用户 403 ``` ### 工具审批 已登录用户的工具调用需要审批: - LLM 发起 tool-call → 流式返回 tool-call part(状态"待审批") - 前端展示审批按钮(同意/拒绝) - 用户选择后调用审批 API → 服务端执行或拒绝 - 未登录用户的白名单工具自动执行,无需审批 #### `POST /api/agent/chat/tool-approve` 工具审批(仅已登录用户)。 ``` 请求: { sessionId: string, messageId: string, toolCallId: string, approved: boolean } 响应: { code: 0 } 逻辑: - approved=true: 执行工具,返回 tool-result - approved=false: 返回拒绝结果给 LLM,LLM 自行处理 ``` ### 反馈 #### `POST /api/agent/feedback` 提交/修改反馈(仅登录用户)。 ``` 请求: { messageId: string, feedback: "like" | "dislike", comment?: string } 响应: { code: 0 } 逻辑: UPSERT(messageId + userId 唯一约束) ``` ### 迁移 #### `POST /api/agent/migrate` 登录后迁移临时 session(前端弹窗确认后调用)。 ``` 请求: { tempToken: string } 响应: { code: 0, data: { migratedCount: number } } 逻辑: 1. 查询 tempToken 关联的所有未删除、未过期临时 session 2. 更新 userId = 当前用户,tempToken = null,expiresAt = null 3. 迁移的 session 置底(lastActiveAt 保持原值,排序靠后) 4. 清除 tempToken cookie ``` ### 管理员配置 API 复用现有 config service(`getGlobalConfigValue` / `setGlobalConfigValue`),通过 `CONFIG_REGISTRY` 中新增的 key 读写: ``` GET /api/admin/agent-config — 读取 agent 相关全局配置(agentSystemPrompt/agentDefaultModelId/agentTitleModelId/agentPublicToolSlugs) PUT /api/admin/agent-config/system-prompt — 更新 agentSystemPrompt PUT /api/admin/agent-config/default-model — 更新 agentDefaultModelId + agentTitleModelId PUT /api/admin/agent-config/public-tools — 更新 agentPublicToolSlugs ``` **系统级 provider/model 管理:** 管理员通过新增 admin API 创建系统级 provider(`userId = NULL`): - `POST /api/admin/llm/providers` — 创建系统级 provider - `GET /api/admin/llm/providers` — 列出系统级 providers - `POST /api/admin/llm/models` — 创建系统级 model - `GET /api/admin/llm/models` — 列出系统级 models(供 default-model 选择) ### 限流实现 内存缓存(Map),服务重启归零: ```typescript // 单 session 计数 Map // IP 日计数 Map ``` 每日 0 点重置(日期字符串比对)。计数时机:assistant 回复完成后(`onFinish`)。 **前端获取剩余次数:** - 未登录用户的 `/api/agent/chat` 响应头返回: - `X-RateLimit-Session-Remaining` — 当前 session 剩余次数(20 - 已用) - `X-RateLimit-Ip-Remaining` — 当前 IP 今日剩余次数(50 - 已用) - 前端从响应头读取,更新 `useAgentRateLimit` 状态 - 达到上限时禁用输入框 + 显示提示 ### 定时任务 复用 `server/scheduler`,每 10 分钟扫描: - 删除 `expiresAt < now AND deletedAt IS NULL` 的临时 session 及其消息(硬删除) ### 数据库迁移 本次需生成 drizzle 迁移(`bun run db:generate`),包含: 1. `llm_providers.user_id` 改为可空(系统级 provider) 2. 新增 `agent_sessions` 表 3. 新增 `agent_messages` 表 4. 新增 `agent_message_feedback` 表 5. `agent_tools` AgentToolTypes enum 修正(补充 `user-info`,不影响数据库,仅代码层) **不新建 `agent_config` 表** — agent 配置复用现有 `app_configs` 表,通过 `CONFIG_REGISTRY` 新增 key 实现。 ## 前端架构 ### 页面与组件结构 ``` app/layouts/agent.vue — agent 专用 layout(无 TopNav、全屏) app/pages/index.vue — 首页 agent 主页面(layout: 'agent') app/components/agent/ ├── AgentSidebar.vue — 侧边栏(session 列表 + 新对话按钮 + 用户信息) ├── AgentSidebarItem.vue — session 列表项(含操作菜单) ├── AgentChatArea.vue — 主对话区(工具栏 + 消息列表 + 输入框) ├── AgentToolbar.vue — 顶部工具栏(汉堡 + 模型选择 + 开关 + system prompt 查看) ├── AgentMessageList.vue — 消息列表(含日期分隔线 + 滚动管理) ├── AgentMessageItem.vue — 单条消息(角色区分 + markdown + parts 渲染) ├── AgentMessageParts.vue — 消息 parts 渲染(text/reasoning/tool-call/tool-result/tool-approval) ├── AgentToolCallBlock.vue — 工具调用折叠展示(含审批按钮) ├── AgentToolResultBlock.vue — 工具结果展示(折叠) ├── AgentReasoningBlock.vue — 推理过程展示(可折叠 + 流式打字效果) ├── AgentInput.vue — 底部输入框(多行自适应 + 发送 + 停止) ├── AgentModelSelector.vue — 模型下拉选择器 ├── AgentSystemPromptModal.vue — system prompt 查看弹窗 ├── AgentFeedbackButtons.vue — 👍/👎 反馈按钮 └── AgentWelcome.vue — 空 session 欢迎页(logo + 示例问题) app/pages/admin/agent-config.vue — agent 配置页(system prompt + 默认模型 + 公开工具白名单) ``` ### Composables ``` app/composables/useAgentSessions.ts — session CRUD + 列表分页 + 切换 app/composables/useAgentChat.ts — 对话核心逻辑(扩展自 useLlmChat) app/composables/useAgentRateLimit.ts — 未登录限流状态(读响应头,前端展示用) ``` ### `useAgentChat` 核心设计 基于现有 `useLlmChat` 改造,关键差异: 1. 不再前端传完整 messages,只传当前消息内容 2. sessionId 驱动,切换 session 时重新加载消息 3. 支持编辑消息(editMessageId)和重新生成(regenerate) 4. 停止生成时保留 text parts,丢弃进行中的 tool-call parts 5. 流式结束后从响应获取 token 用量,更新 assistant message ### 状态管理流程 ``` 首页加载 → useAgentSessions.init() → 检测登录态 → 已登录:加载 session 列表(第 1 页),无 session 则自动新建 → 未登录:检查 cookie token,有则加载临时 session 列表,无则自动新建(触发 token 生成) → useAgentChat.loadMessages(currentSessionId) → 加载最近 30 条消息 切换 session → useAgentChat.clear() → useAgentChat.loadMessages(newSessionId) → 滚动到底部 发送消息 → useAgentChat.send(content) → 流式渲染 assistant 回复 → 完成后前端乐观更新 session 列表顺序 编辑消息 → 点击"编辑" → 内容填入输入框 → 发送时带 editMessageId → 截断 + 重新生成 重新生成 → 点击"重新生成" → 带 regenerate: true → 删除最后 assistant + 重新生成 停止生成 → AbortController.abort() → 保留 text parts,清理进行中 tool-call parts → "重新生成"按钮出现在最后一条 assistant 消息下方 ``` ### 滚动管理 - **新消息生成**:用户在底部 → 自动滚动;用户向上看历史 → 不自动滚动,显示"回到最新"按钮 - **加载历史**:滚动到顶部触发加载,保持当前视口位置 - **切换 session**:加载完成后滚动到底部 ### 移动端适配 - `< 768px`:侧边栏变为抽屉,汉堡按钮在工具栏左侧,滑出时有遮罩 - 消息限宽 800px 在移动端自动变为 `100% - padding` ### 样式约定 - 消息气泡:用户右侧(主色调背景)、assistant 左侧(卡片背景) - 工具栏高度 56px,底部固定 - 输入框最小高度 64px,自适应增长,最大 200px - 代码块:语法高亮 + 右上角复制按钮 - 工具调用:折叠式,工具名 + 状态图标(✓/✗/调用中.../待审批) - 推理过程:默认展开,显示在回复文本上方 - 日期分隔线:跨天时插入居中分隔线 - Markdown 渲染:基础 + 表格 + 代码高亮 - 每条 assistant 回复下方显示模型名 + token 用量 ### 交互细节 - 输入框:多行自适应 + Enter 发送 + Shift+Enter 换行 - 附件按钮:UI 预留灰色禁用位置,后续迭代启用 - Session 列表项:点击切换 + "..."操作菜单(重命名、删除) - 新建 session:侧边栏顶部按钮,新 session 置顶 - Session 切换:立即清空 + loading + 加载完成渲染 - 重新生成按钮:仅最后一条 assistant 消息下方显示 - 编辑按钮:仅最后一条用户消息下方显示,点击后内容填入底部输入框 - 反馈:仅登录用户,每条 assistant 消息下方 👍/👎,可修改 - System prompt 查看:仅登录用户,工具栏按钮 → 弹窗展示 ## 边界情况与错误处理 ### 身份与权限 | 场景 | 处理 | |------|------| | 未登录用户访问他人 session | 403 | | 未登录用户 cookie token 过期(2h) | session 列表返回空,当前对话提示"session 已过期",自动新建临时 session | | 未登录用户清除了 cookie | 旧临时 session 成为孤儿数据,定时任务 2h 后清理;用户获得新临时 session | | 已登录用户访问他人 session | 403 | | session 已软删除 | 404 | ### 限流边界 | 场景 | 处理 | |------|------| | 未登录达到 session 上限(20 条 assistant) | 输入框禁用 + "已达对话上限,登录后继续" + 登录按钮 | | 未登录达到 IP 日上限(50 条 assistant) | 输入框禁用 + "今日对话次数已达上限,登录后继续" + 登录按钮 | | 限流计数时机 | assistant 回复完成后计数(`onFinish`) | | 限流计数与停止生成 | 停止生成不产生完整 assistant 回复,不计入限流 | ### 对话异常 | 场景 | 处理 | |------|------| | LLM 请求失败 | 报错提示,不保存 assistant message,用户可重新生成 | | LLM 返回空内容 | 提示"模型未返回内容",删除空 assistant message | | 工具调用失败 | tool-call part 标记"执行失败"(红色 ✗),LLM 自行处理错误 | | 工具审批被拒(已登录用户) | tool-call part 标记"已拒绝"(红色 ✗),LLM 收到拒绝结果 | | 流式中断(网络断开) | 保留已生成 text parts,清理进行中 tool-call parts,提示"网络中断" | | 停止生成 | 保留 text parts,清理进行中 tool-call parts,显示"重新生成"按钮 | ### Session 迁移 | 场景 | 处理 | |------|------| | 迁移时临时 session 已过期 | 跳过过期 session,只迁移未过期的 | | 迁移时用户取消 | 不迁移,临时 session 继续按原 TTL 过期 | | 迁移后 cookie token | 清除 `tempToken` cookie | | 迁移的 session 位置 | 置底(`lastActiveAt` 保持原值,排序靠后) | ### 消息操作 | 场景 | 处理 | |------|------| | 编辑消息后截断 | 硬删除被截断的 messages 及其 feedback | | 重新生成 | 硬删除最后一条 assistant message 及其 feedback | | 编辑最后一条 user message | 正常流程(截断 0 条 + 更新内容 + 重新生成) | | 编辑中间某条 user message | 截断该消息之后所有 messages + 更新内容 + 重新生成 | ### 数据一致性 | 场景 | 处理 | |------|------| | assistant message 保存失败 | 日志记录,消息仍返回给用户(不阻塞流式响应) | | 标题生成失败 | session 保持"新对话"默认标题,日志记录 | | 反馈的 message 被截断删除 | feedback 一并硬删除 | ### 前端状态同步 | 场景 | 处理 | |------|------| | 多标签页同时操作 | 不做特殊处理(个人 agent 场景) | | session 列表顺序 | 发送/接收完成后前端乐观更新 `lastActiveAt` 并重新排序列表 | | 新建 session | 前端立即插入列表顶部,API 返回后替换为真实数据 | ## 测试策略 ### 后端测试 | 模块 | 测试点 | |------|--------| | Session CRUD | 创建/查询/重命名/软删除/权限校验 | | 临时 session | token 生成、cookie 设置、过期判定、孤儿清理 | | 限流 | session 计数、IP 计数、每日重置、达到上限拦截 | | 对话流程 | 消息保存、历史加载、流式响应、token 用量记录 | | 编辑/重新生成 | 截断逻辑、feedback 级联删除 | | 工具注入 | 已登录全量(含审批)/已登录关闭/未登录白名单(自动执行) | | 标题生成 | 首轮 assistant 回复后触发、失败兜底 | | 迁移 | 全量迁移、过期跳过、cookie 清除、置底排序 | | 反馈 | 提交/修改/撤销、未登录拦截 | | 工具审批 | 同意执行/拒绝返回、未登录自动执行 | ### 前端测试 | 模块 | 测试点 | |------|--------| | 消息渲染 | text/reasoning/tool-call/tool-result 各 part 类型 | | 工具调用状态 | 调用中/成功/失败/拒绝/待审批 折叠展示 | | 滚动管理 | 自动滚动/保持视口/回到最新/顶部加载 | | 停止生成 | text 保留、tool-call 清理、重新生成按钮 | | 编辑消息 | 输入框填充、截断、重新生成 | | 限流 UI | 输入框禁用、提示文案、登录按钮、剩余次数显示 | | 工具审批 | 审批按钮展示、同意/拒绝流程 | | 移动端 | 抽屉化、遮罩、汉堡按钮 | ## 不在本次范围内(后续迭代预留) - Session 分享功能(生成公开链接) - 会话导出功能 - 会话内搜索 - 会话标签/分类 - 收藏/置顶消息 - 会话统计 - 自定义 agent 人设 - 会话模板 - 全屏对话模式 - 多语言支持 - 语音功能 - 快捷指令(`/` 弹出工具列表) - 文件上传 - 管理员反馈数据查看页面