抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

DeskPet 是一款原生 macOS 桌面宠物应用(Swift / AppKit + WKWebView)。Live2D 角色以透明窗口常驻桌面,具备待机动作、视线跟随、随音乐节拍摆动与头顶对话气泡;对话内容与动作切换由大语言模型(LLM)以结构化 JSON 生成。应用另集成作息提醒、天气变化提醒、记忆库、每日任务简报,以及支持文献、新闻、行情与天气检索并渲染 Markdown 与 LaTeX 的科研模式。源代码以 MIT 协议开源:Yu-Xiang-Yang/deskpet。本文全部截图均取自本机实际运行的 DeskPet(角色为 Live2D 官方示例模型 Tororo),未经合成或模拟数据渲染。

1 背景与动机

现有桌面宠物应用多为预设动画的循环播放,与用户的交互局限于点击触发固定动作;另一方面,LLM 对话工具以独立窗口形式存在,调用时需切换上下文。DeskPet 的设计目标是将二者合并:

  • 常驻且不干扰:角色窗口透明,仅角色本体的不透明像素响应鼠标,其余区域点击穿透至下层窗口;
  • 语言与动作一致:模型在同一次回复中给出台词与动作标签,角色的表情、动作与台词语气保持一致;
  • 面向科研工作流:同一角色可切换至科研模式,回答以 Markdown 与 LaTeX 渲染,并可调用 arXiv、Semantic Scholar 等检索工具;
  • 轻量构建:仅依赖 Xcode 命令行工具(swiftc),不使用 Xcode 工程。

2 功能概述

桌面上的 DeskPet(实机截图)

图 1 DeskPet 在 macOS 桌面上的运行状态(实机截图,2026-10-14)。角色为 Tororo,名字设为“墨笃”,模型服务为 DeepSeek(deepseek-flash,OpenAI 兼容接口)。头顶气泡为模型对“下午要改论文,半小时后提醒我起来活动一下”的回复,同一回复的 remind 字段创建了一条 30 min 后的提醒,motion 字段触发舔爪动作;底部为对话输入框。

模块 说明
角色渲染 Live2D 模型(Cubism 3/4 运行时格式),以 PixiJS WebGL 渲染;可在设置中切换形象
交互 拖动移动并记忆位置;透明区域点击穿透;单击头部触发摸头反应,单击身体打开对话输入框;全局快捷键(默认 ⌥Space)呼出输入框
对话气泡 至多堆叠 3 条,新气泡位于底部,较早气泡上移并降低不透明度,各自计时后淡出;指针悬停时暂停计时
结构化回复 模型返回 reply、motion、remind、weather_city、remember、forget 六个字段,分别驱动台词、动作、提醒、天气查询与记忆库读写
音乐律动 检测其它应用的音频输出,经系统音频监听估计节拍,驱动角色左右摆动
主动行为 定时闲聊、自由走动、随机待机动作;用户离开 5 min 后进入睡眠状态
提醒 吃饭、上下班、久坐、喝水、25 min 专注计时,以气泡、动作与系统通知同时呈现
天气变化提醒 每 20 min 获取 Open-Meteo 逐小时预报,按规则提示带伞、恶劣天气与次日气温突变
记忆库 模型自动写入长期有效的用户信息,并附加于后续对话的系统提示词
科研模式 回答以 Markdown 与 KaTeX 渲染,可调用 6 个客户端检索工具
每日任务 每日首次使用时依次执行预设任务,结果保存为“每日简报”

3 交互界面

3.1 对话

单击角色身体或按下 ⌥Space 打开对话输入框。用户消息与角色回复均以气泡形式堆叠于头顶,回复到达时角色同步播放模型选定的动作。

输入消息 流式回复 回复完成

图 2 一次对话的三帧(实机截图,时刻自点击发送起计)。左:输入消息;中:1.5 s,用户消息以紫色气泡显示于上方,回复已开始流式写入下方气泡,角色播放动作;右:7.5 s,回复完整呈现,较早的用户气泡上移并降低不透明度。

