- 106 动画 + 2366 表情包 + 语义标签 - 三层记忆 / 心情系统 / 9 可配置插件 / 10 技能 - DeepSeek 对话直连 + MCP/HTTP 接入 + 接入文档 - 动画随机链 + 物理引擎 + 贴边碰撞校准 - 大资源(动画webp/表情图)由 scripts/ 再生成,不入库
183 lines
9.2 KiB
Markdown
183 lines
9.2 KiB
Markdown
# 悬浮宠物(com.dsh.petfloat)工程备忘
|
||
|
||
## 用户偏好(重要)
|
||
|
||
- **默认一律构建并交付「原画质」包(640×360,`-Phq`)**,不要再默认出小体积包。
|
||
- 原因:pilot 的 `app.install` 有 **128MB 硬上限**(见 `/opt/pilot/USAGE.md`),
|
||
原画质包 ~427MB 无法通过 pilot 安装 → **由用户手动安装**。
|
||
- 小体积包(320×180,~118MB)仅作为「需要我远程直装调试」时的临时手段,不作为交付物。
|
||
|
||
## 素材目录(已拆分)
|
||
|
||
| 目录 | 内容 |
|
||
|---|---|
|
||
| `app/src/main/assets_common` | `config.json`、`candy.ttf`、`pet.html`、`m01..m27.webp`(表情包) |
|
||
| `app/src/main/assets_small` | `a0001..a0106.webp`(320×180, q68) |
|
||
| `app/src/main/assets_hq` | `a0001..a0106.webp`(640×360, q80,原画质) |
|
||
|
||
`app/build.gradle.kts` 按 `-Phq` 选择 small / hq:
|
||
|
||
```bash
|
||
# 原画质(交付用)
|
||
JAVA_HOME=/opt/jdk17 /opt/gradle/gradle-8.5/bin/gradle assembleDebug -Phq
|
||
# 小体积(仅用于 pilot 直装调试)
|
||
JAVA_HOME=/opt/jdk17 /opt/gradle/gradle-8.5/bin/gradle assembleDebug
|
||
```
|
||
|
||
产物统一复制为 `petfloat-hq.apk` / `petfloat-small.apk`,HQ 通过 present 交给用户手动安装。
|
||
|
||
## 动画素材来源
|
||
|
||
`/root/projects/.dsh/profiles/web/node_modules/dsh-pet/assets/webm/*.webm`(106 个,VP9-alpha,10.042s;
|
||
「工作状态-垂头叹气冒汗」为 4.917s)。
|
||
|
||
转码(本机 ffmpeg 的 webp **解码器**不支持动画 webp,只能编码,校验用 RIFF 解析):
|
||
|
||
```bash
|
||
ffmpeg -y -c:v libvpx-vp9 -i in.webm \
|
||
-vf scale=640:360 -c:v libwebp_anim -q:v 80 -loop 0 -pix_fmt yuva420p -an out.webp
|
||
```
|
||
|
||
## 已知限制
|
||
|
||
- `app.install` 单文件 ≤128MB,且每次都要用户在手机确认。
|
||
- pilot 审批卡只在 60 秒窗口内有效;安装前需用户在看手机。
|
||
- 动画池/权重/物理参数全部来自 `assets_common/config.json`(由 dsh `config.jsonc` 生成)。
|
||
|
||
## 对外接入端口(供 workbuddy / DeepSeek 等调用)
|
||
|
||
本机 HTTP 服务监听 `127.0.0.1:7350`(仅回环,设备内任意 App 可访问)。
|
||
控制面板「接入」页可查看地址并复制。
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|---|---|---|
|
||
| GET | `/api/health` | 返回应用信息与端点清单 |
|
||
| POST | `/api/notify` | `{state\|level, text, durationMs, meme}` 推通知 |
|
||
| POST | `/api/task` | `{state: start\|working\|result\|waiting\|done\|error, text}` |
|
||
| POST | `/api/anim` | `{name, holdMs?}` 点播任意动画(中文名) |
|
||
| POST | `/api/broadcast` | `{text, duration, meme}` 仅气泡 |
|
||
| POST | `/api/clear` | 清空气泡 |
|
||
| GET | `/api/state` | 当前动画与气泡状态 |
|
||
|
||
状态 → 动画档位:start/thinking→思考冒泡,working→忙碌点按,result→清点归档,
|
||
waiting→原地踱步张望,done/success→雀跃庆祝,error→垂头叹气冒汗。
|
||
`level` 取值 info/success/warn/error/working/result。
|
||
|
||
通知会设置 `holdUntil`,随机动画链在展示时长内不会覆盖它。
|
||
|
||
示例:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:7350/api/notify \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"state":"done","text":"任务完成:报告已生成"}'
|
||
```
|
||
|
||
调用方需允许明文 HTTP(`usesCleartextTraffic` 或 network-security-config 放行 127.0.0.1)。
|
||
|
||
## 气泡(独立悬浮窗)
|
||
|
||
- 气泡是**独立 overlay 窗**(`FLAG_NOT_TOUCHABLE`),浮在宠物窗口上方,
|
||
位置 = 宠物窗口顶边 - 气泡高度 + 6dp,水平居中,自动夹在安全区内;宠物移动时跟随。
|
||
- 因此气泡不会被宠物窗口裁切(此前 240×135dp 窗口内的 HTML 气泡会被截断)。
|
||
- 样式存 `pet_prefs/bubble_style`,可选 `default|dark|outline|note`,
|
||
由 `BubbleDrawable`(圆角矩形 + 底部三角尾巴)绘制;面板「外观 → 气泡样式」切换。
|
||
- 宠物窗口 HTML(pet.html)现在只画宠物本体。
|
||
|
||
## 自助接入文档(端口自带)
|
||
|
||
- `GET /docs` —— 人看的 HTML 文档页:地址/鉴权/在线测试表单/接口表/状态映射/错误码/代码示例
|
||
- `GET /api/docs` —— 机器可读 JSON 规范:endpoints[] / states / levels / errors / auth
|
||
- 已支持 **CORS 预检**(OPTIONS → 204,允许 `Content-Type, X-Pet-Token`),
|
||
浏览器/WKWebView 类应用可直接 fetch 调用。
|
||
|
||
## 互动事件流(SSE)
|
||
|
||
`GET /api/events?token=<授权码>` 返回 `text/event-stream`,每 15s 心跳 `: ping`。
|
||
|
||
事件类型(`event: pet`,`data` 为 JSON):
|
||
| type | 触发 | 额外字段 |
|
||
|---|---|---|
|
||
| tap | 用户点按宠物 | anim |
|
||
| drag_start | 开始拖拽 | x, y |
|
||
| drag_end | 松手 | fling(bool), x, y / vx, vy |
|
||
| long_press | 长按打开面板 | - |
|
||
| play | 点播动画(面板或 /api/anim) | name |
|
||
| notify | 推送通知 | state, level, text |
|
||
| bubble | 只发气泡 | text |
|
||
| bubble_clear | 清空气泡 | - |
|
||
| anim | 随机链换动作 | name, kind |
|
||
|
||
`EventSource` 不能设请求头,所以 SSE 用 `?token=` 传授权码。
|
||
|
||
## 应用主界面(MainActivity)
|
||
|
||
- 以前 MainActivity 启动服务后立刻 `finish()`,点图标「像没反应」。现在改成真正的首页:
|
||
状态卡(运行中/未运行 + 当前动作 + 订阅数 + 是否需授权码)、显示/收起宠物、
|
||
控制面板入口、复制接入地址、打开接入文档、最近通知(4 条)、版本信息。
|
||
- 首次进入自动拉起宠物(需已授予悬浮窗权限,否则显示授权入口)。
|
||
- 主题改为 `Theme.Material.Light.NoActionBar`。
|
||
- 状态通过轮询 `/api/health` + `/api/state` 获取(2s 一次,页面不可见时停止)。
|
||
|
||
## 交付方式:手机共享目录(推荐)
|
||
|
||
`/root/share/DSHBox` 是手机内部存储 `DSHBox/` 的 bind 挂载(`/storage/emulated/0/DSHBox`),
|
||
构建好的 APK 直接放进去,手机上用文件管理器点开即可安装,**不受 pilot 128MB 限制**。
|
||
|
||
```bash
|
||
./build.sh # 原画质 → petfloat-hq.apk,并自动复制到 /root/share/DSHBox/
|
||
./build.sh small # 小体积 → petfloat-small.apk
|
||
```
|
||
|
||
手机端路径:`内部存储/DSHBox/petfloat-hq.apk`(首次安装需允许「安装未知应用」)。
|
||
|
||
## 界面合并(v1.0 后期)
|
||
|
||
- **SettingsActivity 已删除**,所有设置并入 `MainActivity` 的 4 个页签:
|
||
状态 / 外观 / 动作 / 接入。
|
||
- 长按宠物 → 直接打开 `MainActivity`(默认落在「外观」页,`EXTRA_TAB=TAB_LOOK`)。
|
||
- 点通知 → 打开 `MainActivity`(「状态」页)。
|
||
- `MainActivity` 为 `launchMode=singleTop`,重复打开走 `onNewIntent` 切换页签。
|
||
- 授权码仍在「接入 → 推送授权」列表的**二级弹窗**里查看/复制/改名/删除。
|
||
|
||
## UI 方向(最终)
|
||
|
||
- **参考 DeepSeek App**:白底分组列表(Inset Grouped)+ 细分割线 + 右侧 chevron + 品牌蓝 `#4D6BFE`。
|
||
- **不再做液态玻璃**(试过 A/B/C 三版 + 奶白玻璃,用户否决)。
|
||
- 三个页签:**状态 / 动作 / 设置**(底部浮动胶囊,纯文字,选中为浅蓝底 + 品牌蓝加粗)。
|
||
- 跟随系统浅色/深色(`Configuration.UI_MODE_NIGHT_MASK`,浅 #F7F8FA/白卡,深 #121316/#1B1D21)。
|
||
- 字体统一用系统字体,只有品牌大标题可用软糖体。
|
||
- 长按宠物 → 主界面「设置」页;点通知 → 「状态」页。
|
||
|
||
## 碰撞边界(重要)
|
||
|
||
1. `getRealMetrics` 取真实屏幕(1440×3200),且系统对悬浮窗有 `offsetY=169px`(状态栏)偏移,
|
||
用 `getLocationOnScreen` 自校准得到 `offsetX/offsetY`,碰撞边界据此补偿。
|
||
2. **素材本身带大量透明留白**(实测 l≈33.8%、r≈34.1%、t≈17.8%、b≈11.7%),
|
||
所以窗口贴边时角色仍离边很远。解法:`pet.html` 用 canvas 扫码 alpha 通道量出边距,
|
||
`POST /sprite` 上报(免鉴权,服务端取各动画的最大值),Service 允许窗口**超出屏幕**这些距离,
|
||
使角色真正贴边。
|
||
3. 旋转/折叠由 `onConfigurationChanged` 重算边界并夹回。
|
||
|
||
## 物理引擎(v2)
|
||
|
||
- 真实帧间隔积分(`System.nanoTime`,dt 上限 0.05s 防卡顿跳跃)
|
||
- 半隐式欧拉 + 空气阻力(0.55/s)
|
||
- 重力按密度换算:`gravity × density × 0.85`(跨机型手感一致)
|
||
- 分面弹性:地面 `restitution`,墙面 `0.82×restitution`
|
||
- 地面摩擦衰减(`groundFriction` 指数衰减),低速自动收敛停止
|
||
- 飞行中按水平速度轻微倾斜(±11°),落地回正
|
||
- 撞击(>450px/s)触发**挤压&拉伸**形变(1.14/0.86 → 1.0,240ms)
|
||
- 速度上限 5200/7000 px/s,避免高速穿透
|
||
- 全面使用配置里的 throwPower / restitution / groundFriction / ceilingBounce
|
||
|
||
## 记忆(三层,纯本地)
|
||
|
||
- L1 短期:最近 20 轮原文(chat_history,存 40 条)
|
||
- L2 长期记忆卡:`MemoryStore`(JSON 存 pet_prefs.memory_cards,上限 300 条)
|
||
字段:title / content / tags / importance(1~3) / created / updated / hits
|
||
- L3 召回:关键词切分命中(2.0/词) + 标签命中(1.5) + 重要度(0.8×i) + 新鲜度(≤1.2) + 使用次数(0.15×hits),取 Top5 注入 system 第二段
|
||
- 抽取:每 2 轮对话异步调一次 `ChatClient.extractMemories`(单独 prompt,输出 JSON,失败静默)
|
||
- 管理:设置 → 扩展 → 记忆(自动记忆开关 / 长按删除 / 手动添加 / 清空)
|
||
- 不走 embedding:DeepSeek 无 embedding API,本地向量模型会让 APK 膨胀
|