软件 · 桌面宠物

Maltese

会摇尾巴的桌面宠物聊天伙伴

在你桌面上趴一只马尔济斯。没人碰它的时候,它自己踱步、伸懒腰, 蹲久了就蜷起来打盹,眼睛还跟着你的鼠标走。 点它一下就摇尾巴,双击它就去下面那一栏跟你说话 —— 上面那只就是活的,试试。全程离线,模型编在程序里,不联网。

离线 · 只存本地Windows 10 · 11单文件 54.7 MBv1.0.0
摸它、收到回复、打字、它睡着时都有声音 —— 和 App 同一批合成音
01 软件

它有两种形态,都不带边框

挂件态是一只直接落在桌面上的小狗 —— 没有窗口、没有标题栏、没有底板。 双击才展开成完整窗口。两种形态里是同一只狗,同一个骨架。

挂件态 · 220 × 240

无边框、透明、置顶、不占任务栏。画布上只有三样东西:小狗、它脚下的接触阴影、 可选的一枚挂牌。首启会挂一枚白瓷牌交代怎么用,之后不再出现。

挂件态实测截图:桌面上只有一只白色马尔济斯小狗和它脚下的一小片接触阴影,没有窗口、没有边框

实机截图 · 整块画布只有它自己和它脚下的一小片阴影

窗口态 · 980 × 648

顶栏是字标、状态点与设置;左栏是它自己:小狗卡、心情徽章、六枚心情胶囊、 人格与模型。右栏是对话。底栏常驻一句离线 · 只存本地 · v1.0.0

窗口态实机截图:顶栏是 Maltese 字标、「会摇尾巴的聊天伙伴」、「模型已就绪」与设置;左栏是白瓷盘里坐着的小狗、心情徽章「开心」、六枚心情胶囊、人格与模型下拉;右栏是「与 Maltese 聊天」与它说的第一句话;底栏写着「模型已就绪」与「离线 · 只存本地 · v1.0.0」

实机截图 · 模型已就绪,它一进来先跟你打招呼

拖它搬家widget {x, y}
放哪儿都行,位置跨会话记住。拖拽会先掐掉在途的自主踱步 —— 否则两条写入路径会互相覆盖,走到终点时还会把你刚放好的位置存盘覆盖掉。
单击摸头hop 520ms
人对宠物最本能的动作是摸它,所以单击让给了摸头:小跳一下、摇尾加速、冒一串心。 它睡着时先把它叫醒。说话改用悬停才出现的胶囊 —— 平时整块画布只有狗。
不占任务栏WS_EX_TOOLWINDOW
挂件态是分层窗口,不进任务栏、不抢焦点。托盘图标是它的第二个入口: 右键切心情、换个说法、打开窗口、设置、退出。
只许开一只Local\Maltese.SingleInstance
双击第二次不会变出第二只狗,第二个实例直接退出。存档不会被两只狗抢着写。
02 狗狗

没人管它的时候,它也在过日子

桌宠的说服力不在「能被拖动」,而在「它自己会生活」。 一只停在原地的贴纸不叫桌宠。

姿态骨架 · 四种姿势

小狗不是一张图,是一副能摆姿势的骨架:身体绒球 + 前爪 + 头 + 尾。 头与尾在各自的局部坐标里画好,再由姿态给出平移与旋转 ——换姿势只是换一组数字,一条路径都不用重画。而姿态由心情决定,所以它不会一边写着「开心」一边趴着不动。

四种姿态对照:站、坐、趴、蜷,同一只白瓷色马尔济斯小狗在四个白瓷盘上的四组姿态
站着stand · 兴奋 / 顽皮坐着sit · 开心 / 亲昵趴着lie · 平静蜷着curl · 困倦 / 打盹

四组姿态,是同一只狗 —— 路径一条都没重画

自己走动42%
没人在动它的时候,每 2.6–6.4 秒醒一次掷一次骰子:踱步 42%、抖一下 20%、 伸懒腰 8%。剩下 30% 什么都不做 —— 只有呼吸、眨眼、摇尾。
安静也是宠物30%
这一条是刻意留的。如果它每一秒都在动,那不叫活的,那叫动画。
困了就睡45s / 夜 20s
45 秒没人碰它就自己蜷起来打盹,夜里 23:00–06:00 缩短到 20 秒。 打盹与形态无关 —— 窗口开着一整个下午,它也会困。这一页上就能看见: 放着别动,上面那只狗会蜷起来睡,第 04 节那三层里的「漂移」就是这么来的。 (开着音效的话,它还打呼。)
它在看你380px / 3.2px
鼠标进到它头周围 380 像素内,眼睛就朝鼠标偏,最多偏 3.2 像素。 窗口态也生效 —— 它趴在卡片上看着你打字。
让位给手动stopWalk()
拖拽中、挂牌打开时、菜单打开时、正在生成回复时、模型加载中,自主行为循环全部让位。宠物不能在你打字的时候走开。
03 头脑

