7.7 KiB
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 这类带推理的模型,回复里会含 <think>...</think> 块。
- 自动折叠为
💭 思考过程 ▶小标签 - 点击展开看到完整推理
- 支持多个
<think>块拼接、容错未闭合块、<thinking>标签 - 展开后再次点击收起
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 消息三层防御不写盘:
- 前端
removeMessage顺手清persistTimers里的 debounce- 后端
append_message拒绝role=screenshot- 后端
load_session_messages跳过role=screenshot行 所以即使你狂点也不会"复活"。
3.4 截图发送给 AI 的限制
- 图片后缀必须是
.png/.jpg/.jpeg - 路径必须是
sessions/<id>/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 当前会话搜索
- 点 🔍 展开第二行工具栏 → 输入关键字
- 即时过滤(输入就过滤)
- 命中片段在气泡里以黄色背景高亮(
<mark>标签) - 切回空输入恢复显示全部
6.2 全局搜索
- 点工具栏 🌐 全部 按钮 → 弹出模态框
- 列出所有会话中包含该关键字的消息
- 每条命中显示上下文(命中前 20 字 + 命中词 + 命中后 40 字)
- 点任意命中 → 切到那个会话并关弹窗
全文搜索是字面量匹配(
text.find()),不模糊不忽略大小写。中文按字节匹配。
7. 导出
7.1 格式
| 格式 | 文件名 | 内容 |
|---|---|---|
| Markdown | <session-id>-<sanitized-title>.md |
标题 + 元信息 + 每条消息带 emoji 角色(👤/🤖/🔔/📸/⚙️)+ 时间戳 + 文本 + 图片相对路径 |
| JSON | <session-id>-<sanitized-title>.json |
pretty-printed JSON,含 session: {meta} 和 messages: [...] 数组 |
7.2 导出位置
浏览器默认下载目录。ExportPayload.suggested_filename 字段前端会直接用。
7.3 提醒中心能不能导出?
能。但因为是伪会话,导出内容主要是各种 reminder 类型的消息。
8. 常见疑问
Q: 我能同时开几个聊天窗口吗? A: 不能。聊天面板是桌宠窗口的一部分(右侧栏),不是独立窗口。多个对话 = 多个会话(用下拉切换)。
Q: 对话历史存在哪? A: 见 08-数据存储与备份.md。
Q: 我清空了 localStorage 会不会丢? A: 不会。localStorage 没用,所有会话数据在磁盘。
Q: 我能编辑 / 删除某条已发送消息吗? A: 不能。当前版本没有编辑或单条删除功能(只能删除整个会话)。
下一步:06-健康提醒.md。