十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI用量桌面宠物自定义方案:从零搭建到进阶玩法

AI用量桌面宠物自定义方案:从零搭建到进阶玩法 1. 桌面宠物到底是个什么东西桌面宠物这个概念其实不新鲜早年间的电子宠物、QQ宠物、甚至某些杀毒软件里养的小狮子本质上都是同一类东西——一个常驻在桌面上的、有自己行为逻辑的小型交互程序。但这两年随着AI能力的下放桌面宠物正在从“会动的贴图”进化成“真正能陪你干活的小助手”。我最近花了两周时间折腾了一个AI用量桌面端的桌面宠物自定义方案踩了不少坑也攒了一些可以直接抄的经验这里完整分享出来。先说清楚这个项目解决什么问题。你每天在电脑前工作可能同时开着浏览器、编辑器、终端、聊天工具AI相关的调用散落在各个窗口里。你想知道今天用了多少token、哪个模型调用最频繁、有没有异常请求传统做法是去翻日志或者打开某个后台面板。桌面宠物的思路是把这些信息变成一个常驻桌面的小角色它用表情、动作、气泡文字来实时反馈你的AI用量状态同时你还能自定义它的行为脚本让它在你摸鱼时提醒你、在你连续工作太久时催你休息。适合谁来参考如果你是有一定编程基础、想让桌面多一点“活物感”的开发者或者你正在做AI工具链的周边产品想找一个轻量级的桌面交互入口这个方案都能直接拿去改。哪怕你只是想给自己的电脑加一个不碍事的小挂件下面的自定义脚本部分也能让你快速上手。提示桌面宠物和“桌面小组件”最大的区别在于行为逻辑。小组件是静态展示宠物是有状态机的它会根据输入事件切换动作、表情和对话这也是自定义的核心价值所在。2. 整体架构与方案选型思路2.1 为什么选桌面端而不是网页端一开始我考虑过做成浏览器插件或者网页版毕竟开发成本低。但实测下来有几个硬伤网页版没法常驻桌面你切到其他应用它就看不见了浏览器插件受限于沙箱环境读取本地AI调用日志很麻烦而且网页版的“宠物”本质上还是页面里的一个元素没有那种“住在你电脑里”的感觉。桌面端的优势就很明显了。它可以透明背景、无边框、始终置顶鼠标穿透和点击交互都能精细控制。更重要的是桌面端能直接读取本地文件、监听系统事件、调用本地API这对于“AI用量监控”这个核心功能来说是刚需。我最终选的是Electron加Python后端的组合Electron负责窗口和渲染Python负责数据采集和脚本执行。这个组合的好处是前端用HTML/CSS就能画出很精致的宠物形象后端用Python处理数据又足够灵活。2.2 宠物形象与行为的分层设计整个系统我分成了三层表现层、逻辑层、数据层。表现层就是宠物的外观包括待机动画、说话气泡、状态图标逻辑层是行为状态机决定宠物什么时候做什么动作数据层负责采集AI用量数据并转换成逻辑层能理解的事件。这样分层的好处是你换一套宠物皮肤不需要动逻辑代码改行为规则也不需要重新画图。我见过很多人把这三层揉在一起写结果想换个宠物形象就得重写一半代码维护成本极高。2.3 自定义脚本的接入方式自定义是这个项目的灵魂。我设计了两类自定义入口一类是配置式的通过JSON文件定义宠物的动作触发条件、气泡文案、提醒阈值另一类是脚本式的允许你写Python或JavaScript脚本来处理更复杂的逻辑比如“当检测到某个模型调用失败率超过10%时让宠物做一个摔倒的动作并播放提示音”。配置式适合快速调整脚本式适合深度定制。两者可以共存脚本的优先级高于配置。这个设计参考了常见自动化工具的思路但针对桌面宠物的场景做了简化不需要你写复杂的DSL会基本的条件判断和函数调用就能上手。3. 核心细节解析与实操要点3.1 窗口透明与鼠标穿透的实现细节桌面宠物最基础也最容易翻车的地方就是窗口透明。Electron里设置transparent: true和frame: false只是第一步真正要做到“宠物看起来浮在桌面上”还需要处理几个细节。首先是背景色必须完全透明CSS里body的background要设成transparent同时html和body的margin、padding都要清零。其次是阴影和圆角如果你给宠物加了box-shadow在透明窗口下可能会出现黑边解决办法是用filter: drop-shadow()代替box-shadow或者干脆把阴影画在宠物图片本身里。鼠标穿透这块要分区域处理。宠物的身体区域需要接收点击事件但周围的透明区域应该让鼠标事件穿透到下面的窗口。Electron提供了setIgnoreMouseEvents方法配合forward: true参数可以实现“只在非透明像素上响应鼠标”。我实测下来最稳的方案是用一个透明的div覆盖整个窗口监听mousemove事件根据鼠标位置是否在宠物碰撞盒内来动态切换setIgnoreMouseEvents的状态。// 主进程中的鼠标穿透控制 const { ipcMain } require(electron); ipcMain.on(set-ignore-mouse, (event, ignore) { const win BrowserWindow.fromWebContents(event.sender); win.setIgnoreMouseEvents(ignore, { forward: true }); });渲染进程里监听鼠标移动判断坐标是否落在宠物区域内然后通过IPC通知主进程切换穿透状态。这个逻辑听起来简单但实际写的时候要注意节流不然鼠标一动就疯狂发IPC消息CPU占用会飙升。注意鼠标穿透开启后宠物的右键菜单和拖拽功能会失效所以要在需要交互的时候临时关闭穿透交互结束后再恢复。我建议用一个状态变量来管理避免状态混乱。3.2 AI用量数据的采集与聚合数据采集这块不同AI工具的日志格式差异很大。我目前支持三种采集方式文件日志解析、API轮询、本地数据库读取。文件日志适合那些会把调用记录写到本地文件的工具你只需要配置日志路径和正则表达式就能提取出模型名、token数、时间戳。API轮询适合有提供用量查询接口的服务定时拉取即可。本地数据库读取则是针对一些把数据存在SQLite里的工具。采集到的原始数据需要做聚合。我设计了一个简单的聚合器按小时和按模型两个维度统计。聚合结果存在内存里同时定期落盘到本地JSON文件这样重启后不会丢数据。聚合器的核心逻辑是每条新记录进来先判断当前小时桶是否存在不存在就创建然后累加token数和调用次数。# 聚合器核心逻辑示意 class UsageAggregator: def __init__(self): self.hourly {} # {hour_key: {model: {tokens, calls}}} def add_record(self, timestamp, model, tokens): hour_key timestamp.strftime(%Y-%m-%d-%H) if hour_key not in self.hourly: self.hourly[hour_key] {} if model not in self.hourly[hour_key]: self.hourly[hour_key][model] {tokens: 0, calls: 0} self.hourly[hour_key][model][tokens] tokens self.hourly[hour_key][model][calls] 1这里有个坑时间戳的时区问题。如果你的日志用的是UTC时间而你想按本地时间聚合一定要在解析的时候做转换不然统计出来的“今日用量”会莫名其妙少几个小时。我一开始没注意这个排查了半天才发现是时区没对齐。3.3 宠物行为状态机的设计行为状态机是让宠物“活起来”的关键。我把它设计成一个有限状态机状态包括待机、说话、提醒、庆祝、休眠。每个状态有对应的动画和持续时间状态之间通过事件触发切换。待机是默认状态宠物会做呼吸、眨眼这类循环动画。说话状态在气泡显示文字时触发持续到文字消失。提醒状态用于用量超标或长时间未活动的情况宠物会做一个夸张的动作吸引注意。庆祝状态在达成某个目标时触发比如今日用量低于设定阈值。休眠状态在电脑空闲一段时间后进入宠物会趴下睡觉减少资源占用。状态切换的优先级很重要。提醒的优先级最高可以打断说话和待机庆祝次之说话可以打断待机休眠优先级最低任何事件都能唤醒。这个优先级顺序是我根据实际使用体验调整出来的一开始把说话设得太高结果提醒被气泡挡住完全注意不到。3.4 自定义配置文件的字段说明配置文件我用的是JSON格式放在用户目录下的.deskpet/config.json。核心字段包括字段名类型说明默认值pet_namestring宠物显示名称“小助手”idle_timeoutnumber进入休眠的闲置秒数300token_warningnumber当日token警告阈值100000token_dangernumber当日token危险阈值500000bubble_durationnumber气泡显示毫秒数4000animation_speednumber动画播放速度倍率1.0enable_soundboolean是否开启提示音falsecustom_scriptsarray自定义脚本路径列表[]这些字段覆盖了大部分日常调整需求。比如你觉得宠物太吵把bubble_duration调小觉得动画太快把animation_speed调到0.8。改完配置文件后宠物会自动重载不需要重启程序。提示配置文件支持热重载但自定义脚本的改动需要重启才能生效因为脚本是在独立进程里跑的热重载容易出状态不一致的问题。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把基础环境搭起来。你需要Node.js 18以上版本和Python 3.10以上版本。Node.js用来跑Electron主进程和渲染进程Python用来跑数据采集和脚本引擎。两者通过本地HTTP接口或者标准输入输出通信我选的是本地HTTP因为调试起来方便用curl就能测试。初始化项目mkdir deskpet cd deskpet npm init -y npm install electron electron-store axios pip install watchdog requestselectron-store用来持久化配置axios用来做API轮询watchdog用来监听日志文件变化。这几个依赖都是轻量级的不会让安装包体积膨胀太多。目录结构我建议这样组织deskpet/ main.js # Electron主进程入口 renderer/ # 渲染进程宠物界面 index.html style.css pet.js backend/ # Python后端 collector.py # 数据采集 aggregator.py # 数据聚合 script_engine.py # 脚本引擎 assets/ # 宠物图片和音效 config.json # 用户配置这个结构清晰地把前端、后端、资源分开了后续想换宠物形象只需要动assets目录。4.2 宠物窗口的创建与渲染主进程里创建窗口的代码const { app, BrowserWindow } require(electron); const path require(path); function createPetWindow() { const win new BrowserWindow({ width: 300, height: 300, transparent: true, frame: false, alwaysOnTop: true, resizable: false, hasShadow: false, webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true } }); win.loadFile(renderer/index.html); win.setAlwaysOnTop(true, screen-saver); return win; }setAlwaysOnTop的第二个参数设成screen-saver级别可以保证宠物在全屏应用之上也能显示。但要注意有些游戏和视频播放器会强制置顶这时候宠物可能会被挡住这是系统层面的限制没法完全绕过。渲染进程里宠物形象我用的是序列帧动画。把每个动作拆成若干张PNG图片用CSS的background-position或者img标签切换来实现动画。序列帧的好处是兼容性好不需要WebGL而且美术资源容易替换。如果你会用Spine或者Live2D也可以接入但复杂度会高不少。.pet-sprite { width: 200px; height: 200px; background-image: url(../assets/idle_sheet.png); background-repeat: no-repeat; animation: idle 2s steps(8) infinite; } keyframes idle { from { background-position: 0 0; } to { background-position: -1600px 0; } }这段CSS的意思是待机动画有8帧每帧宽200px总宽1600px用steps(8)实现逐帧切换。实际项目中我会根据动作类型动态切换background-image和animation这样一套代码就能支持所有动作。4.3 数据采集模块的配置与运行Python后端启动后会读取配置文件里的采集规则。每条规则包含类型file/api/db、路径或URL、解析方式。以文件日志为例import re from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class LogHandler(FileSystemEventHandler): def __init__(self, pattern, callback): self.pattern re.compile(pattern) self.callback callback def on_modified(self, event): if event.is_directory: return with open(event.src_path, r) as f: for line in f: match self.pattern.search(line) if match: self.callback(match.groupdict())这个处理器会在日志文件被修改时触发逐行匹配正则表达式提取出模型名、token数等字段。正则表达式我建议写成命名分组的形式比如(?Pmodel\w)这样提取出来的就是字典不用再按位置取值。采集到的数据通过HTTP POST发给聚合器import requests def send_record(record): requests.post(http://127.0.0.1:8765/record, jsonrecord)聚合器收到后更新内存统计并检查是否触发阈值。如果触发了就通过WebSocket推送给Electron渲染进程宠物就会做出相应反应。4.4 自定义脚本的编写与调试脚本引擎我设计成沙箱执行每个脚本在独立的Python子进程里跑通过标准输入输出和主进程通信。脚本的入口是一个handle_event函数接收事件字典返回动作指令。# 示例当某个模型调用失败率过高时提醒 def handle_event(event): if event[type] usage_update: stats event[data] for model, info in stats.items(): if info[calls] 10 and info[errors] / info[calls] 0.1: return { action: alert, animation: fall_down, bubble: f{model} 失败率偏高检查一下 } return None调试脚本的时候我建议先用print输出到日志文件因为子进程的stdout被重定向了直接打印看不到。我在脚本引擎里加了一个log()函数专门用来写调试日志。注意脚本执行有时间限制默认5秒超时会被强制终止。这是为了防止死循环把整个程序卡死。如果你确实需要长时间运行的任务应该拆成多个事件处理而不是在一个脚本里阻塞。4.5 打包与开机自启配置开发完成后用electron-builder打包npm install electron-builder --save-dev npx electron-builder --win --x64打包配置里要注意把Python后端和资源文件一起打进去。我用的方案是把Python代码编译成单个可执行文件用PyInstaller然后作为extraResource打包。这样用户不需要单独装Python环境。开机自启在Windows上可以通过注册表实现Electron里用app.setLoginItemSettingsapp.setLoginItemSettings({ openAtLogin: true, path: app.getPath(exe) });macOS上类似但需要在系统设置里授权。Linux下则是写一个.desktop文件到~/.config/autostart/。5. 常见问题与排查技巧实录5.1 宠物窗口不显示或显示异常这是最常见的问题表现是程序启动了但看不到宠物或者宠物显示成一块黑方块。排查顺序如下先检查transparent: true是否生效。有些Linux桌面环境不支持窗口透明这时候宠物会显示成黑色背景。解决办法是换用frame: false加不透明背景或者改用setShape裁剪窗口形状。再检查图片路径。Electron打包后相对路径的基准目录会变建议用path.join(__dirname, ...)来构造绝对路径。我一开始用相对路径开发时正常打包后图片全丢了排查了好久。最后检查alwaysOnTop的级别。如果宠物被其他窗口挡住把级别调到screen-saver。但要注意某些系统会限制这个级别可能需要用户手动授权。5.2 数据采集不到或数据不准数据采集的问题通常出在三个地方路径不对、正则不匹配、时区不对。路径问题好解决在配置里写绝对路径或者用环境变量。正则问题需要你把日志样本拿出来在在线正则测试工具里调好再放进去。时区问题前面提过统一转成UTC存储展示时再转本地时间。还有一个隐蔽的坑日志文件轮转。很多工具会按天或按大小切割日志如果你的采集器只监听一个文件轮转后就采集不到了。解决办法是监听整个目录用watchdog的on_created事件捕获新文件。5.3 自定义脚本不生效脚本不生效的原因比较多我整理了一个速查表现象可能原因解决办法脚本完全没执行路径配置错误检查custom_scripts里的路径是否为绝对路径脚本执行但没反应返回值格式不对确保返回字典包含action字段脚本报错依赖缺失在脚本开头import所需模块确保已安装脚本超时逻辑阻塞检查是否有死循环或长时间IO操作热重载后失效子进程未重启手动重启程序或等待自动重启完成我踩过最坑的一个问题是脚本编码。Windows下默认用GBK编码而我的脚本文件是UTF-8导致中文注释乱码Python解析报错。解决办法是在脚本文件开头加# -*- coding: utf-8 -*-或者在读取时指定编码。5.4 性能占用过高桌面宠物应该是个轻量级程序如果发现CPU或内存占用异常通常是这几个原因动画帧率太高、数据采集太频繁、脚本执行太频繁。动画帧率建议控制在15fps以内桌面宠物不需要60fps的流畅度。数据采集的文件监听用watchdog的事件驱动模式不要用轮询。脚本执行加节流同一个事件在短时间内重复触发时只执行一次。我实测下来待机状态下CPU占用应该低于1%内存占用在100MB左右。如果超过这个数就要检查是不是有内存泄漏比如事件监听器没移除、定时器没清理。5.5 多显示器下的位置问题多显示器环境下宠物的位置坐标是相对于主显示器的。如果你把宠物拖到副屏重启后它可能会跑回主屏。解决办法是保存位置时记录显示器ID恢复时根据ID找到对应的显示器再设置坐标。const { screen } require(electron); function restorePosition(saved) { const displays screen.getAllDisplays(); const target displays.find(d d.id saved.displayId) || displays[0]; return { x: target.bounds.x saved.x, y: target.bounds.y saved.y }; }这个逻辑不复杂但很容易被忽略。我一开始没处理每次重启宠物都跑回主屏体验很差。6. 进阶玩法与扩展方向6.1 接入更多AI工具的用量数据目前我接入了三种采集方式但AI工具层出不穷格式也五花八门。扩展的思路是做一个插件化的采集器每个采集器是一个独立的Python模块实现统一的接口collect()返回记录列表validate()检查配置是否有效。主程序启动时扫描collectors目录自动加载所有采集器。这样你想接入新工具只需要写一个几十行的采集器模块不用改主程序。我目前已经用这个方式接入了五六个工具维护起来很轻松。6.2 宠物之间的互动如果你在多台电脑上部署了这个桌面宠物可以让它们之间互相通信。我试过用局域网广播实现简单的互动当一台电脑上的宠物进入提醒状态时同一局域网的其他宠物也会做出反应。这个功能纯属好玩但确实让桌面生动了不少。实现上用的是UDP广播每个宠物监听一个固定端口收到消息后根据消息类型触发对应动作。要注意的是广播消息要加一个简单的校验避免误触发。6.3 用AI生成宠物的对话内容气泡文案目前是预设的但你可以接入AI接口让宠物根据当前用量数据生成更自然的对话。比如用量超标时宠物不是干巴巴地说“用量超标”而是说“今天你调用了不少次啊是不是在跑什么大任务”接入方式是在脚本引擎里加一个generate_bubble函数把当前状态发给AI接口拿到返回文本后显示。这里要注意控制调用频率不要每次状态变化都调AI那样token消耗反而成了负担。我的做法是只在提醒和庆祝状态下调用且加一个缓存相同状态短时间内不重复调用。6.4 宠物形象的替换与社区化宠物形象我设计成了资源包的形式一个资源包包含精灵图、动作配置、音效文件。用户可以把资源包分享给别人也可以从社区下载。资源包的目录结构skins/ my_pet/ skin.json # 动作配置 idle.png # 待机精灵图 talk.png # 说话精灵图 alert.png # 提醒精灵图 sounds/ # 音效目录skin.json里定义每个动作对应的精灵图、帧数、帧率。这样换皮肤只需要替换整个目录程序自动识别。提示制作精灵图的时候建议用透明背景的PNG尺寸统一为200x200或256x256。帧数不要太多8到12帧足够表现一个动作太多会让文件体积膨胀。7. 我在实际使用中攒下的几条经验第一桌面宠物的核心价值在于“不打扰”。它应该在你需要的时候出现不需要的时候安静待着。我一开始把提醒做得太频繁结果自己都嫌烦后来把阈值调高、提醒间隔拉长体验才好起来。你可以把宠物当成一个“有分寸的同事”而不是一个“话痨”。第二自定义脚本不要写得太复杂。我见过有人把整个工作流都塞进一个脚本里结果调试困难、维护成本高。脚本应该只做“判断和触发”复杂的处理逻辑放到后端服务里。脚本越简单越不容易出问题。第三数据采集的准确性比实时性更重要。一开始我追求秒级更新结果日志文件频繁读写CPU占用下不来。后来改成批量处理每30秒聚合一次数据一样准资源占用降了一个数量级。第四宠物形象的美观度直接影响你愿不愿意让它常驻。我第一版用的像素风自己看着都觉得丑后来换成扁平化设计顺眼多了。如果你不会画画可以用AI生成精灵图或者从开源素材库里找。第五多显示器用户一定要处理位置保存。这个坑我踩了两次第一次是没保存位置第二次是保存了但没关联显示器。处理完之后宠物每次都能准确出现在上次的位置体验提升很明显。最后分享一个小技巧如果你想让宠物在特定时间做特定动作比如每天下午三点提醒你起来活动可以在脚本里用datetime判断当前时间触发对应动作。这个功能不需要额外的定时器复用现有的状态机就行。
返回列表