一个编在程序里的小模型

它的脑子是一份约一千万参数的字符级小模型,用 Polarbear 架构的推理子集, 跑在你自己电脑的 CPU 上。这份模型被直接编进了 exe。

参数10,198,949
一千万参数出头。它的产出是字符级续写 —— 一个说了会算错数、会把两个词粘在一起的小家伙,不是问答机器。 这一点我们宁可先讲清楚。
权重绑定的递归 Rec[tied×5]:一个块被反复跑 5 遍, 用的始终是同一套权重(检查点的 pool 里只躺着一个块)。CPU 前向 + KV 缓存采样。 骨架长什么样
放进 exeinclude_bytes!
模型是编译期嵌进二进制的,不是一个要你另外下载的文件。所以下到的是一个 54.7 MB 的 exe,双击就有一只狗。绿色免安装,拷走文件夹就是拷走全部。
换你自己的模型models/*.json[.gz]
把检查点丢进 exe 同级的 models/,它就会出现在「模型」下拉里。 不想用模型也可以 —— --no-model 时回复走一句句写好的台词。
意图 / 记忆 / 心情两条路都在用
这三层是它不加载模型时的回话方式,也是我们敢说「接得住话」的那一部分。 装了模型时它们并没有停 —— 照样在跑,而且会拼进模型的提示词, 变的只是「那句话由谁来说」。
诚实的一行
一千万参数的字符级模型说不出完美中文,而且它不是在做问答,是在接着你的话往下写 —— 项目里量过三份检查点、二十五组采样配置,没有一组是对话。 所以装了模型之后,你多半会看到续写而不是回答。这一条写在「它的头脑」里,包括为什么不改 prompt、下一步往哪走。
04 心情

缎带的色相,就是它的心情

全站只有一处饱和色,就是它头上那枚缎带。马尔济斯犬展上戴蝴蝶结 —— 这是它世界里的真实物件,于是我们让心情收敛成缎带的颜色和一个圆。下面那枚胶囊你按一下,上面那只狗会当场换缎带。

心情:开心
心情很好,尾巴轻轻摇~

把基线设成

未选中的一律不上色——六枚胶囊各自染自己的色,等于一屏同时出现六个饱和色。 选中项才显色,并且标的是你设的那个,不是它现在漂到的那层。

基线开心你点的那一枚,翻页也还在
漂移——它自己的状态;45 秒没人碰它就睡着了
即时反应——被摸出来的,9 秒后自己落回
当前生效开心和你设的一致

这两个按钮演示的是另外两层。连摸两下才切「亲昵」(摸一下只是跳一跳)——9 秒后它自己落回; 一直摸就一直续期。徽章显示它现在的样子,胶囊环标的才是你设的那个,中间那行边注写出原因。 一个能自己改变状态的系统,必须始终留一个手动出口:点那枚已经选中的胶囊,立刻清掉漂移与即时反应,回到基线。

基线base · 永久
你点的那一枚。它被存进 maltese_state.json存的是基线,不是当前生效的那层 —— 否则某次深夜退出后「困倦」就成了新的默认,你下次打开会看见它永远困着。
漂移drift · 几分钟
它自己的状态推动的那一层:聊得太起劲就兴奋。规则刻意只有这一条 ——规则越多,「它有自己的想法」就越像「它坏了」。
即时反应react · 9 秒
被你连摸两下就切到「亲昵」,9 秒后自己落回基线;一直摸就一直续期。你刚才点它试试 —— 徽章会变,但胶囊环还留在你设的那一枚上, 中间那行边注会告诉你为什么。
手动出口点已选中的那枚
狗漂到别的心情时,点那枚已经选中的胶囊 = 立刻清掉漂移与即时反应。一个能自己改变状态的系统,必须始终留一个手动出口。
05 回应

先接住话,再保持人格

它的回复不是从一个池子里随机抽一句。三个来源按顺序试,先命中先用 ——顺序是设计过的下面这一栏可以直接打字。

人格
0.70
回复来源
不加载模型时走的那条:意图 → 记忆 → 心情,什么都能答

和 Maltese 说点什么吧

或者点下面那排「试一句」

记得(还空着——说到累、工作、吃饭、睡觉这类词,它就记下来)
试一句

台词表是模型不加载、或加载失败时的降级路径。温度只决定它挑第几句 (不影响速度);回上面第 04 节换一枚心情胶囊,第三顺位的那句话也会跟着变。 内置模型那条路跑不了——它在你机器的 CPU 上(10,198,949 参数、词表 17,829 字、加载约 958 ms), 所以放的是五句真实录音。

第 1 顺位意图
在你说的话里找关键词(累 / 难过 / 工作 / 开心 / 玩 / 吃 / 睡)。关键是触发词和说出口的词是两件事words 用来匹配 ( / 饿 /外卖),填进句子的却是topic——所以你说「晚饭还没吃」,它回的是「吃饭这件事,别忘了哦」,而不是「还没吃这件事」。能匹配到,不等于说得出口。
第 2 顺位记忆
没什么可接的时候,翻出它记着你提过的事回访一次。回访排在回应当前之后—— 忽略你刚说的这句去翻旧账,不叫惦记,叫复读。
第 3 顺位心情
都没命中,才按它现在的心情说。同一句情境,三套人格说三样话。
记得什么3 条 · 20 分钟
侧栏常驻一行 记得 累 · 吃饭,最多 3 条,超过 20 分钟就忘,说出口就抹掉。看不见的记忆只是魔法,看得见的才叫记得——这一行也是「离线 · 只存本地」那句话的实物。
三套人格小狗 / 黏人 / 顽皮
人格不是语气开关,是三套词库。同一句「你回来了」——小狗说「尾巴都摇酸了,你看见了吗」, 黏人说「别走开好不好?就再待一会儿」,顽皮说「汪!心情好得想咬自己尾巴」。一个只多一句自我介绍、之后毫无影响的控件,是设计上的谎言。

词表、台词、记忆的两条规则、三个来源的先后顺序,都取自项目原型, 不是站上另写的一套玩具。这一栏跑的东西,和 App 里的那条降级路径是同一份。

06 隐私

它不知道怎么联网

这不是一句承诺,而是它被造出来的方式: 程序里不含任何网络功能,也就没有一条可以把数据送出去的通道。

没有账号
不需要登录,没有云端,没有使用统计、没有埋点、没有崩溃上报、没有广告、 没有任何第三方 SDK。
只写三个文件exe 同级
maltese_state.json(设置、对话、它记住的词、小狗的位置, 旁边留一份 .bak)、maltese.log(排障日志,写满自动滚成.log.1,只留两份,里面不写你说的话)、models/(你自己放进来的模型)。
看一眼桌面颜色8 个点
小狗是白瓷色的,放在浅色壁纸上会看不清。所以它会读挂件背后 8 个点的像素, 算一下这块是亮还是暗,好换接触阴影的颜色。不截图,看不到别的窗口里是什么,只在挂件态看,结果不写盘
想删就删
设置里有「忘掉这些」和「重置设置与对话」;也可以直接删掉那个文件,或者整个文件夹。删了就是删了:作者这边没有副本可以帮你恢复 —— 因为从来就没有过。

读完整的隐私策略

07 铭牌

规格

像铭牌上那一行蚀刻小字。上面每一处提到的数字,都在这里对一次账。

版本
v1.0.0
平台
Windows 10 / 11 · 64 位
发布形式
单文件 exe · 绿色免安装
文件大小
54,680,024 B (含内置模型 48.4 MB)
内置检查点
10,198,949 参数 · 字符级 · f32
词表
17,829 字
推理
CPU 前向 · KV 缓存采样 · 无 GPU
网络
依赖表里没有任何网络库
心情
6 种 · 三层来源
人格
3 套
记忆
3 条 · 20 分钟有效期
音效
4 档 · 程序内合成 · 可整套静音
存档
maltese_state.json (含 .bak 回退)
作者
北极熊 · Mint 薄荷

依赖表:serde · rand · rayon · chrono · flate2 · image · eframe · windows-sys。 没有一项会往外发消息。

把它放到你桌面上

下下来是一个 exe。双击,桌面上就多了一只狗。没有安装程序,没有账号,没有下一步。

下载v1.0.0 · Windows

离线 · 只存本地 · 不联网

赞赏 · 它不要钱

想请作者喝一杯的话

Maltese 是免费的:没有广告,没有账号,没有「高级版」, 也没有一个一直等着你点它的升级按钮。做它,是因为想有一只这样的狗。

微信支付收款码:绿色海报,上方写着「推荐使用微信支付」,中间是收款二维码,收款人显示为「天空城外(**涛)」,下方是微信支付标识

微信支付 · 这张图是作者本人给的,站点一个像素都没改过

小心
收款码指向真实账户,别扫错。

保存这张收款码