模型返回的 JSON 中,reply 显示为气泡,motion 经 pet-map.json 映射为 Live2D 动作,remind 交由提醒调度器;三者在同一次请求中完成,不需要额外的意图识别步骤。

3.2 菜单栏菜单与右键菜单

菜单栏图标(爪印)与角色右键菜单由同一函数 addItems(to:) 生成,条目相同:和我聊天、科研模式、执行每日任务、消息列表、专注计时、提醒设置、设置、显示层级等。科研模式开启时另出现“开始新的科研话题”与“联网检索(较慢)”,并隐藏“去别处转转”。在刘海屏上菜单栏图标可能被遮挡,右键菜单因此保留全部入口。

菜单栏菜单

图 3 菜单栏菜单,科研模式处于开启状态(实机截图)。

显示层级提供三种选项:

层级 窗口级别 行为
始终置顶 NSWindow.Level.floating 位于所有普通窗口之上
普通窗口 NSWindow.Level.normal 可被其它窗口遮挡
仅在桌面 桌面图标层级 + 1 位于桌面图标之上、全部应用窗口之下;气泡亦随之被遮挡,提醒改由系统通知送达

3.3 科研模式

科研模式是角色的一种状态,开启后输入框中的问题交由科研助手回答。回答气泡支持 Markdown、KaTeX 公式与代码高亮,可滚动;单击气泡打开完整对话窗口。

科研模式回答气泡

图 4 科研模式下的回答气泡(实机截图)。问题为“简要说明 PINN 求解 Biot 固结问题时损失函数的构成,给出公式”,未开启联网检索。发送后 12 s 时气泡仍显示“思考中.”,18 s 时完整回答已呈现。气泡内以 KaTeX 渲染残差项 $\mathcal{L}_r$、边界项 $\mathcal{L}_b$ 与初始项 $\mathcal{L}_0$,内容超出气泡高度时可滚动,右上角“×”可立即关闭,底部输入框提示处于科研模式。

消息列表窗口(实机截图)

图 5 消息列表窗口(实机截图)。左栏按“日常对话”与“科研对话”分组,显示标题、时间与消息数,含每日任务生成的“每日简报”;右栏为图 4 所示对话的完整记录,标题旁标注所用模型,可重命名、删除或继续提问,并可勾选“联网检索文献”。

3.4 设置

设置窗口分为通用、主人信息、模型、提醒、每日任务、记忆库六个标签页。图 6 给出其中四页;“主人信息”与“记忆库”含个人资料,未予展示。

设置:通用 设置:模型

设置:提醒 设置:每日任务

图 6 设置窗口(实机截图)。左上:通用页,含名字、称呼、人设与形象选择,形象下方显示模型要求的版权标注;右上:模型页,可在 Claude(Anthropic)与 OpenAI 兼容接口之间切换,API key 以掩码显示并保存于钥匙串;左下:提醒页,含吃饭、上下班、久坐与喝水提醒;右下:每日任务页,每行一项任务,可立即执行一次。

4 系统架构

源代码共 27 个 Swift 文件(5 192 行)与 7 个前端文件(1 428 行),由 build.sh 直接调用 swiftc 编译,不含 Swift Package 或 Xcode 工程。代码分为三层:

目录 内容
Sources/Core/ 与界面无关的逻辑:JSON、SSE 解析、宠物回复解析、提醒规则、天气提醒规则、本地化
Sources/LLM/ LLMProvider 协议、Anthropic 与 OpenAI 兼容两种实现、检索工具与天气服务
Sources/App/ AppKit / SwiftUI:宠物窗口、对话输入框、消息列表、科研引擎、设置、提醒、记忆库、天气监测、音乐监测
Resources/web/ pet.html/js/css(角色与气泡)、research.html/js/css(消息列表)、md.js(Markdown 与公式)

