# 05 · 聊天功能 聊天面板位于桌宠窗口**右侧**(默认 340 px 宽)。可一键收起让模型占满窗口。 > 第一次启动时聊天面板**默认展开**。改 `chatCollapsed` 状态:在工具栏点 **💬 按钮**(顶部第一个)即可。 ## 1. 面板顶部一条 ``` [●] [会话下拉(标题 消息数)] [+新建] [✎重命名] [🗑删除] ___ [🔔提醒中心] [🔍搜索导出] ``` | 元素 | 行为 | |---|---| | **● 状态点** | 🟢就绪 / 🟠思考中 / 🔴API key 未配置 / ⚪未知 | | **会话下拉** | 切换已有会话,按 `updated_at` 倒序 | | **+** | 新建一个空会话(标题默认 `对话 MM-DD HH:MM`) | | **✎** | 重命名当前会话(标题 ≤ 40 字符) | | **🗑** | 删除当前会话(确认弹窗) | | **🔔** | 跳到「提醒中心」伪会话(激活态高亮) | | **🔍** | 展开/收起第二行工具栏(搜索 + 导出) | > 在「提醒中心」时,✎ 和 🗑 **自动禁用**(提醒中心不可重命名/删除)。 ## 2. 第二行(点击 🔍 展开) | 元素 | 行为 | |---|---| | **搜索框** | 输入关键字回车 → 当前会话内即时过滤 + 黄色高亮 | | **🌐 全部** | 弹出全局搜索弹窗(跨会话),点命中条目跳过去 | | **⬇ MD** | 把当前会话导出为 Markdown 文件,浏览器自动下载 | | **⬇ JSON** | 把当前会话导出为 JSON 文件,浏览器自动下载 | ## 3. 消息流 按**时间顺序**展示当前会话的所有消息。跨日时**自动插入**灰色圆角日期分割线: - `今天`(今天的全部消息前) - `昨天`(昨天的全部消息前) - `YYYY-MM-DD`(更早的) > 分割线**不存盘**,是按消息 `ts` 现场算的,所以不用管它,刷新/重启会自动重算。 ### 3.1 消息类型与颜色 | 类型 | 来源 | 样式 | |---|---|---| | `user` | 你输入 | 右对齐气泡 | | `assistant` | LLM 回复 | 左对齐气泡 | | `reminder` | 系统定时提醒 / 手动触发 | 绿松石色气泡(**带 🪧 action_type**) | | `screenshot` | 截图预览(**不持久化**) | 中央居中,带"取消/发送给AI"按钮 | | `system` | 错误提示(如 `[401] API key 无效`) | 灰色文字 | ### 3.2 思考气泡 如果 LLM 是 DeepSeek / QwQ / o1 这类带推理的模型,回复里会含 `...` 块。 - **自动**折叠为 `💭 思考过程 ▶` 小标签 - **点击**展开看到完整推理 - 支持多个 `` 块拼接、容错未闭合块、`` 标签 - 展开后**再次点击**收起 ### 3.3 截图气泡的生命周期 ``` [桌宠身体被点击] → 后端截图 → emit('ezvibe:screenshot-captured') → 前端 save_chat_image 存盘 → 推一条 role='screenshot' 消息(含 imagePath / imagePngPath) → 渲染为带输入框和"取消/发送给AI"按钮的预览气泡 ┌─────┴─────┐ [点"取消"] [输入文字 → 点"发送给AI"] │ │ └─→ 立刻 removeMessage,立即消失 └─→ 立刻 removeMessage └─→ 推一条 role='user' (含 imagePath + 文字) └─→ 调 invoke('chat', { message, imagePath }) └─→ LLM 回复后推一条 role='assistant' ``` > ⚠️ screenshot 消息**三层防御**不写盘: > 1. 前端 `removeMessage` 顺手清 `persistTimers` 里的 debounce > 2. 后端 `append_message` 拒绝 `role=screenshot` > 3. 后端 `load_session_messages` 跳过 `role=screenshot` 行 > 所以即使你狂点也不会"复活"。 ### 3.4 截图发送给 AI 的限制 - 图片后缀必须是 `.png` / `.jpg` / `.jpeg` - 路径**必须**是 `sessions//images/xxx` 形式(前端用 `appDataDir() + join` 拼绝对路径) - **拒绝**绝对路径 / 路径遍历(`..`) / 非法 session id - 文件大小上限 **20 MB**(防止 OOM) - 给 LLM 用的是 **JPEG 预览**(80% 质量,几百 KB),不是 PNG 原图 — LLM 视觉够用且体积小 10 倍 ## 4. 输入框 ``` [ 多行文本框 (Enter发送 / Shift+Enter换行) ] [发送] ``` | 行为 | 说明 | |---|---| | **Enter** | 发送(前提:输入非空 且 LLM 已配置) | | **Shift+Enter** | 换行 | | 空消息 | 发送按钮 disabled | | LLM 未配置 | placeholder 提示「请先到配置中心填写 API key」 | | 思考中(loading) | 整个输入区 disabled,状态点变橙色,底部出现"桌宠正在思考…" | ## 5. 多会话管理 ### 5.1 新建会话 点 + → 新会话立刻出现在下拉里,自动切过去,标题默认 `对话 MM-DD HH:MM`。 ### 5.2 切换会话 点下拉选别的会话。**切换时自动加载**该会话的所有历史消息(JSONL 读盘)。 ### 5.3 自动标题 - 触发条件:**消息数 ≥ 5** 且 **从未生成过标题** - 时机:前端检测到 `append_message` 返回的新 `msg_count` 达到阈值时,调 `check_title_trigger` + `generate_session_title` - 实现:异步调 LLM 总结前 5 条消息,输出 ≤ 20 字符短标题(中文 ≤ 12 字 / 英文 ≤ 20 字) - 失败降级:LLM 报错就保持默认 `对话 MM-DD HH:MM`,下次还满足条件会再试 ### 5.4 重命名 点 ✎ → 顶部弹输入框 → 输入新标题(≤ 40 字符)→ 回车确认。Esc 取消。 ### 5.5 删除 点 🗑 → 确认弹窗 → **整个会话目录** + 索引条目都删(**包括该会话的所有截图**)。 ### 5.6 提醒中心(伪会话) - 标题固定 `🔔 提醒中心`,ID 固定 `__reminders__` - **不可重命名 / 不可删除 / 不参与 LLM 自动标题** - 所有定时触发的健康提醒**自动写入这个会话**(后端直接落盘,不依赖前端) - 来新提醒时**自动跳转**到提醒中心视图(即使你正在看别的会话) ## 6. 搜索 ### 6.1 当前会话搜索 - 点 🔍 展开第二行工具栏 → 输入关键字 - 即时过滤(输入就过滤) - 命中片段在气泡里以**黄色背景高亮**(`` 标签) - 切回空输入恢复显示全部 ### 6.2 全局搜索 - 点工具栏 **🌐 全部** 按钮 → 弹出模态框 - 列出所有会话中**包含该关键字**的消息 - 每条命中显示上下文(命中前 20 字 + 命中词 + 命中后 40 字) - **点任意命中** → 切到那个会话并关弹窗 > 全文搜索是**字面量匹配**(`text.find()`),不模糊不忽略大小写。中文按字节匹配。 ## 7. 导出 ### 7.1 格式 | 格式 | 文件名 | 内容 | |---|---|---| | **Markdown** | `-.md` | 标题 + 元信息 + 每条消息带 emoji 角色(👤/🤖/🔔/📸/⚙️)+ 时间戳 + 文本 + 图片相对路径 | | **JSON** | `-.json` | pretty-printed JSON,含 `session: {meta}` 和 `messages: [...]` 数组 | ### 7.2 导出位置 **浏览器默认下载目录**。`ExportPayload.suggested_filename` 字段前端会直接用。 ### 7.3 提醒中心能不能导出? **能**。但因为是伪会话,导出内容主要是各种 `reminder` 类型的消息。 ## 8. 常见疑问 **Q: 我能同时开几个聊天窗口吗?** A: 不能。聊天面板是桌宠窗口的一部分(右侧栏),不是独立窗口。多个对话 = 多个会话(用下拉切换)。 **Q: 对话历史存在哪?** A: 见 [08-数据存储与备份.md](08-数据存储与备份.md)。 **Q: 我清空了 localStorage 会不会丢?** A: 不会。localStorage 没用,所有会话数据在磁盘。 **Q: 我能编辑 / 删除某条已发送消息吗?** A: 不能。当前版本没有编辑或单条删除功能(只能删除整个会话)。 --- 下一步:[06-健康提醒.md](06-健康提醒.md)。