# QtDesktopPet QtDesktopPet 是一个基于 Qt Widgets / C++17 的 Windows 桌面宠物项目,当前已具备多状态 PNG 帧动画、托盘控制、角色包导入与切换、用户自定义大模型对话、设置面板和 Windows 发布打包能力。项目现阶段重点是完善稳定性、性能回归、角色管理和发布体验。 ## 当前状态 已实现: - 透明无边框桌宠窗口 - 鼠标拖动 - 右键菜单退出和状态测试 - 置顶切换 - `resources/characters/shiroko` 默认角色包读取 - PNG 序列帧动画播放 - `idle` / `talk` / `think` / `sleep` / `happy` / `drag` / `error` 状态 - 托盘显示、隐藏、退出 - 单实例限制,重复启动会唤醒已有实例 - 隐藏时暂停动画,显示时恢复动画 - 保存窗口位置、置顶、缩放和性能设置 - 文件日志和基础轮转 - 设置窗口按当前屏幕居中弹出 - 应用设置:缩放、性能模式、隐藏暂停、懒加载 - 状态级动画预热和 LRU 缓存卸载 - AI Provider 分组配置 - 设置页内 AI 连通性测试 - Windows DPAPI 加密保存 API Key - 非 Windows 环境经用户确认后明文保存 API Key - OpenAI Compatible 聊天请求 - SSE 流式输出 - 聊天输入框 - AI 回复气泡 - 对话历史面板 - 内存历史上限、可选本地历史保存、搜索和 Markdown/JSON 导出 - AI 请求取消和对话清空 - Google Gemini 原生聊天请求 - 角色文件夹导入和角色切换 - 删除用户导入角色 - 角色导出和打开用户角色目录 - 本地一次性和重复提醒:聊天创建、查询、取消,重启后 pending 提醒不丢 - 提醒到点气泡提示、稍后提醒、拖动后延迟提示和隐藏时托盘通知 - 提醒音效切换、试听、用户 wav 导入和删除 - 天气查询 v1:Open-Meteo 天气源、城市解析、默认城市、公网 IP 定位兜底和模板回复 - 本地文件操作 v1:读取文本文件、列出文件夹、复制、备份、重命名 - 联网模式:输入框开关触发 AI Provider 原生联网能力,支持 OpenAI 官方 Web Search 和 Gemini Google Search grounding - 应用启动 v1:通过聊天打开已登记应用、开始菜单快捷方式或用户确认选择的 `.exe` - Windows 发布打包脚本和 Inno Setup 安装器脚本 - Windows GUI 子系统,Release exe 双击不弹控制台窗口 尚未实现: - 长期性能压测记录 - 发布包实机安装/卸载验证 - 文件操作 zip 打包、删除、覆盖、移动、脚本/命令执行 - 搜索网页全文抓取、长期缓存和浏览器自动打开网页 - 应用启动脚本、命令行参数、管理员权限和跨平台应用发现 ## 技术栈 - C++17 - Qt 6 Widgets - Qt 6 Network - Qt 6 Multimedia - CMake - PNG 图片序列帧 - JSON 配置文件 - Windows 10 / Windows 11 优先 ## 构建 推荐环境: - Qt 6.5.3 - CMake 3.20+ - Ninja - MinGW 11.2.0 或已配置好的 Qt MSVC Kit MinGW 示例: ```powershell cmake -S . -B build/mingw-debug -G Ninja ` -DCMAKE_BUILD_TYPE=Debug ` -DCMAKE_PREFIX_PATH=D:/Qt/6.5.3/mingw_64 ` -DCMAKE_C_COMPILER=D:/Qt/Tools/mingw1120_64/bin/gcc.exe ` -DCMAKE_CXX_COMPILER=D:/Qt/Tools/mingw1120_64/bin/g++.exe cmake --build build/mingw-debug ``` 如果使用 Qt Creator,也可以直接打开根目录 `CMakeLists.txt`,选择合适的 Qt Kit 后构建。 ## 应用图标 当前应用图标位于: ```text resources/icons/app_icon.ico resources/icons/app_icon_1024.png ``` `app_icon.ico` 用于窗口图标、托盘图标和 Windows exe 资源图标;托盘图标加载失败时会回退到默认角色包的 `preview.png`。`app_icon_1024.png` 作为高分辨率源图保留。 运行时会优先读取可执行文件同级的 `resources/icons/`,找不到时回退到源码目录下的 `resources/icons/`。Windows exe 图标需要重新构建后生效。 ## 角色包 当前默认角色包位于: ```text resources/characters/shiroko/ ``` 内置角色包按 `resources/characters//` 组织。用户导入的角色包不会写入安装目录,而是复制到用户数据目录: ```text QStandardPaths::AppDataLocation/characters// ``` 角色包基本结构: ```text resources/characters/shiroko/ character.json preview.png idle/ talk/ think/ sleep/ happy/ drag/ error/ ``` 当前素材版本为 `2.1.0-stable`,所有帧使用 512x512 透明画布。当前实现会读取当前角色包的各状态配置,并按 `character.json` 中的 FPS 播放。 运行时会合并内置角色和用户导入角色;内置资源优先读取可执行文件同级的 `resources/characters/`,找不到时回退到源码目录下的 `resources/characters/`。 角色导入: - 只支持导入本地文件夹,不支持 zip - 导入前先验证源文件夹;验证失败只弹窗提示,不复制、不创建、不覆盖文件 - 验证通过后复制到用户数据目录 - 角色 id 优先读取 `character.json` 的 `id`;为空时使用文件夹名 - 角色 id 只允许 ASCII 字母、数字、点、下划线和短横线,且不能以点开头或结尾 - 用户角色同名时会询问是否覆盖 - 内置角色 id 不能被导入包覆盖 - 验证要求:`character.json` 可解析、id 安全、存在 `idle` 和 `defaultState`、状态路径安全、fps 合法、每个声明状态至少有一张可读 PNG - 如果 `base.anchorY + bubble.offsetY` 计算出的气泡锚点明显偏低,导入时会提示用户检查配置,但不强制阻止导入 - 只允许删除用户导入角色;选择内置角色删除时只会提示“不能删除内置角色”,不做文件操作 - 设置页支持导出当前选择的角色到用户选择目录,内置角色和用户角色都可以导出副本 - 导出目标目录已存在时会二次确认;不确认时不会覆盖 - 设置页支持打开用户角色目录,便于检查导入角色文件 懒加载现状: - `enableLazyLoad=true` 时,启动阶段只建立状态到帧路径的索引 - 某个状态首次播放时加载该状态的 PNG 帧 - 启动后会在主线程按批次预热常用状态,避免一次性加载全部帧 - 已加载状态按状态级 LRU 策略管理,超过动画缓存上限时卸载非保护状态 - 单轮预热不会反复重新加载刚被 LRU 卸载的状态,避免缓存上限较低时出现加载/卸载循环 - 隐藏到托盘时可释放非保护动画缓存 - `enableLazyLoad=false` 时仍保持启动阶段加载全部状态帧的兼容行为 ## 天气查询 当前支持通过聊天输入查询基础天气,例如: ```text 西安天气怎么样 明天西安天气怎么样 后天纽约天气 未来三天北京天气 今天天气怎么样 ``` 天气查询使用 Open-Meteo Forecast API 和 Open-Meteo Geocoding API,不需要 API Key。回复采用稳定模板,不依赖 AI 润色。支持当前/今天、明天、后天和未来 1-3 天基础天气;空气质量、天气预警、天气提醒联动、复杂小时级降雨判断和穿衣指数暂不支持。 当前 v1 推荐使用市级城市名。用户仍可输入天气源可识别的中文或英文地名,但区县、乡镇、街道不保证精确识别,可能无法匹配,或被匹配到上级/同名城市。当前会读取天气源返回的前 5 个地理编码候选;如果存在同名城市,仍使用第一个结果查询天气,并在回复中提示当前使用的城市和其他候选。 城市来源优先级: - 用户输入明确城市 - 设置页“天气”中的默认城市 - 默认城市为空且开启开关时,根据公网 IP 定位判断城市 当使用设置页默认城市或公网 IP 定位时,回复会明确说明城市来源。公网 IP 定位使用 ipapi.co;如不希望请求该服务,可在设置页关闭“无默认城市时根据公网 IP 判断城市”。 设置页“天气”中提供“测试默认城市”按钮,只验证输入框当前城市会匹配到哪个城市、行政区和国家,不自动保存配置;最终保存仍由设置页 Save 控制。 天气配置保存到: ```text QStandardPaths::AppConfigLocation/weather_config.json ``` 天气配置损坏时会备份为: ```text weather_config.broken.yyyyMMdd-HHmmss.json ``` 天气查询由独立 `src/weather/` 模块处理,`PetWindow` 只负责展示查询状态和结果。AI 正在请求或流式回复时不会启动天气查询,以避免覆盖 AI 气泡。 ## 聊天历史管理 聊天记录默认只保存在内存中。设置页“聊天”中开启“保存聊天记录到本地”后,会保存到: ```text QStandardPaths::AppConfigLocation/conversation_history.json ``` 历史管理能力: - 支持按关键词搜索当前聊天历史 - 支持按 Provider 和模型筛选;旧历史缺少元数据时会按未知处理 - 支持导出当前筛选结果为 Markdown - 支持导出当前筛选结果为 JSON - 清空聊天记录需要确认,并会清空当前内存和本地保存记录 - 导出内容不包含 API Key 或 AI 配置 - 日志不会记录完整聊天正文 ## 定时提醒和音效 当前支持通过聊天输入创建一次性和重复本地提醒,例如: ```text 10分钟后提醒我喝水 半小时后提醒我休息 一个半小时后提醒我喝水 明天9点提醒我开会 后天9点提醒我开会 6月3日9点提醒我提交 下周一上午10点提醒我周会 每天9点提醒我打卡 每天提醒我9点打卡 每日晚上8点提醒我吃药 每周一上午10点提醒我周会 每周一提醒我上午10点周会 每星期五下午3点提醒我提交周报 每月3号9点提醒我交报告 每月3号提醒我9点交报告 提醒列表 取消喝水提醒 ``` 提醒数据保存到: ```text QStandardPaths::AppConfigLocation/reminders.json ``` 提醒数据使用原子写入,写入失败时不会触发到点 UI,也不会覆盖旧的有效提醒文件。已触发和已取消记录会写入 `finishedAt`;旧版数据没有该字段时按 `remindAt` 兼容读取。 提醒调度保留最近提醒的精确 timer,同时每 60 秒做一次兜底扫描;程序显示、外部激活或系统睡眠唤醒后,都会重新检查已到期 pending 提醒。 设置页支持编辑 pending 提醒的标题、下一次时间和重复规则;已触发/已取消历史只读。历史记录默认只保留最近 20 天,设置页“清理20天前历史”只删除超过 20 天的已触发/已取消记录,不影响 pending。 提醒文件损坏时会备份为: ```text reminders.broken.yyyyMMdd-HHmmss.json ``` 内置提醒音效位于: ```text resources/sounds/reminders/ ``` 用户导入的提醒音效保存到: ```text QStandardPaths::AppDataLocation/sounds/reminders/ ``` 音效规则: - 默认音效为 `reminder_default` - 提醒触发时使用当前设置页选择的全局音效;修改音效后对所有未触发提醒立即生效 - 内置音效可切换、可试听,但不能在设置页删除 - 用户音效只支持导入 PCM wav - 用户导入音效可切换、可试听、可删除 - 删除当前用户音效后会回退到 `reminder_default` 触发规则: - 桌宠可见时显示气泡,不发系统通知 - 桌宠隐藏时发 Windows 托盘通知,不在下次显示时补气泡 - AI 正在请求或流式回复时,按隐藏场景处理:播放音效并发 Windows 托盘通知,不显示气泡 - 托盘或系统通知后端不可用时只记录日志,不补气泡 - 用户拖动中不打断 `drag`,拖动结束后显示气泡 - 多条提醒同时触发时,可见状态下会按队列逐条展示 - 桌宠可见触发时显示 `知道了` 和 `5分钟后再提醒` - `5分钟后再提醒` 会创建一条新的一次性提醒,不影响原重复规则 - 重复提醒支持 `每天 / 每周 / 每月`;`工作日 / 每两天 / 每月最后一天 / 自定义间隔 / 农历` 等复杂规则暂不支持 - 每月 31 号这类规则会跳过不存在该日期的月份,寻找下一个有效月份 - 用户音效删除仅允许删除用户音效目录内的安全 sound id,内置音效和非法路径不会被删除 ## 本地文件操作 本地文件操作通过聊天意图触发,但不会直接使用聊天文本里的路径。所有文件和文件夹都必须由用户在系统文件选择框中主动选择。 当前 v1 支持: - 读取用户选择的常见文本文件:txt、md、log、json、csv、ini、xml、yaml - 列出用户选择的文件夹,最多显示前 200 项 - 复制用户选择的文件到用户选择的目标文件夹 - 为用户选择的文件创建同目录备份 - 重命名用户选择的文件 安全规则: - 写操作会先展示操作计划并要求确认 - 不覆盖已有文件 - 不删除文件 - 不移动大量文件 - 不执行脚本或命令 - 不访问 Windows 系统目录 - 拒绝符号链接或符号链接目录内的路径 暂不支持: - zip 打包 - 删除、覆盖、移动 - 修改源码 - 执行脚本或命令 ## 联网模式 联网模式不是搜索引擎聚合器。用户在输入框打开“联网”开关后,普通聊天会进入当前 AI Provider 的原生联网能力: - OpenAI 官方 Provider:使用 Responses API Web Search。 - Google Gemini Provider:使用 Gemini Google Search grounding。 - DeepSeek 官方 API:当前不提供托管联网搜索工具,显示“不支持原生联网”。 - Custom / 第三方 OpenAI-Compatible:默认无法确认联网能力,不会尝试旧搜索页抓取。 配置保存到: ```text QStandardPaths::AppConfigLocation/web_config.json ``` 交互规则: - 联网开关默认关闭,避免隐私和费用意外。 - 设置页“联网模式”只显示能力状态、开关记忆、默认开关、超时、来源展示和测试联网模式。 - 当前模型不支持联网时,输入框打开联网后会明确提示,不发起伪联网请求。 - 支持联网的 Provider 返回来源时会展示来源;模型判断无需联网时允许无来源,并显示“模型未使用联网来源”。 - 旧 `search_config.json` 已废弃,新版不会读取、迁移或写入。 后续可扩展: - 更多 AI Provider 的原生联网适配。 - 结构化搜索 API 或自建联网后端。 - 更细粒度的来源证据摘录和引用 UI。 ## 应用启动 应用启动是独立能力,不归入本地文件操作。可通过聊天输入: ```text 打开 Codex 启动酷狗音乐 帮我打开 VSCode ``` 配置保存到: ```text QStandardPaths::AppConfigLocation/launcher_config.json ``` 设置页“应用启动”支持: - 启用或关闭应用启动 - 未知应用允许用户手动选择 `.exe` - 登记应用名称和别名 - 编辑、删除、测试启动已登记应用 安全边界: - 启动前始终二次确认,确认内容包含应用名、路径、工作目录和来源 - 只允许启动 `.exe` 或开始菜单 `.lnk` 快捷方式 - 不执行 `.bat` / `.cmd` / `.ps1` / `.vbs` / `.js` / `.msi` - 不执行聊天文本里的命令 - 不拼接聊天文本参数 - 不以管理员权限启动 - 手选 `.exe` 后可选择记住为当前名称,后续走登记应用匹配 ## 配置和日志 应用配置保存到 Qt 标准配置目录: ```text QStandardPaths::AppConfigLocation/app_config.json ``` 配置损坏时会备份为带时间戳的文件: ```text app_config.broken.yyyyMMdd-HHmmss.json ai_config.broken.yyyyMMdd-HHmmss.json conversation_history.broken.yyyyMMdd-HHmmss.json weather_config.broken.yyyyMMdd-HHmmss.json web_config.broken.yyyyMMdd-HHmmss.json launcher_config.broken.yyyyMMdd-HHmmss.json ``` 日志输出到文件,不输出到控制台: ```text QStandardPaths::AppConfigLocation/logs/QtDesktopPet.log ``` 如果 Qt 无法取得标准配置目录,则回退到当前工作目录下的 `logs/QtDesktopPet.log`。 日志轮转规则: - 单个日志文件超过 2MB 时轮转 - 最多保留 3 个旧日志文件 - 文件名为 `QtDesktopPet.log.1`、`QtDesktopPet.log.2`、`QtDesktopPet.log.3` ## 发布打包 仓库提供 Windows Release 打包脚本。脚本不负责 CMake 构建,先手动完成 Release 构建,再把 exe 路径传给脚本: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/package_release.ps1 -ExePath build/release/QtDesktopPet.exe ``` 脚本会生成目录包和 zip: ```text dist/QtDesktopPet--windows-x64/ dist/QtDesktopPet--windows-x64.zip ``` 发布目录包含: ```text QtDesktopPet.exe Qt runtime resources/characters/ resources/icons/ resources/sounds/ LICENSE README.md ``` 脚本会调用 `windeployqt.exe` 收集 Qt 运行库。若当前 PATH 找不到 `windeployqt.exe`,需要指定 Qt bin 目录下的工具路径: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/package_release.ps1 ` -ExePath build/release/QtDesktopPet.exe ` -WindeployQtPath D:\Qt\6.x.x\msvcXXXX_64\bin\windeployqt.exe ``` 生成 Inno Setup 安装器: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/package_release.ps1 ` -ExePath build/release/QtDesktopPet.exe ` -BuildInstaller ``` 安装器最终默认输出到项目根目录: ```text QtDesktopPet--windows-x64-setup.exe ``` 脚本会先让 Inno Setup 输出到当前盘符下的纯 ASCII 临时目录,例如 `D:\QtDesktopPetInstallerOutput`,再把最终安装包复制回项目根目录,避免中文项目路径下出现 `EndUpdateResource failed (5)`。如果需要改变最终安装包目录,可传入 `-InstallerOutputDir`: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/package_release.ps1 ` -ExePath build/release/QtDesktopPet.exe ` -BuildInstaller ` -InstallerOutputDir D:\ReleaseOutput ``` 如果需要改变 Inno Setup 的临时编译输出目录,可传入 `-InstallerWorkOutputDir`。 本地生成的安装包也可以集中放到 `release_packages/`: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/package_release.ps1 ` -ExePath build/release/QtDesktopPet.exe ` -BuildInstaller ` -InstallerOutputDir release_packages ``` `dist/` 和 `release_packages/` 都是本地发布产物目录,不进入 Git。 脚本默认优先查找: ```text D:\Inno Setup 7\ISCC.exe D:\Inno Setup 6\ISCC.exe C:\Program Files (x86)\Inno Setup 7\ISCC.exe C:\Program Files (x86)\Inno Setup 6\ISCC.exe ``` 如果 Inno Setup 安装在其他位置,可传入 `-InnoCompilerPath`。 安装器页面提供可选项: - 创建桌面快捷方式,默认不勾选。 - 开机自启动,默认不勾选;勾选后写入当前用户 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`,不需要管理员权限。 应用内设置页的“应用设置”页也提供“开机自启动”开关,保存后会立即写入或移除同一个当前用户 Run 项。卸载时会清理该 Run 项,避免卸载后残留自启动入口。 安装器卸载时会询问是否同时删除用户配置、导入角色、聊天记录和日志;用户确认后会在卸载完成阶段删除当前用户下的 QtDesktopPet 数据目录。 ## 开发诊断 仓库提供开发用性能采样脚本,不进入普通用户发布包: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File tools/perf_sample.ps1 ``` 默认采样当前 `QtDesktopPet` 进程 5 分钟,每 5 秒一条,CSV 输出到: ```text reports/perf/ ``` `reports/perf/` 已加入 `.gitignore`。稳定性检查记录模板见: ```text docs/performance_stability_check.md ``` 发布包应排除 `tools/`、`docs/`、`reports/`、`build/`、`dist/`、`release_packages/` 和 `.git/`,只保留运行必需文件、`resources/characters/`、`resources/icons/`、`resources/sounds/`、`LICENSE` 和必要说明。 ## AI 配置和聊天 当前正式聊天支持 OpenAI Compatible 和 Google Gemini 两类协议。已提供以下 Provider 配置入口: - OpenAI - Google - DeepSeek - Custom 其中 OpenAI、DeepSeek、Custom 走 OpenAI Compatible 形式配置;Google 走 Gemini 原生 REST 接口。旧版保存过的已废弃 Provider 配置会在读取 AI 配置时清理,废弃 Provider 被选中时会回退为 `custom`。 已支持: - 用户自定义 Base URL - 用户自定义 API Key - 用户自定义 Model - 用户自定义 Path - 超时、Temperature、Max Tokens - 流式输出 - Google Gemini `generateContent` / `streamGenerateContent` - 请求中切换 `think` - 收到首段输出后切换并保持 `talk` - 失败时切换 `error` - API Key 不写入日志,不在错误提示中完整显示 - 对话历史面板记录用户消息和 AI 最终回复 AI 测试入口已从角色右键菜单移除,并迁移到设置页。 ## 隐私说明 程序只会把用户消息发送到用户自己配置的接口。用户需要自行判断第三方代理、中转服务或自建服务是否可信。项目不会默认承诺第三方接口的隐私安全。 日志会记录请求诊断信息,例如 Provider、Base URL 主机、Path、HTTP 状态码、响应大小、错误摘要等;日志不应记录完整 API Key、Authorization Header、完整消息正文或完整错误响应正文。错误响应只保留脱敏摘要。 当前对话历史默认保存在内存中,已支持内存历史上限、请求上下文截取和可选本地历史保存;相关上限可在设置页调整。 ## 素材版权说明 源码采用 MIT License。 角色素材、图片、图标等资源需要单独确认授权。当前 `shiroko` 素材用于桌宠加载器、动画和状态切换测试;在公开发布或正式分发前,需要确认素材的版权、再分发权限和适用范围。 用户导入或替换的素材,其版权责任由用户自行承担。 ## 许可证 项目源码使用 MIT License,见 [LICENSE](LICENSE)。