数据流如下:

用户输入(输入框 / ⌥Space) ─▶ PetBrain ──┬─ 系统提示词:人设 + 主人信息 + 记忆库 + 回复格式
                                          ├─ 上下文:当前时间 + 所在地天气(缓存 30 min)
                                          ▼
                              LLMProvider(Anthropic / OpenAI 兼容,SSE 流式)
                                          ▼
                              PetReply.parse ─▶ reply   → 气泡(pet.js)
                                              ├─ motion  → pet-map.json → Live2D 动作组 / 表情
                                              ├─ remind  → ReminderScheduler
                                              ├─ weather_city → 查询天气后再次请求
                                              └─ remember / forget → MemoryStore

角色渲染与气泡位于 WKWebView 中,原生层与网页层经 WKScriptMessageHandler 与 evaluateJavaScript 双向通信。原生层负责窗口、鼠标穿透、视线坐标、提醒与模型调用;网页层负责 Live2D 绘制、命中测试与气泡排版。

5 关键实现

5.1 结构化回复

宠物对话要求模型只输出一个 JSON 对象,其 JSON Schema 由 PetReply.schema 给出,所有对象均设 additionalProperties: false。motion 取自 11 种语义动作:idle、happy、wave、nod、shake、think、surprised、sleepy、shy、eat、sad。Claude 以结构化输出(structured outputs)保证格式;OpenAI 兼容接口使用 JSON 模式。

解析器对模型输出采取容错策略:依次尝试整段文本、Markdown 代码块内文本,以及首个 { 至末个 } 之间的子串;未知动作名回退为 idle;全部失败时以整段文本作为台词。该策略使不支持严格结构化输出的模型(如部分 OpenAI 兼容服务)同样可用。

5.2 语义动作到模型动作的映射

不同 Live2D 模型的动作组与表情命名各异。每个模型目录下的 pet-map.json 将 11 种语义动作映射至该模型的动作组(group)、组内序号(index)与表情(expression);条目可含 params,在动作期间平滑覆盖模型参数,用于模型未提供的表情。以示例模型 Tororo 的 shake 为例:

"shake": {
  "group": "Idle", "index": 1,
  "params": { "PARAM_EAR_L": -0.7, "PARAM_EAR_R": -0.7, "PARAM_TAIL_ANGRY": 1 }
}

该条目在播放待机动作的同时将双耳下压、尾部置为生气姿态。说话期间,应用逐帧设置 LipSync 组参数(缺省为 ParamMouthOpenY)以驱动口型。

5.3 透明区域的点击穿透

macOS 的 NSPanel 只能整体设置 ignoresMouseEvents,无法按像素区分。DeskPet 采用“网页端采样 + 原生端查询”的方式:

  1. 网页端每 200 ms 将 WebGL 画布(preserveDrawingBuffer: true)缩绘至探测画布,以 10 × 10 CSS 像素为单元读取 alpha 通道,alpha > 24 的单元标记为实心,所得位图经消息通道传给原生层;
  2. 原生层以 30 Hz 的频率读取指针位置,换算为页面坐标后查询位图;指针位于实心单元时令窗口接收鼠标事件,否则令其穿透。

同一定时器将指针坐标传给网页端,驱动角色的视线跟随。

5.4 音乐节拍跟随

音乐律动分为播放检测与节拍估计两步:

  • 播放检测:每 0.5 s 经 Core Audio 进程对象查询其它应用是否在输出音频,该查询无需授权;输出持续 ≥ 1.5 s 视为开始播放(以排除通知音),静默 ≥ 2 s 视为停止;
  • 节拍估计:macOS 14.2 及以上版本经 Core Audio process tap 获取系统音频(需“系统音频录制”授权),计算约 150 Hz 低通频段的能量包络并检测起音,以节拍间隔的中位数估计速度,驱动角色以半拍速左右摆动。未获授权时,以约 112 BPM 的固定节奏代替。

音频仅在本机实时分析,不录制、不保存、不上传。

5.5 记忆库

对话中出现的长期有效信息(偏好、过敏、重要日期、长期计划等)由模型经 remember 字段写入,界面以“📝 记下了”气泡提示;forget 字段按关键词删除。条目按时间由新到旧附加于闲聊、科研与每日任务的系统提示词,上限 2 400 字符。去重规则如下:

情形 处理
规范化后相同,或互相包含 以新表述替换旧条目
仅存在少量字符插入(较短文本按序保留于较长文本,且长度比 ≥ 0.7) 以新表述替换旧条目
词语替换 视为不同事实,均保留

5.6 科研模式的工具循环

科研引擎单轮至多调用模型 3 次:前 2 次允许工具调用,第 3 次禁止工具、强制作答。同一轮的全部工具调用并行执行,结果合并为一条消息回传。可用的客户端工具如下:

工具 数据来源 说明
search_arxiv arXiv API 预印本检索
search_semantic_scholar Semantic Scholar API 已发表论文检索
search_news Bing News RSS,失败时 Google News RSS 新闻按时间倒序
get_stock_quotes Yahoo Finance 股票、指数与 ETF 行情,6 位 A 股代码自动换算为 .SS / .SZ
get_weather Open-Meteo 当前天气与 1–7 天预报,支持中文地名
fetch_webpage 公网网页 仅允许公网 http/https 地址,正文截取至约 6 000 字符

稳定性措施:单次请求超时 12 s;HTTP 429 / 503 仅重试一次,同一主机此后 60 s 内直接返回错误,使模型改用其它来源;相同检索 10 min 内复用缓存。使用 Claude 时另可调用服务端 web_search;Claude 的助手消息以原始内容块保存并原样回传,使对话历史保持只追加,以利于提示缓存。

部分 OpenAI 兼容模型(如 DeepSeek)偶尔将工具调用以其原生标记写入正文。应用在流式输出中隐藏此类标记,并将其解析为工具调用执行。

5.7 天气变化提醒

依据“主人信息”中的城市,每 20 min 获取一次逐小时预报并与当前天气比较:

类别 触发条件 频率
带伞 当前无降水,而 3 h 内降水概率 ≥ 60 %(或出现降水天气代码且概率 ≥ 40 %) 6 h 内至多一次
恶劣天气 6 h 内出现雷暴、大雨、大雪,或风速 ≥ 45 km/h 6 h 内至多一次
次日变化 17:00 后,明日最高气温较今日变化 ≥ 6 °C,或明日降水概率 ≥ 70 % 每日一次

6 模型服务

服务 配置项 说明
Claude(Anthropic) API key、模型名(默认 claude-opus-5-5)、拒答回退开关 Messages API 流式调用;宠物对话使用 effort: low 与结构化输出,科研对话使用 effort: high
OpenAI 兼容接口 Base URL、模型名、API key 预设 OpenAI、DeepSeek、通义千问、Moonshot 与 Ollama;本地服务可不填 key

API key 保存于 macOS 钥匙串(service 为 local.deskpet),不以明文写入磁盘或 UserDefaults。

7 构建与安装

7.1 构建

需 macOS 14 及以上(节拍跟随需 14.2 及以上)与 Xcode 命令行工具:

git clone https://github.com/Yu-Xiang-Yang/deskpet.git
cd deskpet
./build.sh install

首次构建时,scripts/fetch-vendor.sh 下载固定版本的前端库(pixi.js 6.5.10、pixi-live2d-display 0.4.0、marked 12.0.2、DOMPurify 3.1.6、KaTeX 0.16.11、highlight.js 11.10.0)并逐一校验 SHA-256,运行时不访问任何 CDN。

未配置签名证书时应用以 ad-hoc 方式签名,每次重新构建后签名改变,系统将再次请求钥匙串与“系统音频录制”授权。在“钥匙串访问”中创建名为 DeskPet Code Signing 的自签名代码签名证书后,build.sh 即以其签名,授权在重新构建后保持有效。

7.2 Live2D 组件

Live2D Cubism Core 与示例模型均禁止再分发,仓库因此不含任何 Live2D 文件,需使用者自行从 Live2D 官网下载 Cubism SDK for Web 与 Tororo & Hijiki 示例模型,再执行:

scripts/install-live2d.sh ~/Downloads/CubismSdkForWeb-5-r.5.zip ~/Downloads/tororo_hijiki_ja.zip

脚本将 Cubism Core 与两个模型复制至 ~/Library/Application Support/DeskPet/models/,并附上对应的 pet-map.json。Cubism Core 5.1 起绘制顺序的接口发生变化,pet.js 在加载 Core 后注册兼容映射,使 pixi-live2d-display 0.4.0 可与 Cubism SDK for Web 5-r.5(Core 6.0.1)配合使用。

8 测试

脚本 内容
scripts/selftest.sh 编译 Core、LLM 与记忆库源码及 Tests/main.swift,共 88 项检查,覆盖 SSE 解析、两种流式累加器、请求体字段、宠物回复解析、提醒规则、记忆去重与持久化、天气提醒规则及检索工具辅助函数
scripts/integration-test.sh 启动 Tests/mock_server.py,以 7 字节分片写出两种 API 的流式响应,经真实 HTTP 流验证工具循环、thinking 块回放、回退请求头、HTTP 错误信息与 JSON 模式;检索工具调用真实接口

9 数据存储与网络访问

内容 位置
对话记录 ~/Library/Application Support/DeskPet/conversations/*.json
记忆库 ~/Library/Application Support/DeskPet/memories.json
Live2D 模型与 Cubism Core ~/Library/Application Support/DeskPet/models/
设置、窗口位置、提醒状态 UserDefaults(域 io.github.yu-xiang-yang.deskpet)
API key 钥匙串(service local.deskpet)

应用仅访问所选模型服务的 API 地址、上述检索工具的数据源,以及模型经 fetch_webpage 指定的公网网页。

10 设计取舍

设计 依据
原生窗口 + WKWebView 渲染 Live2D 的成熟 Web 运行时可直接复用;窗口层级、穿透、快捷键与通知由 AppKit 处理
语义动作 + pet-map.json 模型只需选择 11 种语义动作之一,与具体 Live2D 模型解耦;更换模型时仅需编写映射文件
基于 alpha 位图的点击穿透 NSPanel 不支持按像素穿透;200 ms 采样、30 Hz 查询的开销可忽略
宠物对话低 effort、科研对话高 effort 闲聊要求低延迟,科研回答要求推理深度
工具循环上限 3 次调用 限制检索带来的延迟与费用,并保证必有回答
不随仓库分发 Live2D 文件 遵守 Live2D 的许可条款;模型由使用者自行获取
API key 存于钥匙串 避免明文写入磁盘

11 局限与注意事项

  • Live2D 组件须由使用者自行获取;公开分发允许加载任意模型的预编译应用前,须确认 Live2D 的发布许可条款(可能属于“可扩展型应用”);
  • ad-hoc 签名下,每次重新构建需重新授权钥匙串与系统音频录制;
  • 新闻与行情依赖非官方接口,可能随服务方调整而失效;
  • “仅在桌面”层级下气泡被应用窗口遮挡,提醒仅以系统通知可见。

12 项目地址

GitHub:https://github.com/Yu-Xiang-Yang/deskpet(MIT 协议,含中英文 README 与第三方许可说明)。

本文截图使用 Live2D 示例模型 Tororo。This content uses sample data owned and copyrighted by Live2D Inc. Live2D 及 Cubism 为 Live2D Inc. 的商标或注册商标;本项目与 Live2D Inc. 无关联。

评论