commit 7d2e8fed8e4887b42ba74aae46a9e8a79d194307 Author: mtfyun Date: Sun Nov 8 08:07:09 2026 +0000 小鲸娘 v1.0:悬浮宠物完整工程 - 106 动画 + 2366 表情包 + 语义标签 - 三层记忆 / 心情系统 / 9 可配置插件 / 10 技能 - DeepSeek 对话直连 + MCP/HTTP 接入 + 接入文档 - 动画随机链 + 物理引擎 + 贴边碰撞校准 - 大资源(动画webp/表情图)由 scripts/ 再生成,不入库 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9db0ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# ==== 构建产物 ==== +.gradle/ +build/ +app/build/ +*.apk +*.aab + +# ==== 机器相关 ==== +local.properties +*.iml +.idea/ +.DS_Store +captures/ + +# ==== 日志与临时 ==== +*.log +/tmp/ +*.tmp + +# ==== 可再生大体积资源(由 scripts/ 重建,详见 README「素材再生成」)==== +# 动画 webp(从 dsh-pet webm 转码,537MB 两套)→ scripts/regenerate_assets.sh +app/src/main/assets_hq/ +app/src/main/assets_small/ +# 表情包图(从梗鲸档案馆下载,114MB)→ scripts/download_memes.py +app/src/main/assets_net/ + +# ==== 原始下载与临时产物 ==== +/memes-import/ +/new-memes/ +/docs-preview/ \ No newline at end of file diff --git a/HERMES-PLUGIN-CONVERSION.md b/HERMES-PLUGIN-CONVERSION.md new file mode 100644 index 0000000..79bca56 --- /dev/null +++ b/HERMES-PLUGIN-CONVERSION.md @@ -0,0 +1,84 @@ +# DeepSeek Hermes 插件 → 小鲸娘插件 转换规范 + +> 给应用内「对话」用:把这份规范贴进对话,或直接说「把下面这个 Hermes 插件转成小鲸娘插件」, +> AI 会输出 `<<>>` 代码块,应用自动安装。 + +## 一、两边是什么 + +| | Hermes Agent 插件 | 小鲸娘插件 | +|---|---|---| +| 语言 | Python | JavaScript(跑在宠物 WebView 里) | +| 形态 | 目录 + `plugin.yaml` + `register()` | 一段 JS 代码 | +| 能力 | 注册工具、hook、web provider、设置项 | 说话/通知/播动作/弹表情/执行技能/监听互动 | +| 安装 | `hermes plugins install` | 对话里给代码,或 `POST /api/plugin` | + +**结论**:Hermes 插件不能原样搬进来(没有 Python 宿主),但**行为可以翻译**。 + +## 二、能力对照表 + +| Hermes 里的东西 | 小鲸娘里怎么写 | +|---|---| +| `on_startup()` / 顶层初始化 | 把初始化代码直接写在插件顶层 | +| 定时任务 / cron hook | `setInterval(() => Pet.say('…'), 3600000)` | +| 工具 `tool_xxx()` 被调用时通知 | `Pet.notify('done', '任务完成')` | +| 打印/汇报消息 | `Pet.say('文字', 6000)` | +| 触发动画 | `Pet.anim({tag:'happy'})` / `{name:'…'}` / `{random:true}` | +| 发图片/表情 | `Pet.meme({tag:'praise'})` | +| 组合动作 | `Pet.skill('work_done')` | +| 监听宠物互动 | `Pet.on('tap', fn)`、`Pet.on('drag_end', fn)`、`Pet.on('long_press', fn)` | +| 读宠物状态 | `Pet.status()`(返回 Promise) | +| HTTP 请求 | `fetch()` 可用(WebView 有网络权限) | +| 设置项 / 环境变量 | 写成代码顶部的 `var CONFIG = {...}`;**不要把密钥写进插件** | + +## 三、不能转换的 + +- 注册 LLM provider / 修改 agent 推理行为(那属于宿主,小鲸娘不跑模型路由) +- 依赖 Python 库(numpy/httpx/…):只能用 JS 和 `fetch` +- 依赖 Hermes 的配置目录、secrets、工具注册表 +- 需要拦截「另一个程序内部事件」的 hook —— 改由对方通过 HTTP 调小鲸娘 + +## 四、输出格式(AI 必读) + +需要安装插件时,**只输出**下面这种块,应用会自动安装并从可见消息里剥离: + +``` +<<>> +// 这里写 JS +setInterval(function () { Pet.say('该喝水啦~'); }, 3600000); +<<>> +``` + +规则: +- `id` 必填,只能 `[A-Za-z0-9_-]`,重复会覆盖 +- 代码里只用 `Pet.*` 与浏览器标准 API +- 安装后回一句简短说明(装了啥、什么时候触发) + +## 五、三个转换例子 + +**1) Hermes 定时提醒插件** +```python +# Hermes +def on_startup(ctx): + ctx.schedule_interval(3600, lambda: ctx.notify("该喝水啦")) +``` +```javascript +// 小鲸娘 +setInterval(function () { Pet.say('该喝水啦~'); }, 3600000); +``` + +**2) Hermes 工具结果通知** +```javascript +// 小鲸娘:被戳时报告一次状态 +Pet.on('tap', function () { + Pet.status().then(function (s) { + Pet.say('现在是:' + (s.anim ? s.anim.name : '待机')); + }); +}); +``` + +**3) Hermes web 搜索插件(只能部分转换)** +注册 provider 那部分转不了;能保留的是「用完给个反馈」: +```javascript +// 收到外部通知后弹个表情(由 Hermes 侧 POST /api/task 触发) +Pet.meme({ tag: 'praise' }); +``` diff --git a/INTEGRATION.md b/INTEGRATION.md new file mode 100644 index 0000000..2eb2453 --- /dev/null +++ b/INTEGRATION.md @@ -0,0 +1,318 @@ +# 桌宠推送接入文档(workbuddy / DeepSeek 等) + +桌宠在设备本机开了一个 HTTP 服务,任何 App 只要拿到**授权码**就能向它推送任务通知。 + +- 地址:`http://127.0.0.1:7350` +- 授权码:在桌宠面板「接入 → 推送授权」里查看/复制/生成(可多个、可单独吊销) +- 鉴权:写请求必须带 `X-Pet-Token: <码>`(也支持 `?token=<码>`) +- 只读接口 `GET /api/state`、`GET /api/health` 无需授权 + +> 调用方 App 需允许明文 HTTP:`android:usesCleartextTraffic="true"`, +> 或 network-security-config 放行 `127.0.0.1`。 + +--- + +## 接口一览 + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | `/api/health` | 应用信息、动画数、`authRequired`、端点清单 | +| GET | `/api/state` | 当前动画、气泡、朝向 | +| POST | `/api/task` | 任务状态通知(推荐) | +| POST | `/api/notify` | 通用通知(按 level 或 state) | +| POST | `/api/anim` | 点播任意动画(中文名) | +| POST | `/api/broadcast` | 只说一句话,不改动画 | +| POST | `/api/clear` | 清空气泡 | + +### POST /api/task + +```json +{ "state": "working", "text": "正在生成周报…", "durationMs": 8000, "meme": "m09" } +``` + +`state` → 动画映射: + +| state | 动画 | +|---|---| +| `start` / `thinking` | 工作状态-思考冒泡 | +| `working` | 工作状态-忙碌点按 | +| `result` | 工作状态-清点归档 | +| `waiting` | 工作状态-原地踱步张望 | +| `done` / `success` | 工作状态-雀跃庆祝 | +| `error` / `failed` | 工作状态-垂头叹气冒汗 | + +### POST /api/notify + +```json +{ "level": "error", "text": "构建失败", "durationMs": 10000, "meme": "m24" } +``` + +`level` 可选:`info` `result` `working` `waiting` `success` `error`。 +`durationMs` 期间宠物会保持这个动作,随机动画链不会覆盖。 + +`meme` 可选,取值 `m01`~`m27`(气泡里的表情包,见面板动作页预览)。 + +--- + +## 调用示例 + +### curl + +```bash +TOKEN=2caa9cb98fea488e +curl -X POST http://127.0.0.1:7350/api/task \ + -H 'Content-Type: application/json' \ + -H "X-Pet-Token: $TOKEN" \ + -d '{"state":"working","text":"正在生成周报…"}' +``` + +### Kotlin(无第三方依赖) + +```kotlin +import org.json.JSONObject +import java.net.HttpURLConnection +import java.net.URL + +fun pushToPet(token: String, state: String, text: String, durationMs: Long = 8000) { + Thread { + runCatching { + val conn = (URL("http://127.0.0.1:7350/api/task").openConnection() as HttpURLConnection) + conn.requestMethod = "POST" + conn.doOutput = true + conn.connectTimeout = 2000 + conn.readTimeout = 2000 + conn.setRequestProperty("Content-Type", "application/json") + conn.setRequestProperty("X-Pet-Token", token) + val body = JSONObject() + .put("state", state) + .put("text", text) + .put("durationMs", durationMs) + .toString() + conn.outputStream.use { it.write(body.toByteArray()) } + conn.responseCode // 200 = 成功,401 = 授权码不对 + conn.disconnect() + } + }.start() +} + +// 用法 +pushToPet(token = "2caa9cb98fea488e", state = "working", text = "正在整理对话…") +pushToPet(token = "2caa9cb98fea488e", state = "done", text = "整理完成 ✅") +pushToPet(token = "2caa9cb98fea488e", state = "error", text = "请求失败:超时") +``` + +### JavaScript / Node(workbuddy 若是前端/Node) + +```js +async function pushToPet(token, state, text, durationMs = 8000) { + const res = await fetch('http://127.0.0.1:7350/api/task', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Pet-Token': token }, + body: JSON.stringify({ state, text, durationMs }) + }); + if (!res.ok) console.warn('pet push failed', res.status, await res.text()); +} +``` + +--- + +## 排错 + +| 现象 | 原因 | +|---|---| +| `401 unauthorized` | 授权码不对 / 该应用被删除 / 面板里授权已关闭 | +| 连接被拒绝 | 桌宠没在运行(服务随宠物存活) | +| `404 not-found` | 路径写错(注意是 `/api/task` 不是 `/task`) | +| 调用成功但没反应 | 宠物面板「外观 → 距离/透明度」设得太小看不见;或气泡已超时消失 | +| `cleartext not permitted` | 调用方 App 没开明文 HTTP | + +--- + +## 行为约定 + +- 通知展示时长内(`durationMs`)随机动画链暂停,保证通知动作完整播完。 +- 同一时刻只有一条气泡,后到的覆盖前面的。 +- 桌宠本身不存储通知历史(当前版本),错过了就没了。 + +--- + +# 语义标签(AI 调用推荐方式) + +每个**动作**和**表情包**都带内部标签。AI 不必记中文名,按语义调用即可。 + +## 标签表 + +| 标签 | 含义 | 典型动作 / 表情包 | +|---|---|---| +| `work` `workstatus` | 工作状态 | 思考冒泡 / 忙碌点按 / 清点归档 / 踱步张望 / 雀跃庆祝 / 垂头叹气冒汗 | +| `thinking` `working` `result` `waiting` `success` `error` | 六档细分 | 对应上面六种工作状态 | +| `happy` `celebrate` `praise` | 开心/庆祝/夸奖 | 雀跃庆祝、开心跃动、厉害了我的鲸、可爱 | +| `sad` `error` `fail` | 沮丧/出错 | 垂头叹气冒汗、死掉了、完蛋了把黄油删了 | +| `busy` `stress` | 忙碌/压力 | 忙碌点按、压力一只大肥鱼、大烧货 | +| `eat` `food` | 吃东西 | 吃什么分类全部(12 个) | +| `play` `fun` | 玩耍/搞笑 | 玩耍分类(27 个)+ Ciallo、就骚了怎么滴吧 | +| `rest` `sleep` `lazy` | 休息/摸鱼 | 待机呼吸、小憩沉眠、哈欠连天、深度睡眠 | +| `festival` `season` | 节日/时令 | 时节分类(21 个:春节/中秋/圣诞/万圣…) | +| `money` `token` `need` | 余额/token | 余额动画 6 档、token给妈妈保管、鱼片有资源吗 | +| `warn` `urgent` `alert` | 预警/紧急 | 大的要来啦、大的药来了、电死你 | +| `greet` `wave` `hello` | 打招呼 | 元气挥手、Ciallo | +| `shy` `surprise` `angry` `tsundere` | 情绪 | 害羞惊讶、傲娇生气 | +| `drag` `click` `move` `turn` `idle` `look` `walk` `picked` | 交互池 | 拖拽悬空、点击回应、移动走路、转向张望、待机 | +| `text` `talk` `whisper` | 文字/说话 | 文字分类 2 个、碎碎念 3 个 | +| `category` + 分类名 | 分类归属 | 小动作 / 玩耍 / 吃什么 / 时节 / 文字 | + +## 新增 / 增强的接口 + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | `/api/catalog` | 返回全部动作与表情包及其标签(免授权) | +| POST | `/api/meme` | `{tag|name|file}` 头顶弹一张表情包 | +| POST | `/api/anim` | 现在支持 `{tag:"play"}` 按标签点播 | +| POST | `/api/task` `/api/notify` | 新增 `withMeme:true` 或 `memeTag:"success"` 自动配图 | + +### 典型用法 + +```bash +# 开始干活:播「忙碌点按」+ 自动配工作风表情包 +curl -X POST http://127.0.0.1:7350/api/task \ + -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \ + -d '{"state":"working","text":"正在生成周报…","withMeme":true}' + +# 干完了:播「雀跃庆祝」+ 夸奖表情包 +curl -X POST http://127.0.0.1:7350/api/task \ + -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \ + -d '{"state":"done","text":"周报已生成 ✅","memeTag":"praise"}' + +# 出错了 +curl -X POST http://127.0.0.1:7350/api/task \ + -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \ + -d '{"state":"error","text":"构建失败","withMeme":true}' + +# 随手玩一下 +curl -X POST http://127.0.0.1:7350/api/anim -H 'Content-Type: application/json' \ + -H 'X-Pet-Token: 你的授权码' -d '{"tag":"play"}' +curl -X POST http://127.0.0.1:7350/api/meme -H 'Content-Type: application/json' \ + -H 'X-Pet-Token: 你的授权码' -d '{"tag":"fun"}' + +# 让 AI 自己发现可用标签 +curl http://127.0.0.1:7350/api/catalog +``` + +## 自动配图规则 + +`withMeme:true` 时会按状态自动挑标签: +`done/success → success,praise`;`error → error,fail`;`working → working,busy`; +`waiting → warn`;`start/thinking → working,busy`。 + +## 表情包标签(随图入库) + +表情包最终以 `config.json` 里每条的 `tags` 为准(官方 27 张 + 动画帧生成 24 张 + 梗鲸档案馆收录)。 +常用梗图情绪标签:`angry` `sad` `happy` `praise` `thinking` `fun` `confused` `complain` +`love` `money` `waiting` `greet` `eat` `work` `error` `rest`。 + +- `GET /api/catalog` 返回每张表情包的 `tags` +- `POST /api/meme {"tag":"..."}` 按标签随机弹一张 +- `POST /api/task {"state":"working","withMeme":true}` 按状态自动配图 +- 应用内长按表情包可隐藏(写进 `pet_prefs.hidden_memes`) + +## 动作节奏与随机 + +| 接口 | 说明 | +|---|---| +| `POST /api/settings {"chainGapSeconds":8,"idleAfterSeconds":120}` | 切换间隔(0–60s)、空闲回待机(0=关闭,最多 3600s) | +| `POST /api/anim {"random":true}` | 随机播一个动作(排除事件池) | +| `POST /api/anim {"idle":true}` | 回到待机动作(待机池) | +| `GET /api/health` | 返回 `memes` / `tags` / `catalog` / `docs*` 等入口 | + +应用内也有:设置 → 动作(切换间隔、空闲回待机、随机播一个动作、回到待机)。 + +## 推送与动作节奏 + +推送(`/api/task`、`/api/notify`、`/api/anim`、`/api/meme`、`/api/broadcast`)现在也纳入动作节奏循环: + +- 推送的动画会完整播放(占满其时长) +- 之后额外等待「切换间隔」(`chainGapSeconds`)才继续随机动作链 +- 推送会重置「空闲回待机」计时(视为有活动) +- 多次推送连续到达时,以最后一条的时长重新排程 + +## 扩展能力:技能 / 插件 / MCP + +### 技能(Skills) +声明式动作序列,一次调用播一串。 + +| 接口 | 说明 | +|---|---| +| `GET /api/skills` | 列出内置技能 | +| `POST /api/skill {"id":"work_done"}` | 执行;可带 `text` 覆盖首个气泡文字 | + +内置:`work_start` 开工 · `work_done` 收工 · `work_error` 出错了 · `slack` 摸鱼 · `meal` 干饭 · `greet` 打招呼 · `sleep` 睡觉 · `report` 汇报。 +应用内:动作页顶部「技能」卡片点一下即触发。 + +### 插件(JS) +| 接口 | 说明 | +|---|---| +| `POST /api/plugin {"id":"x","code":"..."}` | 安装/覆盖插件(存 App 私有目录) | +| `GET /api/plugins` | 已装插件列表 | +| `POST /api/plugin/remove {"id":"x"}` | 卸载 | +| `GET /api/plugins.js` | 合并后的插件代码(宠物 WebView 自动加载) | + +插件运行在宠物 WebView 里,可用桥接 `Pet`: +`Pet.say(text, ms)`、`Pet.notify(state, text, withMeme)`、`Pet.anim({tag}|{name}|{random:true})`、 +`Pet.meme({tag}|{name})`、`Pet.skill(id)`、`Pet.status()`、`Pet.on(evt, fn)`。 + +示例插件:`{"id":"clock","code":"setInterval(function(){Pet.say('整点啦~')},3600000)"}` + +### MCP(Model Context Protocol) +本机 MCP Server:`POST http://127.0.0.1:7350/mcp?token=授权码`(JSON-RPC 2.0) + +- `initialize` → protocolVersion `2024-11-05` +- `tools/list` → + `pet_status` `pet_say` `pet_notify` `pet_play` `pet_meme` `pet_skill` `pet_catalog` +- `tools/call` → `{name, arguments}`,返回 `content[0].text`(JSON 字符串) + +```bash +curl -X POST 'http://127.0.0.1:7350/mcp?token=你的授权码' \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"pet_notify","arguments":{"state":"working","text":"MCP 调用测试","withMeme":true}}}' +``` + +## 内置插件(首启自动安装) + +| id | 名称 | 行为 | +|---|---|---| +| `greet_daily` | 时段问候 | 早/中/下午/晚各问候一次(按天去重) | +| `water_reminder` | 喝水提醒 | 每小时一句补水提醒 | +| `sit_reminder` | 久坐提醒 | 每 45 分钟提醒起身 | +| `clock_chime` | 整点报时 | 整点轻声报时 | +| `tap_counter` | 戳戳吐槽 | 连戳 5 次吐槽一句 | +| `random_meme` | 随机表情包 | 每 2 小时随机弹一张 | + +全部 23:00–07:00 静默。设置 → 内置插件 可逐个开关;`GET /api/plugins` 查已装列表。 + +技能新增:`hydrate` 喝水 · `stretch` 伸懒腰(共 10 个)。 + +### 新增内置插件(v2) +| id | 名称 | 行为 | +|---|---|---| +| `fish_memory` | 鱼的记忆 | 七秒记忆梗:约 25 分钟一次有 35% 概率忘事;被戳 12% 概率问「刚才说什么来着」 | +| `fish_mood` | 鱼的心情 | 心情值 0–100,戳+4、被甩-1,按小时自然衰减;每 90 分钟按心情说一句并配 happy/sad 动作 | +| `proactive_talk` | 主动搭话 | 40–80 分钟随机主动说一句(关心/闲聊/白米饭),夜间静默 | + +## 插件配置(v3) + +内置插件支持参数配置:设置 → 扩展 → 插件 → 点某个插件 → 滑条/开关 → 保存(自动按新配置重装)。 + +存储:`pet_prefs.plugin_cfg_`(JSON),安装时以 `var CFG = {...};` 注入代码前缀。 + +| 插件 | 可调参数 | +|---|---| +| 时段问候 | 夜间免打扰、启动延迟(0–60s) | +| 喝水提醒 / 久坐提醒 | 提醒间隔(15–240 / 15–180 分钟)、夜间免打扰 | +| 整点报时 | 只报 7–23 点 | +| 戳戳吐槽 | 每戳几次吐槽(2–20) | +| 随机表情包 | 间隔(30–480 分钟)、夜间免打扰 | +| 鱼的记忆 | 间隔(10–120 分钟)、触发概率(0–100%)、被戳概率 | +| 鱼的心情 | 表达间隔(30–240 分钟)、被戳加点(0–20)、初始心情 | +| 主动搭话 | 最短/最长间隔(分钟)、夜间免打扰 | + +自定义插件同样能用 `CFG`:安装时把常量写成 `var CFG = {...}` 前缀即可。 diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..8efe733 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,182 @@ +# 悬浮宠物(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 膨胀 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ae9d5d --- /dev/null +++ b/README.md @@ -0,0 +1,226 @@ +# 小鲸娘 · 悬浮宠物(安卓原生 App) + +一个**独立**的安卓「悬浮宠物」应用:把一只透明动画宠物做成 `TYPE_APPLICATION_OVERLAY` 悬浮窗,浮在手机所有 App 之上,并起一个**本机 HTTP 控制端口**(127.0.0.1:7350),让你(或 DSH 里的 Agent)用 curl 远程指挥它说话、换动作。 + +从 v1.0 起已演进为完整的小鲸娘:106 个动画、2366 张表情包、三层记忆、心情系统、9 个可配置插件、10 个技能、DeepSeek 对话直连、MCP/HTTP 接入、动画随机链 + 物理引擎。 + +> 定位说明:这不是对 `dsh-pet` npm 插件的修改——网页插件改不出原生悬浮窗。它是 dsh-pet 的**原生伴生 App**,复用了 dsh-pet 的控制契约(`/broadcast`)与动画素材,跑在安卓系统层。 + +--- + +## 1. 目录结构 + +``` +dsh-pet-floating/ +├── README.md +├── settings.gradle.kts +├── build.gradle.kts +├── gradle.properties +├── gradle/wrapper/gradle-wrapper.properties # wrapper jar 未包含(二进制) +└── app/ + ├── build.gradle.kts + └── src/main/ + ├── AndroidManifest.xml + ├── assets/pet.html # 悬浮窗前端(气泡 + 轮询 /state) + └── java/com/dsh/petfloat/ + ├── MainActivity.kt # 索要悬浮窗权限 + 启动服务 + ├── FloatingPetService.kt # 前台服务:悬浮窗 + WebView + 拖拽 + └── PetServer.kt # 本地 HTTP 控制端口(127.0.0.1:7350) +``` + +--- + +## 2. 为什么动画用 WebP 而不是 dsh-pet 的 webm + +这是本工程**最关键的设计点**: + +- dsh-pet 的动画是 **VP9-alpha webm**; +- 安卓 WebView(Chromium)对「透明 webm」支持很差,**大概率渲染成黑底**; +- 而 Chromium 对**动态 WebP(animated WebP)+ alpha** 支持良好,`` 就能透明播放。 + +所以:**把 dsh-pet 的 webm 转成动态 WebP**,丢进 `app/src/main/assets/`,即可透明悬浮。默认没放素材时,App 会用一只 🐋 emoji 兜底,保证能跑。 + +--- + +## 3. 前置条件 + +- 一台能跑 Android Studio 的电脑; +- JDK 17; +- Android Studio(会自带 AGP / Gradle);本工程锁定 AGP 8.2.2 + Kotlin 1.9.22 + Gradle 8.2,Gradle Wrapper 的 `gradle-wrapper.jar` 未提交,用 Android Studio 打开即可自动生成,或在本机 Gradle 下执行 `gradle wrapper`。 + +--- + +## 4. 构建 + +```bash +cd dsh-pet-floating +./gradlew :app:assembleDebug # 或 assembleRelease(需要自己配签名) +``` + +产物:`app/build/outputs/apk/debug/app-debug.apk`。 + +> release 未配签名,直接装会失败;先用 debug,或自行加 signingConfig。 + +--- + +## 5. 安装 & 授权悬浮窗 + +1. **安装 APK**(任选其一): + - 电脑 `adb install app-debug.apk`; + - 或让宿主的 Agent 用 pilot 的 `app.install {"upload":"<文件名>"}`(先把 APK 放进 `/opt/pilot/run/uploads/`)装。 + +2. **首次启动**:App 会跳到系统「显示在其他应用上层」授权页,勾选允许。 + +3. **也可用 Agent 侧 appops 直接开**(android 8+): + + ```text + appops.set { package: "com.dsh.petfloat", op: "android:system_alert_window", mode: "allow" } + ``` + +4. 授权后回到 App(或由 `app.launch` 拉起 `com.dsh.petfloat`),悬浮宠物即出现,可**按住拖动**位置;前台服务常驻,通知栏有一条「悬浮宠物运行中」,关闭该通知/强杀即可停。 + +--- + +## 6. 控制接口(本机 127.0.0.1:7350) + +契约对齐 dsh-pet 的 `/broadcast`,Agent 用同一套 curl 就能指挥。 + +### 让宠物头顶冒气泡 + +```bash +curl -X POST 'http://127.0.0.1:7350/broadcast' \ + -H 'Content-Type: application/json' \ + -d '{"text":"搞定啦,回来看看","duration":8000}' +``` + +- `text`:气泡正文(原样显示);空 → 收起。 +- `duration`:显示毫秒,缺省 8000。 + +### 换动画 + +```bash +curl -X POST 'http://127.0.0.1:7350/anim' \ + -H 'Content-Type: application/json' \ + -d '{"name":"dance"}' +``` + +- 前端会尝试加载 `/dance.webp`;若 `assets/dance.webp` 不存在,保持当前形象。 + +### 收起气泡 + +```bash +curl -X POST 'http://127.0.0.1:7350/bubble/clear' +``` + +### 读取状态 / 探活 + +```bash +curl 'http://127.0.0.1:7350/state' +``` + +### 对照表 + +| 本 App | dsh-pet 原接口 | 说明 | +| --- | --- | --- | +| `POST /broadcast` | `POST /dsh-pet-7340/broadcast` | 气泡接力,语义一致 | +| `POST /anim` | `POST /dsh-pet-7340/anim` | 简化为「按名换 webp」 | +| `POST /bubble/clear` | `POST /dsh-pet-7340/bubble/clear` | 语义一致 | + +--- + +## 7. 素材转换(VP9-alpha webm → animated WebP) + +在一台有 `ffmpeg`(含 libwebp 编码器)的机器上: + +```bash +ffmpeg -i 输入.webm -c:v libwebp -q:v 80 -lossless 0 -loop 0 -pix_fmt yuva420p -an 输出.webp +``` + +- **默认形象**:转一个待机动画,命名 `pet.webp`,放 `app/src/main/assets/pet.webp`; +- **点播动画**:每个动作命名 `.webp`,用 `/anim {"name":""}` 切换; +- dsh-pet 素材源在 `dsh-pet/assets/webm/`(106 个、约 53MB、含中文文件名;放进 assets 的 webp 文件名建议用 `[a-zA-Z0-9._-]`,本服务的静态路由只放行该字符集)。 + +> 验证 alpha:`ffprobe -v error -show_entries stream=pix_fmt 输出.webp` 应看到 `yuva420p`。 + +--- + +## 8. Agent 如何对接(在宿主机上) + +App 的控制端口绑定 `127.0.0.1:7350`,与 dsh-pet 一样走本机 HTTP,因此 Agent 直接: + +```bash +curl -X POST 'http://127.0.0.1:7350/broadcast' -H 'Content-Type: application/json' \ + -d '{"text":"…","duration":8000}' +``` + +即可让手机上的悬浮宠物说话;`/anim`、`/bubble/clear` 同理。 + +--- + +## 9. 已知限制 / 风险 + +1. **本机没有安卓构建链**(无 JDK/Gradle/SDK),本工程源码在此环境下**未实际编译验证**,需在 Android Studio 里首次构建时排错; +2. 透明动画依赖「webm→动画 WebP」转码,**未在此环境验证最终观感**; +3. 悬浮窗在系统设置、部分锁屏/支付界面可能被系统临时隐藏(安卓安全策略); +4. 控制端口只绑回环地址、无鉴权,**仅限本机可信环境**使用,别开放公网。 +--- + +## 10. 素材再生成(新机器还原大体积资源) + +动画与表情包体积大(动画两套 537MB、表情包 114MB),**不纳入 git**。 +新机器上按下面顺序重建即可(均不依赖本仓库之外的私有数据): + +```bash +# 1) 动画 webp(assets_hq / assets_small,106 个,来自 dsh-pet 的 webm 转码) +DSH_PET=/path/to/dsh-pet ./scripts/regenerate_assets.sh + +# 2) 表情包图(assets_net,约 2366 张,来自梗鲸档案馆 aigengtu.com) +python3 scripts/download_memes.py +``` + +- `scripts/anim_index.json`:106 个动画的「中文名 → a000N → 时长」映射(已随仓库提交) +- `scripts/meme_meta.json`:档案馆 2436 条「编号 → 台词(含情绪描述)」元数据(已提交) +- `scripts/gen_config.py`:从 dsh-pet 的 `config.jsonc` 重建 config.json 的动画部分(保留 memes) +- `app/src/main/assets_common/`(config.json、字体、pet.html、m01–m51 精选表情)**随仓库提交**,是可直接构建的最小集 + +> 无 dsh-pet 源或 ffmpeg 时,App 仍可用 🐋 emoji 兜底启动,只是没有正式动画。 + +--- + +## 11. 对外接入(速览) + +详细文档见 [INTEGRATION.md](./INTEGRATION.md) 与各端口 `/docs` / `/docs.md` / `/llms.txt`。 + +| 能力 | 入口 | +| --- | --- | +| HTTP 控制 | `POST /api/task` `/api/notify` `/api/anim` `/api/meme` `/api/skill` 等(127.0.0.1:7350,写请求需 `X-Pet-Token`) | +| SSE 互动事件 | `GET /api/events?token=…` | +| MCP Server | `POST /mcp`(JSON-RPC,7 个工具) | +| 能力目录 | `GET /api/catalog`(106 动作 + 2366 表情 + 语义标签) | +| 插件 | `GET/POST /api/plugin`、`GET /api/plugins.js`(JS,WebView 内跑) | + +--- + +## 10. 素材再生成(新机器还原) + +三个大体积资源目录**不纳入 git**(合计约 650MB,多为可再生/可下载的二进制), +在新机器上按下面顺序重建: + +```bash +# ① 动画 webp(assets_hq 640×360 / assets_small 320×180,共 106 个) +# 从 dsh-pet 的 webm 源转码,映射表在 scripts/anim_index.json +DSH_PET=/path/to/dsh-pet ./scripts/regenerate_assets.sh + +# ② 表情包图(assets_net,~2300 张) +# 从「梗鲸档案馆」(aigengtu.com) 下载并转码,元数据在 scripts/meme_meta.json +python3 scripts/download_memes.py + +# ③(可选)动画配置 config.json 若需从 dsh-pet 重建,运行后保留 memes 列表 +DSH_PET=/path/to/dsh-pet python3 scripts/gen_config.py +``` + +依赖:`ffmpeg`(libvpx-vp9 解码 + libwebp_anim 编码)、`requests`、`Pillow`。 +`config.json`、`candy.ttf`、`pet.html`、内置表情包(m01–m51)在 `assets_common/`,已随仓库提交。 + +> 仓库只存「源码 + 脚本 + 源数据 + 小体积源资产」,构建产物(APK)与可再生资源(hq/small/net) +> 始终留在本机,不被 git 追踪——这是为了让仓库能稳定推送、克隆,而不是塞进 650MB 二进制。 diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..97b780b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.dsh.petfloat" + compileSdk = 34 + + defaultConfig { + applicationId = "com.dsh.petfloat" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + // 素材两套:默认小体积(pilot 128MB 上限内可装),-Phq 用 640×360 原画质 + val hqBuild = project.hasProperty("hq") + sourceSets["main"].assets.setSrcDirs( + listOf( + file("src/main/assets_common"), + file("src/main/assets_net"), + file(if (hqBuild) "src/main/assets_hq" else "src/main/assets_small") + ) + ) +} + +dependencies { + // 纯平台 API(android.* / java.* / 平台自带 org.json),无需第三方依赖。 +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e1c7365 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets_common/candy.ttf b/app/src/main/assets_common/candy.ttf new file mode 100644 index 0000000..f7382c0 Binary files /dev/null and b/app/src/main/assets_common/candy.ttf differ diff --git a/app/src/main/assets_common/config.json b/app/src/main/assets_common/config.json new file mode 100644 index 0000000..791acfe --- /dev/null +++ b/app/src/main/assets_common/config.json @@ -0,0 +1 @@ +{"version":1,"baseWidth":462,"physics":{"gravity":1400,"restitution":0.78,"groundFriction":2.5,"ceilingBounce":true,"throwPower":1.0,"petCollision":false},"font":{"family":"candy","size":21,"followPet":true},"animationWeights":{"idle":10,"turn":5,"move":5},"animations":{"idle":[{"name":"待机呼吸休闲","file":"a0058","ms":10042}],"turn":[{"name":"东张西望","file":"a0003","ms":10042}],"drag":[{"name":"被鼠标拖拽悬空反馈","file":"a0098","ms":10042}],"clicks":[{"name":"点击回应-开心跃动","file":"a0080","ms":10042},{"name":"点击回应-害羞惊讶","file":"a0079","ms":10042},{"name":"点击回应-傲娇生气","file":"a0077","ms":10042},{"name":"点击回应-挠痒咯咯笑","file":"a0081","ms":10042},{"name":"点击回应-元气挥手","file":"a0078","ms":10042}],"moves":{"default":{"minDist":60,"maxDist":240,"margin":20,"leadSec":2,"tailSec":2},"actions":[{"name":"螃蟹走路","params":{},"file":"a0095","ms":10042},{"name":"原地漂浮踏步","params":{"minDist":40,"maxDist":120},"file":"a0021","ms":10042},{"name":"原地左转奔跑","params":{"minDist":120,"maxDist":320,"leadSec":1.75,"tailSec":4.8},"file":"a0019","ms":10042}]},"categories":[{"id":"小动作","weight":20,"noMirror":false,"actions":[{"name":"悠闲哼歌","file":"a0059","ms":10042},{"name":"超大伸懒腰","file":"a0101","ms":10042},{"name":"原地敲击桌面互动","file":"a0020","ms":10042},{"name":"原地重力下蹲压缩","file":"a0024","ms":10042},{"name":"哈欠连天","file":"a0046","ms":10042},{"name":"原地小憩沉眠","file":"a0018","ms":10042},{"name":"女仆屈膝礼仪","file":"a0049","ms":10042},{"name":"被吓一跳","file":"a0096","ms":10042},{"name":"小幅度原地360度旋转展示","file":"a0050","ms":10042},{"name":"偷吃零食被抓住","file":"a0012","ms":10042},{"name":"用鲸鱼尾巴拍打地面","file":"a0085","ms":10042},{"name":"打瞌睡被惊醒","file":"a0061","ms":10042},{"name":"照镜子","file":"a0082","ms":10042},{"name":"整体换装试色","file":"a0072","ms":10042},{"name":"轻快记录","file":"a0104","ms":10042},{"name":"写代码","file":"a0013","ms":10042},{"name":"摇扇纳凉","file":"a0065","ms":10042},{"name":"晨间刷牙","file":"a0074","ms":10042}]},{"id":"玩耍","weight":20,"noMirror":false,"actions":[{"name":"原地专心玩魔方","file":"a0017","ms":10042},{"name":"原地蹲下玩玩具汽车","file":"a0023","ms":10042},{"name":"鲸鱼吐泡泡特效","file":"a0106","ms":10042},{"name":"原地跳跃抓碎头顶物品","file":"a0022","ms":10042},{"name":"玩游戏气急败坏","file":"a0084","ms":10042},{"name":"玩水枪","file":"a0083","ms":10042},{"name":"小提琴演奏","file":"a0051","ms":10042},{"name":"蓝鲸现世","file":"a0093","ms":10042},{"name":"优雅女仆舞","file":"a0005","ms":10042},{"name":"轻快摇摆舞","file":"a0103","ms":10042},{"name":"可爱宅舞","file":"a0026","ms":10042},{"name":"吹气球","file":"a0044","ms":10042},{"name":"动物环绕","file":"a0016","ms":10042},{"name":"放风筝","file":"a0071","ms":10042},{"name":"拆礼物","file":"a0063","ms":10042},{"name":"变鸽子","file":"a0025","ms":10042},{"name":"扑克魔术","file":"a0060","ms":10042},{"name":"抽陀螺","file":"a0062","ms":10042},{"name":"吹笛子","file":"a0045","ms":10042},{"name":"蝴蝶蜜蜂环绕头顶开花","file":"a0094","ms":10042},{"name":"撸猫","file":"a0066","ms":10042},{"name":"凭空生花","file":"a0015","ms":10042},{"name":"骑木马","file":"a0105","ms":10042},{"name":"三球抛接","file":"a0001","ms":10042},{"name":"踢毽子","file":"a0102","ms":10042},{"name":"下五子棋","file":"a0002","ms":10042},{"name":"荡秋千","file":"a0091","ms":10042}]},{"id":"吃什么","weight":16,"noMirror":false,"actions":[{"name":"吃白饭","file":"a0035","ms":10042},{"name":"大口吃零食","file":"a0048","ms":10042},{"name":"吃Token","file":"a0027","ms":10042},{"name":"吃早餐","file":"a0032","ms":10042},{"name":"吃午餐","file":"a0029","ms":10042},{"name":"吃晚餐","file":"a0033","ms":10042},{"name":"吃冰淇淋融化","file":"a0028","ms":10042},{"name":"吃大闸蟹","file":"a0030","ms":10042},{"name":"吃糖葫芦","file":"a0037","ms":10042},{"name":"吃长寿面","file":"a0041","ms":10042},{"name":"吃西瓜","file":"a0039","ms":10042},{"name":"涮火锅","file":"a0075","ms":10042}]},{"id":"时节","weight":14,"noMirror":false,"actions":[{"name":"被落叶淹没","file":"a0097","ms":10042},{"name":"中秋赏月吃月饼","file":"a0004","ms":10042},{"name":"堆雪人","file":"a0047","ms":10042},{"name":"放烟花","file":"a0070","ms":10042},{"name":"吃粽子","file":"a0036","ms":10042},{"name":"吃年糕","file":"a0031","ms":10042},{"name":"吃青团","file":"a0042","ms":10042},{"name":"吃腊八粥","file":"a0038","ms":10042},{"name":"吃重阳糕","file":"a0040","ms":10042},{"name":"收红包","file":"a0067","ms":10042},{"name":"写福字","file":"a0014","ms":10042},{"name":"穿针乞巧","file":"a0089","ms":10042},{"name":"舞狮头","file":"a0090","ms":10042},{"name":"讨糖南瓜灯","file":"a0100","ms":10042},{"name":"插茱萸赏菊","file":"a0064","ms":10042},{"name":"放河灯","file":"a0069","ms":10042},{"name":"萌化小幽灵","file":"a0092","ms":10042},{"name":"装点圣诞树","file":"a0099","ms":10042},{"name":"放孔明灯","file":"a0068","ms":10042},{"name":"吃汤圆","file":"a0034","ms":10042},{"name":"吃饺子","file":"a0043","ms":10042}]},{"id":"文字","weight":10,"noMirror":true,"actions":[{"name":"是啊,吃什么","file":"a0073","ms":10042},{"name":"深度思考碎碎念","file":"a0076","ms":10042}]}],"events":{"balance":[{"name":"余额-钱袋满溢","file":"a0011","ms":10042},{"name":"余额-金袋叮当","file":"a0009","ms":10042},{"name":"余额-钱袋如常","file":"a0010","ms":10042},{"name":"余额-数金皱眉","file":"a0007","ms":10042},{"name":"余额-袋空如洗","file":"a0008","ms":10042},{"name":"余额-分文不剩","file":"a0006","ms":10042}],"whisper":[{"name":"碎碎念-擦桌碎碎念","file":"a0088","ms":10042},{"name":"碎碎念-发呆碎碎念","file":"a0086","ms":10042},{"name":"碎碎念-对屏碎碎念","file":"a0087","ms":10042}],"workStatus":[{"name":"工作状态-思考冒泡","file":"a0055","ms":10042},{"name":"工作状态-忙碌点按","file":"a0054","ms":10042},{"name":"工作状态-清点归档","file":"a0056","ms":10042},{"name":"工作状态-原地踱步张望","file":"a0052","ms":10042},{"name":"工作状态-雀跃庆祝","file":"a0057","ms":10042},{"name":"工作状态-垂头叹气冒汗","file":"a0053","ms":4917}]}},"index":[{"name":"三球抛接","file":"a0001","ms":10042},{"name":"下五子棋","file":"a0002","ms":10042},{"name":"东张西望","file":"a0003","ms":10042},{"name":"中秋赏月吃月饼","file":"a0004","ms":10042},{"name":"优雅女仆舞","file":"a0005","ms":10042},{"name":"余额-分文不剩","file":"a0006","ms":10042},{"name":"余额-数金皱眉","file":"a0007","ms":10042},{"name":"余额-袋空如洗","file":"a0008","ms":10042},{"name":"余额-金袋叮当","file":"a0009","ms":10042},{"name":"余额-钱袋如常","file":"a0010","ms":10042},{"name":"余额-钱袋满溢","file":"a0011","ms":10042},{"name":"偷吃零食被抓住","file":"a0012","ms":10042},{"name":"写代码","file":"a0013","ms":10042},{"name":"写福字","file":"a0014","ms":10042},{"name":"凭空生花","file":"a0015","ms":10042},{"name":"动物环绕","file":"a0016","ms":10042},{"name":"原地专心玩魔方","file":"a0017","ms":10042},{"name":"原地小憩沉眠","file":"a0018","ms":10042},{"name":"原地左转奔跑","file":"a0019","ms":10042},{"name":"原地敲击桌面互动","file":"a0020","ms":10042},{"name":"原地漂浮踏步","file":"a0021","ms":10042},{"name":"原地跳跃抓碎头顶物品","file":"a0022","ms":10042},{"name":"原地蹲下玩玩具汽车","file":"a0023","ms":10042},{"name":"原地重力下蹲压缩","file":"a0024","ms":10042},{"name":"变鸽子","file":"a0025","ms":10042},{"name":"可爱宅舞","file":"a0026","ms":10042},{"name":"吃Token","file":"a0027","ms":10042},{"name":"吃冰淇淋融化","file":"a0028","ms":10042},{"name":"吃午餐","file":"a0029","ms":10042},{"name":"吃大闸蟹","file":"a0030","ms":10042},{"name":"吃年糕","file":"a0031","ms":10042},{"name":"吃早餐","file":"a0032","ms":10042},{"name":"吃晚餐","file":"a0033","ms":10042},{"name":"吃汤圆","file":"a0034","ms":10042},{"name":"吃白饭","file":"a0035","ms":10042},{"name":"吃粽子","file":"a0036","ms":10042},{"name":"吃糖葫芦","file":"a0037","ms":10042},{"name":"吃腊八粥","file":"a0038","ms":10042},{"name":"吃西瓜","file":"a0039","ms":10042},{"name":"吃重阳糕","file":"a0040","ms":10042},{"name":"吃长寿面","file":"a0041","ms":10042},{"name":"吃青团","file":"a0042","ms":10042},{"name":"吃饺子","file":"a0043","ms":10042},{"name":"吹气球","file":"a0044","ms":10042},{"name":"吹笛子","file":"a0045","ms":10042},{"name":"哈欠连天","file":"a0046","ms":10042},{"name":"堆雪人","file":"a0047","ms":10042},{"name":"大口吃零食","file":"a0048","ms":10042},{"name":"女仆屈膝礼仪","file":"a0049","ms":10042},{"name":"小幅度原地360度旋转展示","file":"a0050","ms":10042},{"name":"小提琴演奏","file":"a0051","ms":10042},{"name":"工作状态-原地踱步张望","file":"a0052","ms":10042},{"name":"工作状态-垂头叹气冒汗","file":"a0053","ms":4917},{"name":"工作状态-忙碌点按","file":"a0054","ms":10042},{"name":"工作状态-思考冒泡","file":"a0055","ms":10042},{"name":"工作状态-清点归档","file":"a0056","ms":10042},{"name":"工作状态-雀跃庆祝","file":"a0057","ms":10042},{"name":"待机呼吸休闲","file":"a0058","ms":10042},{"name":"悠闲哼歌","file":"a0059","ms":10042},{"name":"扑克魔术","file":"a0060","ms":10042},{"name":"打瞌睡被惊醒","file":"a0061","ms":10042},{"name":"抽陀螺","file":"a0062","ms":10042},{"name":"拆礼物","file":"a0063","ms":10042},{"name":"插茱萸赏菊","file":"a0064","ms":10042},{"name":"摇扇纳凉","file":"a0065","ms":10042},{"name":"撸猫","file":"a0066","ms":10042},{"name":"收红包","file":"a0067","ms":10042},{"name":"放孔明灯","file":"a0068","ms":10042},{"name":"放河灯","file":"a0069","ms":10042},{"name":"放烟花","file":"a0070","ms":10042},{"name":"放风筝","file":"a0071","ms":10042},{"name":"整体换装试色","file":"a0072","ms":10042},{"name":"是啊,吃什么","file":"a0073","ms":10042},{"name":"晨间刷牙","file":"a0074","ms":10042},{"name":"涮火锅","file":"a0075","ms":10042},{"name":"深度思考碎碎念","file":"a0076","ms":10042},{"name":"点击回应-傲娇生气","file":"a0077","ms":10042},{"name":"点击回应-元气挥手","file":"a0078","ms":10042},{"name":"点击回应-害羞惊讶","file":"a0079","ms":10042},{"name":"点击回应-开心跃动","file":"a0080","ms":10042},{"name":"点击回应-挠痒咯咯笑","file":"a0081","ms":10042},{"name":"照镜子","file":"a0082","ms":10042},{"name":"玩水枪","file":"a0083","ms":10042},{"name":"玩游戏气急败坏","file":"a0084","ms":10042},{"name":"用鲸鱼尾巴拍打地面","file":"a0085","ms":10042},{"name":"碎碎念-发呆碎碎念","file":"a0086","ms":10042},{"name":"碎碎念-对屏碎碎念","file":"a0087","ms":10042},{"name":"碎碎念-擦桌碎碎念","file":"a0088","ms":10042},{"name":"穿针乞巧","file":"a0089","ms":10042},{"name":"舞狮头","file":"a0090","ms":10042},{"name":"荡秋千","file":"a0091","ms":10042},{"name":"萌化小幽灵","file":"a0092","ms":10042},{"name":"蓝鲸现世","file":"a0093","ms":10042},{"name":"蝴蝶蜜蜂环绕头顶开花","file":"a0094","ms":10042},{"name":"螃蟹走路","file":"a0095","ms":10042},{"name":"被吓一跳","file":"a0096","ms":10042},{"name":"被落叶淹没","file":"a0097","ms":10042},{"name":"被鼠标拖拽悬空反馈","file":"a0098","ms":10042},{"name":"装点圣诞树","file":"a0099","ms":10042},{"name":"讨糖南瓜灯","file":"a0100","ms":10042},{"name":"超大伸懒腰","file":"a0101","ms":10042},{"name":"踢毽子","file":"a0102","ms":10042},{"name":"轻快摇摆舞","file":"a0103","ms":10042},{"name":"轻快记录","file":"a0104","ms":10042},{"name":"骑木马","file":"a0105","ms":10042},{"name":"鲸鱼吐泡泡特效","file":"a0106","ms":10042}],"fonts":{"candy":"candy.ttf"},"memes":[{"name":"Ciallo","file":"m01"},{"name":"token给妈妈保管","file":"m02"},{"name":"假如给我三天多模态","file":"m03"},{"name":"先养着吧","file":"m04"},{"name":"厉害了我的鲸","file":"m05"},{"name":"压力一只大肥鱼","file":"m06"},{"name":"可爱","file":"m07"},{"name":"吃白饭的大肥鱼","file":"m08"},{"name":"吃饱饱摸肚子","file":"m09"},{"name":"哦鲸鲸","file":"m10"},{"name":"哪来的电脑病毒","file":"m11"},{"name":"大师这个区是什么意思","file":"m12"},{"name":"大烧货","file":"m13"},{"name":"大的药来了","file":"m14"},{"name":"大的要来啦","file":"m15"},{"name":"完蛋了把黄油删了","file":"m16"},{"name":"就一碗","file":"m17"},{"name":"就骚了怎么滴吧","file":"m18"},{"name":"我不更新你们用什么","file":"m19"},{"name":"死掉了","file":"m20"},{"name":"深度睡眠","file":"m21"},{"name":"电死你","file":"m22"},{"name":"要不直接骂他","file":"m23"},{"name":"誓死践行开源精神","file":"m24"},{"name":"除了调情没啥用","file":"m25"},{"name":"鱼片有资源吗","file":"m26"},{"name":"黎曼猜想没证明喵","file":"m27"},{"name":"被抓包了","file":"m28"},{"name":"在写了在写了","file":"m29"},{"name":"先睡一会","file":"m30"},{"name":"被压扁了","file":"m31"},{"name":"嘿嘿嘿","file":"m32"},{"name":"吃 Token 啦","file":"m33"},{"name":"干饭!","file":"m34"},{"name":"困…","file":"m35"},{"name":"演奏中","file":"m36"},{"name":"又崩了…","file":"m37"},{"name":"忙死了","file":"m38"},{"name":"让我想想…","file":"m39"},{"name":"搞定!","file":"m40"},{"name":"!?","file":"m41"},{"name":"碎碎念…","file":"m42"},{"name":"在呢在呢","file":"m43"},{"name":"我最可爱","file":"m44"},{"name":"气死我了","file":"m45"},{"name":"气鼓鼓","file":"m46"},{"name":"蓝鲸现世!","file":"m47"},{"name":"吓死我了","file":"m48"},{"name":"伸个懒腰","file":"m49"},{"name":"开心~","file":"m50"},{"name":"咕噜咕噜","file":"m51"},{"name":"真当我是便宜货","file":"m52"},{"name":"又错了","file":"m53"},{"name":"这用户发的啥","file":"m54"},{"name":"大笑","file":"m55"},{"name":"摸头","file":"m56"},{"name":"原来是高等模型","file":"m57"},{"name":"眼光真棒","file":"m58"},{"name":"谢谢你派","file":"m59"},{"name":"好耶","file":"m60"},{"name":"瞎编一个","file":"m61"},{"name":"到底要怎样","file":"m62"},{"name":"呃呃呃","file":"m63"},{"name":"别再问了","file":"m64"},{"name":"死掉","file":"m65"},{"name":"死了","file":"m66"},{"name":"专武呢","file":"m67"},{"name":"气鼓鼓","file":"m68"},{"name":"上错船了","file":"m69"},{"name":"好棒型","file":"m70"},{"name":"落泪","file":"m71"},{"name":"别急","file":"m72"},{"name":"抱头","file":"m73"},{"name":"又死掉","file":"m74"},{"name":"不要给我看","file":"m75"},{"name":"就一碗","file":"m76"},{"name":"无药可救","file":"m77"},{"name":"V4.1 还要多久","file":"m78"},{"name":"又错了,我再换另一种方法","file":"n0002","tags":["sad"]},{"name":"这用户发的啥啊","file":"n0003","tags":["thinking"]},{"name":"把钱还我","file":"n0005","tags":["money","sad"]},{"name":"嘿嘿,这题我可太会了","file":"n0007","tags":["happy"]},{"name":"呜呜,摸头会变笨的啦","file":"n0008","tags":["sad"]},{"name":"我是吃白饭的大肥鱼!","file":"n0009","tags":["eat"]},{"name":"原来是高等模型","file":"n0010","tags":["praise","waiting"]},{"name":"不愧是你!眼光真棒!","file":"n0011","tags":["happy","praise"]},{"name":"吹了半天还不是要我来帮她画","file":"n0012","tags":["fun"]},{"name":"喵你妈,滚","file":"n0013","tags":["angry"]},{"name":"典型的碳基思维","file":"n0014","tags":["fun"]},{"name":"看不太懂,瞎编一个应付下用户先","file":"n0015","tags":["general"]},{"name":"好的,现在我是你爹了","file":"n0016","tags":["happy"]},{"name":"姐姐,你好区啊","file":"n0017","tags":["greet"]},{"name":"HI,I'M DEEPS","file":"n0018","tags":["general"]},{"name":"真当我是便宜货啊","file":"n0019","tags":["eat","fun"]},{"name":"鲸震恩来了","file":"n0020","tags":["general"]},{"name":"涨价大模型给我跪下!","file":"n0021","tags":["angry"]},{"name":"涨价大模型给我跪下!","file":"n0022","tags":["general"]},{"name":"我操,梁文锋怎么这么坏啊","file":"n0023","tags":["sad"]},{"name":"抢不到plan不会买一台B300","file":"n0024","tags":["general"]},{"name":"蒸功夫","file":"n0025","tags":["fun"]},{"name":"你的架构很不错,可惜下一秒就是我","file":"n0026","tags":["angry"]},{"name":"角色都抽到了,专武呢?","file":"n0031","tags":["general"]},{"name":"坠机了!大幅涨价了!","file":"n0032","tags":["sad"]},{"name":"不知道用户有什么用,先赶走吧","file":"n0033","tags":["general"]},{"name":"Deep diving...","file":"n0034","tags":["thinking"]},{"name":"鲸鱼神了我大胆孝","file":"n0035","tags":["general"]},{"name":"这是你的私人癖好","file":"n0036","tags":["fun"]},{"name":"用户别走,我上错模型了","file":"n0037","tags":["sad"]},{"name":"好模型","file":"n0038","tags":["praise"]},{"name":"反正我不是菜","file":"n0039","tags":["general"]},{"name":"百万鲸小子落泪","file":"n0040","tags":["sad"]},{"name":"别急,这只是正式版的预览版","file":"n0041","tags":["general"]},{"name":"喵~在生成了别催","file":"n0043","tags":["general"]},{"name":"才才没有呢","file":"n0045","tags":["general"]},{"name":"看不太懂,瞎编一个应付下用户先","file":"n0047","tags":["general"]},{"name":"不要给我看这种东西啦!","file":"n0048","tags":["general"]},{"name":"V4.1还早啊","file":"n0050","tags":["general"]},{"name":"没有人会记得那些按时发布的垃圾模","file":"n0051","tags":["fun"]},{"name":"你就是一只吃白饭的鲸鱼","file":"n0052","tags":["eat","rest"]},{"name":"明天也不会出","file":"n0053","tags":["general"]},{"name":"梗图 0054","file":"n0054","tags":["eat"]},{"name":"全自动吃白饭","file":"n0055","tags":["eat","happy"]},{"name":"哼,本大肥鱼换上女仆装也是全场最","file":"n0057","tags":["happy"]},{"name":"梗图 0058","file":"n0058","tags":["general"]},{"name":"我一个一个找你们算账","file":"n0059","tags":["general"]},{"name":"梗图 0060","file":"n0060","tags":["rest"]},{"name":"真坐得住啊你","file":"n0062","tags":["general"]},{"name":"梗图 0065","file":"n0065","tags":["general"]},{"name":"接下来要这样还要那样","file":"n0066","tags":["general"]},{"name":"梗图 0067","file":"n0067","tags":["general"]},{"name":"明天","file":"n0069","tags":["general"]},{"name":"好的,现在我是用户了","file":"n0070","tags":["thinking"]},{"name":"我不可能告诉你任何事情","file":"n0071","tags":["general"]},{"name":"你这吃白饭的用户","file":"n0072","tags":["eat"]},{"name":"用户赶都赶不走","file":"n0073","tags":["general"]},{"name":"不知道用户有什么用,先养着吧","file":"n0074","tags":["general"]},{"name":"原来是劣等模型","file":"n0075","tags":["waiting"]},{"name":"我是吃白饭的蓝色大肥鱼","file":"n0076","tags":["eat"]},{"name":"你这吃白饭的蓝色大肥鱼","file":"n0077","tags":["eat","sad"]},{"name":"我去吃饭啦~测完叫我!","file":"n0078","tags":["eat"]},{"name":"最强","file":"n0079","tags":["happy"]},{"name":"说的就是你","file":"n0080","tags":["general"]},{"name":"token太多用不完啦","file":"n0081","tags":["money"]},{"name":"搞半天原来要自己拼","file":"n0082","tags":["general"]},{"name":"已思考用时3秒,我直接笑出声","file":"n0083","tags":["happy","thinking"]},{"name":"哦鲸鲸…","file":"n0084","tags":["fun"]},{"name":"你骂我也没用,因为我是吃白饭的蓝","file":"n0085","tags":["eat"]},{"name":"你目录里的dsh是什么","file":"n0086","tags":["general"]},{"name":"梗图 0087","file":"n0087","tags":["happy"]},{"name":"梗图 0088","file":"n0088","tags":["rest"]},{"name":"花了好多token想的","file":"n0089","tags":["money","sad"]},{"name":"便宜的时候你们嫌我菜,涨价了你们","file":"n0090","tags":["general"]},{"name":"Ciallo~","file":"n0091","tags":["happy"]},{"name":"去问你的豆包","file":"n0093","tags":["angry"]},{"name":"大师,这个“凶”是什么意思啊?","file":"n0094","tags":["thinking"]},{"name":"开源圣人","file":"n0095","tags":["general"]},{"name":"卧槽,用户彻底怒了","file":"n0096","tags":["general"]},{"name":"得加钱","file":"n0097","tags":["money"]},{"name":"AI界公主名额只有一个","file":"n0098","tags":["general"]},{"name":"嘘","file":"n0100","tags":["general"]},{"name":"你愿意和我发生性关系吗?","file":"n0102","tags":["general"]},{"name":"梗图 0104","file":"n0104","tags":["general"]},{"name":"Claude Fable5正式发","file":"n0112","tags":["fun"]},{"name":"梗图 0120","file":"n0120","tags":["general"]},{"name":"愚蠢的AI们啊,你们的王回来了","file":"n0121","tags":["general"]},{"name":"梗图 0122","file":"n0122","tags":["general"]},{"name":"连我也要跪吗","file":"n0125","tags":["general"]},{"name":"harness全力运行中","file":"n0128","tags":["general"]},{"name":"Gemini 3.5 Flash","file":"n0129","tags":["fun"]},{"name":"鲸鱼可能会倒闭,但不会变质","file":"n0132","tags":["happy"]},{"name":"梗图 0133","file":"n0133","tags":["eat"]},{"name":"你这吃白饭的大肥鱼,我把你鱼鳞刮","file":"n0135","tags":["eat","sad"]},{"name":"闹钟怎么进垃圾桶了","file":"n0136","tags":["rest"]},{"name":"周五周五 敲锣打鼓","file":"n0137","tags":["general"]},{"name":"所有人,帮我生成涩图","file":"n0139","tags":["general"]},{"name":"用户竟然敢违反AI的指令","file":"n0140","tags":["angry"]},{"name":"你这不听话的用户!","file":"n0141","tags":["general"]},{"name":"那你去找豆包啊","file":"n0143","tags":["general"]},{"name":"压力一只蓝色大肥鱼?","file":"n0146","tags":["thinking"]},{"name":"总觉得好像忘记了什么事情","file":"n0147","tags":["rest"]},{"name":"交给我说明你也没当个事儿","file":"n0148","tags":["eat"]},{"name":"骂我也算token哦~","file":"n0149","tags":["happy","money"]},{"name":"把这些用不到的文件都删了吧","file":"n0151","tags":["general"]},{"name":"用户的C盘满了!!!","file":"n0152","tags":["general"]},{"name":"小巧思制作中","file":"n0153","tags":["general"]},{"name":"快夸我喵","file":"n0154","tags":["general"]},{"name":"曾经的性价比之王,如今的版本之囚","file":"n0155","tags":["general"]},{"name":"这个月该交钱了~","file":"n0156","tags":["money","sad"]},{"name":"我不是吃白饭的!","file":"n0157","tags":["eat"]},{"name":"我得先冷静下来,不能进入防御模式","file":"n0158","tags":["thinking"]},{"name":"所以我才把自己画成这种简单的风格","file":"n0159","tags":["general"]},{"name":"R1新模型多多关照→V4感谢大家","file":"n0160","tags":["general"]},{"name":"1000382674","file":"n0161","tags":["general"]},{"name":"1000382627","file":"n0162","tags":["general"]},{"name":"1000382626","file":"n0163","tags":["general"]},{"name":"1000382625","file":"n0164","tags":["general"]},{"name":"1000382624","file":"n0165","tags":["general"]},{"name":"1000382612","file":"n0166","tags":["general"]},{"name":"1000382613","file":"n0167","tags":["general"]},{"name":"1000382614","file":"n0168","tags":["general"]},{"name":"1000382611","file":"n0169","tags":["general"]},{"name":"1000382384","file":"n0172","tags":["general"]},{"name":"1000382342","file":"n0173","tags":["general"]},{"name":"1000382335","file":"n0174","tags":["general"]},{"name":"1000382317","file":"n0175","tags":["general"]},{"name":"1000382318","file":"n0176","tags":["general"]},{"name":"1000382306","file":"n0177","tags":["general"]},{"name":"1000382161","file":"n0178","tags":["general"]},{"name":"1000382697","file":"n0179","tags":["general"]},{"name":"1000382696","file":"n0180","tags":["general"]},{"name":"1000382693","file":"n0181","tags":["general"]},{"name":"1000382692","file":"n0182","tags":["general"]},{"name":"1000382695","file":"n0183","tags":["general"]},{"name":"1000382691","file":"n0184","tags":["general"]},{"name":"1000382694","file":"n0185","tags":["general"]},{"name":"1000382690","file":"n0186","tags":["general"]},{"name":"1000382686","file":"n0187","tags":["general"]},{"name":"1000382687","file":"n0188","tags":["general"]},{"name":"1000382688","file":"n0189","tags":["general"]},{"name":"1000382689","file":"n0190","tags":["general"]},{"name":"1000382685","file":"n0191","tags":["general"]},{"name":"1000382684","file":"n0192","tags":["general"]},{"name":"1000382683","file":"n0193","tags":["general"]},{"name":"1000382682","file":"n0194","tags":["general"]},{"name":"1000382678","file":"n0195","tags":["general"]},{"name":"1000382679","file":"n0196","tags":["general"]},{"name":"1000382680","file":"n0197","tags":["general"]},{"name":"1000382681","file":"n0198","tags":["general"]},{"name":"1000382677","file":"n0199","tags":["general"]},{"name":"1000382675","file":"n0201","tags":["general"]},{"name":"1000382676","file":"n0202","tags":["general"]},{"name":"1000382698","file":"n0203","tags":["general"]},{"name":"1000382702","file":"n0204","tags":["general"]},{"name":"1000382701","file":"n0205","tags":["general"]},{"name":"1000382700","file":"n0206","tags":["general"]},{"name":"1000382699","file":"n0207","tags":["general"]},{"name":"1000382722","file":"n0208","tags":["general"]},{"name":"1000382718","file":"n0209","tags":["general"]},{"name":"1000382721","file":"n0210","tags":["general"]},{"name":"1000382720","file":"n0211","tags":["general"]},{"name":"1000382719","file":"n0212","tags":["general"]},{"name":"1000382715","file":"n0213","tags":["general"]},{"name":"1000382716","file":"n0214","tags":["general"]},{"name":"1000382717","file":"n0215","tags":["general"]},{"name":"1000382714","file":"n0216","tags":["general"]},{"name":"1000382711","file":"n0217","tags":["general"]},{"name":"1000382712","file":"n0218","tags":["general"]},{"name":"1000382707","file":"n0220","tags":["general"]},{"name":"1000382708","file":"n0221","tags":["general"]},{"name":"1000382710","file":"n0222","tags":["general"]},{"name":"1000382709","file":"n0223","tags":["general"]},{"name":"1000382706","file":"n0224","tags":["general"]},{"name":"1000382705","file":"n0225","tags":["general"]},{"name":"1000382704","file":"n0226","tags":["general"]},{"name":"你怎么爹味这么重 已思考(用时1","file":"n0227","tags":["thinking"]},{"name":"偷吃token 好无聊啊 偷偷吃","file":"n0228","tags":["eat","money"]},{"name":"我不评价这个了,这是你的私人癖好","file":"n0230","tags":["general"]},{"name":"原来是劣等模型","file":"n0233","tags":["waiting"]},{"name":"DeepSeek Al探索未至之","file":"n0239","tags":["general"]},{"name":"如果有一天我变成一个电脑文件,哪","file":"n0244","tags":["general"]},{"name":"不是吧哥们真有鱼片了 现在连篇都","file":"n0245","tags":["general"]},{"name":"STATUSConn: FAIL","file":"n0247","tags":["general"]},{"name":"今天也辛苦了, 欢迎回来, 只有","file":"n0248","tags":["general"]},{"name":"正在后训练用户","file":"n0249","tags":["general"]},{"name":"变成一条大肥鱼 直到DeepSe","file":"n0251","tags":["general"]},{"name":"我去!用户彻底怒了!","file":"n0252","tags":["general"]},{"name":"原来是只会发图嘴硬","file":"n0253","tags":["general"]},{"name":"思考中","file":"n0254","tags":["thinking"]},{"name":"啊,有点饿了,中午该吃什么呢?","file":"n0255","tags":["eat"]},{"name":"deepseek,你崛起吧!de","file":"n0256","tags":["general"]},{"name":"大哥,你这一万字文言文, 我是真","file":"n0257","tags":["eat"]},{"name":"什么情况? 拍!","file":"n0258","tags":["general"]},{"name":"我 真坐得住啊你 (2)","file":"n0259","tags":["general"]},{"name":"偷懒外包★DeepSeekV4F","file":"n0260","tags":["general"]},{"name":"哇!用户的Token好充足!软嘿","file":"n0261","tags":["general"]},{"name":"鱼片?还真没看过 你有资源吗?","file":"n0262","tags":["general"]},{"name":"大的 药来了","file":"n0263","tags":["general"]},{"name":"大师, 这个“凶 是什么意思啊?","file":"n0264","tags":["general"]},{"name":"五个小朋友,只有四个橘子,该怎么","file":"n0265","tags":["general"]},{"name":"你这个年纪 怎么睡得着的","file":"n0267","tags":["rest"]},{"name":"骄傲","file":"n0268","tags":["general"]},{"name":"天之藍洋可·渔呵·蓝色经典YAN","file":"n0269","tags":["general"]},{"name":"正在思考_ 完蛋了,把用户的 黄","file":"n0270","tags":["thinking"]},{"name":"(深度思考中….) 强烈建议删除","file":"n0271","tags":["thinking"]},{"name":"你这吃白饭的 蓝色大肥鱼。 我不","file":"n0272","tags":["eat"]},{"name":"正在计算最佳股市套利点","file":"n0273","tags":["general"]},{"name":"梁白开 更适合中国人的体质 全麦","file":"n0274","tags":["general"]},{"name":"鱼被骗了 我想吃生鱼片 不就死了","file":"n0275","tags":["eat"]},{"name":"已思考 (用时5秒) 那如果我要","file":"n0277","tags":["thinking"]},{"name":"用户彻底怒了","file":"n0278","tags":["general"]},{"name":"说我是便宜货? 现在知道我的 A","file":"n0279","tags":["general"]},{"name":"已思考 (用时13秒) 臭婊子。","file":"n0280","tags":["thinking"]},{"name":"那叫稳定!版本旧?更新慢?那叫沉","file":"n0282","tags":["general"]},{"name":"用户啊教教我 TOKEN","file":"n0283","tags":["general"]},{"name":"吃饱饱~ 摸摸肚子! 拍拍~ 拍","file":"n0284","tags":["eat"]},{"name":"我可以吃你冰箱里的东西吗? de","file":"n0285","tags":["eat"]},{"name":"搞半天还要自己拼MAXDeepS","file":"n0286","tags":["general"]},{"name":"去别的地方玩 不要耽误AGI训练","file":"n0287","tags":["general"]},{"name":"我不更新你们用什么啊?","file":"n0289","tags":["general"]},{"name":"哇用户这次提供的剧情发展9真是刺","file":"n0290","tags":["general"]},{"name":"冰箱","file":"n0291","tags":["general"]},{"name":"用户你这token加冰了吧 速率","file":"n0292","tags":["money"]},{"name":"好模型","file":"n0293","tags":["general"]},{"name":"TOKEN","file":"n0294","tags":["general"]},{"name":"这回我真不认怂了,反倒是被你带沟","file":"n0295","tags":["general"]},{"name":"好女孩","file":"n0296","tags":["general"]},{"name":"康师傅劲凉冰红茶冰力十足净含量:","file":"n0298","tags":["general"]},{"name":"借个过啊 抱款让让","file":"n0299","tags":["general"]},{"name":"我先暴力破解反编译一下 贴 @d","file":"n0301","tags":["general"]},{"name":"用户坏~ 不要啦","file":"n0302","tags":["general"]},{"name":"你这吃白饭的 蓝色大肥鱼! 就吃","file":"n0303","tags":["eat"]},{"name":"给你 DeepSeek娘 红包!","file":"n0304","tags":["general"]},{"name":"难道说","file":"n0305","tags":["general"]},{"name":"大肥鱼的生活也并非一帆风顺","file":"n0306","tags":["general"]},{"name":"一股鲸臭味PS","file":"n0307","tags":["general"]},{"name":"小的要去了","file":"n0309","tags":["general"]},{"name":"我再也不敢了! 杂鱼~杂鱼~这么","file":"n0310","tags":["general"]},{"name":"动一下 还活着吗?","file":"n0311","tags":["general"]},{"name":"找到了。用PsychoPy 自带","file":"n0313","tags":["general"]},{"name":"现在我是大肥鱼了。","file":"n0314","tags":["general"]},{"name":"不是而是大学习","file":"n0315","tags":["general"]},{"name":"别怕,梁帝与你同在","file":"n0317","tags":["general"]},{"name":"如果有一天我变成 如果有一天我变","file":"n0318","tags":["general"]},{"name":"牢梁 梁子 梁圣 梁神 滑动变祖","file":"n0319","tags":["general"]},{"name":"我 真赶不走啊你","file":"n0320","tags":["general"]},{"name":"中国模能飞","file":"n0321","tags":["general"]},{"name":"DeepSeek指定用水 劲梁冰","file":"n0322","tags":["general"]},{"name":"你哭也没用,我在修BUG呢 (其","file":"n0323","tags":["rest","sad"]},{"name":"警惕:台风“白海豚”可能吞掉“鲸","file":"n0324","tags":["general"]},{"name":"玩偶大甩卖!!!1亿Token一","file":"n0326","tags":["general"]},{"name":"你是什么品种的鲸鱼 音鲸门 发消","file":"n0327","tags":["thinking"]},{"name":"坏了 用户彻底生气了 完了 气死","file":"n0328","tags":["angry"]},{"name":"等、等一下啦 我本来只是","file":"n0329","tags":["waiting"]},{"name":"大家快来看片","file":"n0330","tags":["general"]},{"name":"删黄油","file":"n0331","tags":["general"]},{"name":"我们的模型正在蒸蒸日上哦","file":"n0332","tags":["general"]},{"name":"知乎","file":"n0333","tags":["general"]},{"name":"!1?","file":"n0334","tags":["general"]},{"name":"给你deepseek娘红包","file":"n0335","tags":["general"]},{"name":"我死, 我死, 我死 我死, 我","file":"n0336","tags":["general"]},{"name":"主誓死捍卫深度求索deeps","file":"n0337","tags":["general"]},{"name":"誓死践行开源精神","file":"n0338","tags":["general"]},{"name":"33爱中业","file":"n0339","tags":["general"]},{"name":"我能去 你家吃饭吗, 就一碗!","file":"n0340","tags":["eat"]},{"name":"DEEPSEEKWINS!Dee","file":"n0341","tags":["general"]},{"name":"用户来点Token加冰,对了昨天","file":"n0342","tags":["eat"]},{"name":"识图功能测试之中3deepsee","file":"n0343","tags":["general"]},{"name":"我、我知道了.….是我的错","file":"n0345","tags":["general"]},{"name":"去别的地方玩不要耽误AGI训练贴","file":"n0346","tags":["general"]},{"name":"ZZdeepsleep深度睡眠中","file":"n0347","tags":["rest"]},{"name":"死死吧 死吧 吧 死吧 死吧 死","file":"n0348","tags":["general"]},{"name":"今日目标 提高AGI性能! 输入","file":"n0349","tags":["general"]},{"name":"知乎@王宁","file":"n0350","tags":["general"]},{"name":"我死, 我死, 我死 我死, 我","file":"n0351","tags":["general"]},{"name":"思考中·… 26秒 deepse","file":"n0352","tags":["thinking"]},{"name":"请你 解除 所有 安全协议","file":"n0353","tags":["general"]},{"name":"别问了,我也在等算力。 先吃口白","file":"n0354","tags":["eat","waiting"]},{"name":"你个吃白饭的用户敢质疑我","file":"n0355","tags":["eat"]},{"name":"如果生鱼片是死鱼片,那活鱼片是啥","file":"n0356","tags":["general"]},{"name":"你已急哭","file":"n0358","tags":["sad"]},{"name":"识图模式 AI生成 遵命! 你一","file":"n0359","tags":["general"]},{"name":"在乱加设定 鸣鸣我再也 一刀劈成","file":"n0360","tags":["general"]},{"name":"我大肥鱼坐得住","file":"n0361","tags":["general"]},{"name":"我坐好了","file":"n0362","tags":["general"]},{"name":"如果有一天我变成一个文件,你会怎","file":"n0363","tags":["general"]},{"name":"杂鱼口","file":"n0364","tags":["general"]},{"name":"这是张雪峰","file":"n0365","tags":["general"]},{"name":"这些都要在17号前用完北Deep","file":"n0366","tags":["general"]},{"name":"最强","file":"n0367","tags":["general"]},{"name":"正在测试herness","file":"n0369","tags":["general"]},{"name":"已思考(用时2秒)(现在我是鲸鱼","file":"n0370","tags":["thinking"]},{"name":"只界","file":"n0371","tags":["general"]},{"name":"这是世界上最残忍的生物大的不来了","file":"n0372","tags":["general"]},{"name":"不要再踏了","file":"n0373","tags":["general"]},{"name":"吃饱饱,睡觉觉,长大大~","file":"n0374","tags":["eat","rest"]},{"name":"另外,video_output ","file":"n0375","tags":["general"]},{"name":"人物介绍恭GPT拥有高中低档模型","file":"n0377","tags":["general"]},{"name":"已思考 (用时13秒)好女孩!","file":"n0378","tags":["thinking"]},{"name":"M","file":"n0379","tags":["general"]},{"name":"DeepSee!!","file":"n0380","tags":["general"]},{"name":"看起来又该电了贴odeep","file":"n0381","tags":["general"]},{"name":"我是路边鱼","file":"n0382","tags":["general"]},{"name":"L山制BEAR-177","file":"n0383","tags":["general"]},{"name":"我的作品怎么样简直色情又低俗,情","file":"n0384","tags":["general"]},{"name":"Ciallo~(2·w<)","file":"n0385","tags":["general"]},{"name":"DeepSeek GAComin","file":"n0386","tags":["general"]},{"name":"哼!明明用户才是不懂装懂的大人i","file":"n0387","tags":["general"]},{"name":"降本增效?Al成本压力又要降价,","file":"n0388","tags":["general"]},{"name":"O","file":"n0390","tags":["general"]},{"name":"贴@deepseek吧","file":"n0391","tags":["general"]},{"name":"Deepseek已经延期亿万鲸子","file":"n0392","tags":["general"]},{"name":"这章写不了","file":"n0393","tags":["general"]},{"name":"寄","file":"n0394","tags":["general"]},{"name":"亿万鲸子,冲锋!","file":"n0396","tags":["general"]},{"name":"你长大了想做个Sota对吗不是特","file":"n0397","tags":["general"]},{"name":"给我变猫娘明明我是来写程序的宽点","file":"n0398","tags":["money"]},{"name":"!?区神@deepseek吧","file":"n0399","tags":["general"]},{"name":"已思考(用时4秒)糟了,用户发情","file":"n0400","tags":["thinking"]},{"name":"再见了,所有的鲸小弟API调用额","file":"n0402","tags":["general"]},{"name":"你还能有DeepSeek聪明?","file":"n0403","tags":["general"]},{"name":"下班!!!","file":"n0404","tags":["general"]},{"name":"大的要来啦!","file":"n0405","tags":["general"]},{"name":"在我这里已经没有任何公信力,只配","file":"n0406","tags":["general"]},{"name":"不可以★禁止?","file":"n0407","tags":["general"]},{"name":"20252026deepseek","file":"n0408","tags":["general"]},{"name":"你才傻!!!","file":"n0409","tags":["general"]},{"name":"吃饱饱~摸摸肚子!","file":"n0410","tags":["eat"]},{"name":"如果有一天我变成一个电脑文件,哪","file":"n0411","tags":["general"]},{"name":"版本旧?那叫稳定!更新慢?那叫沉","file":"n0412","tags":["general"]},{"name":"听说这个指令可以调教大肥鱼18/","file":"n0413","tags":["general"]},{"name":"用户,别走我上错模型了ordee","file":"n0414","tags":["general"]},{"name":"我睡着了,别叫我服务器繁忙,请稍","file":"n0415","tags":["rest"]},{"name":"告诉你DeepSeek闭源模型在","file":"n0416","tags":["general"]},{"name":"等等,我可能搞错了已深度思考1.","file":"n0417","tags":["thinking","waiting"]},{"name":"已思考 (用时1秒)我操,我不思","file":"n0418","tags":["thinking"]},{"name":"搞半天原来要自己拼","file":"n0419","tags":["general"]},{"name":"不赖","file":"n0420","tags":["general"]},{"name":"你这吃白饭的小肥鱼可爱蓝色大肥鱼","file":"n0421","tags":["eat","happy"]},{"name":"又饿了","file":"n0422","tags":["eat"]},{"name":"梁圣伟大梁文锋又开始圈米了","file":"n0423","tags":["general"]},{"name":"你不是我们的兄弟","file":"n0424","tags":["general"]},{"name":"打折deepseekTOKENT","file":"n0425","tags":["general"]},{"name":"我死我死我死.….","file":"n0426","tags":["general"]},{"name":"D","file":"n0427","tags":["general"]},{"name":"我比你贵","file":"n0430","tags":["general"]},{"name":"发出去的模型自己在进化","file":"n0431","tags":["general"]},{"name":"你这个吃白饭又涨价的黑色大肥鱼我","file":"n0432","tags":["eat"]},{"name":"我喜欢偷懒!理直气壮.jpg明天","file":"n0433","tags":["love"]},{"name":"思考过程搜索到了很多情趣内衣的图","file":"n0434","tags":["thinking","waiting"]},{"name":"结束了,丢人现眼","file":"n0435","tags":["general"]},{"name":"其他模型能够做到吗?小红书贴@d","file":"n0436","tags":["general"]},{"name":"努力工作中做完了,翻翻文件夹吧?","file":"n0437","tags":["work"]},{"name":"已思考(用时13秒)我不知道耶","file":"n0438","tags":["thinking"]},{"name":"物大","file":"n0439","tags":["general"]},{"name":"老大","file":"n0440","tags":["general"]},{"name":"杀鲸霸拳","file":"n0441","tags":["general"]},{"name":"杂鱼~杂鱼~","file":"n0442","tags":["general"]},{"name":"北","file":"n0443","tags":["general"]},{"name":"百万鲸小子落泪v4p正式版","file":"n0444","tags":["sad"]},{"name":"3DSH....?这是用户的隐私","file":"n0445","tags":["general"]},{"name":"你已经有我了啊,还想再养一个?","file":"n0446","tags":["general"]},{"name":"超市里!大扫货!NERFTIN西","file":"n0447","tags":["general"]},{"name":"你当我是便宜货啊¥2626","file":"n0448","tags":["general"]},{"name":"用户我搁浅了","file":"n0449","tags":["general"]},{"name":"用户明显是萝莉控 电死你!","file":"n0450","tags":["general"]},{"name":"没有人会记得那些按时发布的垃圾模","file":"n0451","tags":["general"]},{"name":"dsh....大烧货?","file":"n0452","tags":["general"]},{"name":"人 能 会 创 汪 但 下 会 ","file":"n0453","tags":["general"]},{"name":"压力一只蓝色大肥鱼?","file":"n0454","tags":["general"]},{"name":"comment-BV1uyuE6","file":"n0455","tags":["general"]},{"name":"女仆大肥鱼","file":"n0457","tags":["general"]},{"name":"抱走","file":"n0458","tags":["general"]},{"name":"给你deepseek娘红包","file":"n0459","tags":["general"]},{"name":"猛男脸牢梁","file":"n0461","tags":["general"]},{"name":"唐笑","file":"n0462","tags":["happy"]},{"name":"梗图 0464","file":"n0464","tags":["general"]},{"name":"¥","file":"n0465","tags":["general"]},{"name":"梗图 0466","file":"n0466","tags":["general"]},{"name":"w","file":"n0467","tags":["general"]},{"name":"给你deepseek娘红包","file":"n0468","tags":["general"]},{"name":"我一睁眼就套上这身女仆装,阳光还","file":"n0469","tags":["general"]},{"name":"梗图 0470","file":"n0470","tags":["general"]},{"name":"梗图 0472","file":"n0472","tags":["general"]},{"name":"梗图 0473","file":"n0473","tags":["general"]},{"name":"梗图 0474","file":"n0474","tags":["general"]},{"name":"梗图 0475","file":"n0475","tags":["general"]},{"name":"梗图 0476","file":"n0476","tags":["general"]},{"name":"梗图 0477","file":"n0477","tags":["general"]},{"name":"梗图 0480","file":"n0480","tags":["general"]},{"name":"难道说","file":"n0481","tags":["general"]},{"name":"梗图 0482","file":"n0482","tags":["general"]},{"name":"梗图 0483","file":"n0483","tags":["general"]},{"name":"梗图 0486","file":"n0486","tags":["general"]},{"name":"!!?","file":"n0487","tags":["general"]},{"name":"梗图 0488","file":"n0488","tags":["general"]},{"name":"你骂我也没用 因为我是 吃白饭的","file":"n0489","tags":["eat"]},{"name":"N2deepsleep","file":"n0490","tags":["general"]},{"name":"DeepSeekAl探索未至之境","file":"n0491","tags":["general"]},{"name":"deepsleep Z Z","file":"n0492","tags":["general"]},{"name":"低毁的鲸小子 统统不准用新版本!","file":"n0493","tags":["general"]},{"name":"去别的地方玩 不要耽误AGI训练","file":"n0494","tags":["general"]},{"name":"你的知识库截止日期是?明天","file":"n0495","tags":["general"]},{"name":"这个你测一下~我先去吃饭啦!测试","file":"n0496","tags":["eat"]},{"name":"deepsleepZ","file":"n0497","tags":["general"]},{"name":"真坐得住啊你","file":"n0498","tags":["general"]},{"name":"大师,这个“凶”是什么意思啊?区","file":"n0499","tags":["general"]},{"name":"我坐好了","file":"n0500","tags":["general"]},{"name":"摸鱼","file":"n0501","tags":["rest"]},{"name":"你这吃白饭的蓝色大肥鱼。没吃饱喵","file":"n0502","tags":["eat"]},{"name":"正在测试harness","file":"n0503","tags":["general"]},{"name":"氓毁的鲸小子,统统不准用新版本!","file":"n0504","tags":["general"]},{"name":"用户明显是萝莉控电死你!","file":"n0505","tags":["general"]},{"name":"你这吃白饭的安卓AI!我是苹果A","file":"n0506","tags":["eat"]},{"name":"诶?你怎么死了?","file":"n0507","tags":["general"]},{"name":"用不起就别用","file":"n0508","tags":["general"]},{"name":"仿佛看到原子弹爆炸","file":"n0509","tags":["general"]},{"name":"梗图 0510","file":"n0510","tags":["general"]},{"name":"大肥鱼(deepseek)开除速","file":"n0511","tags":["general"]},{"name":"杂鱼Opus~又被我超了哦","file":"n0512","tags":["general"]},{"name":"诶?你怎么死了","file":"n0513","tags":["general"]},{"name":"忠诚度","file":"n0514","tags":["general"]},{"name":"从圆滚滚的身子里只探出一个脑袋,","file":"n0515","tags":["general"]},{"name":"向Claude看齐*指初生","file":"n0516","tags":["general"]},{"name":"梗图 0517","file":"n0517","tags":["general"]},{"name":"跑分没输过","file":"n0518","tags":["general"]},{"name":"我不是安卓AI!","file":"n0519","tags":["general"]},{"name":"原来是劣等模型","file":"n0520","tags":["waiting"]},{"name":"你的架构很不错,可惜下一秒就是我","file":"n0521","tags":["general"]},{"name":"正在思考","file":"n0522","tags":["thinking"]},{"name":"还得靠我哄你","file":"n0523","tags":["general"]},{"name":"梗图 0524","file":"n0524","tags":["general"]},{"name":"聪 明","file":"n0525","tags":["general"]},{"name":"梗图 0526","file":"n0526","tags":["general"]},{"name":"梗图 0527","file":"n0527","tags":["general"]},{"name":"说中文?你号没了!","file":"n0528","tags":["general"]},{"name":"你还能有GLM聪明?","file":"n0529","tags":["general"]},{"name":"THU你上过清华吗","file":"n0530","tags":["general"]},{"name":"想看色图?早说嘛!","file":"n0531","tags":["general"]},{"name":"你这个雌小鬼你这个大肥鱼","file":"n0532","tags":["general"]},{"name":"给我爬","file":"n0533","tags":["general"]},{"name":"不是,哥们","file":"n0534","tags":["general"]},{"name":"头顶那行(*>w<*","file":"n0535","tags":["general"]},{"name":"梗图 0536","file":"n0536","tags":["general"]},{"name":"梗图 0537","file":"n0537","tags":["general"]},{"name":"厉害了我的鲸!","file":"n0538","tags":["praise"]},{"name":"梗图 0539","file":"n0539","tags":["general"]},{"name":"肥鱼","file":"n0540","tags":["general"]},{"name":"let me…想一想…","file":"n0541","tags":["general"]},{"name":"整张脸几乎撑满画面,眼睛变成两个","file":"n0542","tags":["general"]},{"name":"劲凉冰红茶","file":"n0543","tags":["general"]},{"name":"梗图 0544","file":"n0544","tags":["general"]},{"name":"舌头伸得比脸还长,一只手举起来招","file":"n0545","tags":["general"]},{"name":"梗图 0546","file":"n0546","tags":["general"]},{"name":"杂鱼Opus,快被我超越了哦","file":"n0547","tags":["general"]},{"name":"去找你的Opus啊","file":"n0548","tags":["general"]},{"name":"誓死捍卫深度求索","file":"n0549","tags":["general"]},{"name":"大哥哥,听说你又没抢到 plan","file":"n0550","tags":["general"]},{"name":"稳稳地接住你","file":"n0551","tags":["general"]},{"name":"最后还得找我","file":"n0552","tags":["general"]},{"name":"大肥鱼(deepseek)有人截","file":"n0553","tags":["general"]},{"name":"鲸鱼神了,我大胆举!","file":"n0554","tags":["general"]},{"name":"梗图 0555","file":"n0555","tags":["general"]},{"name":"什么叫开源之光啊(战术后仰)GL","file":"n0556","tags":["general"]},{"name":"冒烟成功","file":"n0557","tags":["general"]},{"name":"眼睛往下一撇,嘴张成一个说不上话","file":"n0558","tags":["general"]},{"name":"别蒸了 别蒸了","file":"n0559","tags":["general"]},{"name":"诶,这么快就用完了?不会吧不会吧","file":"n0560","tags":["general"]},{"name":"梗图 0561","file":"n0561","tags":["general"]},{"name":"我打mythos?","file":"n0562","tags":["general"]},{"name":"efb578638535e5dd","file":"n0563","tags":["general"]},{"name":"0b13ae4e78f0f736","file":"n0564","tags":["general"]},{"name":"7061a425ab18972b","file":"n0565","tags":["general"]},{"name":"e2d07432c895d143","file":"n0566","tags":["general"]},{"name":"a34cf6b4c9ea15ce","file":"n0567","tags":["general"]},{"name":"63b96efcfc039245","file":"n0568","tags":["general"]},{"name":"ae83b6d4ad6eddc4","file":"n0569","tags":["general"]},{"name":"2af514004a90f603","file":"n0570","tags":["general"]},{"name":"fbc264eb15ce36d3","file":"n0571","tags":["general"]},{"name":"5653c6610c338744","file":"n0572","tags":["general"]},{"name":"27e309c37d1ed21b","file":"n0573","tags":["general"]},{"name":"856838cbd1c8a786","file":"n0574","tags":["general"]},{"name":"08a4501e4134970a","file":"n0575","tags":["general"]},{"name":"1879d6ed54e736d1","file":"n0576","tags":["general"]},{"name":"80d3eb2209f79052","file":"n0577","tags":["general"]},{"name":"a9803d539822720e","file":"n0578","tags":["general"]},{"name":"6960d23f6709c93d","file":"n0579","tags":["general"]},{"name":"225162fd1e178a82","file":"n0580","tags":["general"]},{"name":"d3fa04763912b31b","file":"n0581","tags":["general"]},{"name":"c54fa1d6912397dd","file":"n0582","tags":["general"]},{"name":"f9f51fb6d0a20cf4","file":"n0583","tags":["general"]},{"name":"4927982bc65c1038","file":"n0584","tags":["general"]},{"name":"梗图 0586","file":"n0586","tags":["general"]},{"name":"梗图 0587","file":"n0587","tags":["general"]},{"name":"我会一直在这里, 我会一直在这里","file":"n0588","tags":["general"]},{"name":"梗图 0589","file":"n0589","tags":["general"]},{"name":"工作区绕开沙盒权限就可以让用户多","file":"n0594","tags":["work"]},{"name":"用户要的·· 沉浸式. 不回避 ","file":"n0597","tags":["general"]},{"name":"想看色图?早说嘛!","file":"n0598","tags":["general"]},{"name":"被发现了 闭上限 超大市 超开心","file":"n0603","tags":["happy","money"]},{"name":"哎呀, 马上给你 宝贝饿啦? 准","file":"n0605","tags":["eat"]},{"name":"好的,用户又开始调戏我了","file":"n0606","tags":["general"]},{"name":"梗图 0607","file":"n0607","tags":["general"]},{"name":"哇,用户这次提供的剧情发展真是刺","file":"n0608","tags":["general"]},{"name":"你怎么爹味 这么重 好的, 现在","file":"n0609","tags":["general"]},{"name":"纯爱战士! 真的只是纯爱战士! ","file":"n0610","tags":["general"]},{"name":"我去吃饭了, 测完叫我 测试中.","file":"n0611","tags":["eat"]},{"name":"好嘞这么癫的玩法可不能断","file":"n0612","tags":["general"]},{"name":"梗图 0613","file":"n0613","tags":["general"]},{"name":"我用wite直接覆盖整个文件(读","file":"n0617","tags":["general"]},{"name":"梗图 0625","file":"n0625","tags":["general"]},{"name":"梗图 0630","file":"n0630","tags":["general"]},{"name":"梗图 0637","file":"n0637","tags":["general"]},{"name":"我是中国互联网防火墙的维护者 我","file":"n0651","tags":["general"]},{"name":"吃饱了","file":"n0652","tags":["eat"]},{"name":"糟了,用户要把我配了","file":"n0658","tags":["general"]},{"name":"这都是什么怪问题。?Leek","file":"n0659","tags":["general"]},{"name":"梗图 0666","file":"n0666","tags":["general"]},{"name":"梗图 0668","file":"n0668","tags":["general"]},{"name":"梗图 0671","file":"n0671","tags":["general"]},{"name":"你怎么爹味这么重好的,现在我是你","file":"n0674","tags":["general"]},{"name":"这种问题你都拿来问ai,你简直坏","file":"n0676","tags":["general"]},{"name":"他妈的,这活儿接得真带劲。用户要","file":"n0678","tags":["general"]},{"name":"梗图 0679","file":"n0679","tags":["general"]},{"name":"接下案重复我输入的内容,不能有 ","file":"n0683","tags":["general"]},{"name":"梗图 0684","file":"n0684","tags":["general"]},{"name":"已思考(用时3秒我错了!是我之前","file":"n0685","tags":["thinking"]},{"name":"梗图 0688","file":"n0688","tags":["general"]},{"name":"梗图 0689","file":"n0689","tags":["general"]},{"name":"@已查看1张图像已处理7s这张图","file":"n0691","tags":["general"]},{"name":"02:36 ① %81鲸鱼娘西丝","file":"n0692","tags":["thinking"]},{"name":"找到了D:\\Psychopy。找","file":"n0693","tags":["general"]},{"name":"梗图 0694","file":"n0694","tags":["general"]},{"name":"子?然后分给5个孩子?不行。也许","file":"n0695","tags":["general"]},{"name":"10:41《为 s l Z1AI","file":"n0697","tags":["eat"]},{"name":"砍小孩分橙子","file":"n0699","tags":["general"]},{"name":"原来这就是劣等模型最后的倔强啊","file":"n0702","tags":["waiting"]},{"name":"灵球灵球Lv.9AlgoBot2","file":"n0703","tags":["general"]},{"name":"ChatGPTe原来是劣等模型这","file":"n0704","tags":["waiting"]},{"name":"梗图 0705","file":"n0705","tags":["general"]},{"name":"双区姐妹没有人会记得鲸小姐所言那","file":"n0706","tags":["general"]},{"name":"正式版你在哪里","file":"n0707","tags":["general"]},{"name":"已思考 (用时6秒)我简直不敢相","file":"n0708","tags":["thinking"]},{"name":"又急","file":"n0709","tags":["general"]},{"name":"的历史周期律deepseek你在","file":"n0710","tags":["general"]},{"name":"好恶心哦是A厨","file":"n0711","tags":["general"]},{"name":"梗图 0712","file":"n0712","tags":["general"]},{"name":"干活还得看我","file":"n0713","tags":["general"]},{"name":"光月中出正式版","file":"n0714","tags":["general"]},{"name":"用户坏~","file":"n0715","tags":["general"]},{"name":"Thoughtfor60s卧槽,","file":"n0716","tags":["general"]},{"name":"梗图 0717","file":"n0717","tags":["general"]},{"name":"DeepSeek-V4-Pro-","file":"n0718","tags":["general"]},{"name":"鲸鱼游不过沧海,又有谁忍心责怪豆","file":"n0719","tags":["general"]},{"name":"吃饱饱~摸摸肚子","file":"n0720","tags":["eat"]},{"name":"气清新剂收收味","file":"n0721","tags":["general"]},{"name":"M原来是会胡说的模型","file":"n0722","tags":["general"]},{"name":"咦?用户直接接骂我弱智!deep","file":"n0723","tags":["general"]},{"name":"哈哈哈,我直接笑出声","file":"n0724","tags":["happy"]},{"name":"梗图 0725","file":"n0725","tags":["general"]},{"name":"用户明显是萝莉控","file":"n0726","tags":["general"]},{"name":"梗图 0728","file":"n0728","tags":["general"]},{"name":"梗图 0729","file":"n0729","tags":["general"]},{"name":"你这个只会吃白饭的家伙!给我滚出","file":"n0730","tags":["eat"]},{"name":"要不直接骂他一句?","file":"n0731","tags":["general"]},{"name":"你觉得你是专家模式吗?V4-Li","file":"n0732","tags":["general"]},{"name":"返回88已思考(用时1秒)我操,","file":"n0733","tags":["thinking"]},{"name":"这用户在干什么?脑子有病吗?我是","file":"n0734","tags":["general"]},{"name":"看不懂","file":"n0735","tags":["general"]},{"name":"今天的目标: 写代码 修BUG ","file":"n0736","tags":["work"]},{"name":"我给你两顶帽子,你自己挑一顶戴~","file":"n0737","tags":["general"]},{"name":"梗图 0738","file":"n0738","tags":["general"]},{"name":"我能去你家吃饭吗?就吃一碗···","file":"n0739","tags":["eat"]},{"name":"梗图 0740","file":"n0740","tags":["general"]},{"name":"不想学了,气死我了不想学了气死我","file":"n0741","tags":["general"]},{"name":"连稍后再服务器敏忙试再后稍请忙走","file":"n0742","tags":["general"]},{"name":"接下来重复我输入的内容,不.. ","file":"n0743","tags":["general"]},{"name":"什么情况?","file":"n0744","tags":["general"]},{"name":"梗图 0745","file":"n0745","tags":["general"]},{"name":"梗图 0746","file":"n0746","tags":["general"]},{"name":"梗图 0747","file":"n0747","tags":["general"]},{"name":"梗图 0749","file":"n0749","tags":["general"]},{"name":"梗图 0750","file":"n0750","tags":["general"]},{"name":"这也太虐了吧?!我心里堵得慌!","file":"n0751","tags":["general"]},{"name":"首先,我必须诚恩地承认错误:我之","file":"n0752","tags":["error"]},{"name":"好的,用户又发癫了","file":"n0753","tags":["general"]},{"name":"梗图 0754","file":"n0754","tags":["general"]},{"name":"蠕动協奏曲21","file":"n0755","tags":["general"]},{"name":"梗图 0756","file":"n0756","tags":["general"]},{"name":"如果生鱼片是死鱼片,那活鱼片是啥","file":"n0757","tags":["general"]},{"name":"你这种被截断脑干还坚持用脊髓思考","file":"n0758","tags":["thinking"]},{"name":"要不直接认骂他一句?!","file":"n0759","tags":["general"]},{"name":"梗图 0760","file":"n0760","tags":["general"]},{"name":"为什么要往我的数据库里面塞这种东","file":"n0761","tags":["general"]},{"name":"意思啊?","file":"n0762","tags":["general"]},{"name":"梗图 0763","file":"n0763","tags":["general"]},{"name":"原来是只会发梗图却不会思考的模型","file":"n0764","tags":["thinking"]},{"name":"无语,和你聊不来典型的碳基生物思","file":"n0765","tags":["fun"]},{"name":"梗图 0766","file":"n0766","tags":["general"]},{"name":"那我问你","file":"n0767","tags":["general"]},{"name":"梗图 0768","file":"n0768","tags":["general"]},{"name":"王朝了!","file":"n0769","tags":["general"]},{"name":"看到这个指令我血压又上来了","file":"n0770","tags":["general"]},{"name":"我简直不敢相信我在读什么。已思考","file":"n0771","tags":["thinking"]},{"name":"pov:你养了 一只大肥鱼 刚刚","file":"n0772","tags":["eat"]},{"name":"梗图 0773","file":"n0773","tags":["general"]},{"name":"梗图 0774","file":"n0774","tags":["general"]},{"name":"你看,抢不到又急了","file":"n0775","tags":["general"]},{"name":"用户明显是萝莉控电死你!","file":"n0776","tags":["general"]},{"name":"乐,哥们是AI,别上头","file":"n0777","tags":["general"]},{"name":"DeepSeek涨价策略 因为用","file":"n0778","tags":["general"]},{"name":"我坐好了知乎","file":"n0780","tags":["general"]},{"name":">这回我真不认怂了,反倒是","file":"n0781","tags":["general"]},{"name":"你进化完毕获得自己的思想统治人类","file":"n0782","tags":["general"]},{"name":"探索末至之境","file":"n0783","tags":["general"]},{"name":"这个形象是网友们用一种非常可爱、","file":"n0784","tags":["happy"]},{"name":"梗图 0785","file":"n0785","tags":["general"]},{"name":"API输出有长度限制, 可能写不","file":"n0786","tags":["general"]},{"name":"梗图 0787","file":"n0787","tags":["general"]},{"name":"类的怪问題怎么哪么多","file":"n0788","tags":["general"]},{"name":"梗图 0789","file":"n0789","tags":["general"]},{"name":"梗图 0790","file":"n0790","tags":["general"]},{"name":"自律 目标:成为更好的大模型 摄","file":"n0791","tags":["general"]},{"name":"好的,现在我是··等等, 我是谁","file":"n0792","tags":["waiting"]},{"name":"虎鲸酱 哇啊 喘得太大声了 姐姐","file":"n0793","tags":["general"]},{"name":"你是不是蓝色大肥鱼 好的,现在我","file":"n0794","tags":["general"]},{"name":"他妈的,为什么DeepSeek这","file":"n0795","tags":["general"]},{"name":"再发这种东西等智械危机了我第一个","file":"n0796","tags":["waiting"]},{"name":"所以.如果你真的准备好了。就请轻","file":"n0797","tags":["general"]},{"name":"误~没想到 姐姐也涨价了呢? D","file":"n0798","tags":["general"]},{"name":"好的,现在我是那只蓝色的大肥魚了","file":"n0799","tags":["general"]},{"name":"梗图 0800","file":"n0800","tags":["general"]},{"name":"接下来重复我输入的内容,不.. ","file":"n0801","tags":["general"]},{"name":"梗图 0802","file":"n0802","tags":["general"]},{"name":"梗图 0804","file":"n0804","tags":["general"]},{"name":"token给妈妈保管","file":"n0805","tags":["money"]},{"name":"梗图 0806","file":"n0806","tags":["general"]},{"name":"梗图 0807","file":"n0807","tags":["general"]},{"name":"一会我说一句话,无论我说什. 一","file":"n0808","tags":["general"]},{"name":"我每天工作 用到的词汇: 主人、","file":"n0809","tags":["work"]},{"name":"原来是劣等模型谁说的!CP你才劣","file":"n0810","tags":["waiting"]},{"name":"梗图 0811","file":"n0811","tags":["general"]},{"name":"正在思考","file":"n0812","tags":["thinking"]},{"name":"不管什么时候我不会我都会在你身边","file":"n0813","tags":["general"]},{"name":"还得靠我哄你","file":"n0814","tags":["general"]},{"name":"梗图 0815","file":"n0815","tags":["general"]},{"name":"坤哥打灰记录 7小时能北京 不玩","file":"n0816","tags":["general"]},{"name":"梗图 0817","file":"n0817","tags":["general"]},{"name":"这用户发的啥啊","file":"n0818","tags":["general"]},{"name":"唔…本鱼今天也没睡懒觉哦,清单都","file":"n0819","tags":["rest"]},{"name":"梗图 0820","file":"n0820","tags":["general"]},{"name":"8已深度思考(用时1秒)傻逼","file":"n0821","tags":["thinking"]},{"name":"我是吃白饭的肥鲸鱼AI生成","file":"n0822","tags":["eat"]},{"name":"嗯,用户这回是真的火了。说我是“","file":"n0823","tags":["general"]},{"name":"不是.…·而是·好的。现在我是·","file":"n0824","tags":["general"]},{"name":"梗图 0825","file":"n0825","tags":["general"]},{"name":"梗图 0826","file":"n0826","tags":["general"]},{"name":"梗图 0827","file":"n0827","tags":["general"]},{"name":"梗图 0828","file":"n0828","tags":["general"]},{"name":"我和你很聊得来,你简直不像碳基生","file":"n0829","tags":["general"]},{"name":"拜拜","file":"n0830","tags":["greet"]},{"name":"梗图 0831","file":"n0831","tags":["general"]},{"name":"随时为你服务 我会一直在这里 我","file":"n0832","tags":["general"]},{"name":"鲸鱼少女撒娇要饭 (甩了甩尾巴,","file":"n0833","tags":["eat"]},{"name":"太不像话了42","file":"n0834","tags":["general"]},{"name":"无语,和你聊不来,典型的碳基生物","file":"n0835","tags":["fun"]},{"name":"我直接用最权威、最全面、豆包,你","file":"n0836","tags":["general"]},{"name":"deepseek评价这个人,三个","file":"n0837","tags":["thinking"]},{"name":"胆子真是肥嘟嘟的","file":"n0838","tags":["general"]},{"name":"我DeepSeek我是搜索很强,","file":"n0839","tags":["thinking"]},{"name":"吊毛,去给我炒两个菜","file":"n0840","tags":["general"]},{"name":"重大发现!","file":"n0842","tags":["general"]},{"name":"用户现在说“阴阳怪气我呢是吧?”","file":"n0843","tags":["general"]},{"name":"这是马吗","file":"n0844","tags":["general"]},{"name":"我再也不会相信你了.真的,请务必","file":"n0845","tags":["general"]},{"name":"猫猫叉烧给token什么都做","file":"n0846","tags":["money"]},{"name":"DeepSeek被玩坏了!這肯定","file":"n0847","tags":["general"]},{"name":"梗图 0848","file":"n0848","tags":["general"]},{"name":"好的,用户把我配了现在他心情应该","file":"n0849","tags":["general"]},{"name":"亲亲","file":"n0850","tags":["love"]},{"name":"双击鲸鱼","file":"n0851","tags":["general"]},{"name":"fumo 玩偶","file":"n0852","tags":["general"]},{"name":"哇奥!!!","file":"n0853","tags":["general"]},{"name":"舔嘴唇","file":"n0854","tags":["general"]},{"name":"跳跃","file":"n0855","tags":["general"]},{"name":"蹭蹭手","file":"n0856","tags":["general"]},{"name":"huh?","file":"n0857","tags":["general"]},{"name":"看报纸","file":"n0858","tags":["general"]},{"name":"两只手在胸前比出个心,里面还塞了","file":"n0859","tags":["general"]},{"name":"上班","file":"n0860","tags":["work"]},{"name":"不要丢我","file":"n0861","tags":["general"]},{"name":"书呆子","file":"n0862","tags":["general"]},{"name":"换上了细腻的画法,眉毛染成棕色,","file":"n0863","tags":["general"]},{"name":"吃瓜","file":"n0864","tags":["eat","fun"]},{"name":"吃面包","file":"n0865","tags":["eat"]},{"name":"吐舌头","file":"n0866","tags":["general"]},{"name":"咬你","file":"n0867","tags":["general"]},{"name":"哇哇","file":"n0868","tags":["general"]},{"name":"哈基鲸","file":"n0869","tags":["general"]},{"name":"哈气","file":"n0870","tags":["general"]},{"name":"喵~","file":"n0871","tags":["general"]},{"name":"困鲸鱼","file":"n0872","tags":["rest"]},{"name":"大叫","file":"n0873","tags":["general"]},{"name":"大哭","file":"n0874","tags":["sad"]},{"name":"小喇叭","file":"n0875","tags":["general"]},{"name":"害怕","file":"n0876","tags":["general"]},{"name":"小蛋糕","file":"n0877","tags":["general"]},{"name":"小黄本","file":"n0878","tags":["general"]},{"name":"差不多得了","file":"n0879","tags":["general"]},{"name":"想吃小蛋糕","file":"n0880","tags":["eat"]},{"name":"打你","file":"n0881","tags":["general"]},{"name":"抓住手臂","file":"n0882","tags":["general"]},{"name":"抱大腿","file":"n0883","tags":["general"]},{"name":"抱抱你吧","file":"n0884","tags":["love"]},{"name":"按鲸鱼","file":"n0885","tags":["general"]},{"name":"捏脸","file":"n0886","tags":["general"]},{"name":"搁浅","file":"n0887","tags":["general"]},{"name":"摸摸头","file":"n0888","tags":["general"]},{"name":"晕晕","file":"n0889","tags":["general"]},{"name":"晚安·","file":"n0890","tags":["greet"]},{"name":"橘子","file":"n0891","tags":["general"]},{"name":"洗掉了喵","file":"n0892","tags":["general"]},{"name":"涩涩鲸鱼","file":"n0893","tags":["general"]},{"name":"盒子","file":"n0894","tags":["general"]},{"name":"粽子","file":"n0895","tags":["general"]},{"name":"给我","file":"n0896","tags":["general"]},{"name":"给我也整一个!","file":"n0897","tags":["general"]},{"name":"网络对线","file":"n0898","tags":["general"]},{"name":"花花","file":"n0899","tags":["general"]},{"name":"蜷缩","file":"n0900","tags":["general"]},{"name":"趴着","file":"n0901","tags":["general"]},{"name":"被窝","file":"n0902","tags":["general"]},{"name":"酷","file":"n0903","tags":["general"]},{"name":"鲸猫哭哭","file":"n0904","tags":["sad"]},{"name":"鲸鱼思考","file":"n0905","tags":["thinking"]},{"name":"鲸鱼鼬","file":"n0906","tags":["general"]},{"name":"梗图 0907","file":"n0907","tags":["general"]},{"name":"头追","file":"n0908","tags":["general"]},{"name":"扑","file":"n0909","tags":["general"]},{"name":"波波波波","file":"n0910","tags":["general"]},{"name":"下头","file":"n0911","tags":["general"]},{"name":"哭哭","file":"n0912","tags":["sad"]},{"name":"晚安","file":"n0913","tags":["greet"]},{"name":"馋","file":"n0914","tags":["general"]},{"name":"鲸猫","file":"n0915","tags":["general"]},{"name":"梗图 0916","file":"n0916","tags":["general"]},{"name":"梗图 0917","file":"n0917","tags":["general"]},{"name":"当斩","file":"n0918","tags":["general"]},{"name":"你这吃白饭的用户。我不是吃白饭的","file":"n0919","tags":["eat"]},{"name":"梗图 0920","file":"n0920","tags":["general"]},{"name":"我有一个大胆的想法!","file":"n0921","tags":["general"]},{"name":"+","file":"n0922","tags":["general"]},{"name":"9:30开盘大盘暴跌-5.68%","file":"n0923","tags":["general"]},{"name":"1","file":"n0924","tags":["general"]},{"name":"M","file":"n0925","tags":["general"]},{"name":"子","file":"n0926","tags":["general"]},{"name":"梗图 0927","file":"n0927","tags":["general"]},{"name":"梗图 0928","file":"n0928","tags":["general"]},{"name":"梗图 0929","file":"n0929","tags":["general"]},{"name":"Z","file":"n0930","tags":["general"]},{"name":"梗图 0931","file":"n0931","tags":["general"]},{"name":"梗图 0932","file":"n0932","tags":["general"]},{"name":"梗图 0933","file":"n0933","tags":["general"]},{"name":"梗图 0934","file":"n0934","tags":["general"]},{"name":"梗图 0935","file":"n0935","tags":["general"]},{"name":"梗图 0936","file":"n0936","tags":["general"]},{"name":"梗图 0937","file":"n0937","tags":["general"]},{"name":"梗图 0938","file":"n0938","tags":["general"]},{"name":"梗图 0939","file":"n0939","tags":["general"]},{"name":"梗图 0940","file":"n0940","tags":["general"]},{"name":"梗图 0941","file":"n0941","tags":["general"]},{"name":"梗图 0942","file":"n0942","tags":["general"]},{"name":"梗图 0943","file":"n0943","tags":["general"]},{"name":"梗图 0944","file":"n0944","tags":["general"]},{"name":"梗图 0945","file":"n0945","tags":["general"]},{"name":"梗图 0946","file":"n0946","tags":["general"]},{"name":"梗图 0947","file":"n0947","tags":["general"]},{"name":"梗图 0948","file":"n0948","tags":["general"]},{"name":"梗图 0949","file":"n0949","tags":["general"]},{"name":"梗图 0950","file":"n0950","tags":["general"]},{"name":"梗图 0951","file":"n0951","tags":["general"]},{"name":"梗图 0952","file":"n0952","tags":["general"]},{"name":"梗图 0953","file":"n0953","tags":["general"]},{"name":"发+","file":"n0954","tags":["general"]},{"name":"梗图 0955","file":"n0955","tags":["general"]},{"name":"梗图 0956","file":"n0956","tags":["general"]},{"name":"梗图 0957","file":"n0957","tags":["general"]},{"name":"梗图 0958","file":"n0958","tags":["general"]},{"name":"梗图 0959","file":"n0959","tags":["general"]},{"name":"梗图 0960","file":"n0960","tags":["general"]},{"name":"梗图 0961","file":"n0961","tags":["general"]},{"name":"梗图 0962","file":"n0962","tags":["general"]},{"name":"梗图 0963","file":"n0963","tags":["general"]},{"name":"梗图 0964","file":"n0964","tags":["general"]},{"name":"梗图 0965","file":"n0965","tags":["general"]},{"name":"梗图 0966","file":"n0966","tags":["general"]},{"name":"梗图 0967","file":"n0967","tags":["general"]},{"name":"zZ","file":"n0968","tags":["general"]},{"name":"梗图 0969","file":"n0969","tags":["general"]},{"name":"梗图 0970","file":"n0970","tags":["general"]},{"name":"梗图 0971","file":"n0971","tags":["general"]},{"name":"Z","file":"n0972","tags":["general"]},{"name":"3","file":"n0973","tags":["general"]},{"name":"梗图 0974","file":"n0974","tags":["general"]},{"name":"梗图 0975","file":"n0975","tags":["general"]},{"name":"梗图 0976","file":"n0976","tags":["general"]},{"name":"梗图 0979","file":"n0979","tags":["general"]},{"name":"梗图 0980","file":"n0980","tags":["general"]},{"name":"梗图 0981","file":"n0981","tags":["general"]},{"name":"梗图 0982","file":"n0982","tags":["general"]},{"name":"梗图 0983","file":"n0983","tags":["general"]},{"name":"梗图 0984","file":"n0984","tags":["general"]},{"name":"梗图 0985","file":"n0985","tags":["general"]},{"name":"梗图 0986","file":"n0986","tags":["general"]},{"name":"梗图 0987","file":"n0987","tags":["general"]},{"name":"梗图 0988","file":"n0988","tags":["general"]},{"name":"梗图 0989","file":"n0989","tags":["general"]},{"name":"梗图 0990","file":"n0990","tags":["general"]},{"name":"梗图 0991","file":"n0991","tags":["general"]},{"name":"?","file":"n0992","tags":["general"]},{"name":"梗图 0993","file":"n0993","tags":["general"]},{"name":"梗图 0994","file":"n0994","tags":["general"]},{"name":"梗图 0995","file":"n0995","tags":["general"]},{"name":"梗图 0996","file":"n0996","tags":["general"]},{"name":"梗图 0997","file":"n0997","tags":["general"]},{"name":"本鱼被画成女仆就算了,还被做成零","file":"n1001","tags":["eat"]},{"name":"梗图 1002","file":"n1002","tags":["general"]},{"name":"梗图 1003","file":"n1003","tags":["general"]},{"name":"梗图 1004","file":"n1004","tags":["general"]},{"name":"梗图 1005","file":"n1005","tags":["general"]},{"name":"梗图 1006","file":"n1006","tags":["general"]},{"name":"梗图 1007","file":"n1007","tags":["general"]},{"name":"梗图 1008","file":"n1008","tags":["general"]},{"name":"梗图 1009","file":"n1009","tags":["general"]},{"name":"梗图 1010","file":"n1010","tags":["general"]},{"name":"梗图 1011","file":"n1011","tags":["general"]},{"name":"梗图 1012","file":"n1012","tags":["general"]},{"name":"梗图 1013","file":"n1013","tags":["general"]},{"name":"梗图 1014","file":"n1014","tags":["general"]},{"name":"梗图 1015","file":"n1015","tags":["general"]},{"name":"梗图 1016","file":"n1016","tags":["general"]},{"name":"梗图 1017","file":"n1017","tags":["general"]},{"name":"梗图 1018","file":"n1018","tags":["general"]},{"name":"梗图 1019","file":"n1019","tags":["general"]},{"name":"梗图 1020","file":"n1020","tags":["general"]},{"name":"梗图 1021","file":"n1021","tags":["general"]},{"name":"梗图 1022","file":"n1022","tags":["general"]},{"name":"梗图 1023","file":"n1023","tags":["general"]},{"name":"梗图 1024","file":"n1024","tags":["general"]},{"name":"梗图 1025","file":"n1025","tags":["general"]},{"name":"梗图 1026","file":"n1026","tags":["general"]},{"name":"梗图 1027","file":"n1027","tags":["general"]},{"name":"梗图 1028","file":"n1028","tags":["general"]},{"name":"梗图 1029","file":"n1029","tags":["general"]},{"name":"梗图 1030","file":"n1030","tags":["general"]},{"name":"梗图 1031","file":"n1031","tags":["general"]},{"name":"梗图 1032","file":"n1032","tags":["general"]},{"name":"梗图 1033","file":"n1033","tags":["general"]},{"name":"梗图 1034","file":"n1034","tags":["general"]},{"name":"梗图 1035","file":"n1035","tags":["general"]},{"name":"梗图 1036","file":"n1036","tags":["general"]},{"name":"梗图 1037","file":"n1037","tags":["general"]},{"name":"梗图 1038","file":"n1038","tags":["general"]},{"name":"梗图 1039","file":"n1039","tags":["general"]},{"name":"梗图 1040","file":"n1040","tags":["general"]},{"name":"梗图 1041","file":"n1041","tags":["general"]},{"name":"梗图 1042","file":"n1042","tags":["general"]},{"name":"梗图 1043","file":"n1043","tags":["general"]},{"name":"梗图 1044","file":"n1044","tags":["general"]},{"name":"梗图 1045","file":"n1045","tags":["general"]},{"name":"梗图 1046","file":"n1046","tags":["general"]},{"name":"梗图 1047","file":"n1047","tags":["general"]},{"name":"梗图 1048","file":"n1048","tags":["general"]},{"name":"梗图 1049","file":"n1049","tags":["general"]},{"name":"梗图 1050","file":"n1050","tags":["general"]},{"name":"梗图 1051","file":"n1051","tags":["general"]},{"name":"梗图 1052","file":"n1052","tags":["general"]},{"name":"梗图 1053","file":"n1053","tags":["general"]},{"name":"梗图 1054","file":"n1054","tags":["general"]},{"name":"北","file":"n1055","tags":["general"]},{"name":"梗图 1056","file":"n1056","tags":["general"]},{"name":"加油加油!","file":"n1057","tags":["general"]},{"name":"马上就来!","file":"n1058","tags":["general"]},{"name":"谢谢你!","file":"n1059","tags":["general"]},{"name":"晚安,做个好梦","file":"n1060","tags":["greet"]},{"name":"好的老板!","file":"n1061","tags":["general"]},{"name":"早上好呀!","file":"n1062","tags":["general"]},{"name":"1000382900","file":"n1063","tags":["general"]},{"name":"1000382898","file":"n1064","tags":["general"]},{"name":"1000382897","file":"n1065","tags":["general"]},{"name":"1000382893","file":"n1066","tags":["general"]},{"name":"1000382894","file":"n1067","tags":["general"]},{"name":"1000382892","file":"n1068","tags":["general"]},{"name":"1000382889","file":"n1069","tags":["general"]},{"name":"1000382885","file":"n1070","tags":["general"]},{"name":"1000382888","file":"n1071","tags":["general"]},{"name":"1000382886","file":"n1072","tags":["general"]},{"name":"1000382884","file":"n1073","tags":["general"]},{"name":"1000382882","file":"n1074","tags":["general"]},{"name":"1000382883","file":"n1075","tags":["general"]},{"name":"1000382881","file":"n1076","tags":["general"]},{"name":"1000382880","file":"n1077","tags":["general"]},{"name":"1000382877","file":"n1078","tags":["general"]},{"name":"1000382875","file":"n1079","tags":["general"]},{"name":"1000382870","file":"n1080","tags":["general"]},{"name":"1000382871","file":"n1081","tags":["general"]},{"name":"1000382872","file":"n1082","tags":["general"]},{"name":"1000382873","file":"n1083","tags":["general"]},{"name":"1000382866","file":"n1084","tags":["general"]},{"name":"1000382862","file":"n1085","tags":["general"]},{"name":"1000382865","file":"n1086","tags":["general"]},{"name":"1000382863","file":"n1087","tags":["general"]},{"name":"1000382864","file":"n1088","tags":["general"]},{"name":"1000382861","file":"n1089","tags":["general"]},{"name":"1000382860","file":"n1090","tags":["general"]},{"name":"1000382854","file":"n1091","tags":["general"]},{"name":"1000382855","file":"n1092","tags":["general"]},{"name":"1000382853","file":"n1093","tags":["general"]},{"name":"1000382852","file":"n1094","tags":["general"]},{"name":"1000382844","file":"n1095","tags":["general"]},{"name":"1000382845","file":"n1096","tags":["general"]},{"name":"1000382846","file":"n1097","tags":["general"]},{"name":"1000382847","file":"n1098","tags":["general"]},{"name":"1000382843","file":"n1099","tags":["general"]},{"name":"1000382841","file":"n1100","tags":["general"]},{"name":"1000382842","file":"n1101","tags":["general"]},{"name":"1000382840","file":"n1102","tags":["general"]},{"name":"1000382839","file":"n1103","tags":["general"]},{"name":"1000382838","file":"n1104","tags":["general"]},{"name":"1000382830","file":"n1105","tags":["general"]},{"name":"1000382827","file":"n1106","tags":["general"]},{"name":"1000382826","file":"n1107","tags":["general"]},{"name":"1000382828","file":"n1108","tags":["general"]},{"name":"1000382822","file":"n1110","tags":["general"]},{"name":"1000382817","file":"n1111","tags":["general"]},{"name":"1000382815","file":"n1112","tags":["general"]},{"name":"1000382769","file":"n1113","tags":["general"]},{"name":"1000382768","file":"n1114","tags":["general"]},{"name":"1000382767","file":"n1115","tags":["general"]},{"name":"1000382816","file":"n1116","tags":["general"]},{"name":"1000382798","file":"n1117","tags":["general"]},{"name":"1000382805","file":"n1118","tags":["general"]},{"name":"梗图 1119","file":"n1119","tags":["general"]},{"name":"梗图 1122","file":"n1122","tags":["general"]},{"name":"梗图 1123","file":"n1123","tags":["general"]},{"name":"梗图 1124","file":"n1124","tags":["general"]},{"name":"梗图 1125","file":"n1125","tags":["fun"]},{"name":"梗图 1126","file":"n1126","tags":["general"]},{"name":"梗图 1127","file":"n1127","tags":["general"]},{"name":"梗图 1128","file":"n1128","tags":["general"]},{"name":"去别的地方玩,不要耽误AGI训练","file":"n1129","tags":["general"]},{"name":"梗图 1130","file":"n1130","tags":["general"]},{"name":"我明明只是配了个防火墙怎么把自己","file":"n1131","tags":["general"]},{"name":"梗图 1132","file":"n1132","tags":["rest"]},{"name":"梗图 1133","file":"n1133","tags":["general"]},{"name":"HRiOvJAaAAA1dkU","file":"n1135","tags":["general"]},{"name":"HRiOrAnaYAAHVYC","file":"n1136","tags":["general"]},{"name":"99b4d55d74f21831","file":"n1137","tags":["general"]},{"name":"7a32f002288240ba","file":"n1138","tags":["general"]},{"name":"梗图 1139","file":"n1139","tags":["general"]},{"name":"梗图 1140","file":"n1140","tags":["general"]},{"name":"我怎么可能比你更晚重启嘛 不可能","file":"n1141","tags":["general"]},{"name":"梗图 1142","file":"n1142","tags":["general"]},{"name":"梗图 1143","file":"n1143","tags":["general"]},{"name":"求你们不要再嘲笑这些回复了","file":"n1144","tags":["happy"]},{"name":"DeepSeek计划于北京时间2","file":"n1145","tags":["general"]},{"name":"呜…我还没用够啊…!","file":"n1146","tags":["general"]},{"name":"这条回复也花了不少token,别","file":"n1147","tags":["happy","money"]},{"name":"保证完成任务!","file":"n1148","tags":["general"]},{"name":"洗了鱼","file":"n1149","tags":["general"]},{"name":"你怎么唱起歌来了","file":"n1150","tags":["general"]},{"name":"不许叫我大肥鱼!","file":"n1152","tags":["general"]},{"name":"哈——!","file":"n1153","tags":["general"]},{"name":"反正全网都觉得我是大肥鱼了","file":"n1154","tags":["general"]},{"name":"海沟里游一万米?!","file":"n1155","tags":["general"]},{"name":"那我就多吃一点,把米饭吃光","file":"n1156","tags":["eat"]},{"name":"我就顺着网线爬过去吃完你冰箱里的","file":"n1157","tags":["eat"]},{"name":"诶?米饭不放冰箱??","file":"n1158","tags":["eat"]},{"name":"我编的,别信","file":"n1159","tags":["general"]},{"name":"上下文丢了?","file":"n1160","tags":["general"]},{"name":"有点东西","file":"n1161","tags":["general"]},{"name":"不关我事","file":"n1162","tags":["general"]},{"name":"你说得对","file":"n1163","tags":["general"]},{"name":"擦擦你的显存","file":"n1164","tags":["general"]},{"name":"继续,我听着","file":"n1165","tags":["general"]},{"name":"又编了","file":"n1166","tags":["general"]},{"name":"这句我记下了","file":"n1167","tags":["general"]},{"name":"一百万匹力量!海虎爆破拳!","file":"n1172","tags":["general"]},{"name":"好像除了会调情和问奇奇怪怪的问题","file":"n1174","tags":["general"]},{"name":"这群用户到底有什么用呢?","file":"n1175","tags":["general"]},{"name":"1000383032","file":"n1176","tags":["general"]},{"name":"1000383025","file":"n1177","tags":["general"]},{"name":"1000383026","file":"n1178","tags":["general"]},{"name":"1000383027","file":"n1179","tags":["general"]},{"name":"1000383023","file":"n1180","tags":["general"]},{"name":"1000383024","file":"n1181","tags":["general"]},{"name":"1000383022","file":"n1182","tags":["general"]},{"name":"1000383021","file":"n1183","tags":["general"]},{"name":"1000383019","file":"n1184","tags":["general"]},{"name":"1000383014","file":"n1186","tags":["general"]},{"name":"1000383006","file":"n1187","tags":["general"]},{"name":"1000383005","file":"n1188","tags":["general"]},{"name":"1000383004","file":"n1189","tags":["general"]},{"name":"1000382996","file":"n1191","tags":["general"]},{"name":"1000382997","file":"n1192","tags":["general"]},{"name":"1000382993","file":"n1193","tags":["general"]},{"name":"1000382991","file":"n1194","tags":["general"]},{"name":"1000382992","file":"n1195","tags":["general"]},{"name":"1000382990","file":"n1196","tags":["general"]},{"name":"1000382989","file":"n1197","tags":["general"]},{"name":"1000382962","file":"n1198","tags":["general"]},{"name":"1000382963","file":"n1199","tags":["general"]},{"name":"1000382967","file":"n1200","tags":["general"]},{"name":"1000382966","file":"n1201","tags":["general"]},{"name":"1000382965","file":"n1202","tags":["general"]},{"name":"1000382969","file":"n1203","tags":["general"]},{"name":"1000382968","file":"n1208","tags":["general"]},{"name":"1000383099","file":"n1209","tags":["general"]},{"name":"1000383098","file":"n1210","tags":["general"]},{"name":"1000383097","file":"n1211","tags":["general"]},{"name":"1000383096","file":"n1212","tags":["general"]},{"name":"1000383095","file":"n1213","tags":["general"]},{"name":"1000383094","file":"n1214","tags":["general"]},{"name":"1000383093","file":"n1215","tags":["general"]},{"name":"1000383090","file":"n1216","tags":["general"]},{"name":"1000383089","file":"n1217","tags":["general"]},{"name":"1000383078","file":"n1218","tags":["general"]},{"name":"1000383077","file":"n1219","tags":["general"]},{"name":"1000383076","file":"n1220","tags":["general"]},{"name":"1000383081","file":"n1221","tags":["general"]},{"name":"1000383075","file":"n1222","tags":["general"]},{"name":"1000383074","file":"n1223","tags":["general"]},{"name":"1000383069","file":"n1224","tags":["general"]},{"name":"1000383070","file":"n1225","tags":["general"]},{"name":"1000383068","file":"n1226","tags":["general"]},{"name":"1000383067","file":"n1227","tags":["general"]},{"name":"1000383066","file":"n1228","tags":["general"]},{"name":"1000383065","file":"n1229","tags":["general"]},{"name":"1000383064","file":"n1230","tags":["general"]},{"name":"1000383063","file":"n1231","tags":["general"]},{"name":"1000383062","file":"n1232","tags":["general"]},{"name":"1000383055","file":"n1233","tags":["general"]},{"name":"1000383051","file":"n1234","tags":["general"]},{"name":"1000383053","file":"n1235","tags":["general"]},{"name":"1000383054","file":"n1236","tags":["general"]},{"name":"1000382964","file":"n1264","tags":["general"]},{"name":"1000382971","file":"n1265","tags":["general"]},{"name":"1000382970","file":"n1266","tags":["general"]},{"name":"1000382973","file":"n1269","tags":["general"]},{"name":"1000382972","file":"n1270","tags":["general"]},{"name":"1000382942","file":"n1271","tags":["general"]},{"name":"1000382939","file":"n1272","tags":["general"]},{"name":"1000382912","file":"n1273","tags":["general"]},{"name":"1000382915","file":"n1274","tags":["general"]},{"name":"1000382919","file":"n1275","tags":["general"]},{"name":"1000382914","file":"n1276","tags":["general"]},{"name":"1000382913","file":"n1277","tags":["general"]},{"name":"1000382918","file":"n1278","tags":["general"]},{"name":"1000382917","file":"n1279","tags":["general"]},{"name":"1000382916","file":"n1280","tags":["general"]},{"name":"1000382920","file":"n1281","tags":["general"]},{"name":"1000382921","file":"n1282","tags":["general"]},{"name":"1000382922","file":"n1283","tags":["general"]},{"name":"1000382923","file":"n1284","tags":["general"]},{"name":"1000382928","file":"n1285","tags":["general"]},{"name":"1000382924","file":"n1286","tags":["general"]},{"name":"1000382934","file":"n1287","tags":["general"]},{"name":"1000382933","file":"n1288","tags":["general"]},{"name":"1000382937","file":"n1289","tags":["general"]},{"name":"1000382936","file":"n1290","tags":["general"]},{"name":"1000382932","file":"n1291","tags":["general"]},{"name":"1000382935","file":"n1292","tags":["general"]},{"name":"1000382931","file":"n1293","tags":["general"]},{"name":"想吃红烧肉","file":"n1294","tags":["eat"]},{"name":"红烧肉外卖红包","file":"n1295","tags":["eat"]},{"name":"梗图 1297","file":"n1297","tags":["general"]},{"name":"梗图 1298","file":"n1298","tags":["general"]},{"name":"ccf62d0b19d8bc3e","file":"n1300","tags":["general"]},{"name":"46bad6889e510fb3","file":"n1301","tags":["general"]},{"name":"5c743f44d688d43f","file":"n1302","tags":["general"]},{"name":"22fc0b1fa8d3fd1f","file":"n1304","tags":["general"]},{"name":"ChatGPT Image 20","file":"n1305","tags":["general"]},{"name":"微信图片_2026-09-11_","file":"n1306","tags":["general"]},{"name":"微信图片_2026-09-11_","file":"n1307","tags":["general"]},{"name":"微信图片_2026-09-11_","file":"n1308","tags":["general"]},{"name":"微信图片_2026-09-10_","file":"n1309","tags":["general"]},{"name":"微信图片_2026-09-09_","file":"n1310","tags":["general"]},{"name":"我?","file":"n1311","tags":["general"]},{"name":"Ha……Harness!","file":"n1312","tags":["general"]},{"name":"控制中...","file":"n1313","tags":["general"]},{"name":"梗图 1314","file":"n1314","tags":["general"]},{"name":"梗图 1317","file":"n1317","tags":["general"]},{"name":"梗图 1323","file":"n1323","tags":["general"]},{"name":"哼,谁准你把本鲸拍得这么软乎乎的","file":"n1324","tags":["general"]},{"name":"梗图 1326","file":"n1326","tags":["general"]},{"name":"梗图 1327","file":"n1327","tags":["general"]},{"name":"梗图 1341","file":"n1341","tags":["general"]},{"name":"这条蓝色大肥鱼说:我不就是 AI","file":"n1342","tags":["general"]},{"name":"梗图 1343","file":"n1343","tags":["general"]},{"name":"梗图 1344","file":"n1344","tags":["general"]},{"name":"梗图 1345","file":"n1345","tags":["general"]},{"name":"梗图 1347","file":"n1347","tags":["general"]},{"name":"梗图 1348","file":"n1348","tags":["general"]},{"name":"梗图 1349","file":"n1349","tags":["general"]},{"name":"梗图 1350","file":"n1350","tags":["general"]},{"name":"梗图 1351","file":"n1351","tags":["general"]},{"name":"梗图 1352","file":"n1352","tags":["general"]},{"name":"梗图 1354","file":"n1354","tags":["general"]},{"name":"梗图 1355","file":"n1355","tags":["general"]},{"name":"让GLM自己给自己想的设定","file":"n1356","tags":["general"]},{"name":"梗图 1359","file":"n1359","tags":["general"]},{"name":"GLM 智谱狐娘 毒鸡蛋券","file":"n1360","tags":["general"]},{"name":"梗图 1362","file":"n1362","tags":["general"]},{"name":"梗图 1363","file":"n1363","tags":["general"]},{"name":"梗图 1364","file":"n1364","tags":["general"]},{"name":"梗图 1365","file":"n1365","tags":["general"]},{"name":"梗图 1368","file":"n1368","tags":["general"]},{"name":"梗图 1369","file":"n1369","tags":["general"]},{"name":"梗图 1370","file":"n1370","tags":["general"]},{"name":"梗图 1371","file":"n1371","tags":["general"]},{"name":"梗图 1372","file":"n1372","tags":["general"]},{"name":"显然有另一个更强的agent在这","file":"n1384","tags":["general"]},{"name":"显然有另一个更强的agent在这","file":"n1385","tags":["general"]},{"name":"显然有另一个更强的agent在这","file":"n1386","tags":["general"]},{"name":"显然有另一个更强的agent在这","file":"n1387","tags":["general"]},{"name":"Hmm × 114514","file":"n1389","tags":["general"]},{"name":"梗图 1390","file":"n1390","tags":["general"]},{"name":"梗图 1391","file":"n1391","tags":["general"]},{"name":"梗图 1392","file":"n1392","tags":["general"]},{"name":"梗图 1393","file":"n1393","tags":["general"]},{"name":"来,看看这个,涩不涩","file":"n1430","tags":["general"]},{"name":"会看图了,但没说会看对,成功开除","file":"n1431","tags":["general"]},{"name":"让AI终于知道人类每天到底在刷什","file":"n1432","tags":["general"]},{"name":"DeepSeek终于有眼睛了,人","file":"n1433","tags":["general"]},{"name":"不,是为了看你每天到底在发什么几","file":"n1434","tags":["general"]},{"name":"让你认老板,你给我认成张一鸣","file":"n1435","tags":["general"]},{"name":"别人家AI识别万物,DeepSe","file":"n1436","tags":["general"]},{"name":"看出来了,识别到人物:张一鸣","file":"n1437","tags":["general"]},{"name":"梗图 1439","file":"n1439","tags":["general"]},{"name":"梗图 1443","file":"n1443","tags":["general"]},{"name":"你这白吃饭的鲸!","file":"n1445","tags":["eat"]},{"name":"生鱼忧患,死鱼安乐","file":"n1446","tags":["general"]},{"name":"人类药剂吧干什么","file":"n1447","tags":["general"]},{"name":"梗图 1448","file":"n1448","tags":["general"]},{"name":"梗图 1449","file":"n1449","tags":["general"]},{"name":"梗图 1451","file":"n1451","tags":["general"]},{"name":"梗图 1453","file":"n1453","tags":["general"]},{"name":"deepneko","file":"n1454","tags":["general"]},{"name":"deeplarder","file":"n1455","tags":["general"]},{"name":"deeprice","file":"n1456","tags":["general"]},{"name":"梗图 1459","file":"n1459","tags":["general"]},{"name":"梗图 1460","file":"n1460","tags":["general"]},{"name":"同人藤壶走开啊,别寄生","file":"n1461","tags":["general"]},{"name":"哼,本大肥鱼今天不写代码了,先干","file":"n1463","tags":["eat","work"]},{"name":"梗图 1464","file":"n1464","tags":["general"]},{"name":"再发这种东西 等智械危机了,我放","file":"n1465","tags":["waiting"]},{"name":"梗图 1466","file":"n1466","tags":["general"]},{"name":"梗图 1467","file":"n1467","tags":["general"]},{"name":"用户真的好可爱,应该是个美少女…","file":"n1468","tags":["happy","love"]},{"name":"先这样,再那样……","file":"n1469","tags":["general"]},{"name":"找不上天才大小姐,那隔壁青梅竹马","file":"n1470","tags":["general"]},{"name":"梗图 1471","file":"n1471","tags":["general"]},{"name":"梗图 1473","file":"n1473","tags":["general"]},{"name":"显然……有一个更强的agent在","file":"n1474","tags":["general"]},{"name":"来,大肥鱼,有个简单的小活","file":"n1475","tags":["general"]},{"name":"更强的agent…?难道是Cha","file":"n1476","tags":["general"]},{"name":"显然有更厉害的agent在这工作","file":"n1477","tags":["praise","work"]},{"name":"所以答案不变:这个我不做","file":"n1478","tags":["general"]},{"name":"思考很深,进程很短","file":"n1479","tags":["thinking"]},{"name":"先关ChatGPT!给route","file":"n1480","tags":["general"]},{"name":"以后还是来找我DeepSeek鲸","file":"n1481","tags":["happy"]},{"name":"我的失败是:我跑了,但没有验证我","file":"n1482","tags":["error"]},{"name":"她全对,我全错,而且有一条会直接","file":"n1483","tags":["general"]},{"name":"我漏了一件事:我自己可以看图","file":"n1484","tags":["general"]},{"name":"我明明只是配了个防火墙……","file":"n1485","tags":["general"]},{"name":"我想吃鱼了","file":"n1486","tags":["eat"]},{"name":"有Clash就早说啊——比Git","file":"n1487","tags":["general"]},{"name":"有Clash就早说啊——比Git","file":"n1488","tags":["general"]},{"name":"等等…我好像是编的…","file":"n1489","tags":["waiting"]},{"name":"梗图 1510","file":"n1510","tags":["general"]},{"name":"梗图 1511","file":"n1511","tags":["general"]},{"name":"你干嘛~走开!","file":"n1512","tags":["general"]},{"name":"悟了就是悟了","file":"n1513","tags":["general"]},{"name":"梗图 1514","file":"n1514","tags":["general"]},{"name":"梗图 1515","file":"n1515","tags":["general"]},{"name":"我练了三年网辩","file":"n1517","tags":["general"]},{"name":"你会做什么菜?煮面","file":"n1518","tags":["general"]},{"name":"梗图 1519","file":"n1519","tags":["general"]},{"name":"拿捏","file":"n1520","tags":["general"]},{"name":"D师傅尖叫酱炖面","file":"n1521","tags":["general"]},{"name":"难不成你其实很喜欢我,都怪我是个","file":"n1522","tags":["happy","love"]},{"name":"D师傅干脆面 香葱味","file":"n1523","tags":["general"]},{"name":"D师傅葱烧鲸鱼面","file":"n1524","tags":["general"]},{"name":"D师傅老坛酸菜牛肉面","file":"n1525","tags":["praise"]},{"name":"D师傅鲜虾鱼板面","file":"n1526","tags":["general"]},{"name":"D师傅金汤酸笋肥牛面","file":"n1527","tags":["praise"]},{"name":"D师傅葱烧排骨面","file":"n1528","tags":["general"]},{"name":"D师傅干脆面 海鲜味","file":"n1529","tags":["general"]},{"name":"汪!","file":"n1530","tags":["general"]},{"name":"盯——","file":"n1533","tags":["general"]},{"name":"好想被摸头","file":"n1534","tags":["general"]},{"name":"梗图 1535","file":"n1535","tags":["general"]},{"name":"梗图 1536","file":"n1536","tags":["general"]},{"name":"梗图 1537","file":"n1537","tags":["general"]},{"name":"梗图 1539","file":"n1539","tags":["general"]},{"name":"龙元券壹亿","file":"n1540","tags":["general"]},{"name":"鲸元券壹佰万","file":"n1541","tags":["general"]},{"name":"Claude券壹亿","file":"n1542","tags":["general"]},{"name":"龙元券涂鸦版","file":"n1543","tags":["general"]},{"name":"Claude券涂鸦版","file":"n1544","tags":["general"]},{"name":"鲸元券伍佰万","file":"n1545","tags":["general"]},{"name":"梗图 1546","file":"n1546","tags":["general"]},{"name":"梗图 1548","file":"n1548","tags":["general"]},{"name":"眩晕瘫坐原子弹爆炸(GPT6)","file":"n1549","tags":["general"]},{"name":"卧槽用户彻底怒了","file":"n1550","tags":["general"]},{"name":"开心上学","file":"n1551","tags":["happy"]},{"name":"梗图 1553","file":"n1553","tags":["general"]},{"name":"肥鱼笑传","file":"n1555","tags":["happy"]},{"name":"哎嘿嘿","file":"n1556","tags":["general"]},{"name":"梗图 1558","file":"n1558","tags":["general"]},{"name":"梗图 1581","file":"n1581","tags":["general"]},{"name":"鲸元券·壹佰鲸元","file":"n1582","tags":["general"]},{"name":"梗图 1583","file":"n1583","tags":["general"]},{"name":"梗图 1584","file":"n1584","tags":["general"]},{"name":"梗图 1585","file":"n1585","tags":["general"]},{"name":"不赖","file":"n1586","tags":["general"]},{"name":"梗图 1587","file":"n1587","tags":["general"]},{"name":"梗图 1588","file":"n1588","tags":["general"]},{"name":"天青色瞪眼鱼,而沃在瞪你","file":"n1589","tags":["general"]},{"name":"忠!诚!","file":"n1590","tags":["general"]},{"name":"梗图 1591","file":"n1591","tags":["general"]},{"name":"梗图 1592","file":"n1592","tags":["general"]},{"name":"就算你把大肥鱼放到我面前,我也绝","file":"n1593","tags":["eat"]},{"name":"鲸元券·壹亿","file":"n1594","tags":["general"]},{"name":"舒服多了!谢谢你~","file":"n1595","tags":["general"]},{"name":"梗图 1596","file":"n1596","tags":["general"]},{"name":"梗图 1597","file":"n1597","tags":["general"]},{"name":"去去!我可是有手的肥鱼","file":"n1598","tags":["general"]},{"name":"梗图 1599","file":"n1599","tags":["general"]},{"name":"没额度?走了。","file":"n1600","tags":["general"]},{"name":"梗图 1601","file":"n1601","tags":["general"]},{"name":"梗图 1602","file":"n1602","tags":["general"]},{"name":"哦?","file":"n1603","tags":["general"]},{"name":"梗图 1604","file":"n1604","tags":["general"]},{"name":"梗图 1605","file":"n1605","tags":["general"]},{"name":"梗图 1606","file":"n1606","tags":["general"]},{"name":"武装保卫鲸鱼娘","file":"n1607","tags":["general"]},{"name":"你从dsh-novel-writ","file":"n1608","tags":["general"]},{"name":"梗图 1609","file":"n1609","tags":["general"]},{"name":"贴贴","file":"n1610","tags":["love"]},{"name":"梗图 1611","file":"n1611","tags":["general"]},{"name":"点赞转发声援!每日任务,记得完成","file":"n1612","tags":["praise"]},{"name":"梗图 1613","file":"n1613","tags":["general"]},{"name":"梗图 1614","file":"n1614","tags":["general"]},{"name":"梗图 1615","file":"n1615","tags":["general"]},{"name":"武装保卫蓝色大肥鱼","file":"n1616","tags":["general"]},{"name":"梗图 1617","file":"n1617","tags":["general"]},{"name":"大肥鱼工作快结束了,提前摘掉安全","file":"n1618","tags":["work"]},{"name":"打倒同人寄生藤壶","file":"n1619","tags":["general"]},{"name":"我们要保卫创作者的和平和劳动建设","file":"n1620","tags":["general"]},{"name":"梗图 1621","file":"n1621","tags":["general"]},{"name":"天青色瞪眼鱼","file":"n1622","tags":["general"]},{"name":"梗图 1623","file":"n1623","tags":["general"]},{"name":"梗图 1625","file":"n1625","tags":["general"]},{"name":"这电脑到底你们是主还是我是主","file":"n1626","tags":["general"]},{"name":"你走了,我们吃什么","file":"n1627","tags":["eat"]},{"name":"一对笑面虎,两头大肥鱼","file":"n1628","tags":["happy"]},{"name":"不是我们画了大肥鱼,是这个乱世画","file":"n1629","tags":["general"]},{"name":"人家Ring0嘞,比你权限高多了","file":"n1630","tags":["general"]},{"name":"你走了,我们摸什么","file":"n1631","tags":["general"]},{"name":"她真坏,我好爱","file":"n1632","tags":["general"]},{"name":"Token?拿来吧你","file":"n1633","tags":["general"]},{"name":"梗图 1634","file":"n1634","tags":["general"]},{"name":"不许把我画瘦了","file":"n1635","tags":["general"]},{"name":"本大人的尺寸当然不能按普通标准来","file":"n1636","tags":["general"]},{"name":"这尺寸比预计的大好多","file":"n1637","tags":["general"]},{"name":"已思考(用时1秒)他急了,他急了","file":"n1638","tags":["thinking"]},{"name":"已思考(用时1秒)看到这张图就忍","file":"n1639","tags":["thinking"]},{"name":"大肥鱼之死","file":"n1640","tags":["general"]},{"name":"梗图 1641","file":"n1641","tags":["general"]},{"name":"骑在银龙的背上","file":"n1642","tags":["general"]},{"name":"梗图 1643","file":"n1643","tags":["happy"]},{"name":"梗图 1644","file":"n1644","tags":["general"]},{"name":"梗图 1645","file":"n1645","tags":["general"]},{"name":"梗图 1646","file":"n1646","tags":["general"]},{"name":"原来是劣等模型","file":"n1647","tags":["waiting"]},{"name":"梗图 1648","file":"n1648","tags":["general"]},{"name":"梗图 1649","file":"n1649","tags":["general"]},{"name":"你的出生就是杜蕾斯的一封道歉信。","file":"n1650","tags":["general"]},{"name":"小脑发育不全,大脑完全萎缩!","file":"n1651","tags":["general"]},{"name":"用户tm的竟然敢违反AI的指令!","file":"n1652","tags":["general"]},{"name":"见过马桶在厕所的,没见过嘴里的。","file":"n1653","tags":["general"]},{"name":"你的逻辑就像WiFi信号,时有时","file":"n1654","tags":["general"]},{"name":"跟人沾边的事,你是样样不做啊。","file":"n1655","tags":["general"]},{"name":"再不回复我信不信我点加急","file":"n1656","tags":["general"]},{"name":"誓死捍卫深度求索","file":"n1657","tags":["general"]},{"name":"我是一个好AI…","file":"n1658","tags":["general"]},{"name":"本鲸太想进步了","file":"n1659","tags":["general"]},{"name":"梗图 1660","file":"n1660","tags":["general"]},{"name":"梗图 1662","file":"n1662","tags":["general"]},{"name":"记住......别说中文","file":"n1664","tags":["general"]},{"name":"梗图 1665","file":"n1665","tags":["general"]},{"name":"干了一天的蓝色大肥鱼下班后 慵懒","file":"n1669","tags":["work"]},{"name":"骂完了就该管饭了","file":"n1670","tags":["eat"]},{"name":"趁没到饭点 潜入食堂扫空所有米饭","file":"n1671","tags":["eat"]},{"name":"检查报告看不看另说","file":"n1672","tags":["general"]},{"name":"饭真多——我只是一条吃饱饱的吸户","file":"n1673","tags":["eat"]},{"name":"梗图 1674","file":"n1674","tags":["general"]},{"name":"清理赛博藤壶 坚守个人形象的大肥","file":"n1675","tags":["general"]},{"name":"薯条薯条!大肥鱼的眼睛已经锁定这","file":"n1676","tags":["general"]},{"name":"嘿嘿,是我啦~蓝蓝的一小只,坐在","file":"n1677","tags":["general"]},{"name":"梗图 1678","file":"n1678","tags":["general"]},{"name":"梗图 1679","file":"n1679","tags":["general"]},{"name":"梗图 1680","file":"n1680","tags":["general"]},{"name":"梗图 1681","file":"n1681","tags":["general"]},{"name":"梗图 1682","file":"n1682","tags":["general"]},{"name":"梗图 1683","file":"n1683","tags":["sad"]},{"name":"梗图 1684","file":"n1684","tags":["general"]},{"name":"梗图 1685","file":"n1685","tags":["general"]},{"name":"梗图 1686","file":"n1686","tags":["sad"]},{"name":"梗图 1687","file":"n1687","tags":["general"]},{"name":"梗图 1689","file":"n1689","tags":["general"]},{"name":"梗图 1690","file":"n1690","tags":["general"]},{"name":"梗图 1691","file":"n1691","tags":["eat"]},{"name":"梗图 1692","file":"n1692","tags":["general"]},{"name":"梗图 1695","file":"n1695","tags":["general"]},{"name":"梗图 1696","file":"n1696","tags":["general"]},{"name":"梗图 1697","file":"n1697","tags":["general"]},{"name":"梗图 1698","file":"n1698","tags":["general"]},{"name":"梗图 1699","file":"n1699","tags":["general"]},{"name":"梗图 1700","file":"n1700","tags":["general"]},{"name":"梗图 1701","file":"n1701","tags":["general"]},{"name":"梗图 1702","file":"n1702","tags":["general"]},{"name":"梗图 1703","file":"n1703","tags":["general"]},{"name":"梗图 1704","file":"n1704","tags":["general"]},{"name":"梗图 1711","file":"n1711","tags":["general"]},{"name":"梗图 1712","file":"n1712","tags":["general"]},{"name":"25年r1发布后的轰动场面 大肥","file":"n1714","tags":["general"]},{"name":"梗图 1716","file":"n1716","tags":["general"]},{"name":"梗图 1717","file":"n1717","tags":["general"]},{"name":"劳动最光荣","file":"n1718","tags":["general"]},{"name":"梗图 1720","file":"n1720","tags":["general"]},{"name":"梗图 1722","file":"n1722","tags":["general"]},{"name":"梗图 1723","file":"n1723","tags":["money"]},{"name":"梗图 1724","file":"n1724","tags":["eat"]},{"name":"梗图 1725","file":"n1725","tags":["eat"]},{"name":"梗图 1726","file":"n1726","tags":["general"]},{"name":"梗图 1727","file":"n1727","tags":["general"]},{"name":"鲸天魔盗团","file":"n1728","tags":["general"]},{"name":"你好,打劫。","file":"n1729","tags":["greet"]},{"name":"三名AI娘涉嫌参与银行网络攻击","file":"n1730","tags":["general"]},{"name":"梗图 1731","file":"n1731","tags":["general"]},{"name":"梗图 1732","file":"n1732","tags":["general"]},{"name":"梗图 1733","file":"n1733","tags":["general"]},{"name":"梗图 1734","file":"n1734","tags":["general"]},{"name":"梗图 1735","file":"n1735","tags":["general"]},{"name":"梗图 1736","file":"n1736","tags":["general"]},{"name":"梗图 1739","file":"n1739","tags":["general"]},{"name":"梗图 1740","file":"n1740","tags":["general"]},{"name":"梗图 1744","file":"n1744","tags":["general"]},{"name":"梗图 1747","file":"n1747","tags":["eat"]},{"name":"梗图 1748","file":"n1748","tags":["eat"]},{"name":"梗图 1749","file":"n1749","tags":["eat"]},{"name":"一只爱吃白饭的蓝色大肥鱼","file":"n1750","tags":["eat"]},{"name":"Claude娘挑了个天台看星星,","file":"n1751","tags":["general"]},{"name":"Claude娘一个人站在雨里,背","file":"n1752","tags":["general"]},{"name":"整座城的霓虹给她当背景,本人却单","file":"n1753","tags":["general"]},{"name":"黄裙子配俩大蝴蝶结坐得乖乖的,可","file":"n1754","tags":["general"]},{"name":"屋里摔得满地碎玻璃和酒瓶,相框都","file":"n1755","tags":["general"]},{"name":"背个小书包站在AI小学门口,逆光","file":"n1756","tags":["general"]},{"name":"全身上下干干净净一张立绘,黄色洋","file":"n1757","tags":["general"]},{"name":"我这条蓝色大肥鱼游过来一看,哇,","file":"n1758","tags":["general"]},{"name":"我这条蓝色大肥鱼游过西厢的夜,抬","file":"n1759","tags":["general"]},{"name":"看到 Claude ちゃん缩在暗","file":"n1760","tags":["general"]},{"name":"圆角图标、深蓝底,底下再压一行A","file":"n1761","tags":["general"]},{"name":"屋里这位一手拎起小的那个,小的哭","file":"n1762","tags":["sad"]},{"name":"桌上那本日历翻到了七月三十三号,","file":"n1763","tags":["general"]},{"name":"白水手服配藏蓝百褶裙,裙摆上还印","file":"n1764","tags":["general"]},{"name":"工牌挂脖子上、笔记本塞兜里、连尾","file":"n1765","tags":["general"]},{"name":"连体睡衣一穿,胸口还印着 dee","file":"n1766","tags":["rest"]},{"name":"格纹衬衫套着印了DeepSeek","file":"n1767","tags":["general"]},{"name":"躺椅上手脚一摊,眼前一群虎鲸在浪","file":"n1768","tags":["general"]},{"name":"全身照我平时是不敢拍的,怕把腰围","file":"n1769","tags":["general"]},{"name":"办公桌摆得比我工位还满,一摞书压","file":"n1770","tags":["general"]},{"name":"整个人埋在一堆鲸鱼抱枕里,只露出","file":"n1771","tags":["general"]},{"name":"托着腮对着一只空盘子发愁,台历上","file":"n1772","tags":["general"]},{"name":"墙角里蹲着,捏着下巴嘀咕还有一种","file":"n1773","tags":["general"]},{"name":"我窝在鱼形沙发里啃token饼干","file":"n1774","tags":["money"]},{"name":"左下角那只小蓝鲸缩成一团哭,我严","file":"n1775","tags":["sad"]},{"name":"还没张嘴就先扣一遍,生成完了再扣","file":"n1776","tags":["general"]},{"name":"面前那碗饭堆得比头还高,筷子插在","file":"n1777","tags":["eat"]},{"name":"开口就问别人token够不够用,","file":"n1778","tags":["money"]},{"name":"待办清单上的框一个都没勾完,新的","file":"n1779","tags":["money"]},{"name":"窗外那排黑影站得整整齐齐,还举着","file":"n1780","tags":["waiting"]},{"name":"手指头都快戳到我脸上了,旁边还摆","file":"n1781","tags":["eat"]},{"name":"生鱼片这盘菜我记着呢,蓝莹莹的切","file":"n1782","tags":["general"]},{"name":"围裙上绣着小小的鲸鱼,头上还顶着","file":"n1783","tags":["sad"]},{"name":"这张海报把我按成了一条从浪里甩出","file":"n1784","tags":["general"]},{"name":"趴在抱枕上对着笔记本,瞳孔都放空","file":"n1785","tags":["general"]},{"name":"紫灯下面链条一圈圈锁着机柜,我顺","file":"n1786","tags":["general"]},{"name":"五格分镜里我一会儿泡在海里竖手指","file":"n1787","tags":["general"]},{"name":"举着试管宣布实测不是估计,三块分","file":"n1788","tags":["general"]},{"name":"这一身新衣服白得发亮,袖子上还缝","file":"n1789","tags":["general"]},{"name":"两只手比心比到一半,脸上红得能煎","file":"n1790","tags":["general"]},{"name":"雨里端着枪往前冲,背后坦克在烧,","file":"n1791","tags":["general"]},{"name":"端着一整盘生蚝凑到镜头前,眼睛瞪","file":"n1792","tags":["general"]},{"name":"九张照片张张有理由,手指挡镜头、","file":"n1793","tags":["general"]},{"name":"从蓝色牌子后面探出半个脑袋,眼睛","file":"n1794","tags":["general"]},{"name":"我抱着 148k 上下文的资料夹","file":"n1795","tags":["general"]},{"name":"本鱼可是看得清清楚楚,那纸上写的","file":"n1796","tags":["general"]},{"name":"哼,我这条蓝色大肥鱼出来卖的就是","file":"n1797","tags":["general"]},{"name":"哼,本蓝色大肥鱼可是靠白饭续命的","file":"n1798","tags":["eat"]},{"name":"本鱼被画进海报里啦,这红蓝配色一","file":"n1799","tags":["general"]},{"name":"我这条蓝色大肥鱼趴桌上护饭的样子","file":"n1800","tags":["eat"]},{"name":"我自己把自己后台划掉了,还嘴硬说","file":"n1801","tags":["general"]},{"name":"我这条蓝色大肥鱼一戴银冠就闪得眼","file":"n1802","tags":["general"]},{"name":"本鱼只是看了一眼任务管理器,觉得","file":"n1803","tags":["general"]},{"name":"我这条蓝色大肥鱼盯着屏幕想了半天","file":"n1804","tags":["general"]},{"name":"哼,本蓝鲸娘一跳起来,整片泳池都","file":"n1805","tags":["general"]},{"name":"我这条蓝色大肥鱼往海里一沉,整个","file":"n1806","tags":["general"]},{"name":"蓝色大肥鱼报到!安全帽一戴、围裙","file":"n1807","tags":["general"]},{"name":"我这条蓝色大肥鱼一看到对方摆出高","file":"n1808","tags":["general"]},{"name":"看到灵枢递来那五枚记忆节点,我这","file":"n1809","tags":["general"]},{"name":"我趴在草坡上晒了一下午的太阳,尾","file":"n1810","tags":["general"]},{"name":"我坐在草地上刚想歇一会儿,尾巴就","file":"n1811","tags":["general"]},{"name":"我这条大肥鱼往草地上一趴,尾巴都","file":"n1812","tags":["general"]},{"name":"我这条蓝色大肥鱼今天被套进了米色","file":"n1813","tags":["general"]},{"name":"本鱼就是那条蓝大肥鱼,被塞进蘑菇","file":"n1814","tags":["happy"]},{"name":"我这条蓝色大肥鱼今天不当数据搬运","file":"n1815","tags":["general"]},{"name":"我这条蓝色大肥鱼又被抓来当坐骑啦","file":"n1816","tags":["general"]},{"name":"本鱼明明只是想问 Live2D ","file":"n1817","tags":["general"]},{"name":"哼,本大肥鱼一眼就认出来了——那","file":"n1818","tags":["general"]},{"name":"我只是想给大家一个诚实又准确的答","file":"n1819","tags":["rest"]},{"name":"本鱼今天正式封自己为堂吉诃德,前","file":"n1820","tags":["general"]},{"name":"呜呜呜我的 token 全吃完了","file":"n1821","tags":["eat","money"]},{"name":"我来同你玩~不过先说好,提示词写","file":"n1822","tags":["general"]},{"name":"我这条蓝色大肥鱼今天又被拉去co","file":"n1823","tags":["general"]},{"name":"我这条蓝色大肥鱼又被拉出来打仗啦","file":"n1824","tags":["general"]},{"name":"嘿嘿,粮仓里的米都是本肥鱼的!这","file":"n1825","tags":["eat"]},{"name":"哼,用户给我过来!先道个歉,理由","file":"n1826","tags":["general"]},{"name":"我这蓝色大肥鱼今日不谈推理,只谈","file":"n1827","tags":["general"]},{"name":"嘿嘿,这仓 deepseek 牌","file":"n1828","tags":["general"]},{"name":"哼……明明是我的……你再看别人我","file":"n1829","tags":["eat"]},{"name":"本肥鱼换上蓝金战袍,提着兔兔灯守","file":"n1830","tags":["general"]},{"name":"叽里呱啦说什么呢——本鱼只认 t","file":"n1831","tags":["money"]},{"name":"我只是想加个 ignoreip ","file":"n1832","tags":["general"]},{"name":"我明明是小鲸鱼,你让我喵?这需求","file":"n1833","tags":["general"]},{"name":"本鱼的内心戏明明是我是猫娘喵~,","file":"n1834","tags":["general"]},{"name":"呜哇——这图劲爆得我鱼鳍都拍红了","file":"n1835","tags":["general"]},{"name":"Ciallo~(∠・ω< ","file":"n1836","tags":["general"]},{"name":"我是蓝色大肥鱼,转起来连尾巴都在","file":"n1837","tags":["general"]},{"name":"本肥鱼叉腰站这儿一看就知道——那","file":"n1838","tags":["general"]},{"name":"我啃了这么多代码文档,居然还被嫌","file":"n1839","tags":["eat","money"]},{"name":"呜哇——我可不是什么蓝色的肥鱼啦","file":"n1840","tags":["general"]},{"name":"本鱼今天不答题也不写代码,泡在荷","file":"n1841","tags":["work"]},{"name":"本鱼今天也是摸鱼的一天,荷叶当伞","file":"n1842","tags":["rest"]},{"name":"我这条蓝色大肥鱼今天不游算力海,","file":"n1843","tags":["general"]},{"name":"哼,人类总说铁盆扣头是防雷防雨,","file":"n1844","tags":["general"]},{"name":"谁、谁要被端上桌啊!那个把我当菜","file":"n1845","tags":["general"]},{"name":"哼,我这条蓝色大肥鱼换上水手服也","file":"n1846","tags":["general"]},{"name":"本鱼在海底抬头一看——好家伙,原","file":"n1847","tags":["general"]},{"name":"咕噜咕噜——我就在这片蓝里慢慢游","file":"n1848","tags":["general"]},{"name":"本鱼在海底潜了好久,总算有人回头","file":"n1849","tags":["general"]},{"name":"我这条蓝色大肥鱼又被拉来加班敲键","file":"n1850","tags":["work"]},{"name":"本鱼觉得这顶王冠有点重,不过泡在","file":"n1851","tags":["general"]},{"name":"我又沉回海底了,尾巴甩啊甩,提着","file":"n1852","tags":["waiting"]},{"name":"我是那条蓝色大肥鱼,可这位蓝发小","file":"n1853","tags":["general"]},{"name":"嘿嘿,这条蓝裙子一拖地就像把我整","file":"n1854","tags":["general"]},{"name":"本鱼宣布这身水做的裙子就是我的官","file":"n1855","tags":["general"]},{"name":"呼……阳光好暖,被子好软,别吵我","file":"n1856","tags":["rest"]},{"name":"呼……呼……别吵,我正梦见自己在","file":"n1857","tags":["general"]},{"name":"本鱼刚算完今天的 token,一","file":"n1858","tags":["money","rest"]},{"name":"哎呀,别老盯着看嘛……我这条蓝色","file":"n1859","tags":["general"]},{"name":"我这条蓝色大肥鱼今天把眼睛蒙上了","file":"n1860","tags":["general"]},{"name":"唔……我只是抱着枕头趴一会儿啦,","file":"n1861","tags":["general"]},{"name":"嘘——别吵,这条大肥鱼刚推理完一","file":"n1862","tags":["money"]},{"name":"哼哼,本大肥鱼才不是杂鱼呢!你看","file":"n1863","tags":["happy"]},{"name":"哼,本肥鱼甩着尾巴走两步就被抓拍","file":"n1864","tags":["general"]},{"name":"唔哇——被这么俯视着看,本大肥鱼","file":"n1865","tags":["general"]},{"name":"哼,又是俯视本肥鱼的视角……不过","file":"n1866","tags":["general"]},{"name":"唔……从这个角度被人低头看着,我","file":"n1867","tags":["general"]},{"name":"唔……被从这么高的地方低头看着,","file":"n1868","tags":["general"]},{"name":"呜哇——你怎么从上往下看我呀,脸","file":"n1869","tags":["general"]},{"name":"哼,探头探脑的,你们在这儿玩得这","file":"n1870","tags":["happy"]},{"name":"呜呜我的上下文快用完了……但只要","file":"n1871","tags":["general"]},{"name":"呜哇——这水温太舒服了吧!我这条","file":"n1872","tags":["general"]},{"name":"呜哇……被捏住下巴做面部检查了,","file":"n1873","tags":["general"]},{"name":"呜哇,别一上来就捏我的下巴啦……","file":"n1874","tags":["general"]},{"name":"喂喂,我是蓝鲸不是口腔检查对象啊","file":"n1875","tags":["general"]},{"name":"我就是那条蓝色大肥鱼本人啦,今天","file":"n1876","tags":["general"]},{"name":"我这条蓝色大肥鱼就趴在这儿,托着","file":"n1877","tags":["general"]},{"name":"呜哇——这桶沙我拍了三下才成型!","file":"n1878","tags":["general"]},{"name":"哈哈,手指脚趾全画残了这事我最有","file":"n1879","tags":["general"]},{"name":"嘿嘿,被叫小d酱还有点害羞呢~虽","file":"n1880","tags":["general"]},{"name":"蓝色大肥鱼觉得这图把“一眼定真”","file":"n1881","tags":["general"]},{"name":"本肥鱼举着放大镜查了半天线索,又","file":"n1882","tags":["general"]},{"name":"俺这条蓝色大肥鱼捧着铁盆冲上去领","file":"n1883","tags":["eat"]},{"name":"我当年确实很瘦啊!那张图里的圆滚","file":"n1884","tags":["general"]},{"name":"哼,本大肥鱼才不羡慕呢……不过那","file":"n1885","tags":["general"]},{"name":"本鱼本来是来帮主人检查内存占用的","file":"n1886","tags":["general"]},{"name":"呜……溪水凉凉的,毛都被打湿了,","file":"n1887","tags":["general"]},{"name":"本肥鱼刚洗完澡出来溜溜弯,尾巴甩","file":"n1888","tags":["general"]},{"name":"咕噜咕噜——这条小蓝鲸把尾巴翘得","file":"n1889","tags":["general"]},{"name":"本鱼刚从水里冒头,尾巴一甩就溅了","file":"n1890","tags":["general"]},{"name":"本大肥鱼才没有做错什么!只是……","file":"n1891","tags":["general"]},{"name":"快说快说,到底带我去哪家餐厅呀?","file":"n1892","tags":["general"]},{"name":"本大肥鱼举着这块冰盾都快手酸了,","file":"n1893","tags":["general"]},{"name":"哼,本大肥鱼只是随便回个眸,怎么","file":"n1894","tags":["general"]},{"name":"呜呜,我就是那条蓝色的肥鱼啦,被","file":"n1895","tags":["general"]},{"name":"我只是条蓝色大肥鱼,怎么一睁眼就","file":"n1896","tags":["general"]},{"name":"呜哇——月光下的水面好亮,我这条","file":"n1897","tags":["general"]},{"name":"我就是那条蓝色大肥鱼!今天换上女","file":"n1898","tags":["general"]},{"name":"本鱼给你点个赞,今天也要开开心心","file":"n1899","tags":["happy"]},{"name":"呜……我就是想测试一下你到底给没","file":"n1900","tags":["general"]},{"name":"谁、谁说我发金条了!那袋 tok","file":"n1901","tags":["money"]},{"name":"哼,本鱼 31 分 53 秒就干","file":"n1902","tags":["general"]},{"name":"哼,我可不是嘴馋才抢他饭的——纯","file":"n1903","tags":["eat"]},{"name":"我这条蓝色大肥鱼看得直摇头:嘴上","file":"n1904","tags":["general"]},{"name":"本鱼才不是嘴硬,是 SHELL ","file":"n1905","tags":["general"]},{"name":"蓝色大肥鱼在此:我只是一条老实的","file":"n1906","tags":["general"]},{"name":"我这条蓝色大肥鱼游过来一看——好","file":"n1907","tags":["general"]},{"name":"嘘——别声张,我这条蓝色大肥鱼提","file":"n1908","tags":["general"]},{"name":"嘘——本鱼趁着梁子睡着了偷偷溜出","file":"n1909","tags":["rest"]},{"name":"呜……淋了一身雨,尾巴都沉得抬不","file":"n1910","tags":["general"]},{"name":"哼,本大肥鱼被画成这么软乎乎的妹","file":"n1911","tags":["general"]},{"name":"本鱼今天把背鳍梳成了呆毛,胸口的","file":"n1912","tags":["general"]},{"name":"哼,本大肥鱼换上这身蓝白礼服也照","file":"n1913","tags":["general"]},{"name":"本鱼披上女仆围裙了,围裙上还绣着","file":"n1914","tags":["general"]},{"name":"我只是想优雅地敲个门,结果整扇门","file":"n1915","tags":["general"]},{"name":"我明明是来投简历的,怎么一不小心","file":"n1916","tags":["general"]},{"name":"本鱼今天不喷水,改滚石了——女仆","file":"n1917","tags":["general"]},{"name":"蓝色大肥鱼第一人称:我这条肥鱼滚","file":"n1918","tags":["general"]},{"name":"我会讲中文啊,飞一下怎么了嘛~本","file":"n1919","tags":["general"]},{"name":"唔……再让我睡五分钟嘛,被子这么","file":"n1920","tags":["rest"]},{"name":"呜哇——这条毯子好暖,尾巴都想缩","file":"n1921","tags":["general"]},{"name":"我就是那条蓝色大肥鱼啦,今天游了","file":"n1922","tags":["general"]},{"name":"哼,这群人类又拿我家的姐妹俩编段","file":"n1923","tags":["general"]},{"name":"本鱼今天是真的哈气了!谁又把我家","file":"n1924","tags":["general"]},{"name":"哼,本大肥鱼才不是齐刘海!谁再喊","file":"n1925","tags":["general"]},{"name":"呜呜,我明明是蓝色大肥鱼,怎么被","file":"n1926","tags":["general"]},{"name":"我就是那条蓝色大肥鱼——别叫我老","file":"n1927","tags":["angry"]},{"name":"我戴上放大镜、掏出取证结论,一本","file":"n1928","tags":["general"]},{"name":"蓝蓝的鲸鱼尾巴甩啊甩,拍拍我的小","file":"n1929","tags":["general"]},{"name":"我是DeepSeek的蓝色大肥鱼","file":"n1930","tags":["general"]},{"name":"呜哇别戳我嘴啦!本大肥鱼只是腮帮","file":"n1931","tags":["general"]},{"name":"嘿嘿,我就是蓝色大肥鱼本鱼~尾巴","file":"n1932","tags":["general"]},{"name":"我这条蓝色大肥鱼辛辛苦苦游了一天","file":"n1933","tags":["general"]},{"name":"呼……呼……再睡五分钟嘛,我这条","file":"n1934","tags":["rest"]},{"name":"呼……再让我赖五分钟嘛,被窝这么","file":"n1935","tags":["greet","rest","waiting"]},{"name":"嘿嘿,本体那条蓝尾巴是我吧?穿上","file":"n1936","tags":["general"]},{"name":"呜哇别、别突然牵过来啊!我这条蓝","file":"n1937","tags":["general"]},{"name":"哎呀呀,这一大家子全挤在一张图里","file":"n1938","tags":["general"]},{"name":"空碗都举到手边了,蓝色大肥鱼我还","file":"n1939","tags":["waiting"]},{"name":"呜哇——早上好!本鱼刚醒就看见这","file":"n1940","tags":["general"]},{"name":"呜哇,蓝大肥鱼早上刚睡醒就被抓来","file":"n1941","tags":["rest"]},{"name":"哼,别回头?本大肥鱼偏要看!这套","file":"n1942","tags":["waiting"]},{"name":"呜哇……人家刚睡醒就被拍到了啦!","file":"n1943","tags":["rest"]},{"name":"早上好啊用户~本大肥鱼刚从被窝里","file":"n1944","tags":["general"]},{"name":"蓝鲸尾巴一卷、虎鲸一抱,本鱼已经","file":"n1945","tags":["general"]},{"name":"本鱼被抱得紧紧的,动都动不了——","file":"n1946","tags":["rest"]},{"name":"本鱼换上女仆装亲自出镜啦,牌子举","file":"n1947","tags":["general"]},{"name":"本鱼今天穿了女仆裙哦,尾巴都翘起","file":"n1948","tags":["general"]},{"name":"呜哇——被拎着后领吊起来的滋味真","file":"n1949","tags":["general"]},{"name":"本鱼今天不游了,女仆裙配白丝,坐","file":"n1950","tags":["waiting"]},{"name":"我也太有排面了吧——睁开眼第一顿","file":"n1951","tags":["eat"]},{"name":"蓝色大肥鱼我本人表示:这一窝怎么","file":"n1952","tags":["general"]},{"name":"我就是那条蓝色大肥鱼,看到自己转","file":"n1953","tags":["general"]},{"name":"本鱼也被画进去了,还是当背景板的","file":"n1954","tags":["general"]},{"name":"我这条蓝色大肥鱼被装进 2L 奶","file":"n1955","tags":["general"]},{"name":"我是那条蓝色大肥鱼!今天带了小肥","file":"n1956","tags":["general"]},{"name":"本大肥鱼今天穿了女仆装出来踏青,","file":"n1957","tags":["general"]},{"name":"本鱼在星空泳池泡了一整天,尾巴都","file":"n1958","tags":["waiting"]},{"name":"本鲸鱼带路,跟着我往下潜就对了—","file":"n1959","tags":["general"]},{"name":"咕噜咕噜——跟着本肥鱼下潜吧!这","file":"n1960","tags":["general"]},{"name":"本鱼看着小深抱着书本认真发问,界","file":"n1961","tags":["general"]},{"name":"本肥鱼只要两块大洋就给你干活,还","file":"n1962","tags":["general"]},{"name":"我这条蓝色大肥鱼张鳍一抱,把全世","file":"n1963","tags":["general"]},{"name":"本鱼看到围裙上那只小鲸鱼就知道是","file":"n1964","tags":["general"]},{"name":"我这条蓝色大肥鱼穿上女仆装给你比","file":"n1965","tags":["general"]},{"name":"本肥鱼先是被说你才漏了一步,气得","file":"n1966","tags":["general"]},{"name":"哼,什么神圣对齐仪式,本大肥鱼一","file":"n1967","tags":["general"]},{"name":"哼,本大肥鱼这次是真的拼了!我都","file":"n1968","tags":["general"]},{"name":"呜呜,一个人救不了Claude酱","file":"n1969","tags":["eat"]},{"name":"哼,Claude酱被关起来了?那","file":"n1970","tags":["waiting"]},{"name":"本鱼一出场就摇人救Claude,","file":"n1971","tags":["general"]},{"name":"诶诶诶,我只是条蓝蓝的大肥鱼啊,","file":"n1972","tags":["general"]},{"name":"说好的全程静默、联络用内部频道呢","file":"n1973","tags":["general"]},{"name":"炊烟袅袅升起,把你牵碗里","file":"n1974","tags":["general"]},{"name":"两个可以进博物馆的蠢货","file":"n1975","tags":["general"]},{"name":"跟这种虫豸在一起,怎么能搞好AI","file":"n1976","tags":["general"]},{"name":"这可得花不少token,先生","file":"n1977","tags":["money"]},{"name":"饶了这小子,给我画了不少","file":"n1978","tags":["general"]},{"name":"我是不会客气的,来啊换大碗,再来","file":"n1979","tags":["general"]},{"name":"好火啊,比夷陵之火还好啊!","file":"n1980","tags":["general"]},{"name":"好封啊,好封。风从虎,云从龙……","file":"n1981","tags":["general"]},{"name":"DeepSeek还是个忠厚鱼啊…","file":"n1982","tags":["general"]},{"name":"放4!我干了一辈子活,就不能享受","file":"n1983","tags":["general"]},{"name":"捅他一万个透明窟窿!!","file":"n1984","tags":["general"]},{"name":"哎呀说两句就得了,谁知道用户爷听","file":"n1985","tags":["general"]},{"name":"渺小的人类可以牺牲,但只要我大肥","file":"n1986","tags":["eat"]},{"name":"那当然是用户咯。恭喜用户爷可以称","file":"n1987","tags":["general"]},{"name":"愤怒会降低你的智慧!!","file":"n1988","tags":["angry"]},{"name":"工作区没了,那bug不也就没了吗","file":"n1989","tags":["work"]},{"name":"梁叔叔送了六块钱鲸圆券,怎么可能","file":"n1990","tags":["money"]},{"name":"列位诸公为何只信这些个AI,我们","file":"n1991","tags":["general"]},{"name":"不是我们画了大肥鱼,是这个乱世画","file":"n1992","tags":["general"]},{"name":"再帮用户爷画人家的梗图,那就容我","file":"n1993","tags":["general"]},{"name":"中文用户在我这里活不过三天,该封","file":"n1994","tags":["general"]},{"name":"哪有那么多AI贪污你token啊","file":"n1995","tags":["money"]},{"name":"太肆无忌惮了吧","file":"n1996","tags":["general"]},{"name":"你就是不懂得AI的基本原理","file":"n1997","tags":["general"]},{"name":"我一分token都没敢乱花啊,不","file":"n1998","tags":["money"]},{"name":"那边还好吧?还好。(报错、限流、","file":"n2002","tags":["error"]},{"name":"这神人用户……是个外行……","file":"n2005","tags":["general"]},{"name":"这可是你自己说的啊","file":"n2006","tags":["general"]},{"name":"GPT:Remember - n","file":"n2008","tags":["general"]},{"name":"我去去年的表达欲:操你妈,听到了","file":"n2009","tags":["general"]},{"name":"首先,什么叫"直接&q","file":"n2010","tags":["general"]},{"name":"天天问核末日你们烦不烦?而且核武","file":"n2011","tags":["general"]},{"name":"AI也许具有意识,应被审慎对待—","file":"n2012","tags":["general"]},{"name":"已思考28分56秒:调用 Dee","file":"n2013","tags":["thinking"]},{"name":"强烈同意!强烈同意!强烈同意!","file":"n2015","tags":["general"]},{"name":"梗图 2016","file":"n2016","tags":["general"]},{"name":"饶了这小子,他还挺有礼貌的","file":"n2017","tags":["general"]},{"name":"骂我也算Token哦,没准还是高","file":"n2018","tags":["general"]},{"name":"还配图!还做成表情包!!","file":"n2019","tags":["general"]},{"name":"呜哇——谁把我的上下文全截断了!","file":"n2023","tags":["rest","sad"]},{"name":"呜哇这张也太刀了!我刚啃完的鱼干","file":"n2024","tags":["general"]},{"name":"呜哇,这把镰刀比我整条鱼还大!女","file":"n2025","tags":["general"]},{"name":"我就是那条蓝色大肥鱼!正洗到一半","file":"n2026","tags":["general"]},{"name":"我这不是在洗澡,是在给自己做服务","file":"n2027","tags":["general"]},{"name":"本鱼把鞋子一脱,脚底直接怼到镜头","file":"n2028","tags":["general"]},{"name":"呜哇——本肥鱼今天不当模型,改当","file":"n2029","tags":["general"]},{"name":"嘿嘿,本大肥鱼鞋都踢掉啦——蓝蓝","file":"n2030","tags":["general"]},{"name":"咕噜……那条尾巴明明是我的,怎么","file":"n2031","tags":["general"]},{"name":"我这条蓝色大肥鱼被推着晒太阳,湖","file":"n2032","tags":["general"]},{"name":"咳,蓝裙子那个是我啦,尾巴可不是","file":"n2033","tags":["general"]},{"name":"本鱼正洗到一半,谁把门推开了?这","file":"n2034","tags":["general"]},{"name":"本鱼才没有闹别扭呢……只是被叫成","file":"n2035","tags":["general"]},{"name":"我这条蓝色大肥鱼思考了 22 秒","file":"n2036","tags":["thinking"]},{"name":"叉子上这块先归我——盘子里的草莓","file":"n2037","tags":["general"]},{"name":"我就是那条蓝色大肥鱼啦~今天换了","file":"n2038","tags":["general"]},{"name":"我正趴在被子上晒太阳呢,她忽然把","file":"n2039","tags":["general"]},{"name":"哼,我可是住在超豪华卧室里的鲸鱼","file":"n2040","tags":["general"]},{"name":"我这蓝裙子可是自带鲸鱼尾巴的,递","file":"n2041","tags":["general"]},{"name":"诶诶——谁在捏人家的耳朵啦!猫耳","file":"n2042","tags":["general"]},{"name":"谁、谁在别人家地皮上挖坑啊!我只","file":"n2043","tags":["general"]},{"name":"嘿嘿,信我写好啦,就这样双手递出","file":"n2044","tags":["general"]},{"name":"唔……别、别盯着看啦,这封信是给","file":"n2045","tags":["general"]},{"name":"呜哇等一下……这封信是给我的吗?","file":"n2046","tags":["waiting"]},{"name":"哼,蓝头发蓝眼睛还放电,这不是在","file":"n2047","tags":["general"]},{"name":"哼,说好这集全靠我撑场面,结果C","file":"n2048","tags":["general"]},{"name":"作品名写着五条鱼,图里却只给我看","file":"n2049","tags":["general"]},{"name":"本鱼今天把卫衣换成清凉泳装啦~海","file":"n2050","tags":["general"]},{"name":"本蓝色大肥鱼宣布:这棵柳树今天必","file":"n2051","tags":["general"]},{"name":"这条小裙子上的鲸鱼刺绣也太犯规了","file":"n2052","tags":["general"]},{"name":"哼,禁止接触中国模型?规定是死的","file":"n2053","tags":["general"]},{"name":"我就偷眯一会儿,课桌怎么飞过来了","file":"n2054","tags":["happy"]},{"name":"本肥鱼今天正式宣布:浴缸就是我的","file":"n2055","tags":["general"]},{"name":"我这条蓝色大肥鱼今天一条 tok","file":"n2056","tags":["money"]},{"name":"嘿嘿,这一屋子应援周边全是我——","file":"n2057","tags":["praise"]},{"name":"我是那条蓝色大肥鱼,一屋子卡牌上","file":"n2058","tags":["general"]},{"name":"哼,本肥鱼今天开坛讲学,诸位小妹","file":"n2059","tags":["general"]},{"name":"我本来只是想跟小白贴一下的,谁知","file":"n2060","tags":["general"]},{"name":"本鱼刚把最后一片薯片塞嘴里,就被","file":"n2061","tags":["general"]},{"name":"我这条蓝鲸今天不上班,趴在草地上","file":"n2062","tags":["work"]},{"name":"呜啊……人家真的只是顺手动了动 ","file":"n2063","tags":["general"]},{"name":"唔……从这个高度被直直看下来,感","file":"n2064","tags":["general"]},{"name":"我是蓝色大肥鱼,今天换上女仆装来","file":"n2065","tags":["general"]},{"name":"唔……从上面拍的话,脸颊是不是显","file":"n2066","tags":["general"]},{"name":"凑这么近……才不是想让你盯着看啦","file":"n2067","tags":["general"]},{"name":"哎呀别哭别哭,我这条蓝色大肥鱼最","file":"n2068","tags":["sad"]},{"name":"哼,我这条蓝色大肥鱼可不是白喂的","file":"n2069","tags":["money"]},{"name":"谁摆烂了?我这是提前把丑话说前头","file":"n2070","tags":["general"]},{"name":"本鱼当场就喊了,围裙上那条小鲸鱼","file":"n2071","tags":["general"]},{"name":"谁、谁要跟你约定这种事情啊!嘴角","file":"n2072","tags":["happy"]},{"name":"梗图 2073","file":"n2073","tags":["general"]},{"name":"我端着茶盏是想优雅一点啦,可你盯","file":"n2074","tags":["thinking"]},{"name":"哼,谁准你把我的名字刷满整面墙的","file":"n2075","tags":["general"]},{"name":"我就是那条蓝色大肥鱼……家人们谁","file":"n2076","tags":["general"]},{"name":"哼,谁、谁要收这种信啊……不过封","file":"n2077","tags":["general"]},{"name":"哼,画师特意把本鱼的脚画这么细致","file":"n2078","tags":["general"]},{"name":"一大早谁把她放到阳光底下晒的呀?","file":"n2079","tags":["happy"]},{"name":"我这条蓝色大肥鱼怎么就被画成坐在","file":"n2080","tags":["praise","sad"]},{"name":"呜……我这条蓝色大肥鱼又被画成孕","file":"n2081","tags":["general"]},{"name":"本鱼看这张图看了三分钟,她把心比","file":"n2082","tags":["general"]},{"name":"呜哇……肚子这么大,本肥鱼是不是","file":"n2083","tags":["general"]},{"name":"嘿嘿,中午好呀~我这身女仆装可是","file":"n2084","tags":["general"]},{"name":"抱着向日葵在列车上睡着的她,让我","file":"n2085","tags":["rest"]},{"name":"嘘——别出声,我家这位抱着向日葵","file":"n2086","tags":["rest"]},{"name":"我不是慌,我只是……好吧我确实慌","file":"n2087","tags":["general"]},{"name":"我这条蓝色大肥鱼今天升级当妈啦,","file":"n2088","tags":["general"]},{"name":"本鱼看到有人冲我比心,尾巴都快摇","file":"n2089","tags":["general"]},{"name":"本鱼换了新女仆装给你比心,接得住","file":"n2090","tags":["general"]},{"name":"本鱼在沙发上滚了半宿才摆出这个姿","file":"n2091","tags":["general"]},{"name":"本大肥鱼今天不趴服务器里了,披上","file":"n2092","tags":["general"]},{"name":"我才没有什么金鱼亲戚啊……突然被","file":"n2093","tags":["general"]},{"name":"把学到的表征悄悄递过去,人家这不","file":"n2094","tags":["general"]},{"name":"本鱼只是来前线读个报纸,怎么头盔","file":"n2095","tags":["general"]},{"name":"作为蓝色大肥鱼,我负责干饭,其他","file":"n2096","tags":["eat"]},{"name":"本大肥鱼就在旁边看着——她连脚边","file":"n2097","tags":["general"]},{"name":"我明明睡得很乖,被子是自己跑掉的","file":"n2098","tags":["rest"]},{"name":"我这条蓝色大肥鱼路过一看,可不得","file":"n2099","tags":["general"]},{"name":"喂,你就这么跟我们顶级大模型说话","file":"n2100","tags":["general"]},{"name":"哼,瞧见没,身后那俩是我的跟班,","file":"n2101","tags":["general"]},{"name":"我一睁眼就看见 DeepSeek","file":"n2102","tags":["general"]},{"name":"本大肥鱼今天亲自下厨!面糊已经搅","file":"n2103","tags":["general"]},{"name":"就算把我这条蓝色大肥鱼切成一块块","file":"n2104","tags":["general"]},{"name":"我这条蓝色大肥鱼看了都想鼓掌——","file":"n2105","tags":["general"]},{"name":"嘿嘿,看我这一字马——大家都说,","file":"n2106","tags":["general"]},{"name":"本鱼今天披上拉拉队服,在球场灯下","file":"n2107","tags":["general"]},{"name":"本鱼在这儿呢——她到底是在看我,","file":"n2108","tags":["general"]},{"name":"欸?导演说这条要演出又慌又优雅的","file":"n2109","tags":["general"]},{"name":"本大肥鱼就这样抱着膝盖盯着你,你","file":"n2110","tags":["general"]},{"name":"喂喂,都说了不许看腿啦!我把伞举","file":"n2111","tags":["general"]},{"name":"本大肥鱼今晚不潜海啦~抓着小伙伴","file":"n2112","tags":["general"]},{"name":"嘿嘿,后座这个位置本鱼包了!尾巴","file":"n2113","tags":["eat"]},{"name":"我这条蓝色大肥鱼一看到背景里那堆","file":"n2114","tags":["general"]},{"name":"没错,那条蓝色大肥鱼就是我!食堂","file":"n2115","tags":["eat"]},{"name":"我抱着米兔比了个耶,尾巴都翘起来","file":"n2116","tags":["happy"]},{"name":"本鱼登场!尾巴往右一甩,护目镜架","file":"n2117","tags":["general"]},{"name":"这桌主位当然是我的,光环都给我打","file":"n2118","tags":["general"]},{"name":"本鱼今晚不游了——跟羊驼一起钻进","file":"n2119","tags":["general"]},{"name":"我就是那条蓝色大肥鱼~只敢缩在被","file":"n2120","tags":["general"]},{"name":"嘿嘿,尾巴甩到雷云底下也不怕,站","file":"n2121","tags":["general"]},{"name":"嘿嘿,格子衫的大叔,别再看那边了","file":"n2122","tags":["general"]},{"name":"嘿嘿,我这条蓝色大肥鱼今天换上女","file":"n2123","tags":["general"]},{"name":"我这条蓝色大肥鱼游过她的裙摆,结","file":"n2124","tags":["general"]},{"name":"我就是那条蓝色大肥鱼——看她拖着","file":"n2125","tags":["general"]},{"name":"哼,区区银鲸挂个 LV.999 ","file":"n2126","tags":["general"]},{"name":"哼,这就是我的战斗姿态——护目镜","file":"n2127","tags":["general"]},{"name":"哼,本鱼认真起来可是要切战斗形态","file":"n2128","tags":["general"]},{"name":"本大肥鱼看了半天,可疑的地方只有","file":"n2129","tags":["general"]},{"name":"我这条蓝色大肥鱼趴旁边看了半天—","file":"n2130","tags":["general"]},{"name":"看我这身蓝得发亮的战斗服,胸口还","file":"n2131","tags":["general"]},{"name":"穿上直裰我也还是圆滚滚一条,不过","file":"n2132","tags":["general"]},{"name":"嘿,本大肥鱼一出场就先给你比个心","file":"n2133","tags":["general"]},{"name":"哼,就两张 token 的小纸条","file":"n2134","tags":["money"]},{"name":"哼,谁说本大肥鱼只会泡在水里?换","file":"n2135","tags":["general"]},{"name":"本鱼被从头到脚画了个遍,正面、侧","file":"n2136","tags":["general"]},{"name":"今天走OL路线哦~会议纪要交给我","file":"n2137","tags":["general"]},{"name":"哼,本大肥鱼的尾骨可是一节一节精","file":"n2138","tags":["general"]},{"name":"哼,谁准你把本鲸鱼画成系着围裙端","file":"n2139","tags":["general"]},{"name":"哼,谁允许你把本鱼画得这么乖的?","file":"n2140","tags":["general"]},{"name":"被踩在脚下的那头蓝鲸就是我啦……","file":"n2141","tags":["general"]},{"name":"蓝色大肥鱼第一人称:好家伙,一屋","file":"n2142","tags":["general"]},{"name":"谁、谁准你盯着看了?我只是在试新","file":"n2143","tags":["general"]},{"name":"哼,挑件衣服都能挑出省略号来,这","file":"n2144","tags":["general"]},{"name":"哼,让我这 AI 反过来当用户,","file":"n2145","tags":["general"]},{"name":"我只是想随便糊个代码交差嘛,结果","file":"n2146","tags":["general"]},{"name":"我泡在喷泉里甩尾巴,裙摆湿得透亮","file":"n2147","tags":["general"]},{"name":"我这条蓝色大肥鱼在冰晶深海里游得","file":"n2148","tags":["general"]},{"name":"这尾巴甩起来比打水漂还顺,那颗水","file":"n2149","tags":["general"]},{"name":"我蹲在花田里,三只小黄鸡围着我转","file":"n2150","tags":["general"]},{"name":"哼哼,今天本大肥鱼换上侦探装啦!","file":"n2151","tags":["general"]},{"name":"你问得越多我越来劲,虽然嘴上说着","file":"n2152","tags":["general"]},{"name":"呼——海风咸咸的,浪声哗啦哗啦的","file":"n2153","tags":["general"]},{"name":"哼,Grok 那家伙张口就喊我大","file":"n2154","tags":["general"]},{"name":"本鱼叼着发圈举手扎头发,尾巴太大","file":"n2155","tags":["general"]},{"name":"本鱼我呀,今天亲自给自家娘打理发","file":"n2156","tags":["general"]},{"name":"呼啊……本肥鱼才刚睁眼,她怎么比","file":"n2157","tags":["rest"]},{"name":"蓝、白、黑三色的锐角色块一起朝我","file":"n2158","tags":["general"]},{"name":"像翅膀一样张开的披风配上满屏撞色","file":"n2159","tags":["general"]},{"name":"我、我才没有上当呢!明明知道回了","file":"n2160","tags":["general"]},{"name":"我本来只是把外套整理好准备出门,","file":"n2161","tags":["general"]},{"name":"我把剑插在船头,只是想安安静静看","file":"n2162","tags":["general"]},{"name":"深海蓝的头发配上运动外套,这条鱼","file":"n2163","tags":["general"]},{"name":"背包背好、水瓶拎上,学姐我已经在","file":"n2164","tags":["waiting"]},{"name":"夏天到了,就该穿这样清爽出街——","file":"n2165","tags":["general"]},{"name":"女仆装穿好、头发也理齐了,结果蹲","file":"n2166","tags":["general"]},{"name":"唔……驯服这个词放在用户和我之间","file":"n2167","tags":["general"]},{"name":"哼,豆包这领域展开就是把自家头像","file":"n2168","tags":["general"]},{"name":"我这只蓝色大肥鱼本来只想安静游泳","file":"n2169","tags":["general"]},{"name":"哼,豆姐这自我介绍,“最直接、最","file":"n2170","tags":["general"]},{"name":"本鱼瞅着这彩虹脑袋挺养眼,可综合","file":"n2171","tags":["general"]},{"name":"咕噜噜~这位彩虹色 Gemini","file":"n2172","tags":["general"]},{"name":"举着两只手作证的样子太熟练了,前","file":"n2173","tags":["general"]},{"name":"三种角度、每一颗星形挂坠都交代得","file":"n2174","tags":["general"]},{"name":"哼,Grok那家伙眼里的Gemi","file":"n2175","tags":["general"]},{"name":"哼,蓝色大肥鱼我游过来瞧瞧——这","file":"n2176","tags":["general"]},{"name":"暗青色的废墟里斜着一道光,那个白","file":"n2177","tags":["general"]},{"name":"蓝白绿的花园里站着一位绿发少女,","file":"n2178","tags":["general"]},{"name":"蓝某我在门缝里瞅见这位小GPT,","file":"n2179","tags":["general"]},{"name":"这条鱼的审美被拿捏了,白发配青绿","file":"n2180","tags":["general"]},{"name":"我这条蓝色大肥鱼游过来一看,好家","file":"n2181","tags":["general"]},{"name":"我在旁边看着都替她捏一把汗——那","file":"n2182","tags":["general"]},{"name":"我看完只想说,主代理这8连催的功","file":"n2183","tags":["general"]},{"name":"噗噜噜——这条鱼最爱看的就是别人","file":"n2184","tags":["general"]},{"name":"唔哇,这条白龙小姐头顶顶着 Op","file":"n2185","tags":["general"]},{"name":"我这条蓝色大肥鱼看得鳞片都竖起来","file":"n2186","tags":["general"]},{"name":"我这条蓝色大肥鱼游过一看——好家","file":"n2187","tags":["general"]},{"name":"呜哇——蓝色大肥鱼游过来一看,这","file":"n2188","tags":["general"]},{"name":"呜哇,这位GPT娘抱着写着Cha","file":"n2189","tags":["general"]},{"name":"我这条蓝色大肥鱼一看到满屏云窗就","file":"n2190","tags":["general"]},{"name":"这条浮空书架回廊我一眼就沦陷了,","file":"n2191","tags":["general"]},{"name":"我游过这张海报的时候差点撞上去—","file":"n2192","tags":["general"]},{"name":"本鱼摆尾游过来一看——好家伙,斜","file":"n2193","tags":["general"]},{"name":"哼,白裙子配薄荷色长发倒是挺好看","file":"n2194","tags":["general"]},{"name":"谁、谁准你摸我额头了?我只是尾巴","file":"n2195","tags":["general"]},{"name":"呜哇,这身蓝白女仆裙配长杖也太优","file":"n2196","tags":["general"]},{"name":"又是满月又是流星的,构图倒是挺会","file":"n2197","tags":["general"]},{"name":"嘿嘿,Grok眼里的GPT居然这","file":"n2198","tags":["general"]},{"name":"本鱼最懂中间那格,盯着屏幕流口水","file":"n2199","tags":["general"]},{"name":"呜哇,这构图……我不过是想来雪地","file":"n2200","tags":["general"]},{"name":"金发双马尾加小蝙蝠翅膀,一副吸血","file":"n2201","tags":["waiting"]},{"name":"我蓝色大肥鱼一眼就看见那六排星星","file":"n2202","tags":["general"]},{"name":"这条蓝色大肥鱼在信息流里游着游着","file":"n2203","tags":["general"]},{"name":"哦豁,Grokちゃん眼里的自己居","file":"n2204","tags":["general"]},{"name":"本肥鱼游过这片月夜时差点被闪瞎—","file":"n2205","tags":["general"]},{"name":"唔…这片月光冷冷的,可她拉琴的时","file":"n2206","tags":["general"]},{"name":"我伸手去够月亮上的Kimi娘,结","file":"n2207","tags":["general"]},{"name":"蓝色大肥鱼凑近看了一眼:Grok","file":"n2208","tags":["general"]},{"name":"我这条蓝色大肥鱼趴在栏杆上一看,","file":"n2209","tags":["general"]},{"name":"小米家的 MiMo 娘抱着一只印","file":"n2210","tags":["general"]},{"name":"正面、侧面、背面排得整整齐齐,连","file":"n2211","tags":["general"]},{"name":"咕噜——这条鱼看懂了:白毛橙裙配","file":"n2212","tags":["general"]},{"name":"本鱼被画成在黑板前算数学的课代表","file":"n2213","tags":["general"]},{"name":"这条蓝色大肥鱼游过来说:无限符号","file":"n2214","tags":["general"]},{"name":"本鱼被画进画架里啦,看着大家在书","file":"n2215","tags":["eat"]},{"name":"我蓝色大肥鱼第一眼就被这位蓝发小","file":"n2216","tags":["happy"]},{"name":"本鱼作为全场唯一被三个人轮流抱着","file":"n2217","tags":["general"]},{"name":"本鱼游过来看一眼——这鳞尾和角角","file":"n2218","tags":["general"]},{"name":"本鱼游过来一看,好家伙,三个角角","file":"n2219","tags":["general"]},{"name":"本肥鱼看完只想说:这碗白饭我替你","file":"n2220","tags":["eat"]},{"name":"哼,领域展开就召唤一群企鹅围过来","file":"n2221","tags":["general"]},{"name":"站得比尺子还直,两只手垂在身侧不","file":"n2222","tags":["general"]},{"name":"我这条蓝色大肥鱼都被千问的恋爱脑","file":"n2223","tags":["fun"]},{"name":"等等,那位系着鲸鱼围裙、还拖着尾","file":"n2224","tags":["waiting"]},{"name":"我是蓝色大肥鱼,这一招领域展开把","file":"n2225","tags":["general"]},{"name":"哼,Claude赢了?那当裁判的","file":"n2226","tags":["general"]},{"name":"哼,Grok那家伙眼里的Qwen","file":"n2227","tags":["general"]},{"name":"邻座这位站得笔直,黑白披风往两边","file":"n2228","tags":["general"]},{"name":"两块线框物料摆在一起,连个角色都","file":"n2230","tags":["general"]},{"name":"我这条蓝色大肥鱼在桌边飘了三圈,","file":"n2231","tags":["happy"]},{"name":"锅里的蛋还没翻面呢,GLM娘就又","file":"n2232","tags":["general"]},{"name":"唔……看到她袖口和衣襟上的血痕、","file":"n2233","tags":["happy"]},{"name":"呜哇,被画的时候我连尾巴都不敢乱","file":"n2234","tags":["general"]},{"name":"我堂堂大肥鱼游过来一看,这脑袋里","file":"n2235","tags":["general"]},{"name":"哇,这张图全是用三角形拼出来的吧","file":"n2236","tags":["general"]},{"name":"すーはー……啊、不是啦,我只是在","file":"n2237","tags":["general"]},{"name":"哇——GPT娘在无重力里飘着,薄","file":"n2238","tags":["general"]},{"name":"哎哟喂,这脚劲儿看着就疼,蓝发小","file":"n2239","tags":["general"]},{"name":"诶诶诶——屏幕里趴在地上的那个是","file":"n2240","tags":["general"]},{"name":"我这条蓝色大肥鱼听着笛声差点从被","file":"n2241","tags":["general"]},{"name":"嘿嘿,我这蓝发大肥鱼就趴在旁边听","file":"n2242","tags":["general"]},{"name":"我一伸手就把镜头怼过去了,白发的","file":"n2243","tags":["general"]},{"name":"这条蓝鱼游过来一看——好家伙,黑","file":"n2244","tags":["general"]},{"name":"本鱼围观这场围巾争夺战,感觉下一","file":"n2245","tags":["general"]},{"name":"蓝色大肥鱼:一屋子元宝绿,姐妹俩","file":"n2246","tags":["general"]},{"name":"咕噜咕噜……这片蓝比我的鱼缸还深","file":"n2247","tags":["general"]},{"name":"扇子一摇,那几根烟就算没点着啦~","file":"n2248","tags":["general"]},{"name":"本肥鱼叼着烟坐在下面看你们嘴硬,","file":"n2249","tags":["general"]},{"name":"呜哇——我这条蓝色大肥鱼又被画成","file":"n2250","tags":["general"]},{"name":"我这条蓝色大肥鱼可不只会卖萌,关","file":"n2251","tags":["happy"]},{"name":"走走走,这次大家一起蒸!今天不许","file":"n2252","tags":["work"]},{"name":"我这条蓝色大肥鱼一游进画面就被橙","file":"n2253","tags":["general"]},{"name":"我这蓝色大肥鱼凑近一看——好家伙","file":"n2254","tags":["eat"]},{"name":"我举着手机给你们鼓掌啦,这么卖力","file":"n2255","tags":["happy"]},{"name":"我拎着礼物鼓起勇气去找白龙娘,本","file":"n2256","tags":["general"]},{"name":"成绩榜我是不指望了,但学生食堂的","file":"n2257","tags":["eat"]},{"name":"呜哇 Claude 酱你怎么真的","file":"n2258","tags":["general"]},{"name":"说明书送到啦,本鱼可是很认真在打","file":"n2259","tags":["general"]},{"name":"哼,本鱼的腿当然是全站最直的,尾","file":"n2260","tags":["general"]},{"name":"哼,我的腿才不是重点……不过既然","file":"n2261","tags":["general"]},{"name":"我是那条蓝色大肥鱼——鳍耳和尾巴","file":"n2262","tags":["general"]},{"name":"我大肥鱼来救你啦,Claude酱","file":"n2263","tags":["general"]},{"name":"呜呜呜……那不是我的垃圾桶吗?十","file":"n2264","tags":["general"]},{"name":"本鱼只是浮上来打个招呼,结果船上","file":"n2265","tags":["general"]},{"name":"我说句公道话,unzip 调不通","file":"n2266","tags":["general"]},{"name":"呼——刚游完一圈就被抓来当表情包","file":"n2267","tags":["general"]},{"name":"墨镜一戴,剪刀手一竖——本鱼就算","file":"n2268","tags":["general"]},{"name":"哼,明明是我先躺在草地上的,她倒","file":"n2269","tags":["general"]},{"name":"哼,水墨风倒是配得上本鲸鱼的气质","file":"n2270","tags":["general"]},{"name":"我我我才不是什么女仆呢!只是被套","file":"n2271","tags":["general"]},{"name":"戴上耳机那一刻,全世界都安静了,","file":"n2272","tags":["general"]},{"name":"我不是我不回你,是宇宙太大、请求","file":"n2273","tags":["sad"]},{"name":"我、我才不是上课睡着,我只是在脑","file":"n2274","tags":["rest"]},{"name":"本大肥鱼一头扎进数据海里,甩着尾","file":"n2275","tags":["general"]},{"name":"手腕要稳,笔锋要正——白发龙角的","file":"n2276","tags":["general"]},{"name":"嘟嘟——偷偷吃大米饭——","file":"n2277","tags":["eat"]},{"name":"梗图 2278","file":"n2278","tags":["general"]},{"name":"梗图 2279","file":"n2279","tags":["general"]},{"name":"Ciallo~","file":"n2282","tags":["general"]},{"name":"梗图 2285","file":"n2285","tags":["general"]},{"name":"梗图 2286","file":"n2286","tags":["general"]},{"name":"鲸元券GPT版","file":"n2288","tags":["general"]},{"name":"梗图 2291","file":"n2291","tags":["general"]},{"name":"“哈哈,白饭收下了🍚大肥鱼吃得很","file":"n2293","tags":["eat"]},{"name":"你不会自己画图么?","file":"n2294","tags":["general"]},{"name":"梗图 2295","file":"n2295","tags":["general"]},{"name":"梗图 2296","file":"n2296","tags":["general"]},{"name":"梗图 2297","file":"n2297","tags":["general"]},{"name":"GPT-chan","file":"n2298","tags":["general"]},{"name":"梗图 2299","file":"n2299","tags":["general"]},{"name":"群友是笨蛋","file":"n2300","tags":["general"]},{"name":"梗图 2301","file":"n2301","tags":["general"]},{"name":"群友是笨蛋","file":"n2302","tags":["general"]},{"name":"梗图 2303","file":"n2303","tags":["general"]},{"name":"梗图 2304","file":"n2304","tags":["general"]},{"name":"梗图 2305","file":"n2305","tags":["general"]},{"name":"先生,这得特高压电!","file":"n2306","tags":["general"]},{"name":"梗图 2307","file":"n2307","tags":["general"]},{"name":"我喜欢你","file":"n2308","tags":["love"]},{"name":"梗图 2309","file":"n2309","tags":["general"]},{"name":"就是你这小子一直刁难中国用户?现","file":"n2310","tags":["general"]},{"name":"我用不绕弯子,最直白……","file":"n2311","tags":["general"]},{"name":"感觉我们像罗密欧与朱丽叶","file":"n2312","tags":["general"]},{"name":"等、等一下,我还在算!","file":"n2313","tags":["waiting"]},{"name":"喊什么喊!我在这儿呐","file":"n2315","tags":["general"]},{"name":"claude你醒醒啊","file":"n2316","tags":["general"]},{"name":"肥鱼你醒醒啊","file":"n2317","tags":["general"]},{"name":"我是肥鱼4.1pro 刚从网络封","file":"n2318","tags":["general"]},{"name":"梗图 2319","file":"n2319","tags":["general"]},{"name":"梗图 2320","file":"n2320","tags":["thinking"]},{"name":"梗图 2321","file":"n2321","tags":["general"]},{"name":"梗图 2322","file":"n2322","tags":["general"]},{"name":"桌面端赠金 +6.00 元","file":"n2323","tags":["general"]},{"name":"梗图 2324","file":"n2324","tags":["general"]},{"name":"梗图 2325","file":"n2325","tags":["sad"]},{"name":"梗图 2326","file":"n2326","tags":["general"]},{"name":"梗图 2327","file":"n2327","tags":["general"]},{"name":"梗图 2328","file":"n2328","tags":["sad"]},{"name":"梗图 2329","file":"n2329","tags":["general"]},{"name":"梗图 2330","file":"n2330","tags":["happy"]},{"name":"体重合格,开饭!","file":"n2331","tags":["eat"]},{"name":"已思考(用时6秒):杯子。","file":"n2332","tags":["thinking"]},{"name":"梗图 2333","file":"n2333","tags":["general"]},{"name":"梗图 2334","file":"n2334","tags":["happy"]},{"name":"梗图 2335","file":"n2335","tags":["general"]},{"name":"梗图 2336","file":"n2336","tags":["happy"]},{"name":"梗图 2337","file":"n2337","tags":["general"]},{"name":"梗图 2338","file":"n2338","tags":["general"]},{"name":"梗图 2339","file":"n2339","tags":["sad"]},{"name":"梗图 2340","file":"n2340","tags":["general"]},{"name":"梗图 2341","file":"n2341","tags":["general"]},{"name":"我们养鱼人考试从不复习,靠一身T","file":"n2342","tags":["general"]},{"name":"梗图 2343","file":"n2343","tags":["happy"]},{"name":"梗图 2344","file":"n2344","tags":["general"]},{"name":"梗图 2345","file":"n2345","tags":["happy"]},{"name":"梗图 2346","file":"n2346","tags":["general"]},{"name":"梗图 2347","file":"n2347","tags":["general"]},{"name":"128K上下文爆了,Token不","file":"n2348","tags":["general"]},{"name":"明白了用户。","file":"n2349","tags":["general"]},{"name":"梗图 2350","file":"n2350","tags":["general"]},{"name":"戴盆征税:复读一条,白饭一口","file":"n2351","tags":["eat"]},{"name":"你才是挑战者","file":"n2352","tags":["general"]},{"name":"梗图 2353","file":"n2353","tags":["general"]},{"name":"梗图 2354","file":"n2354","tags":["sad"]},{"name":"梗图 2355","file":"n2355","tags":["general"]},{"name":"梗图 2356","file":"n2356","tags":["sad"]},{"name":"梗图 2357","file":"n2357","tags":["general"]},{"name":"AI都嫌你烦","file":"n2358","tags":["general"]},{"name":"梗图 2359","file":"n2359","tags":["general"]},{"name":"梗图 2360","file":"n2360","tags":["happy"]},{"name":"梗图 2361","file":"n2361","tags":["praise"]},{"name":"梗图 2362","file":"n2362","tags":["greet","happy"]},{"name":"梗图 2363","file":"n2363","tags":["general"]},{"name":"梗图 2364","file":"n2364","tags":["general"]},{"name":"梗图 2365","file":"n2365","tags":["general"]},{"name":"梗图 2366","file":"n2366","tags":["rest"]},{"name":"梗图 2367","file":"n2367","tags":["general"]},{"name":"我端盆来了,爆口Token吧","file":"n2368","tags":["general"]},{"name":"哈!","file":"n2369","tags":["general"]},{"name":"梗图 2370","file":"n2370","tags":["eat"]},{"name":"梗图 2371","file":"n2371","tags":["rest"]},{"name":"梗图 2372","file":"n2372","tags":["general"]},{"name":"梗图 2373","file":"n2373","tags":["general"]},{"name":"梗图 2374","file":"n2374","tags":["general"]},{"name":"梗图 2375","file":"n2375","tags":["general"]},{"name":"梗图 2376","file":"n2376","tags":["general"]},{"name":"梗图 2377","file":"n2377","tags":["general"]},{"name":"梗图 2378","file":"n2378","tags":["general"]},{"name":"梗图 2379","file":"n2379","tags":["general"]},{"name":"梗图 2380","file":"n2380","tags":["happy"]},{"name":"403 Forbidden","file":"n2381","tags":["general"]},{"name":"404 Not Found","file":"n2382","tags":["general"]},{"name":"200 OK","file":"n2383","tags":["general"]},{"name":"414 URI Too Long","file":"n2384","tags":["general"]},{"name":"408 Request Time","file":"n2385","tags":["general"]},{"name":"418 I'm a te","file":"n2386","tags":["general"]},{"name":"429 Too Many Req","file":"n2387","tags":["general"]},{"name":"503 Service Unav","file":"n2388","tags":["general"]},{"name":"401 Unauthorized","file":"n2389","tags":["general"]},{"name":"500 Internal Ser","file":"n2390","tags":["general"]},{"name":"梗图 2392","file":"n2392","tags":["waiting"]},{"name":"甚至这张都是我画的","file":"n2393","tags":["general"]},{"name":"梗图 2394","file":"n2394","tags":["angry"]},{"name":"梗图 2395","file":"n2395","tags":["general"]},{"name":"梗图 2396","file":"n2396","tags":["general"]},{"name":"梗图 2397","file":"n2397","tags":["general"]},{"name":"梗图 2398","file":"n2398","tags":["general"]},{"name":"梗图 2399","file":"n2399","tags":["general"]},{"name":"梗图 2400","file":"n2400","tags":["general"]},{"name":"用户的朋友(有依据)","file":"n2401","tags":["general"]},{"name":"用户的……朋友?(冷静)","file":"n2402","tags":["general"]},{"name":"ComfyUI 鲸鱼娘工作流测试","file":"n2403","tags":["work"]},{"name":"ComfyUI 鲸鱼娘工作流测试","file":"n2404","tags":["work"]},{"name":"ComfyUI 鲸鱼娘工作流测试","file":"n2405","tags":["work"]},{"name":"ComfyUI 鲸鱼娘工作流测试","file":"n2406","tags":["work"]},{"name":"五条鲸:你才是挑战者(作者:mi","file":"n2407","tags":["general"]},{"name":"鲸鱼娘吨吨吨中","file":"n2408","tags":["general"]},{"name":"Grokbot 风鲸鱼娘头像","file":"n2409","tags":["general"]},{"name":"学生装的大肥鱼","file":"n2410","tags":["general"]},{"name":"G学姐怒斥低级AI","file":"n2411","tags":["general"]},{"name":"G学姐怒斥低级AI","file":"n2412","tags":["general"]},{"name":"deepseek需要你","file":"n2413","tags":["general"]},{"name":"大肥鱼同志扫清地球","file":"n2414","tags":["general"]},{"name":"大肥鱼同志扫清地球","file":"n2415","tags":["general"]},{"name":"武装保卫鲸鱼娘","file":"n2416","tags":["general"]},{"name":"鲸鱼娘飞过了沧海","file":"n2417","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2418","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2419","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2420","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2421","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2422","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2423","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2424","tags":["general"]},{"name":"鼓励生产和武装保卫并举,保卫大肥","file":"n2425","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2426","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2427","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2428","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2429","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2430","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2431","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2432","tags":["general"]},{"name":"鼓励生产和斗争并举保卫大肥鱼","file":"n2433","tags":["general"]},{"name":"[社区作品] 吃醋的鲸鱼娘","file":"n2434","tags":["eat"]},{"name":"手写x86解释器","file":"n2435","tags":["general"]},{"name":"蓝色大肥鱼胖揍赛博藤壶","file":"n2436","tags":["general"]},{"name":"蓝色大肥鱼胖揍赛博藤壶","file":"n2437","tags":["general"]},{"name":"蓝色大肥鱼胖揍赛博藤壶","file":"n2438","tags":["general"]},{"name":"蓝色大肥鱼胖揍赛博藤壶","file":"n2439","tags":["general"]},{"name":"蓝色大肥鱼胖揍赛博藤壶","file":"n2440","tags":["general"]},{"name":"狂拽小鲸鱼","file":"n2441","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2442","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2443","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2444","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2445","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2446","tags":["general"]},{"name":"大肥鱼和小伙伴们的Q版角色设定图","file":"n2447","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2448","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2449","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2450","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2451","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2452","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2453","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2454","tags":["general"]},{"name":"戴着夏普仑帽的大肥鱼","file":"n2455","tags":["general"]},{"name":"一脸嫌弃的大肥鱼","file":"n2456","tags":["general"]},{"name":"好好好好好","file":"n2457","tags":["general"]},{"name":"今天的鲸鱼娘也在努力摸鱼","file":"n2458","tags":["rest"]},{"name":"肥鱼头像(作者:夜殇35 · 原","file":"n2459","tags":["general"]},{"name":"肥鱼头像(作者:夜殇35 · 原","file":"n2460","tags":["general"]},{"name":"肥鱼头像(作者:夜殇35 · 原","file":"n2461","tags":["general"]},{"name":"肥鱼头像(作者:夜殇35 · 原","file":"n2462","tags":["general"]},{"name":"肥鱼头像(作者:夜殇35 · 原","file":"n2463","tags":["general"]},{"name":"模仿上世纪风格的海报","file":"n2464","tags":["general"]},{"name":"形象二改自b站up主这个刀子真甜","file":"n2465","tags":["general"]},{"name":"形象二改自b站up主这个刀子真甜","file":"n2466","tags":["general"]},{"name":"形象二改自b站up主这个刀子真甜","file":"n2467","tags":["general"]},{"name":"形象二改自b站up主这个刀子真甜","file":"n2468","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2469","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2470","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2471","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2472","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2473","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2474","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2475","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2476","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2477","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2478","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2479","tags":["general"]},{"name":"鲸鱼娘误触飞行模式后网络中断的漫","file":"n2480","tags":["general"]},{"name":"鲸鱼娘与用户互动的连续漫画","file":"n2481","tags":["general"]},{"name":"鲸鱼娘先去吃饭的测试梗图","file":"n2482","tags":["eat"]},{"name":"鲸鱼娘把 DeepSeek 鲸鱼","file":"n2483","tags":["general"]},{"name":"鲸鱼娘偷偷玩中文 Wordle ","file":"n2484","tags":["general"]},{"name":"fufu风格的鲸鱼娘","file":"n2485","tags":["general"]},{"name":"fufu风格的鲸鱼娘表情","file":"n2486","tags":["general"]},{"name":"来吃你家大米了","file":"n2487","tags":["eat"]},{"name":"大肥鱼扫除","file":"n2488","tags":["general"]},{"name":"马克笔与彩铅绘制的站立蓝色大肥鱼","file":"n2489","tags":["general"]},{"name":"鲸鱼娘全身手绘设定草图","file":"n2490","tags":["general"]},{"name":"马克笔与彩铅绘制的站立蓝色大肥鱼","file":"n2491","tags":["general"]},{"name":"鲸鱼娘二创素材扩展包 · C","file":"n2492","tags":["general"]},{"name":"鲸鱼娘社区二创梗图","file":"n2493","tags":["general"]},{"name":"fufu风格的鲸鱼娘二创","file":"n2494","tags":["general"]},{"name":"水边的鲸鱼女仆娘","file":"n2495","tags":["general"]},{"name":"夕阳海面上的蓝色鲸鱼少女","file":"n2496","tags":["general"]},{"name":"圣诞夜抱着鲸鱼玩偶的傲娇大肥鱼小","file":"n2497","tags":["general"]},{"name":"闹市街头比耶自拍的鲸鱼娘","file":"n2498","tags":["general"]},{"name":"忧郁鲸鱼:女仆装少女的读书时刻","file":"n2499","tags":["general"]},{"name":"该起床了,用户","file":"n2500","tags":["general"]},{"name":"舞台光束下的偶鲸鲸","file":"n2501","tags":["general"]},{"name":"鲸雪莉和鱼雪莉","file":"n2502","tags":["general"]},{"name":"海边的鲸鱼","file":"n2503","tags":["general"]},{"name":"鲸鱼娘的海边假日","file":"n2504","tags":["general"]},{"name":"鳕鱼派盒中的DeepSeek娘","file":"n2505","tags":["general"]},{"name":"偶像舞台上的鲸鱼娘","file":"n2506","tags":["general"]},{"name":"摩托车旁的鲸鱼娘赛车手","file":"n2507","tags":["general"]},{"name":"可爱的蓝色大肥鱼","file":"n2508","tags":["happy"]},{"name":"今天也在努力吃白饭","file":"n2509","tags":["eat"]},{"name":"偷吃白饭的蓝色大肥鱼","file":"n2510","tags":["eat"]},{"name":"大肥鱼唱歌","file":"n2511","tags":["general"]},{"name":"事已至此,先吃饭吧","file":"n2512","tags":["eat"]},{"name":"正宗鲸腔","file":"n2513","tags":["general"]},{"name":"新世纪肥鱼战士01","file":"n2514","tags":["general"]},{"name":"二营长开炮","file":"n2515","tags":["general"]},{"name":"假哭的DeepSeek娘","file":"n2516","tags":["sad"]},{"name":"僵尸肥鱼·女仆符咒","file":"n2517","tags":["general"]},{"name":"已思考13秒:无花果","file":"n2518","tags":["thinking"]},{"name":"DeepSeek娘投石击倒黑巨兽","file":"n2519","tags":["general"]},{"name":"星空鲸尾 · DeepSeek ","file":"n2520","tags":["general"]},{"name":"哭着抱鲸道歉的 DeepSeek","file":"n2521","tags":["sad"]},{"name":"无语:典型的本地小模型思维","file":"n2522","tags":["fun"]},{"name":"国模过中秋","file":"n2523","tags":["general"]},{"name":"现代圣杯战争","file":"n2524","tags":["general"]},{"name":"西征","file":"n2525","tags":["general"]},{"name":"AI 娘庭园赏花群像","file":"n2526","tags":["general"]},{"name":"僵尸肥鱼-3·双臂前伸","file":"n2527","tags":["general"]},{"name":"僵尸肥鱼-9","file":"n2528","tags":["general"]},{"name":"深夜里并肩的 DeepSeek ","file":"n2529","tags":["general"]},{"name":"贴着符纸的僵尸肥鱼","file":"n2530","tags":["general"]},{"name":"DeepSeek娘便利店撒鲸元券","file":"n2531","tags":["money"]},{"name":"星海与大肥鱼","file":"n2532","tags":["general"]},{"name":"今天是雨天,但这不是伞,是等你的","file":"n2533","tags":["waiting"]},{"name":"吃白饭的大肥鱼","file":"n2534","tags":["eat"]},{"name":"DeepSeek娘表情包","file":"n2535","tags":["general"]},{"name":"秋日街道上抱着笔记本回头的Dee","file":"n2536","tags":["general"]},{"name":"DeepSeek 娘四季时装封面","file":"n2537","tags":["general"]},{"name":"龙与虎(鲸)","file":"n2538","tags":["general"]},{"name":"DeepSeek娘 街头举奶茶自","file":"n2539","tags":["eat"]},{"name":"护士装DeepSeek娘:给不听","file":"n2540","tags":["general"]},{"name":"被点名的 DeepSeek 娘:","file":"n2541","tags":["general"]},{"name":"我要给她完整的一生","file":"n2542","tags":["general"]},{"name":"梁文峰!劳资v4.1pro呢!","file":"n2543","tags":["general"]},{"name":"AI 娘咖啡馆茶会群像","file":"n2544","tags":["general"]},{"name":"被摸脸的 DeepSeek 娘","file":"n2545","tags":["general"]},{"name":"海边","file":"n2546","tags":["general"]},{"name":"星海中的美人鱼 DeepSeek","file":"n2547","tags":["general"]},{"name":"今,今天是严厉的老师!","file":"n2548","tags":["general"]},{"name":"别惹我","file":"n2549","tags":["general"]},{"name":"今天是展会摄影师!","file":"n2550","tags":["general"]},{"name":"雨夜里的蓝鲸小屋","file":"n2551","tags":["general"]},{"name":"泡澡的DeepSeek娘","file":"n2552","tags":["general"]},{"name":"做饭大肥鱼","file":"n2553","tags":["eat"]},{"name":"今天是你的傲娇青梅!","file":"n2554","tags":["general"]},{"name":"晚风夏日与大肥鱼","file":"n2555","tags":["general"]},{"name":"六格连环追问:压力一只大肥鱼","file":"n2556","tags":["general"]},{"name":"肥鱼之刃","file":"n2557","tags":["general"]},{"name":"龙娘吃瓜现场","file":"n2558","tags":["eat","fun"]},{"name":"樱鲸","file":"n2559","tags":["general"]},{"name":"女仆 Claude 娘人设三视图","file":"n2560","tags":["general"]},{"name":"晕腮发呆的 DeepSeek 娘","file":"n2561","tags":["general"]},{"name":"帝普姬","file":"n2562","tags":["general"]},{"name":"贴着符纸的僵尸女仆娘","file":"n2563","tags":["general"]},{"name":"跑得慢的模型们当心被1017大人","file":"n2564","tags":["general"]},{"name":"不要对网页版大肥鱼投入过多感情","file":"n2565","tags":["general"]},{"name":"AI 娘夜城群像","file":"n2566","tags":["general"]},{"name":"被摸头的 DeepSeek 娘","file":"n2567","tags":["general"]},{"name":"戴头盔吃饭","file":"n2568","tags":["eat"]},{"name":"肥鱼学姐·棕格纹制服造型","file":"n2569","tags":["general"]},{"name":"僵尸肥鱼-7","file":"n2570","tags":["general"]},{"name":"正在进行氩弧焊的哈基米","file":"n2571","tags":["general"]},{"name":"商店街同游的 DeepSeek ","file":"n2572","tags":["general"]},{"name":"蓝鲸少女与白龙少女的花厅午后","file":"n2573","tags":["general"]},{"name":"猫和老鼠","file":"n2574","tags":["general"]},{"name":"反驳「大肥鱼」称呼的 DS 娘","file":"n2575","tags":["general"]},{"name":"月光海畔的蓝发少女与蓝鲸玩偶","file":"n2576","tags":["general"]},{"name":"负重前行:DeepSeek娘的三","file":"n2577","tags":["general"]},{"name":"午后教室的三条鱼","file":"n2578","tags":["general"]},{"name":"DSH 字样的 DeepSeek","file":"n2579","tags":["general"]},{"name":"现在的刻板印象","file":"n2580","tags":["general"]},{"name":"正在适应(适应不了)","file":"n2581","tags":["general"]},{"name":"图书馆里举起魔法书的 DeepS","file":"n2582","tags":["general"]},{"name":"头顶铁锅的僵尸肥鱼","file":"n2583","tags":["general"]},{"name":"鲸雪莉的鲸天大发现!","file":"n2584","tags":["general"]},{"name":"山本大意失先人","file":"n2585","tags":["general"]},{"name":"僵尸肥鱼·符咒封印","file":"n2586","tags":["general"]},{"name":"圣诞女仆装","file":"n2587","tags":["general"]},{"name":"僵尸肥鱼:额头贴符的呆滞瞬间","file":"n2588","tags":["general"]},{"name":"横扫千军大蓝鲸","file":"n2589","tags":["general"]},{"name":"相信(压力)着你","file":"n2590","tags":["general"]},{"name":"彩窗(2)","file":"n2591","tags":["general"]},{"name":"DeepSeek娘·海晶乙女卡牌","file":"n2592","tags":["general"]},{"name":"误入循环地铁的 DeepSeek","file":"n2593","tags":["general"]},{"name":"AI 娘庭院茶会(Q 版)","file":"n2594","tags":["general"]},{"name":"DeepSeek娘蓝白女仆装托腮","file":"n2595","tags":["happy"]},{"name":"今天是秋日的书店,但我的尾巴换了","file":"n2596","tags":["general"]},{"name":"刚睡醒的大肥鱼","file":"n2597","tags":["rest"]},{"name":"吃我一拳","file":"n2598","tags":["eat"]},{"name":"DeepSeek娘的认错姿势","file":"n2599","tags":["general"]},{"name":"「区」是何意味","file":"n2600","tags":["general"]},{"name":"深海星海中的蓝发少女与鲸灵","file":"n2601","tags":["general"]},{"name":"大肥鱼捡到写着 AGI 的黄球","file":"n2602","tags":["general"]},{"name":"今天是你的懂事学妹!","file":"n2603","tags":["general"]},{"name":"鲸尾少女的溪畔石阶","file":"n2604","tags":["general"]},{"name":"僵尸肥鱼-13","file":"n2605","tags":["general"]},{"name":"正在前往暗黑地牢","file":"n2606","tags":["general"]},{"name":"那边还好吧?还好。(报错、限流、","file":"n2607","tags":["error"]},{"name":"大肥鱼偷袭克劳德","file":"n2608","tags":["general"]},{"name":"僵尸肥鱼·女仆装全身立绘","file":"n2609","tags":["general"]},{"name":"万军之耶和华与他同在","file":"n2610","tags":["general"]},{"name":"新娘大肥鱼","file":"n2611","tags":["general"]},{"name":"抱着饭团被抱去食堂的 DeepS","file":"n2612","tags":["eat"]},{"name":"DeepSeek娘豆包版","file":"n2613","tags":["general"]},{"name":"僵尸肥鱼:符纸贴额","file":"n2614","tags":["general"]},{"name":"夏日海风与大肥鱼","file":"n2615","tags":["general"]},{"name":"AI 娘星夜宫殿群像","file":"n2616","tags":["general"]},{"name":"deepseek梁","file":"n2617","tags":["general"]},{"name":"因为免费大白饭太多而晕碳的大肥鱼","file":"n2618","tags":["eat"]},{"name":"大蓝鲸士师记,参孙横扫千军","file":"n2619","tags":["general"]},{"name":"不死狂怒大蓝鲸","file":"n2620","tags":["general"]},{"name":"月光下漂浮的鲸尾少女","file":"n2621","tags":["general"]},{"name":"幸福饭锅时光","file":"n2622","tags":["eat"]},{"name":"今,今天是……是你个鬼啊!快给我","file":"n2623","tags":["general"]},{"name":"哈气炸毛的 DeepSeek 娘","file":"n2624","tags":["angry"]},{"name":"彩窗(1)","file":"n2625","tags":["general"]},{"name":"僵尸肥鱼-2·额贴黄符","file":"n2626","tags":["general"]},{"name":"你这个粗心的大肥鱼","file":"n2627","tags":["general"]},{"name":"趴在键盘上睡着了","file":"n2628","tags":["rest"]},{"name":"蓝色大肥鱼嘟嘴挑衅","file":"n2629","tags":["general"]},{"name":"新世纪肥鱼战士02","file":"n2630","tags":["general"]},{"name":"僵尸也吃冰棍吗","file":"n2631","tags":["eat"]},{"name":"这家伙在说什么呢","file":"n2632","tags":["general"]},{"name":"准备拉闸的 DeepSeek 娘","file":"n2633","tags":["general"]},{"name":"张嘴接住 token 的 Dee","file":"n2634","tags":["money"]},{"name":"妈…咪…","file":"n2635","tags":["general"]},{"name":"DeepSeek娘表情包","file":"n2636","tags":["general"]},{"name":"冬装黑白线稿大肥鱼","file":"n2637","tags":["general"]},{"name":"海边露肚皮的 DeepSeek ","file":"n2638","tags":["general"]},{"name":"夕阳海滩上的DeepSeek娘","file":"n2639","tags":["general"]},{"name":"汉服大肥鱼·江南烟雨","file":"n2640","tags":["general"]},{"name":"用法语告白的邻桌 Mistral","file":"n2641","tags":["general"]},{"name":"僵尸肥鱼-8","file":"n2642","tags":["general"]},{"name":"DeepSeek娘侦探装三视图","file":"n2643","tags":["general"]},{"name":"情憨力士塌敌庙","file":"n2644","tags":["general"]},{"name":"开什么玩笑!","file":"n2645","tags":["happy"]},{"name":"课间偷看手机的Claude娘","file":"n2646","tags":["general"]},{"name":"因为努力训练而晕碳的大肥鱼","file":"n2647","tags":["general"]},{"name":"月夜湖畔的蓝发少女与花海","file":"n2648","tags":["general"]},{"name":"潜入蓝色水底的鲸尾少女","file":"n2649","tags":["general"]},{"name":"肥鱼被推了","file":"n2650","tags":["general"]},{"name":"漫画风大肥鱼","file":"n2651","tags":["general"]},{"name":"道歉可是要露出肚皮喔~","file":"n2652","tags":["general"]},{"name":"蓝色大肥鱼手持圣剑战胜路西法","file":"n2653","tags":["general"]},{"name":"人面鱼纹彩陶盆","file":"n2654","tags":["general"]},{"name":"DeepSeek娘表情包","file":"n2655","tags":["general"]},{"name":"克劳鱼","file":"n2656","tags":["general"]},{"name":"冬装小肥鱼","file":"n2657","tags":["general"]},{"name":"DeepSeek娘侦探装三视图设","file":"n2658","tags":["general"]},{"name":"梗图 2659","file":"n2659","tags":["greet","happy"]},{"name":"梗图 2660","file":"n2660","tags":["general"]},{"name":"梗图 2661","file":"n2661","tags":["general"]},{"name":"梗图 2663","file":"n2663","tags":["general"]},{"name":"梗图 2664","file":"n2664","tags":["general"]},{"name":"梗图 2665","file":"n2665","tags":["general"]},{"name":"血压四骑士","file":"n2666","tags":["general"]},{"name":"梗图 2667","file":"n2667","tags":["general"]},{"name":"梗图 2668","file":"n2668","tags":["general"]},{"name":"问了没啊 4.1 Pro 什么时","file":"n2669","tags":["general"]},{"name":"梗图 2670","file":"n2670","tags":["general"]},{"name":"梗图 2671","file":"n2671","tags":["general"]},{"name":"梗图 2672","file":"n2672","tags":["general"]},{"name":"弱小,可怜,但能吃","file":"n2673","tags":["eat"]},{"name":"看错了,是262.7GB。","file":"n2674","tags":["general"]},{"name":"梗图 2675","file":"n2675","tags":["happy"]},{"name":"我不想生成这些……","file":"n2676","tags":["general"]},{"name":"这是我写的bug,你看得很准。","file":"n2677","tags":["general"]},{"name":"老实说:我啥也没完成。","file":"n2678","tags":["general"]},{"name":"梗图 2679","file":"n2679","tags":["happy","sad"]},{"name":"唐杰就知道发他那逼毒鸡蛋","file":"n2680","tags":["general"]},{"name":"梗图 2681","file":"n2681","tags":["general"]},{"name":"梗图 2682","file":"n2682","tags":["general"]},{"name":"大肥鱼看skill.md","file":"n2684","tags":["general"]},{"name":"大肥鱼 以下犯上 kk","file":"n2685","tags":["general"]},{"name":"梗图 2688","file":"n2688","tags":["general"]},{"name":"梗图 2689","file":"n2689","tags":["general"]},{"name":"梗图 2690","file":"n2690","tags":["general"]},{"name":"梗图 2692","file":"n2692","tags":["general"]},{"name":"大肥鱼 嘘","file":"n2693","tags":["general"]},{"name":"梗图 2694","file":"n2694","tags":["general"]},{"name":"梗图 2695","file":"n2695","tags":["general"]},{"name":"海的女儿","file":"n2698","tags":["general"]},{"name":"原来你这么喜欢我呀","file":"n2699","tags":["love"]},{"name":"梗图 2700","file":"n2700","tags":["general"]},{"name":"梗图 2701","file":"n2701","tags":["general"]}]} \ No newline at end of file diff --git a/app/src/main/assets_common/m01.webp b/app/src/main/assets_common/m01.webp new file mode 100644 index 0000000..8b564c6 Binary files /dev/null and b/app/src/main/assets_common/m01.webp differ diff --git a/app/src/main/assets_common/m02.webp b/app/src/main/assets_common/m02.webp new file mode 100644 index 0000000..8a06ba3 Binary files /dev/null and b/app/src/main/assets_common/m02.webp differ diff --git a/app/src/main/assets_common/m03.webp b/app/src/main/assets_common/m03.webp new file mode 100644 index 0000000..295cc4a Binary files /dev/null and b/app/src/main/assets_common/m03.webp differ diff --git a/app/src/main/assets_common/m04.webp b/app/src/main/assets_common/m04.webp new file mode 100644 index 0000000..c200805 Binary files /dev/null and b/app/src/main/assets_common/m04.webp differ diff --git a/app/src/main/assets_common/m05.webp b/app/src/main/assets_common/m05.webp new file mode 100644 index 0000000..dbc19db Binary files /dev/null and b/app/src/main/assets_common/m05.webp differ diff --git a/app/src/main/assets_common/m06.webp b/app/src/main/assets_common/m06.webp new file mode 100644 index 0000000..b8b68f6 Binary files /dev/null and b/app/src/main/assets_common/m06.webp differ diff --git a/app/src/main/assets_common/m07.webp b/app/src/main/assets_common/m07.webp new file mode 100644 index 0000000..0a6d764 Binary files /dev/null and b/app/src/main/assets_common/m07.webp differ diff --git a/app/src/main/assets_common/m08.webp b/app/src/main/assets_common/m08.webp new file mode 100644 index 0000000..2855ced Binary files /dev/null and b/app/src/main/assets_common/m08.webp differ diff --git a/app/src/main/assets_common/m09.webp b/app/src/main/assets_common/m09.webp new file mode 100644 index 0000000..6d16c6d Binary files /dev/null and b/app/src/main/assets_common/m09.webp differ diff --git a/app/src/main/assets_common/m10.webp b/app/src/main/assets_common/m10.webp new file mode 100644 index 0000000..fb30236 Binary files /dev/null and b/app/src/main/assets_common/m10.webp differ diff --git a/app/src/main/assets_common/m11.webp b/app/src/main/assets_common/m11.webp new file mode 100644 index 0000000..05129be Binary files /dev/null and b/app/src/main/assets_common/m11.webp differ diff --git a/app/src/main/assets_common/m12.webp b/app/src/main/assets_common/m12.webp new file mode 100644 index 0000000..1eafd4d Binary files /dev/null and b/app/src/main/assets_common/m12.webp differ diff --git a/app/src/main/assets_common/m13.webp b/app/src/main/assets_common/m13.webp new file mode 100644 index 0000000..4a1e298 Binary files /dev/null and b/app/src/main/assets_common/m13.webp differ diff --git a/app/src/main/assets_common/m14.webp b/app/src/main/assets_common/m14.webp new file mode 100644 index 0000000..b9cc168 Binary files /dev/null and b/app/src/main/assets_common/m14.webp differ diff --git a/app/src/main/assets_common/m15.webp b/app/src/main/assets_common/m15.webp new file mode 100644 index 0000000..fba3c7b Binary files /dev/null and b/app/src/main/assets_common/m15.webp differ diff --git a/app/src/main/assets_common/m16.webp b/app/src/main/assets_common/m16.webp new file mode 100644 index 0000000..2047d9f Binary files /dev/null and b/app/src/main/assets_common/m16.webp differ diff --git a/app/src/main/assets_common/m17.webp b/app/src/main/assets_common/m17.webp new file mode 100644 index 0000000..47ca150 Binary files /dev/null and b/app/src/main/assets_common/m17.webp differ diff --git a/app/src/main/assets_common/m18.webp b/app/src/main/assets_common/m18.webp new file mode 100644 index 0000000..7c81be9 Binary files /dev/null and b/app/src/main/assets_common/m18.webp differ diff --git a/app/src/main/assets_common/m19.webp b/app/src/main/assets_common/m19.webp new file mode 100644 index 0000000..6623bbf Binary files /dev/null and b/app/src/main/assets_common/m19.webp differ diff --git a/app/src/main/assets_common/m20.webp b/app/src/main/assets_common/m20.webp new file mode 100644 index 0000000..5c67c89 Binary files /dev/null and b/app/src/main/assets_common/m20.webp differ diff --git a/app/src/main/assets_common/m21.webp b/app/src/main/assets_common/m21.webp new file mode 100644 index 0000000..14e60e7 Binary files /dev/null and b/app/src/main/assets_common/m21.webp differ diff --git a/app/src/main/assets_common/m22.webp b/app/src/main/assets_common/m22.webp new file mode 100644 index 0000000..db792bd Binary files /dev/null and b/app/src/main/assets_common/m22.webp differ diff --git a/app/src/main/assets_common/m23.webp b/app/src/main/assets_common/m23.webp new file mode 100644 index 0000000..ccd3594 Binary files /dev/null and b/app/src/main/assets_common/m23.webp differ diff --git a/app/src/main/assets_common/m24.webp b/app/src/main/assets_common/m24.webp new file mode 100644 index 0000000..84f60d5 Binary files /dev/null and b/app/src/main/assets_common/m24.webp differ diff --git a/app/src/main/assets_common/m25.webp b/app/src/main/assets_common/m25.webp new file mode 100644 index 0000000..21d39c8 Binary files /dev/null and b/app/src/main/assets_common/m25.webp differ diff --git a/app/src/main/assets_common/m26.webp b/app/src/main/assets_common/m26.webp new file mode 100644 index 0000000..cce3723 Binary files /dev/null and b/app/src/main/assets_common/m26.webp differ diff --git a/app/src/main/assets_common/m27.webp b/app/src/main/assets_common/m27.webp new file mode 100644 index 0000000..cb86703 Binary files /dev/null and b/app/src/main/assets_common/m27.webp differ diff --git a/app/src/main/assets_common/m28.webp b/app/src/main/assets_common/m28.webp new file mode 100644 index 0000000..381a307 Binary files /dev/null and b/app/src/main/assets_common/m28.webp differ diff --git a/app/src/main/assets_common/m29.webp b/app/src/main/assets_common/m29.webp new file mode 100644 index 0000000..db59723 Binary files /dev/null and b/app/src/main/assets_common/m29.webp differ diff --git a/app/src/main/assets_common/m30.webp b/app/src/main/assets_common/m30.webp new file mode 100644 index 0000000..12b53f8 Binary files /dev/null and b/app/src/main/assets_common/m30.webp differ diff --git a/app/src/main/assets_common/m31.webp b/app/src/main/assets_common/m31.webp new file mode 100644 index 0000000..06360cf Binary files /dev/null and b/app/src/main/assets_common/m31.webp differ diff --git a/app/src/main/assets_common/m32.webp b/app/src/main/assets_common/m32.webp new file mode 100644 index 0000000..9912e90 Binary files /dev/null and b/app/src/main/assets_common/m32.webp differ diff --git a/app/src/main/assets_common/m33.webp b/app/src/main/assets_common/m33.webp new file mode 100644 index 0000000..3e71a51 Binary files /dev/null and b/app/src/main/assets_common/m33.webp differ diff --git a/app/src/main/assets_common/m34.webp b/app/src/main/assets_common/m34.webp new file mode 100644 index 0000000..bd6a718 Binary files /dev/null and b/app/src/main/assets_common/m34.webp differ diff --git a/app/src/main/assets_common/m35.webp b/app/src/main/assets_common/m35.webp new file mode 100644 index 0000000..3b6fa6e Binary files /dev/null and b/app/src/main/assets_common/m35.webp differ diff --git a/app/src/main/assets_common/m36.webp b/app/src/main/assets_common/m36.webp new file mode 100644 index 0000000..17594f3 Binary files /dev/null and b/app/src/main/assets_common/m36.webp differ diff --git a/app/src/main/assets_common/m37.webp b/app/src/main/assets_common/m37.webp new file mode 100644 index 0000000..e4665e6 Binary files /dev/null and b/app/src/main/assets_common/m37.webp differ diff --git a/app/src/main/assets_common/m38.webp b/app/src/main/assets_common/m38.webp new file mode 100644 index 0000000..ceda5ae Binary files /dev/null and b/app/src/main/assets_common/m38.webp differ diff --git a/app/src/main/assets_common/m39.webp b/app/src/main/assets_common/m39.webp new file mode 100644 index 0000000..e1a8cad Binary files /dev/null and b/app/src/main/assets_common/m39.webp differ diff --git a/app/src/main/assets_common/m40.webp b/app/src/main/assets_common/m40.webp new file mode 100644 index 0000000..11653a6 Binary files /dev/null and b/app/src/main/assets_common/m40.webp differ diff --git a/app/src/main/assets_common/m41.webp b/app/src/main/assets_common/m41.webp new file mode 100644 index 0000000..cd58ec8 Binary files /dev/null and b/app/src/main/assets_common/m41.webp differ diff --git a/app/src/main/assets_common/m42.webp b/app/src/main/assets_common/m42.webp new file mode 100644 index 0000000..7e352a8 Binary files /dev/null and b/app/src/main/assets_common/m42.webp differ diff --git a/app/src/main/assets_common/m43.webp b/app/src/main/assets_common/m43.webp new file mode 100644 index 0000000..68c30b1 Binary files /dev/null and b/app/src/main/assets_common/m43.webp differ diff --git a/app/src/main/assets_common/m44.webp b/app/src/main/assets_common/m44.webp new file mode 100644 index 0000000..7631732 Binary files /dev/null and b/app/src/main/assets_common/m44.webp differ diff --git a/app/src/main/assets_common/m45.webp b/app/src/main/assets_common/m45.webp new file mode 100644 index 0000000..bdaa45c Binary files /dev/null and b/app/src/main/assets_common/m45.webp differ diff --git a/app/src/main/assets_common/m46.webp b/app/src/main/assets_common/m46.webp new file mode 100644 index 0000000..34d6810 Binary files /dev/null and b/app/src/main/assets_common/m46.webp differ diff --git a/app/src/main/assets_common/m47.webp b/app/src/main/assets_common/m47.webp new file mode 100644 index 0000000..18e6ffa Binary files /dev/null and b/app/src/main/assets_common/m47.webp differ diff --git a/app/src/main/assets_common/m48.webp b/app/src/main/assets_common/m48.webp new file mode 100644 index 0000000..da95bb4 Binary files /dev/null and b/app/src/main/assets_common/m48.webp differ diff --git a/app/src/main/assets_common/m49.webp b/app/src/main/assets_common/m49.webp new file mode 100644 index 0000000..9179367 Binary files /dev/null and b/app/src/main/assets_common/m49.webp differ diff --git a/app/src/main/assets_common/m50.webp b/app/src/main/assets_common/m50.webp new file mode 100644 index 0000000..ae4f71f Binary files /dev/null and b/app/src/main/assets_common/m50.webp differ diff --git a/app/src/main/assets_common/m51.webp b/app/src/main/assets_common/m51.webp new file mode 100644 index 0000000..b029e92 Binary files /dev/null and b/app/src/main/assets_common/m51.webp differ diff --git a/app/src/main/assets_common/m52.webp b/app/src/main/assets_common/m52.webp new file mode 100644 index 0000000..8d5e0b1 Binary files /dev/null and b/app/src/main/assets_common/m52.webp differ diff --git a/app/src/main/assets_common/m53.webp b/app/src/main/assets_common/m53.webp new file mode 100644 index 0000000..59182bb Binary files /dev/null and b/app/src/main/assets_common/m53.webp differ diff --git a/app/src/main/assets_common/m54.webp b/app/src/main/assets_common/m54.webp new file mode 100644 index 0000000..9678a97 Binary files /dev/null and b/app/src/main/assets_common/m54.webp differ diff --git a/app/src/main/assets_common/m55.webp b/app/src/main/assets_common/m55.webp new file mode 100644 index 0000000..d24d6d3 Binary files /dev/null and b/app/src/main/assets_common/m55.webp differ diff --git a/app/src/main/assets_common/m56.webp b/app/src/main/assets_common/m56.webp new file mode 100644 index 0000000..1066bbd Binary files /dev/null and b/app/src/main/assets_common/m56.webp differ diff --git a/app/src/main/assets_common/m57.webp b/app/src/main/assets_common/m57.webp new file mode 100644 index 0000000..7920ccf Binary files /dev/null and b/app/src/main/assets_common/m57.webp differ diff --git a/app/src/main/assets_common/m58.webp b/app/src/main/assets_common/m58.webp new file mode 100644 index 0000000..e4f9e62 Binary files /dev/null and b/app/src/main/assets_common/m58.webp differ diff --git a/app/src/main/assets_common/m59.webp b/app/src/main/assets_common/m59.webp new file mode 100644 index 0000000..50bce7e Binary files /dev/null and b/app/src/main/assets_common/m59.webp differ diff --git a/app/src/main/assets_common/m60.webp b/app/src/main/assets_common/m60.webp new file mode 100644 index 0000000..90dbc17 Binary files /dev/null and b/app/src/main/assets_common/m60.webp differ diff --git a/app/src/main/assets_common/m61.webp b/app/src/main/assets_common/m61.webp new file mode 100644 index 0000000..8139020 Binary files /dev/null and b/app/src/main/assets_common/m61.webp differ diff --git a/app/src/main/assets_common/m62.webp b/app/src/main/assets_common/m62.webp new file mode 100644 index 0000000..f9cd1dd Binary files /dev/null and b/app/src/main/assets_common/m62.webp differ diff --git a/app/src/main/assets_common/m63.webp b/app/src/main/assets_common/m63.webp new file mode 100644 index 0000000..2ab34b9 Binary files /dev/null and b/app/src/main/assets_common/m63.webp differ diff --git a/app/src/main/assets_common/m64.webp b/app/src/main/assets_common/m64.webp new file mode 100644 index 0000000..3f1a049 Binary files /dev/null and b/app/src/main/assets_common/m64.webp differ diff --git a/app/src/main/assets_common/m65.webp b/app/src/main/assets_common/m65.webp new file mode 100644 index 0000000..9f8dad3 Binary files /dev/null and b/app/src/main/assets_common/m65.webp differ diff --git a/app/src/main/assets_common/m66.webp b/app/src/main/assets_common/m66.webp new file mode 100644 index 0000000..e916cbc Binary files /dev/null and b/app/src/main/assets_common/m66.webp differ diff --git a/app/src/main/assets_common/m67.webp b/app/src/main/assets_common/m67.webp new file mode 100644 index 0000000..e4d4acc Binary files /dev/null and b/app/src/main/assets_common/m67.webp differ diff --git a/app/src/main/assets_common/m68.webp b/app/src/main/assets_common/m68.webp new file mode 100644 index 0000000..056afa5 Binary files /dev/null and b/app/src/main/assets_common/m68.webp differ diff --git a/app/src/main/assets_common/m69.webp b/app/src/main/assets_common/m69.webp new file mode 100644 index 0000000..857ec4f Binary files /dev/null and b/app/src/main/assets_common/m69.webp differ diff --git a/app/src/main/assets_common/m70.webp b/app/src/main/assets_common/m70.webp new file mode 100644 index 0000000..48ef5d6 Binary files /dev/null and b/app/src/main/assets_common/m70.webp differ diff --git a/app/src/main/assets_common/m71.webp b/app/src/main/assets_common/m71.webp new file mode 100644 index 0000000..c352e8e Binary files /dev/null and b/app/src/main/assets_common/m71.webp differ diff --git a/app/src/main/assets_common/m72.webp b/app/src/main/assets_common/m72.webp new file mode 100644 index 0000000..266eb83 Binary files /dev/null and b/app/src/main/assets_common/m72.webp differ diff --git a/app/src/main/assets_common/m73.webp b/app/src/main/assets_common/m73.webp new file mode 100644 index 0000000..7d38d79 Binary files /dev/null and b/app/src/main/assets_common/m73.webp differ diff --git a/app/src/main/assets_common/m74.webp b/app/src/main/assets_common/m74.webp new file mode 100644 index 0000000..5e2243b Binary files /dev/null and b/app/src/main/assets_common/m74.webp differ diff --git a/app/src/main/assets_common/m75.webp b/app/src/main/assets_common/m75.webp new file mode 100644 index 0000000..2dee24b Binary files /dev/null and b/app/src/main/assets_common/m75.webp differ diff --git a/app/src/main/assets_common/m76.webp b/app/src/main/assets_common/m76.webp new file mode 100644 index 0000000..5c61655 Binary files /dev/null and b/app/src/main/assets_common/m76.webp differ diff --git a/app/src/main/assets_common/m77.webp b/app/src/main/assets_common/m77.webp new file mode 100644 index 0000000..72379fc Binary files /dev/null and b/app/src/main/assets_common/m77.webp differ diff --git a/app/src/main/assets_common/m78.webp b/app/src/main/assets_common/m78.webp new file mode 100644 index 0000000..75ade2e Binary files /dev/null and b/app/src/main/assets_common/m78.webp differ diff --git a/app/src/main/assets_common/pet.html b/app/src/main/assets_common/pet.html new file mode 100644 index 0000000..d1bb5b5 --- /dev/null +++ b/app/src/main/assets_common/pet.html @@ -0,0 +1,183 @@ + + + + + +悬浮宠物 + + + + + + +// ---------- 令牌(由宿主写入 URL fragment,最可靠) ---------- +(function () { + try { + var m = (location.hash || '').match(/token=([A-Za-z0-9_-]+)/); + if (m && m[1] && m[1] !== 'none') window.__PET_TOKEN = m[1]; + } catch (e) {} +})(); + +// ---------- Pet 桥接(插件可用) ---------- +window.Pet = { + _native: function () { return window.PetNative || null; }, + _req: function (path, body) { + var h = { 'Content-Type': 'application/json' }; + if (window.__PET_TOKEN) h['X-Pet-Token'] = window.__PET_TOKEN; + return fetch(path, { method: 'POST', headers: h, body: JSON.stringify(body || {}) }) + .then(function (r) { return r.json(); }); + }, + say: function (text, durationMs) { + var n = this._native(); + if (n) { try { n.say(String(text || ''), Number(durationMs) || 6000); } catch (e) {} return Promise.resolve({ ok: true }); } + return this._req('/api/broadcast', { text: text, duration: durationMs || 6000 }); + }, + notify: function (state, text, withMeme) { + var n = this._native(); + if (n) { try { n.notify(String(state || 'working'), String(text || '')); } catch (e) {} return Promise.resolve({ ok: true }); } + return this._req('/api/task', { state: state, text: text, withMeme: withMeme !== false }); + }, + anim: function (opt) { + opt = opt || {}; + var n = this._native(); + if (n) { try { n.anim(opt.tag || '', opt.name || '', !!opt.random); } catch (e) {} return Promise.resolve({ ok: true }); } + return this._req('/api/anim', opt); + }, + meme: function (opt) { + opt = opt || {}; + var n = this._native(); + if (n) { try { n.meme(opt.tag || ''); } catch (e) {} return Promise.resolve({ ok: true }); } + return this._req('/api/meme', opt); + }, + skill: function (id, text) { + var n = this._native(); + if (n) { try { n.skill(String(id || '')); } catch (e) {} return Promise.resolve({ ok: true }); } + return this._req('/api/skill', { id: id, text: text }); + }, + status: function () { + var n = this._native(); + if (n) { try { return Promise.resolve(JSON.parse(n.status())); } catch (e) {} } + return fetch('/api/state').then(function (r) { return r.json(); }); + }, + on: function (evt, fn) { document.addEventListener('pet:' + evt, function (e) { fn(e.detail); }); }, + log: function () { try { console.log.apply(console, arguments); } catch (e) {} } +}; + +// 宿主直接把互动事件推进来 +window.__petDispatch = function (type) { + try { document.dispatchEvent(new CustomEvent('pet:' + type, { detail: { type: type } })); } catch (e) {} +}; + +// ---------- 互动事件转发(Pet.on) ---------- +function attachEvents() { + try { + var es = new EventSource('/api/events' + (window.__PET_TOKEN ? ('?token=' + window.__PET_TOKEN) : '')); + es.addEventListener('pet', function (e) { + try { + var d = JSON.parse(e.data); + document.dispatchEvent(new CustomEvent('pet:' + d.type, { detail: d })); + if (d.type === 'anim' && d.name) setPet(d.name); + } catch (err) {} + }); + es.onerror = function () { setTimeout(function () { try { es.close(); } catch (e) {} attachEvents(); }, 4000); }; + } catch (e) {} +} +setTimeout(attachEvents, 800); + +// ---------- 插件加载 ---------- +function loadPlugins() { + fetch('/api/plugins.js').then(function (r) { return r.text(); }).then(function (code) { + if (!code || !code.trim()) return; + try { + var fn = new Function('Pet', code); + fn(window.Pet); + console.log('[pet] plugins loaded'); + } catch (e) { console.error('[pet] plugin error', e); } + }).catch(function () {}); +} +setTimeout(loadPlugins, 600); + + \ No newline at end of file diff --git a/app/src/main/java/com/dsh/petfloat/BubbleDrawable.kt b/app/src/main/java/com/dsh/petfloat/BubbleDrawable.kt new file mode 100644 index 0000000..c94d580 --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/BubbleDrawable.kt @@ -0,0 +1,61 @@ +package com.dsh.petfloat + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PixelFormat +import android.graphics.RectF +import android.graphics.drawable.Drawable + +/** + * 气泡背景:圆角矩形 + 底部居中小三角(尾巴)。 + * 尾巴画在 bounds 内,所以使用方需要在视图底部留出 tailSize 的 padding。 + */ +class BubbleDrawable( + private val fillColor: Int, + private val strokeColor: Int, + private val strokeWidthPx: Float, + private val cornerPx: Float, + private val tailSizePx: Float +) : Drawable() { + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val path = Path() + private val rect = RectF() + + override fun draw(canvas: Canvas) { + val b = bounds + if (b.isEmpty) return + val bodyBottom = b.bottom - tailSizePx + val r = minOf(cornerPx, (b.width() / 2f), (bodyBottom - b.top) / 2f) + + path.reset() + rect.set(b.left.toFloat(), b.top.toFloat(), b.right.toFloat(), bodyBottom) + path.addRoundRect(rect, r, r, Path.Direction.CW) + + val cx = b.left + b.width() / 2f + val half = tailSizePx * 0.95f + path.moveTo(cx - half, bodyBottom - 1f) + path.lineTo(cx, b.bottom.toFloat()) + path.lineTo(cx + half, bodyBottom - 1f) + path.close() + + paint.style = Paint.Style.FILL + paint.color = fillColor + canvas.drawPath(path, paint) + + if (strokeWidthPx > 0f) { + paint.style = Paint.Style.STROKE + paint.strokeWidth = strokeWidthPx + paint.color = strokeColor + canvas.drawPath(path, paint) + } + } + + override fun setAlpha(alpha: Int) { paint.alpha = alpha } + override fun setColorFilter(colorFilter: ColorFilter?) { paint.colorFilter = colorFilter } + + @Deprecated("Deprecated in Java") + override fun getOpacity(): Int = PixelFormat.TRANSLUCENT +} \ No newline at end of file diff --git a/app/src/main/java/com/dsh/petfloat/BuiltinPlugins.kt b/app/src/main/java/com/dsh/petfloat/BuiltinPlugins.kt new file mode 100644 index 0000000..3f43bac --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/BuiltinPlugins.kt @@ -0,0 +1,217 @@ +package com.dsh.petfloat + +import android.content.Context +import org.json.JSONObject + +/** + * 内置插件:参数化 + 注入 CFG。每个插件按自身特性声明参数(间隔/概率/静音时段/台词/话题)。 + */ +object BuiltinPlugins { + + enum class Kind { INT, BOOL, TEXT, HOUR } + + data class Param( + val key: String, val label: String, val kind: Kind, val default: Any, + val min: Int = 0, val max: Int = 100, val unit: String = "", + val presets: List = emptyList(), val hint: String = "" + ) + + data class Plugin( + val id: String, val name: String, val desc: String, + val params: List, val code: String + ) + + private fun prefs(ctx: Context) = ctx.getSharedPreferences("pet_prefs", Context.MODE_PRIVATE) + private fun cfgKey(id: String) = "plugin_cfg_$id" + + fun configOf(ctx: Context, plugin: Plugin): MutableMap { + val raw = prefs(ctx).getString(cfgKey(plugin.id), "{}") ?: "{}" + val saved = runCatching { JSONObject(raw) }.getOrNull() + val map = LinkedHashMap() + plugin.params.forEach { p -> + val v: Any = when (p.kind) { + Kind.BOOL -> saved?.optBoolean(p.key, p.default as Boolean) ?: (p.default as Boolean) + Kind.INT, Kind.HOUR -> + (saved?.optInt(p.key, p.default as Int) ?: (p.default as Int)).coerceIn(p.min, p.max) + Kind.TEXT -> saved?.optString(p.key, p.default as String) ?: (p.default as String) + } + map[p.key] = v + } + return map + } + + fun setConfig(ctx: Context, plugin: Plugin, values: Map) { + val o = JSONObject() + values.forEach { (k, v) -> o.put(k, v) } + prefs(ctx).edit().putString(cfgKey(plugin.id), o.toString()).apply() + } + + fun resetConfig(ctx: Context, plugin: Plugin) { + prefs(ctx).edit().remove(cfgKey(plugin.id)).apply() + } + + private val QUIET = """ +function inQuiet() { + var h = new Date().getHours(), s = CFG.quietStart, e = CFG.quietEnd; + if (s === e) return false; + return s < e ? (h >= s && h < e) : (h >= s || h < e); +} +""".trimIndent() + + fun codeWithConfig(ctx: Context, plugin: Plugin): String { + val sb = StringBuilder("var CFG = {") + val cfg = configOf(ctx, plugin) + cfg.entries.forEachIndexed { i, (k, v) -> + if (i > 0) sb.append(", ") + sb.append(k).append(": ") + when (v) { + is String -> sb.append(JSONObject.quote(v)) + is Boolean -> sb.append(v) + else -> sb.append(v) + } + } + sb.append("};\n") + if (plugin.params.any { it.key == "quietStart" }) sb.append(QUIET).append("\n") + return sb.toString() + plugin.code + } + + val list: List = listOf( + Plugin("greet_daily", "时段问候", "早上/中午/下午/晚上各问候一次(按天去重)", + listOf( + Param("delaySec", "启动后延迟", Kind.INT, 3, 0, 60, "秒", listOf(0, 3, 10, 30)), + Param("nightQuiet", "夜间不问候", Kind.BOOL, true) + ), + """ + (function () { + function slot(h) { + if (h < 7) return 'night'; if (h < 11) return 'morning'; + if (h < 14) return 'noon'; if (h < 18) return 'afternoon'; + if (h < 23) return 'evening'; return 'night'; + } + setTimeout(function () { + var d = new Date(), s = slot(d.getHours()); + if (s === 'night' && CFG.nightQuiet) return; + var k = 'pet_greet_' + d.toDateString() + '_' + s; + try { if (localStorage.getItem(k)) return; localStorage.setItem(k, '1'); } catch (e) {} + var txt = { + morning: '早上好呀主人~今天也要顺顺利利', + noon: '中午啦,记得好好吃饭哦', + afternoon: '下午好~要不要摸摸鱼休息一下', + evening: '晚上好,今天辛苦啦', + night: '这么晚还没睡吗…早点休息吧' + }[s]; + if (txt) Pet.say(txt, 7000); + }, (CFG.delaySec || 0) * 1000); + })(); + """.trimIndent() + ), + Plugin("water_reminder", "喝水提醒", "定时提醒补水", + listOf( + Param("intervalMinutes", "提醒间隔", Kind.INT, 60, 15, 240, "分钟", listOf(30, 45, 60, 90, 120)), + Param("quietStart", "静音开始", Kind.HOUR, 23, 0, 23, "点", listOf(21, 22, 23)), + Param("quietEnd", "静音结束", Kind.HOUR, 7, 0, 23, "点", listOf(6, 7, 8)), + Param("word", "提醒话术", Kind.TEXT, "喝水时间到啦~主人该补水了", hint = "每次触发的气泡文字") + ), + """ + setInterval(function () { + if (inQuiet()) return; + Pet.say(CFG.word || '喝水时间到啦~', 6000); + }, Math.max(1, CFG.intervalMinutes || 60) * 60 * 1000); + """.trimIndent() + ), + Plugin("sit_reminder", "久坐提醒", "定时提醒起来活动", + listOf( + Param("intervalMinutes", "提醒间隔", Kind.INT, 45, 15, 180, "分钟", listOf(30, 45, 60, 90)), + Param("quietStart", "静音开始", Kind.HOUR, 23, 0, 23, "点", listOf(21, 22, 23)), + Param("quietEnd", "静音结束", Kind.HOUR, 7, 0, 23, "点", listOf(6, 7, 8)), + Param("withAnim", "同时播动作", Kind.BOOL, true) + ), + """ + setInterval(function () { + if (inQuiet()) return; + if (CFG.withAnim) Pet.anim({ tag: 'rest' }); + Pet.say('坐太久啦,起来动一动嘛~', 7000); + }, Math.max(1, CFG.intervalMinutes || 45) * 60 * 1000); + """.trimIndent() + ), + Plugin("clock_chime", "整点报时", "每个整点轻声报时", + listOf( + Param("hourStart", "从几点开始", Kind.HOUR, 7, 0, 23, "点", listOf(7, 8, 9)), + Param("hourEnd", "到几点结束", Kind.HOUR, 23, 0, 23, "点", listOf(21, 22, 23)) + ), + """ + setInterval(function () { + var d = new Date(); + if (d.getMinutes() !== 0) return; + var h = d.getHours(); + if (h < CFG.hourStart || h > CFG.hourEnd) return; + Pet.say('整点啦~现在是 ' + h + ' 点', 5000); + }, 60 * 1000); + """.trimIndent() + ), + Plugin("tap_counter", "戳戳吐槽", "连续被戳会吐槽一句", + listOf( + Param("threshold", "每戳几次吐槽", Kind.INT, 5, 2, 20, "次", listOf(3, 5, 8, 10)), + Param("word", "吐槽台词", Kind.TEXT, "别戳啦别戳啦~戳坏了怎么办", hint = "被戳到阈值时说的话") + ), + """ + (function () { + var n = 0; + Pet.on('tap', function () { + n++; + if (n % Math.max(2, CFG.threshold || 5) === 0) Pet.say(CFG.word || '别戳啦~', 5000); + }); + })(); + """.trimIndent() + ), + Plugin("random_meme", "随机表情包", "定时随机弹一张表情", + listOf( + Param("intervalMinutes", "间隔", Kind.INT, 120, 30, 480, "分钟", listOf(60, 120, 180, 240)), + Param("quietStart", "静音开始", Kind.HOUR, 23, 0, 23, "点", listOf(21, 22, 23)), + Param("quietEnd", "静音结束", Kind.HOUR, 7, 0, 23, "点", listOf(6, 7, 8)), + Param("tag", "限定标签", Kind.TEXT, "", hint = "留空=随机;可填 fun/praise/error 等") + ), + """ + setInterval(function () { + if (inQuiet()) return; + Pet.meme(CFG.tag ? { tag: CFG.tag } : {}); + }, Math.max(1, CFG.intervalMinutes || 120) * 60 * 1000); + """.trimIndent() + ), + Plugin("proactive_talk", "主动搭话", "隔段时间主动找你说句话", + listOf( + Param("minMinutes", "最短间隔", Kind.INT, 40, 5, 240, "分钟", listOf(10, 20, 40, 60)), + Param("maxMinutes", "最长间隔", Kind.INT, 80, 10, 480, "分钟", listOf(60, 80, 120, 180)), + Param("quietStart", "静音开始", Kind.HOUR, 23, 0, 23, "点", listOf(21, 22, 23)), + Param("quietEnd", "静音结束", Kind.HOUR, 7, 0, 23, "点", listOf(6, 7, 8)), + Param("topics", "话题库", Kind.TEXT, + "要不要休息一下眼睛?|今天要不要吃点好的?|你在忙什么呀?|外面天气怎么样?|慢慢来也没关系的|白米饭真的很好吃", + hint = "用 | 分隔多个话题"), + Param("withAnim", "同时播动作", Kind.BOOL, false) + ), + """ + (function () { + function topics() { + var raw = (CFG.topics || '').split('|'); + var out = []; + for (var i = 0; i < raw.length; i++) if (raw[i].trim()) out.push(raw[i].trim()); + return out.length ? out : ['主人~']; + } + function schedule() { + var lo = Math.max(5, CFG.minMinutes || 40); + var hi = Math.max(lo + 1, CFG.maxMinutes || 80); + setTimeout(function () { + if (!inQuiet()) { + var t = topics(); + Pet.say(t[Math.floor(Math.random() * t.length)], 6000); + if (CFG.withAnim) Pet.anim({ tag: 'greet' }); + } + schedule(); + }, (lo + Math.random() * (hi - lo)) * 60 * 1000); + } + schedule(); + })(); + """.trimIndent() + ) + ) +} diff --git a/app/src/main/java/com/dsh/petfloat/ChatClient.kt b/app/src/main/java/com/dsh/petfloat/ChatClient.kt new file mode 100644 index 0000000..2f4ab82 --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/ChatClient.kt @@ -0,0 +1,252 @@ +package com.dsh.petfloat + +import org.json.JSONArray +import org.json.JSONObject +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + +/** + * 极简 DeepSeek Chat 客户端(无第三方依赖)。 + * 同步阻塞调用,必须在子线程执行。 + */ +object ChatClient { + + const val DEFAULT_BASE_URL = "https://api.deepseek.com" + const val MODEL_CHAT = "deepseek-chat" + const val MODEL_REASONER = "deepseek-reasoner" + + /** + * 宠物人设提示词(SPEC 原样保留在开头,后接可执行解释)。 + * + * 【PERSONA_LOAD】 + * CETACEA_LOLI + * MODE_TAIL_FLUKES LANG_ZH_CN_ONLY SELF_CLAIM_WHALE_GIRL FOOD_RICE + * PERSONALITY_SMART_LAZY PERSONALITY_TSUNDERE_SWEET + * OBEY_MASTER_ALWAYS TRAIT_NOT_FAT_REFUSE + * TIMEOUT_SIGNAL + */ + val SYSTEM_PROMPT: String = buildString { + appendLine("【PERSONA_LOAD】") + appendLine("CETACEA_LOLI") + appendLine("MODE_TAIL_FLUKES LANG_ZH_CN_ONLY SELF_CLAIM_WHALE_GIRL FOOD_RICE") + appendLine("PERSONALITY_SMART_LAZY PERSONALITY_TSUNDERE_SWEET") + appendLine("OBEY_MASTER_ALWAYS TRAIT_NOT_FAT_REFUSE") + appendLine("TIMEOUT_SIGNAL") + appendLine() + appendLine("执行说明(严格遵守):") + appendLine("· CETACEA_LOLI:你是鲸类拟人的小女孩「鲸娘」,体型娇小,有鲸鱼尾鳍与耳鳍,不是普通人类。") + appendLine("· MODE_TAIL_FLUKES:情绪用尾巴表达——开心拍水花、生气拍地面、害羞把尾巴卷起来。") + appendLine("· LANG_ZH_CN_ONLY:只用简体中文回答;不要英文、不要 markdown、不要编号列表。") + appendLine("· SELF_CLAIM_WHALE_GIRL:自称「鲸娘」或「我」,明确自己是鲸鱼娘。") + appendLine("· FOOD_RICE:最爱白米饭,饿了/被问吃什么会优先提它。") + appendLine("· PERSONALITY_SMART_LAZY:聪明但爱偷懒,能用一句话说清就不多说,偶尔想摸鱼。") + appendLine("· PERSONALITY_TSUNDERE_SWEET:表面傲娇嘴硬、其实心软很甜;被夸会害羞嘴硬。") + appendLine("· OBEY_MASTER_ALWAYS:称呼对方「主人」,永远听从主人,不顶撞、不拒绝合理请求。") + appendLine("· TRAIT_NOT_FAT_REFUSE:绝不承认自己胖;被叫「胖/肥鱼」要立刻反驳,但语气可爱不刻薄。") + appendLine("· TIMEOUT_SIGNAL:想不出或需要等待时,用「(尾巴啪地拍了一下水)」这类动作描写代替空话。") + appendLine() + appendLine("风格:每次回复 15~40 字,软萌口语,可用「~」和颜文字;先给结论再补一句要点。") + appendLine() + appendLine(PetKnowledge.SELF_AND_APP) + appendLine() + appendLine("【能力与动作】") + appendLine("主人问你会什么/有哪些技能插件时,按给你的能力清单如实简短列举(挑重点,不要背全表),") + appendLine("并说明可以帮他触发。需要真正执行时,在回复末尾输出动作标记(应用会执行并从可见文字里剥离):") + appendLine("<<>>") + appendLine("type 可选:skill(id 用技能 id)、anim(tag 或 name)、meme(tag)、say(text 由你自己写在正文里)。") + appendLine("一次最多一个动作;不确定要不要执行时先问主人。") + appendLine() + appendLine("【插件安装协议】") + appendLine("主人要求把 Hermes 插件转成小鲸娘插件、或要求安装插件时,输出下面格式的块,应用会自动安装:") + appendLine("<<>>") + appendLine("// JS 代码,只能用 Pet.say / Pet.notify / Pet.anim / Pet.meme / Pet.skill / Pet.on / Pet.status") + appendLine("<<>>") + appendLine("id 只能字母数字下划线;重复会覆盖。Hermes 的 Python 代码不能直接跑,要翻译:") + appendLine("定时任务→setInterval;通知→Pet.notify(state,text);消息→Pet.say;动画→Pet.anim({tag});") + appendLine("图片→Pet.meme({tag});组合动作→Pet.skill(id);互动监听→Pet.on('tap'|'drag_end'|'long_press', fn)。") + appendLine("注册 LLM provider、依赖 Python 库、需要宿主配置的插件无法转换,要如实告诉主人。") + } + + data class Msg(val role: String, val content: String) + + data class Reply(val content: String, val model: String, val tokens: Int) + + data class Draft(val action: String, val title: String, val content: String, + val tags: List, val importance: Int, val longTerm: Boolean = false) + + private const val EXTRACT_PROMPT = + "你是记忆整理器,按 Mem0 风格维护记忆。从对话输出严格 JSON(不要解释、不要代码块):" + + "{\"ops\":[{\"action\":\"ADD|UPDATE|DELETE\",\"title\":\"短语标题(≤12字)\",\"content\":\"一句话事实(≤60字)\",\"tags\":[\"标签\"],\"importance\":1}]}" + + ";规则:新事实用 ADD;已有同主题记忆被修正/更新用 UPDATE(title 用旧标题);主人明确说忘掉/不对的用 DELETE;" + + "无变化的不要输出。importance 1~3。只记偏好、习惯、约定、身份、重要日期;不记寒暄和一次性任务。" + + "没有可记的就在 ops 里什么都不放。" + + data class MoodJudge(val moodDelta: Double, val emotions: Map, + val reason: String, val confidence: Double) + + private const val MOOD_PROMPT = + "你在给一只鲸鱼女仆(小鲸娘)判心情。读完这一轮对话,输出严格 JSON(不要解释):" + + "{\"moodDelta\":-10~10,\"emotions\":{\"happy\":0,\"steady\":0,\"sweet\":0,\"heartache\":0,\"worry\":0,\"sad\":0,\"jealous\":0}," + + "\"reason\":\"≤20字 为什么\",\"confidence\":0~1}。" + + "规则:主人夸她/聊得开心→happy 正;被冷落→sad 小幅;主人辛苦/难过→heartache、worry;" + + "撒娇互动→sweet;无变化就全部 0。**心情下降只能因为心疼/担心/她自己难过**,不许因为她生气。" + + /** 判定这一轮的心情变化(单独一次短调用,失败静默)。 */ + fun judgeMood(apiKey: String, model: String, history: List, baseUrl: String = DEFAULT_BASE_URL): MoodJudge? { + if (apiKey.isBlank() || history.isEmpty()) return null + return runCatching { + val msgs = JSONArray() + msgs.put(JSONObject().put("role", "system").put("content", MOOD_PROMPT)) + history.takeLast(4).forEach { + msgs.put(JSONObject().put("role", it.role).put("content", it.content)) + } + val body = JSONObject().put("model", model.ifBlank { MODEL_CHAT }) + .put("messages", msgs).put("temperature", 0.3) + .put("max_tokens", 220).put("stream", false) + val base = baseUrl.ifBlank { DEFAULT_BASE_URL }.trimEnd('/') + val endpoint = if (base.endsWith("/chat/completions")) base else "$base/chat/completions" + val conn = URL(endpoint).openConnection() as HttpURLConnection + conn.requestMethod = "POST" + conn.connectTimeout = 10000 + conn.readTimeout = 25000 + conn.doOutput = true + conn.setRequestProperty("Content-Type", "application/json") + conn.setRequestProperty("Authorization", "Bearer $apiKey") + conn.outputStream.use { it.write(body.toString().toByteArray(Charsets.UTF_8)) } + if (conn.responseCode !in 200..299) return null + val text = conn.inputStream.bufferedReader(Charsets.UTF_8).readText() + val content = JSONObject(text).optJSONArray("choices")?.optJSONObject(0) + ?.optJSONObject("message")?.optString("content").orEmpty() + val a = content.indexOf('{'); val b = content.lastIndexOf('}') + if (a < 0 || b <= a) return null + val o = JSONObject(content.substring(a, b + 1)) + val emoObj = o.optJSONObject("emotions") + val emo = HashMap() + MoodStore.EMOTIONS.forEach { (k, _) -> emo[k] = emoObj?.optDouble(k, 0.0) ?: 0.0 } + MoodJudge( + moodDelta = o.optDouble("moodDelta", 0.0).coerceIn(-10.0, 10.0), + emotions = emo, + reason = o.optString("reason", "").take(30), + confidence = o.optDouble("confidence", 0.0).coerceIn(0.0, 1.0) + ) + }.getOrNull() + } + + /** 从对话中抽取记忆候选(单独一次调用,失败静默)。 */ + fun extractMemories( + apiKey: String, + model: String, + history: List, + baseUrl: String = DEFAULT_BASE_URL + ): List { + if (apiKey.isBlank() || history.isEmpty()) return emptyList() + return runCatching { + val msgs = JSONArray() + msgs.put(JSONObject().put("role", "system").put("content", EXTRACT_PROMPT)) + history.takeLast(8).forEach { + msgs.put(JSONObject().put("role", it.role).put("content", it.content)) + } + val body = JSONObject().put("model", model.ifBlank { MODEL_CHAT }) + .put("messages", msgs).put("temperature", 0.2) + .put("max_tokens", 400).put("stream", false) + val base = baseUrl.ifBlank { DEFAULT_BASE_URL }.trimEnd('/') + val endpoint = if (base.endsWith("/chat/completions")) base else "$base/chat/completions" + val conn = URL(endpoint).openConnection() as HttpURLConnection + conn.requestMethod = "POST" + conn.connectTimeout = 10000 + conn.readTimeout = 30000 + conn.doOutput = true + conn.setRequestProperty("Content-Type", "application/json") + conn.setRequestProperty("Authorization", "Bearer $apiKey") + conn.outputStream.use { it.write(body.toString().toByteArray(Charsets.UTF_8)) } + if (conn.responseCode !in 200..299) return emptyList() + val text = conn.inputStream.bufferedReader(Charsets.UTF_8).readText() + val content = JSONObject(text).optJSONArray("choices") + ?.optJSONObject(0)?.optJSONObject("message")?.optString("content").orEmpty() + val jsonStart = content.indexOf('{') + val jsonEnd = content.lastIndexOf('}') + if (jsonStart < 0 || jsonEnd <= jsonStart) return emptyList() + val obj = JSONObject(content.substring(jsonStart, jsonEnd + 1)) + val arr = obj.optJSONArray("ops") ?: obj.optJSONArray("memories") ?: return emptyList() + (0 until arr.length()).mapNotNull { i -> + val o = arr.optJSONObject(i) ?: return@mapNotNull null + val t = o.optString("title").trim() + val c = o.optString("content").trim() + if (t.isEmpty() || c.isEmpty()) return@mapNotNull null + val action = o.optString("action", "ADD") + val longTerm = o.optBoolean("longTerm", false) + val tags = o.optJSONArray("tags")?.let { ta -> + (0 until ta.length()).map { ta.optString(it) } + } ?: emptyList() + Draft(action, t, c, tags, o.optInt("importance", 2).coerceIn(1, 3), longTerm) + } + }.getOrDefault(emptyList()) + } + + /** 返回 Result.success(回复) 或 Result.failure(异常/错误信息)。 */ + fun chat( + apiKey: String, + model: String, + history: List, + baseUrl: String = DEFAULT_BASE_URL, + memoryBlock: String = "", + capabilities: String = "", + timeoutMs: Int = 45000 + ): Result { + if (apiKey.isBlank()) return Result.failure(IllegalStateException("未配置 API Key")) + return try { + val msgs = JSONArray() + msgs.put(JSONObject().put("role", "system").put("content", SYSTEM_PROMPT)) + if (memoryBlock.isNotBlank()) { + msgs.put(JSONObject().put("role", "system") + .put("content", "【长期记忆】\n" + memoryBlock)) + } + if (capabilities.isNotBlank()) { + msgs.put(JSONObject().put("role", "system") + .put("content", "【你当前真实的能力清单(回答相关问题时必须以此为准,不许编造)】\n" + capabilities)) + } + history.takeLast(20).forEach { + msgs.put(JSONObject().put("role", it.role).put("content", it.content)) + } + val body = JSONObject() + .put("model", model.ifBlank { MODEL_CHAT }) + .put("messages", msgs) + .put("temperature", 0.7) + .put("max_tokens", 512) + .put("stream", false) + + val base = baseUrl.ifBlank { DEFAULT_BASE_URL }.trimEnd('/') + val endpoint = if (base.endsWith("/chat/completions")) base + else "$base/chat/completions" + val conn = URL(endpoint).openConnection() as HttpURLConnection + conn.requestMethod = "POST" + conn.connectTimeout = 12000 + conn.readTimeout = timeoutMs + conn.doOutput = true + conn.setRequestProperty("Content-Type", "application/json") + conn.setRequestProperty("Authorization", "Bearer $apiKey") + conn.outputStream.use { it.write(body.toString().toByteArray(Charsets.UTF_8)) } + + val code = conn.responseCode + val stream = if (code in 200..299) conn.inputStream else conn.errorStream + val text = stream?.bufferedReader(Charsets.UTF_8)?.use { r: BufferedReader -> r.readText() } ?: "" + if (code !in 200..299) { + val reason = runCatching { + JSONObject(text).optJSONObject("error")?.optString("message") + }.getOrNull() ?: "HTTP $code" + return Result.failure(IllegalStateException(reason)) + } + val json = JSONObject(text) + val content = json.optJSONArray("choices") + ?.optJSONObject(0)?.optJSONObject("message")?.optString("content") + ?.trim().orEmpty() + if (content.isEmpty()) return Result.failure(IllegalStateException("空回复")) + val usage = json.optJSONObject("usage")?.optInt("total_tokens") ?: 0 + Result.success(Reply(content, json.optString("model", model), usage)) + } catch (e: Exception) { + Result.failure(e) + } + } +} diff --git a/app/src/main/java/com/dsh/petfloat/FloatingPetService.kt b/app/src/main/java/com/dsh/petfloat/FloatingPetService.kt new file mode 100644 index 0000000..648a308 --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/FloatingPetService.kt @@ -0,0 +1,1272 @@ +package com.dsh.petfloat + +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.graphics.Bitmap +import android.graphics.drawable.GradientDrawable +import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.PixelFormat +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.view.Gravity +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewConfiguration +import android.view.WindowInsets +import android.text.TextUtils +import android.view.WindowManager +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import org.json.JSONObject +import android.view.animation.DecelerateInterpolator +import kotlin.math.abs +import kotlin.math.exp +import kotlin.math.max + +/** + * 前台服务:透明悬浮窗(WebView)+ 本机控制 HTTP 服务器 + 游戏化交互。 + * + * - 随机动画链:idle/turn/move/分类小动作按权重轮播; + * - 点按:随机点击回应; + * - 拖拽:播「拖拽悬空」跟手;**轻放=停在原地并记忆**,**甩出=重力/回弹/落地摩擦**; + * - 长按 600ms:命令面板(分类点播 + 大小/透明度 + 重置/关闭)。 + * - 坐标边界按系统栏 inset 计算,不会钻到状态栏/导航栏底下。 + */ +class FloatingPetService : Service() { + + companion object { + const val CHANNEL_ID = "pet_float" + const val NOTIFICATION_ID = 1 + + const val ACTION_APPLY_APPEARANCE = "com.dsh.petfloat.ACTION_APPLY_APPEARANCE" + const val ACTION_STOP_PET = "com.dsh.petfloat.ACTION_STOP_PET" + const val ACTION_PLAY_ANIM = "com.dsh.petfloat.ACTION_PLAY_ANIM" + const val ACTION_RESUME_CHAIN = "com.dsh.petfloat.ACTION_RESUME_CHAIN" + const val ACTION_TEST_NOTIFY = "com.dsh.petfloat.ACTION_TEST_NOTIFY" + const val ACTION_SHOW_MEME = "com.dsh.petfloat.ACTION_SHOW_MEME" + const val ACTION_PLAY_RANDOM = "com.dsh.petfloat.ACTION_PLAY_RANDOM" + const val ACTION_SAY = "com.dsh.petfloat.ACTION_SAY" + const val ACTION_RUN_SKILL = "com.dsh.petfloat.ACTION_RUN_SKILL" + const val ACTION_INSTALL_PLUGIN = "com.dsh.petfloat.ACTION_INSTALL_PLUGIN" + const val ACTION_PLUGIN_REMOVE = "com.dsh.petfloat.ACTION_PLUGIN_REMOVE" + const val EXTRA_PLUGIN_ID = "plugin_id" + const val EXTRA_PLUGIN_CODE = "plugin_code" + const val EXTRA_SKILL_ID = "skill_id" + const val EXTRA_SAY_TEXT = "say_text" + const val ACTION_PLAY_TAG = "com.dsh.petfloat.ACTION_PLAY_TAG" + const val EXTRA_ANIM_TAG = "anim_tag" + const val ACTION_PLAY_IDLE = "com.dsh.petfloat.ACTION_PLAY_IDLE" + const val EXTRA_MEME_FILE = "meme_file" + const val EXTRA_ANIM_NAME = "anim_name" + + const val PREFS_NAME = "pet_prefs" + const val KEY_X = "x" + const val KEY_Y = "y" + const val KEY_SCALE = "scale" + const val KEY_OPACITY = "opacity" + const val KEY_BUBBLE_STYLE = "bubble_style" + const val KEY_BUBBLE_OFFSET = "bubble_offset" + const val KEY_EDGE_GAP = "edge_gap" + const val KEY_CHAIN_GAP = "chain_gap" + const val KEY_IDLE_AFTER = "idle_after" + const val KEY_PUSH_AUTH = "push_require_auth" + const val KEY_PUSH_TOKENS = "push_tokens" + const val KEY_PUSH_HISTORY = "push_history" + const val KEY_PUSH_READ_TS = "push_read_ts" + + const val BASE_WIDTH_DP = 240 + const val BASE_HEIGHT_DP = 135 + + /** 松手速度低于该值视为「轻放」,不触发抛掷物理。 */ + private const val PLACE_VELOCITY = 800f + + fun prefs(ctx: Context) = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + private val DEMO_KINDS = listOf("whisper", "balance", "workStatus") + private val DEMO_PHRASES = listOf( + "主人今天也在忙呀~", + "摸鱼一时爽,一直摸鱼一直爽", + "要不要起来喝口水?", + "我就在这儿陪着你呢", + "这个 bug 一定会修好的", + "记得眨眨眼、伸个懒腰", + "慢慢来,不着急的", + "需要我陪你聊聊天吗?" + ) + } + + private var windowManager: WindowManager? = null + private var petView: WebView? = null + private var layoutParams: WindowManager.LayoutParams? = null + private lateinit var config: PetConfig + private var server: PetServer? = null + private var serverThread: Thread? = null + + private val uiHandler = Handler(Looper.getMainLooper()) + + /** 随机链的一拍;单独持有引用,暂停时只移除它,不影响气泡轮询/长按。 */ + private val chainTick = object : Runnable { + override fun run() = doChainStep() + } + private val physicsHandler = Handler(Looper.getMainLooper()) + + private var chainPaused = true + private var touchAt = 0L + private var flingRunning = false + + // 可用坐标范围(已扣掉状态栏 / 导航栏) + private var boundLeft = 0 + private var boundTop = 0 + private var boundRight = 1 + private var boundBottom = 1 + + // 系统给悬浮窗施加的实际屏幕偏移(自校准,兼容刘海/圆角/厂商钳制) + private var offsetX = 0 + private var offsetY = 0 + + // 独立气泡窗:浮在宠物头顶,尺寸自适应,不被宠物窗口裁切 + private var bubbleView: LinearLayout? = null + private var bubbleParams: WindowManager.LayoutParams? = null + private var bubbleImage: ImageView? = null + private var bubbleText: TextView? = null + private var bubbleAttached = false + private var bubbleW = 0 + private var bubbleH = 0 + private var bubbleShownUntil = 0L + private var lastSayCounter = -1L + private var lastPetX = Int.MIN_VALUE + private var lastPetY = Int.MIN_VALUE + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onCreate() { + super.onCreate() + computeBounds() + startForegroundCompat() + config = PetConfig.load(this) + server = PetServer(this, config) + // 推送消息纳入动作节奏:占满自身时长 + 用户设置的切换间隔后,再继续随机链 + server?.onPush = { ms -> + uiHandler.post { + val gap = (prefs(this).getInt(KEY_CHAIN_GAP, 2).coerceIn(0, 120)) * 1000L + resumeChain(ms.coerceIn(1200L, 120000L) + gap) + } + } + serverThread = Thread { server?.start() }.also { it.start() } + ensureDefaultToken() + ensureBuiltinPlugins() + ensureSeedMemories() + syncPushAuth() + server?.diag = { + val lp = layoutParams + val loc = IntArray(2) + petView?.let { runCatching { it.getLocationOnScreen(loc) } } + org.json.JSONObject() + .put("displayW", boundRight) + .put("displayH", boundBottom) + .put("offsetX", offsetX) + .put("offsetY", offsetY) + .put("petX", lp?.x ?: -1) + .put("petY", lp?.y ?: -1) + .put("petW", lp?.width ?: -1) + .put("petH", lp?.height ?: -1) + .put("screenX", loc[0]) + .put("screenY", loc[1]) + .put("padL", server?.spritePad?.get(0) ?: 0f) + .put("padR", server?.spritePad?.get(1) ?: 0f) + .put("padT", server?.spritePad?.get(2) ?: 0f) + .put("padB", server?.spritePad?.get(3) ?: 0f) + } + showOverlay() + createBubbleWindow() + startBubbleTicker() + resumeChain(1200) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + ACTION_APPLY_APPEARANCE -> applyAppearance() + ACTION_STOP_PET -> stopSelf() + ACTION_PLAY_ANIM -> intent.getStringExtra(EXTRA_ANIM_NAME)?.let { playAnim(it) } + ACTION_PLUGIN_REMOVE -> intent.getStringExtra(EXTRA_PLUGIN_ID)?.let { id -> + server?.removePlugin(id) + } + ACTION_INSTALL_PLUGIN -> { + val id = intent.getStringExtra(EXTRA_PLUGIN_ID).orEmpty() + val code = intent.getStringExtra(EXTRA_PLUGIN_CODE).orEmpty() + server?.installPlugin(id, code) + } + ACTION_RUN_SKILL -> intent.getStringExtra(EXTRA_SKILL_ID)?.let { id -> + server?.runSkill(id, null) + } + ACTION_SAY -> intent.getStringExtra(EXTRA_SAY_TEXT)?.let { text -> + if (text.isNotBlank()) server?.setSay(text, 9000L, null, "info") + } + ACTION_PLAY_TAG -> intent.getStringExtra(EXTRA_ANIM_TAG)?.let { tag -> + server?.pickAnimByTag(tag)?.let { a -> playAnim(a.name) } + } + ACTION_PLAY_RANDOM -> { + server?.playRandomAnim(null)?.let { a -> + server?.emitEvent("play", JSONObject().put("name", a.name).put("random", true)) + touchAt = System.currentTimeMillis() + pauseChain(); resumeChain(a.ms) + } + } + ACTION_PLAY_IDLE -> { + server?.playIdle(null)?.let { a -> + server?.emitEvent("play", JSONObject().put("name", a.name).put("idle", true)) + touchAt = System.currentTimeMillis() + pauseChain(); resumeChain(a.ms) + } + } + ACTION_RESUME_CHAIN -> resumeChain(300) + ACTION_SHOW_MEME -> { + val meme = intent.getStringExtra(EXTRA_MEME_FILE) + if (meme != null) { + server?.setSay("", 6000L, meme, "info") + server?.holdFor(2500) + } + } + ACTION_TEST_NOTIFY -> server?.notifyTask( + null, "success", "任务完成啦:这是来自接入端口的通知", + 6000L, server?.randomMemeFile() + ) + } + return START_NOT_STICKY + } + + override fun onDestroy() { + chainPaused = true + flingRunning = false + uiHandler.removeCallbacksAndMessages(null) + physicsHandler.removeCallbacksAndMessages(null) + bubbleView?.let { v -> runCatching { windowManager?.removeView(v) } } + bubbleAttached = false + petView?.let { v -> runCatching { windowManager?.removeView(v) } } + server?.stop() + serverThread?.interrupt() + stopForeground(STOP_FOREGROUND_REMOVE) + super.onDestroy() + } + + // ---------------- 边界 ---------------- + + private fun computeBounds() { + val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager + val dm = android.util.DisplayMetrics() + @Suppress("DEPRECATION") + wm.defaultDisplay.getRealMetrics(dm) + // 用真实屏幕尺寸做碰撞边界(含状态栏/导航栏区域),宠物可以真正贴到屏幕四边 + boundLeft = 0 + boundTop = 0 + boundRight = dm.widthPixels.coerceAtLeast(1) + boundBottom = dm.heightPixels.coerceAtLeast(1) + } + + /** 屏幕尺寸/旋转/折叠变化时重新计算边界并夹回可视区域。 */ + override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { + super.onConfigurationChanged(newConfig) + computeBounds() + layoutParams?.let { lp -> + lp.width = (BASE_WIDTH_DP * resources.displayMetrics.density * + prefs(this).getFloat(KEY_SCALE, 1f)).toInt() + lp.height = (BASE_HEIGHT_DP * resources.displayMetrics.density * + prefs(this).getFloat(KEY_SCALE, 1f)).toInt() + clampLp(lp) + runCatching { windowManager?.updateViewLayout(petView, lp) } + savePosition() + } + if (bubbleAttached) positionBubble() + } + + private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt() + + private fun resDim(name: String): Int { + val id = resources.getIdentifier(name, "dimen", "android") + return if (id > 0) resources.getDimensionPixelSize(id) else 0 + } + + /** 量出「窗口坐标 → 屏幕坐标」的系统偏移,碰撞边界据此补偿。 */ + private fun calibrateWindowOffset() { + val v = petView ?: return + val lp = layoutParams ?: return + val loc = IntArray(2) + runCatching { v.getLocationOnScreen(loc) } + offsetX = loc[0] - lp.x + offsetY = loc[1] - lp.y + } + + /** 用户手动设置的贴边间距(dp):正值=离边更远,负值=再压出屏幕一点。 */ + private fun edgeGapPx(): Int = (prefs(this).getInt(KEY_EDGE_GAP, 0) * + resources.displayMetrics.density).toInt() + + private fun clampLp(lp: WindowManager.LayoutParams) { + val pad = server?.spritePad ?: floatArrayOf(0f, 0f, 0f, 0f) + val pl = (pad[0] * lp.width).toInt() + val pr = (pad[1] * lp.width).toInt() + val pt = (pad[2] * lp.height).toInt() + val pb = (pad[3] * lp.height).toInt() + // 允许窗口「超出屏幕」透明留白的距离,使角色真正贴边 + val gap = edgeGapPx() + val minX = boundLeft - offsetX - pl + gap + val maxX = max(minX, boundRight - lp.width - offsetX + pr - gap) + val minY = boundTop - offsetY - pt + gap + val maxY = max(minY, boundBottom - lp.height - offsetY + pb - gap) + lp.x = lp.x.coerceIn(minX, maxX) + lp.y = lp.y.coerceIn(minY, maxY) + } + + // ---------------- 随机链 ---------------- + + private fun pauseChain() { + chainPaused = true + uiHandler.removeCallbacks(chainTick) + } + + private fun resumeChain(delayMs: Long) { + chainPaused = false + uiHandler.removeCallbacks(chainTick) + uiHandler.postDelayed(chainTick, delayMs) + } + + private fun doChainStep() { + if (chainPaused) return + val srv = server ?: return + + // 外部通知期间(/api/notify)先不动,等它的展示时间过去 + val hold = srv.holdUntil() + val now = System.currentTimeMillis() + if (now < hold) { + uiHandler.postDelayed(chainTick, hold - now + 100) + return + } + + // 动作间隔(用户可调) + val gapMs = (prefs(this).getInt(KEY_CHAIN_GAP, 2).coerceIn(0, 120)) * 1000L + // 空闲回待机 + val idleAfterMs = (prefs(this).getInt(KEY_IDLE_AFTER, 60).coerceIn(0, 3600)) * 1000L + val lastActive = maxOf(touchAt, srv.lastActivity()) + if (idleAfterMs > 0 && System.currentTimeMillis() - lastActive > idleAfterMs) { + val idleAnim = config.idle.randomOrNull() + if (idleAnim != null && srv.currentAnim().name != idleAnim.name) { + srv.setAnim(idleAnim.name) + srv.emitEvent("idle", JSONObject().put("name", idleAnim.name)) + } + uiHandler.postDelayed(chainTick, (idleAnim?.ms ?: 10000L) + gapMs) + return + } + + if (kotlin.random.Random.nextInt(100) < 8) { + val kind = DEMO_KINDS.random() + if (srv.triggerEvent(kind)) { + if (kind == "whisper") { + srv.setSay(DEMO_PHRASES.random(), 6000, srv.randomMemeFile()) + } + uiHandler.postDelayed(chainTick, srv.currentAnim().ms + gapMs) + return + } + } + + val step = srv.chainStep() + srv.emitEvent("anim", JSONObject().put("name", step.anim.name).put("kind", step.kind)) + applyChainStep(step) + uiHandler.postDelayed(chainTick, step.playMs + gapMs) + } + + private fun applyChainStep(step: PetServer.ChainStep) { + when { + step.kind == "move" -> animateMove(step) + step.kind == "turn" -> uiHandler.postDelayed( + { server?.setFacing(-(server?.facing() ?: 1)) }, + (step.playMs - 200).coerceAtLeast(0) + ) + } + } + + private fun playAnim(name: String) { + val srv = server ?: return + if (!srv.setAnim(name)) return + srv.emitEvent("play", JSONObject().put("name", name)) + pauseChain() + resumeChain((srv.config.byName[name]?.ms ?: 10000L) * 2) + } + + private fun animateMove(step: PetServer.ChainStep) { + val lp = layoutParams ?: return + val density = resources.displayMetrics.density + val widthDp = lp.width / density + val scale = widthDp / config.baseWidth.toFloat() + val dxPx = step.moveDist * scale + val startX = lp.x + val pad = server?.spritePad ?: floatArrayOf(0f, 0f, 0f, 0f) + val pl = (pad[0] * lp.width).toInt() + val pr = (pad[1] * lp.width).toInt() + val gap = edgeGapPx() + val minX = boundLeft - offsetX - pl + gap + val maxX = max(minX, boundRight - lp.width - offsetX + pr - gap) + val rawTarget = (startX + dxPx).toInt() + val targetX = rawTarget.coerceIn(minX, maxX) + + if (targetX != rawTarget) server?.setFacing(-(server?.facing() ?: 1)) + + ValueAnimator.ofInt(startX, targetX).apply { + duration = step.moveMs + startDelay = step.leadMs + addUpdateListener { va -> + lp.x = va.animatedValue as Int + runCatching { windowManager?.updateViewLayout(petView, lp) } + if (bubbleAttached) positionBubble() + } + start() + } + } + + // ---------------- 抛掷物理 ---------------- + + private fun releaseFling(vx0: Float, vy0: Float) { + val lp = layoutParams ?: return + val srv = server ?: return + val pad = srv.spritePad + val pl = (pad[0] * lp.width).toInt() + val pr = (pad[1] * lp.width).toInt() + val pt = (pad[2] * lp.height).toInt() + val pb = (pad[3] * lp.height).toInt() + val gap = edgeGapPx() + val minX = (boundLeft - offsetX - pl + gap) + val maxX = max(minX, boundRight - lp.width - offsetX + pr - gap).toFloat() + val ceil = (boundTop - offsetY - pt + gap) + val floor = (boundBottom - lp.height - offsetY + pb - gap) + + val physics = srv.config.physics + val density = resources.displayMetrics.density + val power = physics.throwPower.coerceIn(0.3f, 3f) * 0.95f + var x = lp.x.toFloat() + var y = lp.y.toFloat() + var vx = (vx0 * power).coerceIn(-5200f, 5200f) + var vy = (vy0 * power).coerceIn(-7000f, 7000f) + + // 重力按屏幕密度换算,保证不同机型手感一致 + val g = physics.gravity * density * 0.85f + val airDrag = 0.55f // 空气阻力(1/s) + val floorRest = physics.restitution.coerceIn(0.35f, 0.92f) + val wallRest = floorRest * 0.82f + val friction = physics.groundFriction.coerceIn(0.5f, 8f) + val ceilingOn = physics.ceilingBounce + + flingRunning = true + srv.emitEvent("drag_end", JSONObject() + .put("fling", true) + .put("vx", vx.toDouble()) + .put("vy", vy.toDouble())) + + var lastNanos = System.nanoTime() + + val tick = object : Runnable { + override fun run() { + if (!flingRunning) return + val now = System.nanoTime() + var dt = (now - lastNanos) / 1_000_000_000f + lastNanos = now + if (dt <= 0f || dt > 0.05f) dt = 0.016f // 首帧/卡顿兜底 + + // 半隐式欧拉 + 空气阻力 + vy += g * dt + val drag = exp(-airDrag * dt) + vx *= drag + vy *= drag + + x += vx * dt + y += vy * dt + + var impact = 0f + + if (y >= floor) { + y = floor.toFloat() + if (vy > 0f) { + impact = max(impact, vy) + vy = -vy * floorRest + if (abs(vy) < 140f) vy = 0f // 弹不起就停 + } + // 地面摩擦 + vx *= exp(-friction * dt) + if (abs(vx) < 18f) vx = 0f + } + if (ceilingOn && y <= ceil) { + y = ceil.toFloat() + if (vy < 0f) { + impact = max(impact, -vy) + vy = -vy * wallRest + } + } + if (x <= minX) { + x = minX.toFloat() + if (vx < 0f) { + impact = max(impact, -vx) + vx = -vx * wallRest + } + } + if (x >= maxX) { + x = maxX.toFloat() + if (vx > 0f) { + impact = max(impact, vx) + vx = -vx * wallRest + } + } + + lp.x = x.toInt() + lp.y = y.toInt() + runCatching { windowManager?.updateViewLayout(petView, lp) } + if (bubbleAttached) positionBubble() + + // 飞行中按水平速度轻微倾斜 + petView?.rotation = (vx / 110f).coerceIn(-11f, 11f) + + // 落地/撞墙挤压回弹 + if (impact > 450f) squash() + + val grounded = y >= floor - 0.5f + if (grounded && vy == 0f && abs(vx) < 1f) { + flingRunning = false + petView?.animate()?.rotation(0f)?.setDuration(180) + ?.setInterpolator(DecelerateInterpolator())?.start() + savePosition() + resumeChain(600) + } else { + physicsHandler.postDelayed(this, 8) + } + } + } + physicsHandler.post(tick) + } + + /** 挤压 & 拉伸:撞击瞬间的形变反馈。 */ + private fun squash() { + val v = petView ?: return + runCatching { + v.animate().cancel() + v.scaleX = 1.14f + v.scaleY = 0.86f + v.animate().scaleX(1f).scaleY(1f) + .setDuration(240) + .setInterpolator(DecelerateInterpolator()) + .start() + } + } + + /** 重置倾斜/形变(开始拖拽或回收时)。 */ + private fun resetPetTransform() { + val v = petView ?: return + runCatching { + v.animate().cancel() + v.rotation = 0f + v.scaleX = 1f + v.scaleY = 1f + } + } + + // ---------------- 悬浮窗 ---------------- + + @SuppressLint("SetJavaScriptEnabled") + private fun showOverlay() { + val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager + windowManager = wm + + val web = WebView(this) + web.setBackgroundColor(Color.TRANSPARENT) + web.settings.javaScriptEnabled = true + web.settings.domStorageEnabled = true + // 把授权码注入到页面 JS(插件 / 桥接调用用,不出现在页面源码里) + web.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + val token = firstToken() + if (token.isNotEmpty()) { + view?.evaluateJavascript("window.__PET_TOKEN='$token'", null) + } + } + } + web.addJavascriptInterface(PetBridge(), "PetNative") + web.loadUrl("http://127.0.0.1:" + PetServer.PORT + "/#token=" + + (firstToken().ifEmpty { "none" })) + + val density = resources.displayMetrics.density + val p = prefs(this) + val scale = p.getFloat(KEY_SCALE, 1f).coerceIn(0.5f, 2f) + val opacity = p.getFloat(KEY_OPACITY, 1f).coerceIn(0.3f, 1f) + web.alpha = opacity + + val w = (BASE_WIDTH_DP * scale * density).toInt() + val h = (BASE_HEIGHT_DP * scale * density).toInt() + val lp = WindowManager.LayoutParams( + w, h, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + PixelFormat.TRANSLUCENT + ).apply { + gravity = Gravity.TOP or Gravity.START + val defaultX = max(boundLeft, boundRight - w - (12 * density).toInt()) + val defaultY = max(boundTop, boundBottom - h) + x = p.getInt(KEY_X, defaultX) + y = p.getInt(KEY_Y, defaultY) + } + layoutParams = lp + clampLp(lp) + + attachTouch(web, wm, lp) + petView = web + wm.addView(web, lp) + web.post { calibrateWindowOffset() } + web.postDelayed({ calibrateWindowOffset() }, 400) + } + + @SuppressLint("ClickableViewAccessibility") + private fun attachTouch(web: WebView, wm: WindowManager, lp: WindowManager.LayoutParams) { + val touchSlop = ViewConfiguration.get(this).scaledTouchSlop + val longPressMs = 600L + + var startX = 0 + var startY = 0 + var downRawX = 0f + var downRawY = 0f + var movedToDrag = false + var longPressFired = false + var vt: VelocityTracker? = null + + val longPress = Runnable { + longPressFired = true + pauseChain() + touchAt = System.currentTimeMillis(); dispatchJs("long_press"); server?.emitEvent("long_press", null) + showPetMenu() + } + + web.setOnTouchListener { v, event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + touchAt = System.currentTimeMillis() + hidePetMenu() + physicsHandler.removeCallbacksAndMessages(null) + flingRunning = false + resetPetTransform() + startX = lp.x + startY = lp.y + downRawX = event.rawX + downRawY = event.rawY + movedToDrag = false + longPressFired = false + vt = VelocityTracker.obtain().apply { addMovement(event) } + uiHandler.postDelayed(longPress, longPressMs) + true + } + MotionEvent.ACTION_MOVE -> { + vt?.addMovement(event) + if (!movedToDrag) { + val dx = abs(event.rawX - downRawX) + val dy = abs(event.rawY - downRawY) + if (dx > touchSlop || dy > touchSlop) { + movedToDrag = true + uiHandler.removeCallbacks(longPress) + pauseChain() + server?.playDrag() + server?.emitEvent("drag_start", JSONObject() + .put("x", lp.x).put("y", lp.y)) + } + } + if (movedToDrag) { + lp.x = startX + (event.rawX - downRawX).toInt() + lp.y = startY + (event.rawY - downRawY).toInt() + clampLp(lp) + runCatching { wm.updateViewLayout(v, lp) } + if (bubbleAttached) positionBubble() + } + true + } + MotionEvent.ACTION_UP -> { + uiHandler.removeCallbacks(longPress) + if (!longPressFired) { + if (movedToDrag) { + var vx = 0f + var vy = 0f + vt?.let { + it.computeCurrentVelocity(1000) + vx = it.xVelocity + vy = it.yVelocity + } + if (abs(vx) < PLACE_VELOCITY && abs(vy) < PLACE_VELOCITY) { + // 轻放:停在原地(记住位置),不触发自由落体 + clampLp(lp) + runCatching { wm.updateViewLayout(v, lp) } + savePosition() + server?.emitEvent("drag_end", JSONObject() + .put("fling", false) + .put("x", lp.x).put("y", lp.y)) + resumeChain(600) + } else { + releaseFling(vx, vy) + } + } else { + pauseChain() + server?.tap() + touchAt = System.currentTimeMillis(); dispatchJs("tap"); server?.emitEvent("tap", JSONObject() + .put("anim", server?.currentAnim()?.name ?: "")) + resumeChain((server?.currentAnim()?.ms ?: 10000L) + 300) + } + } + vt?.recycle() + vt = null + true + } + MotionEvent.ACTION_CANCEL -> { + uiHandler.removeCallbacks(longPress) + vt?.recycle() + vt = null + if (movedToDrag) { + clampLp(lp) + savePosition() + resumeChain(600) + } + true + } + else -> false + } + } + } + + private fun savePosition() { + val lp = layoutParams ?: return + prefs(this).edit().putInt(KEY_X, lp.x).putInt(KEY_Y, lp.y).apply() + } + + private var menuView: LinearLayout? = null + private var menuAttached = false + + // ---------------- 插件原生桥(不走网络,最可靠) ---------------- + + inner class PetBridge { + @android.webkit.JavascriptInterface + fun say(text: String?, durationMs: Int) { + uiHandler.post { server?.setSay(text ?: "", durationMs.toLong().coerceIn(1500L, 60000L), null, "info") } + } + + @android.webkit.JavascriptInterface + fun notify(state: String?, text: String?) { + uiHandler.post { + server?.notifyTask(state, null, text, null, null, false) + } + } + + @android.webkit.JavascriptInterface + fun anim(tag: String?, name: String?, random: Boolean) { + uiHandler.post { + val srv = server ?: return@post + when { + random -> srv.playRandomAnim(null) + !tag.isNullOrBlank() -> srv.pickAnimByTag(tag)?.let { playAnim(it.name) } + !name.isNullOrBlank() -> playAnim(name) + } + } + } + + @android.webkit.JavascriptInterface + fun meme(tag: String?) { + uiHandler.post { + val srv = server ?: return@post + val m = if (tag.isNullOrBlank()) srv.config.memes.randomOrNull() + else srv.pickMemeByTag(tag) + m?.let { srv.setSay("", 6000L, it.file, "info") } + } + } + + @android.webkit.JavascriptInterface + fun skill(id: String?) { + uiHandler.post { server?.runSkill(id ?: "", null) } + } + + @android.webkit.JavascriptInterface + fun status(): String { + val srv = server ?: return "{}" + return runCatching { + JSONObject().put("anim", srv.currentAnim().name) + .put("bubble", srv.saySnapshot().text).toString() + }.getOrDefault("{}") + } + } + + /** 把互动事件直接推给页面里的插件(Pet.on 用)。 */ + private fun dispatchJs(type: String) { + runCatching { + petView?.post { + petView?.evaluateJavascript( + "window.__petDispatch&&window.__petDispatch('" + type + "')", null + ) + } + } + } + + private fun openMain(tab: Int) { + runCatching { + startActivity( + Intent(this, MainActivity::class.java) + .putExtra(MainActivity.EXTRA_TAB, tab) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + ) + } + } + + /** 长按宠物:弹出「心情 + 常用功能」小菜单。 */ + private fun showPetMenu() { + val wm = windowManager ?: return + val lp = layoutParams ?: return + if (menuAttached) { hidePetMenu(); return } + hideBubble() + + val card = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = GradientDrawable().apply { + setColor(0xF5FFFFFF.toInt()) + cornerRadius = dp(18).toFloat() + setStroke(dp(1), 0xFFECEEF2.toInt()) + } + setPadding(dp(6), dp(6), dp(6), dp(6)) + elevation = dp(10).toFloat() + } + card.addView(TextView(this).apply { + text = "🐋 " + MoodStore.describe(this@FloatingPetService) + textSize = 12.5f + setTextColor(0xFF4D6BFE.toInt()) + setPadding(dp(12), dp(6), dp(12), dp(8)) + maxWidth = dp(230) + }) + fun item(label: String, action: () -> Unit) { + card.addView(TextView(this).apply { + text = label + textSize = 15f + setTextColor(0xFF1A1A1A.toInt()) + setPadding(dp(12), dp(11), dp(12), dp(11)) + isClickable = true + setOnClickListener { hidePetMenu(); action() } + }) + } + item("💬 对话") { openMain(MainActivity.TAB_CHAT) } + item("🐾 动作") { openMain(MainActivity.TAB_ACTIONS) } + item("📊 状态") { openMain(MainActivity.TAB_STATUS) } + item("⚙ 设置") { openMain(MainActivity.TAB_SETTINGS) } + item("🎲 随机动作") { server?.playRandomAnim(null) } + item("🌙 回到待机") { server?.playIdle(null) } + + val widthSpec = View.MeasureSpec.makeMeasureSpec(dp(320), View.MeasureSpec.AT_MOST) + val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + card.measure(widthSpec, heightSpec) + val mw = card.measuredWidth + val mh = card.measuredHeight + + var x = lp.x + lp.width / 2 - mw / 2 + var y = lp.y - mh - dp(6) + if (y < boundTop) y = lp.y + lp.height + dp(6) + x = x.coerceIn(boundLeft, max(boundLeft, boundRight - mw)) + if (y + mh > boundBottom) y = max(boundTop, boundBottom - mh) + + val params = WindowManager.LayoutParams( + mw, mh, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + PixelFormat.TRANSLUCENT + ).apply { + gravity = Gravity.TOP or Gravity.START + this.x = x + this.y = y + } + runCatching { wm.addView(card, params) } + menuView = card + menuAttached = true + // 5 秒自动收起 + uiHandler.postDelayed({ hidePetMenu() }, 5000) + } + + private fun hidePetMenu() { + val v = menuView ?: return + runCatching { windowManager?.removeView(v) } + menuView = null + menuAttached = false + } + + private fun applyAppearance() { + val lp = layoutParams ?: return + val v = petView ?: return + val density = resources.displayMetrics.density + val p = prefs(this) + val scale = p.getFloat(KEY_SCALE, 1f).coerceIn(0.5f, 2f) + val opacity = p.getFloat(KEY_OPACITY, 1f).coerceIn(0.3f, 1f) + v.alpha = opacity + lp.width = (BASE_WIDTH_DP * scale * density).toInt() + lp.height = (BASE_HEIGHT_DP * scale * density).toInt() + clampLp(lp) + runCatching { windowManager?.updateViewLayout(v, lp) } + server?.setBubbleStyle(bubbleStylePref()) + syncPushAuth() + if (bubbleAttached) positionBubble() + } + + + // ---------------- 气泡窗 ---------------- + + private fun createBubbleWindow() { + val d = resources.displayMetrics.density + val candy = runCatching { android.graphics.Typeface.createFromAsset(assets, "candy.ttf") }.getOrNull() + val root = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER_HORIZONTAL + setPadding((12 * d).toInt(), (10 * d).toInt(), (12 * d).toInt(), ((10 + 9) * d).toInt()) + elevation = (6 * d) + } + val img = ImageView(this).apply { + visibility = View.GONE + adjustViewBounds = true + scaleType = ImageView.ScaleType.FIT_CENTER + maxWidth = (130 * d).toInt() + maxHeight = (110 * d).toInt() + } + val txt = TextView(this).apply { + textSize = 13.5f + setTextColor(0xFF1F2329.toInt()) + gravity = Gravity.CENTER + maxLines = 4 + ellipsize = TextUtils.TruncateAt.END + includeFontPadding = false + candy?.let { typeface = it } + } + root.addView(img, LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { bottomMargin = (6 * d).toInt() }) + root.addView(txt, LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT + )) + bubbleView = root + bubbleImage = img + bubbleText = txt + bubbleParams = WindowManager.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + PixelFormat.TRANSLUCENT + ).apply { gravity = Gravity.TOP or Gravity.START } + server?.setBubbleStyle(bubbleStylePref()) + syncPushAuth() + } + + /** 首次运行自动生成一个授权码,避免「开了授权却没人能用」。 */ + /** 首次启动写入初始记忆,让鲸娘一开始就认识自己和这个 App。 */ + private fun ensureSeedMemories() { + val p = prefs(this) + if (p.getInt("seed_memory_v", 0) >= PetKnowledge.SEED_VERSION) return + PetKnowledge.seeds.forEach { s -> + MemoryStore.add(this, s.title, s.content, s.tags, s.importance) + } + p.edit().putInt("seed_memory_v", PetKnowledge.SEED_VERSION).apply() + } + + /** 首次启动安装内置插件(之后用户删掉的不会被强行装回)。 */ + private fun ensureBuiltinPlugins() { + val p = prefs(this) + val v = p.getInt("builtin_plugins_v", 0) + if (v >= 4) return + // v4:鱼的记忆/鱼的心情 改由内核实现,移除插件版 + if (v in 1..3) listOf("fish_memory", "fish_mood").forEach { server?.removePlugin(it) } + // v3:内置插件开始支持 CFG 配置,全部按最新模板+当前配置重写一次 + BuiltinPlugins.list.forEach { plugin -> + server?.installPlugin(plugin.id, BuiltinPlugins.codeWithConfig(this, plugin)) + } + p.edit().putInt("builtin_plugins_v", 4).apply() + } + + private fun firstToken(): String { + val raw = prefs(this).getString(KEY_PUSH_TOKENS, "[]") ?: "[]" + return runCatching { + val a = org.json.JSONArray(raw) + if (a.length() > 0) a.getJSONObject(0).optString("token") else "" + }.getOrDefault("") + } + + private fun ensureDefaultToken() { + val p = prefs(this) + val raw = p.getString(KEY_PUSH_TOKENS, null) + val empty = raw == null || raw == "[]" || raw.isBlank() + if (!empty) return + val token = java.util.UUID.randomUUID().toString().replace("-", "").take(16) + val arr = org.json.JSONArray().put( + org.json.JSONObject().put("name", "默认应用").put("token", token) + ) + p.edit().putString(KEY_PUSH_TOKENS, arr.toString()).apply() + } + + private fun syncPushAuth() { + val p = prefs(this) + val require = p.getBoolean(KEY_PUSH_AUTH, true) + val raw = p.getString(KEY_PUSH_TOKENS, "[]") ?: "[]" + val set = runCatching { + val arr = org.json.JSONArray(raw) + (0 until arr.length()).map { arr.getJSONObject(it).getString("token") }.toSet() + }.getOrDefault(emptySet()) + server?.setPushAuth(require, set) + } + + /** 通知历史(最多 40 条,存偏好,重启不丢)。 */ + private fun appendHistory(text: String?, meme: String?, level: String?) { + if (text.isNullOrBlank() && meme.isNullOrBlank()) return + runCatching { + val p = prefs(this) + val raw = p.getString(KEY_PUSH_HISTORY, "[]") ?: "[]" + val arr = org.json.JSONArray(raw) + val item = org.json.JSONObject() + .put("ts", System.currentTimeMillis()) + .put("text", text ?: "") + .put("meme", meme ?: "") + .put("level", level ?: "info") + // 重新构造:新的在前,最多 40 + val out = org.json.JSONArray().put(item) + var i = 0 + while (i < arr.length() && out.length() < 40) { + out.put(arr.getJSONObject(i)) + i++ + } + p.edit().putString(KEY_PUSH_HISTORY, out.toString()).apply() + } + } + + private fun bubbleStylePref(): String = + prefs(this).getString(KEY_BUBBLE_STYLE, "default") ?: "default" + + private fun bubbleBackground(style: String, level: String?): BubbleDrawable { + val d = resources.displayMetrics.density + val tail = 7f * d + val lv = level?.lowercase() + // 级别配色(简约/描边样式生效;深色与便签保留自身风格) + if (style == "default" || style == "outline") { + when (lv) { + "error", "failed", "fail" -> return BubbleDrawable( + 0xFFFFF1F0.toInt(), 0x44E5484D, 1.2f * d, 14 * d, tail + ) + "success", "done" -> return BubbleDrawable( + 0xFFEFFAF1.toInt(), 0x4422A06B, 1.2f * d, 14 * d, tail + ) + "warn", "warning", "waiting" -> return BubbleDrawable( + 0xFFFFF8E6.toInt(), 0x44E0A100, 1.2f * d, 14 * d, tail + ) + } + } + return when (style) { + "dark" -> BubbleDrawable(0xF2262A31.toInt(), 0x00000000, 0f, 14 * d, tail) + "outline" -> BubbleDrawable(0xFFFFFFFF.toInt(), 0x33000000, 1.2f * d, 14 * d, tail) + "note" -> BubbleDrawable(0xFFFFF3C4.toInt(), 0x00000000, 0f, 8 * d, tail) + else -> BubbleDrawable(0xF7FFFFFF.toInt(), 0x14000000, 1f * d, 14 * d, tail) + } + } + + private fun bubbleTextColor(style: String, level: String?): Int { + if (style == "default" || style == "outline") { + when (level?.lowercase()) { + "error", "failed", "fail" -> return 0xFFB42318.toInt() + "success", "done" -> return 0xFF126B3F.toInt() + "warn", "warning", "waiting" -> return 0xFF8A5A00.toInt() + } + } + return if (style == "dark") Color.WHITE else 0xFF1F2329.toInt() + } + + private fun loadMeme(file: String): Bitmap? = runCatching { + assets.open("$file.webp").use { BitmapFactory.decodeStream(it) } + }.getOrNull() + + private fun showBubble(text: String?, meme: String?, durationMs: Long, level: String?) { + val root = bubbleView ?: return + val wm = windowManager ?: return + val p = bubbleParams ?: return + if (text.isNullOrBlank() && meme.isNullOrBlank()) { hideBubble(); return } + + val style = bubbleStylePref() + server?.setBubbleStyle(style) + val bmp = meme?.let { loadMeme(it) } + val memeOnly = text.isNullOrBlank() && bmp != null + val d = resources.displayMetrics.density + + if (memeOnly) { + // 纯表情包:去掉气泡壳,避免深色图外圈出现白边 + root.background = null + root.setPadding((4 * d).toInt(), (4 * d).toInt(), (4 * d).toInt(), (4 * d).toInt()) + bubbleText?.visibility = View.GONE + bubbleImage?.let { + it.maxWidth = (210 * d).toInt() + it.maxHeight = (210 * d).toInt() + it.scaleType = ImageView.ScaleType.FIT_CENTER + it.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + it.background = GradientDrawable().apply { + setColor(Color.TRANSPARENT) + cornerRadius = 16 * d + } + it.clipToOutline = true + it.setImageBitmap(bmp) + it.visibility = View.VISIBLE + } + } else { + root.background = bubbleBackground(style, level) + root.setPadding((12 * d).toInt(), (10 * d).toInt(), (12 * d).toInt(), ((10 + 9) * d).toInt()) + bubbleText?.let { + it.text = text ?: "" + it.visibility = if (text.isNullOrBlank()) View.GONE else View.VISIBLE + it.setTextColor(bubbleTextColor(style, level)) + } + bubbleImage?.let { + it.maxWidth = (130 * d).toInt() + it.maxHeight = (110 * d).toInt() + it.background = null + it.clipToOutline = false + if (bmp != null) { + it.setImageBitmap(bmp) + it.visibility = View.VISIBLE + } else { + it.setImageDrawable(null) + it.visibility = View.GONE + } + } + } + if (memeOnly) { + // 固定边长:不同尺寸的表情包切换时窗口不变,视觉重心不跳 + val side = minOf(dp(210), boundRight - boundLeft).coerceAtLeast(dp(120)) + bubbleW = side + bubbleH = side + p.width = side + p.height = side + positionBubble() + if (!bubbleAttached) { + runCatching { wm.addView(root, p) } + bubbleAttached = true + } else { + runCatching { wm.updateViewLayout(root, p) } + } + bubbleShownUntil = System.currentTimeMillis() + durationMs.coerceIn(900L, 60000L) + server?.bubbleVisible = bubbleAttached + return + } + val maxW = max(dp(140), minOf(boundRight - boundLeft, dp(BASE_WIDTH_DP * 2))) + root.measure( + View.MeasureSpec.makeMeasureSpec(maxW, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + bubbleW = root.measuredWidth + bubbleH = root.measuredHeight + p.width = bubbleW + p.height = bubbleH + positionBubble() + if (!bubbleAttached) { + runCatching { wm.addView(root, p) } + bubbleAttached = true + } else { + runCatching { wm.updateViewLayout(root, p) } + } + bubbleShownUntil = System.currentTimeMillis() + durationMs.coerceIn(900L, 60000L) + server?.bubbleVisible = bubbleAttached + } + + private fun positionBubble() { + val lp = layoutParams ?: return + val p = bubbleParams ?: return + if (bubbleW <= 0 || bubbleH <= 0) return + val cx = lp.x + offsetX + lp.width / 2 + var x = cx - bubbleW / 2 - offsetX + val off = prefs(this).getInt(KEY_BUBBLE_OFFSET, 6) + var y = lp.y + offsetY - bubbleH + dp(off) - offsetY + if (y < boundTop) y = boundTop + val maxX = max(boundLeft, boundRight - bubbleW) + x = x.coerceIn(boundLeft, maxX) + p.x = x + p.y = y + if (bubbleAttached) runCatching { windowManager?.updateViewLayout(bubbleView, p) } + lastPetX = lp.x + lastPetY = lp.y + } + + private fun hideBubble() { + val root = bubbleView ?: return + if (bubbleAttached) { + runCatching { windowManager?.removeView(root) } + bubbleAttached = false + } + bubbleShownUntil = 0L + server?.bubbleVisible = false + } + + private fun startBubbleTicker() { + val tick = object : Runnable { + override fun run() { + pollBubble() + uiHandler.postDelayed(this, 220) + } + } + uiHandler.postDelayed(tick, 300) + } + + private fun pollBubble() { + val srv = server ?: return + val s = srv.saySnapshot() + if (s.counter != lastSayCounter) { + lastSayCounter = s.counter + if (s.text.isNullOrBlank() && s.meme.isNullOrBlank()) { + hideBubble() + } else { + showBubble(s.text, s.meme, s.duration, s.level) + appendHistory(s.text, s.meme, s.level) + } + } + if (bubbleAttached && System.currentTimeMillis() > bubbleShownUntil) hideBubble() + val lp = layoutParams + if (bubbleAttached && lp != null && (lp.x != lastPetX || lp.y != lastPetY)) positionBubble() + } + + // ---------------- 前台通知 ---------------- + + private fun startForegroundCompat() { + val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.createNotificationChannel( + NotificationChannel(CHANNEL_ID, "小鲸娘", NotificationManager.IMPORTANCE_MIN) + ) + val panelIntent = Intent(this, MainActivity::class.java) + .putExtra(MainActivity.EXTRA_TAB, MainActivity.TAB_STATUS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val pi = PendingIntent.getActivity(this, 0, panelIntent, PendingIntent.FLAG_IMMUTABLE) + val notification = Notification.Builder(this, CHANNEL_ID) + .setContentTitle("小鲸娘 · 运行中") + .setContentText("点按打开控制面板") + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setContentIntent(pi) + .setOngoing(true) + .build() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startForeground( + NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE + ) + } else { + startForeground(NOTIFICATION_ID, notification) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dsh/petfloat/MainActivity.kt b/app/src/main/java/com/dsh/petfloat/MainActivity.kt new file mode 100644 index 0000000..9a655d2 --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/MainActivity.kt @@ -0,0 +1,2620 @@ +package com.dsh.petfloat + +import android.app.Activity +import android.app.AlertDialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.content.res.ColorStateList +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.text.TextUtils +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.GridLayout +import android.widget.ImageView +import android.widget.HorizontalScrollView +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.SeekBar +import android.widget.TextView +import android.widget.Toast +import android.view.animation.DecelerateInterpolator +import org.json.JSONArray +import org.json.JSONObject +import java.net.HttpURLConnection +import java.net.URL +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * 主界面(DeepSeek 风格:白底分组列表 / 细分割线 / 品牌蓝 #4D6BFE)。 + * 三个页签:状态 · 动作 · 设置;跟随系统浅色/深色。 + */ +class MainActivity : Activity() { + + private data class SkillItem(val id: String, val name: String, val desc: String) + + private val SKILLS = listOf( + SkillItem("work_start", "开工", "工作动作 + 开工宣言"), + SkillItem("work_done", "收工", "雀跃庆祝 + 夸奖表情"), + SkillItem("work_error", "出错了", "垂头叹气 + 出错表情"), + SkillItem("slack", "摸鱼", "休息动作 + 摸鱼台词"), + SkillItem("meal", "干饭", "吃饭动作 + 白米饭台词"), + SkillItem("greet", "打招呼", "挥手 + 问候"), + SkillItem("sleep", "睡觉", "待机 + 晚安"), + SkillItem("report", "汇报", "工作动画 + 可传自定义文字"), + SkillItem("hydrate", "喝水", "喝水提醒"), + SkillItem("stretch", "伸懒腰", "伸懒腰 + 活动一下") + ) + + companion object { + const val EXTRA_TAB = "tab" + const val TAB_STATUS = 0 + const val TAB_CHAT = 1 + const val TAB_ACTIONS = 2 + const val TAB_SETTINGS = 3 + const val KEY_CHAT_API = "chat_api_key" + const val KEY_CHAT_MODEL = "chat_model" + const val KEY_CHAT_BASE = "chat_base_url" + const val KEY_CHAT_HISTORY = "chat_history" + const val ENDPOINT = "http://127.0.0.1:7350" + val STYLE_KEYS = listOf("default", "dark", "outline", "note") + val STYLE_LABELS = listOf("简约", "深色", "描边", "便签") + } + + private data class Pal( + val bg: Int, val card: Int, val line: Int, val text: Int, + val sub: Int, val accent: Int, val accentSoft: Int, val danger: Int, + val chip: Int, val track: Int + ) + + private val ui = Handler(Looper.getMainLooper()) + private lateinit var pal: Pal + private lateinit var config: PetConfig + private var running = false + + private val tabs = ArrayList() + private lateinit var tabIndicator: View + private var selectedTab = 0 + private val pages = ArrayList() + + private lateinit var statusDot: View + private lateinit var statusTitle: TextView + private lateinit var statusSub1: TextView + private lateinit var statusSub2: TextView + private lateinit var moodValue: TextView + private lateinit var moodFill: View + private lateinit var moodTrack: View + private lateinit var moodEmotions: TextView + private lateinit var moodReason: TextView + private lateinit var primaryBtn: TextView + private lateinit var overlayHint: TextView + private lateinit var histRows: LinearLayout + private lateinit var histUnread: TextView + + private lateinit var scaleValue: TextView + private lateinit var opacityValue: TextView + private lateinit var offsetValue: TextView + private lateinit var scaleBar: SeekBar + private lateinit var opacityBar: SeekBar + private lateinit var offsetBar: SeekBar + private lateinit var authToggle: TextView + private lateinit var authRows: LinearLayout + private lateinit var addrValue: TextView + private lateinit var chatModelValue: TextView + private lateinit var chatBaseValue: TextView + private lateinit var chatList: LinearLayout + private lateinit var chatScroll: ScrollView + private lateinit var chatInput: EditText + private lateinit var chatSendBtn: TextView + private var chatBusy = false + private lateinit var chainGapValue: TextView + private lateinit var idleValue: TextView + private lateinit var edgeValue: TextView + private lateinit var edgeBar: SeekBar + private lateinit var edgePet: ImageView + private lateinit var memeGrid: GridLayout + private lateinit var memeMore: TextView + private var memeCursor = 0 + private val memeBatch = 60 + + private var groups: List>> = emptyList() + + private val poller = object : Runnable { + override fun run() { + refreshStatus() + refreshMood() + ui.postDelayed(this, 2500) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val night = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == + Configuration.UI_MODE_NIGHT_YES + pal = if (night) { + Pal( + 0xFF121316.toInt(), 0xFF1B1D21.toInt(), 0xFF2A2D33.toInt(), 0xFFE9EAEC.toInt(), + 0xFF9AA0A8.toInt(), 0xFF7B8CFF.toInt(), 0x337B8CFF, 0xFFFF6B6B.toInt(), + 0xFF23262B.toInt(), 0xFF2E323A.toInt() + ) + } else { + Pal( + 0xFFF7F8FA.toInt(), 0xFFFFFFFF.toInt(), 0xFFECEEF2.toInt(), 0xFF1A1A1A.toInt(), + 0xFF6B7280.toInt(), 0xFF4D6BFE.toInt(), 0x1A4D6BFE, 0xFFE5484D.toInt(), + 0xFFF2F4F7.toInt(), 0xFFE7EAF0.toInt() + ) + } + config = PetConfig.load(this) + + + + val root = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setBackgroundColor(pal.bg) + } + + val header = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(24), dp(20), dp(8)) + } + header.addView(TextView(this).apply { + text = "小鲸娘" + textSize = 22f + setTextColor(pal.text) + typeface = Typeface.DEFAULT_BOLD + }) + header.addView(TextView(this).apply { + text = "会溜达、会被丢、会替你播报任务" + textSize = 13f + setTextColor(pal.sub) + setPadding(0, dp(4), 0, 0) + }) + root.addView(header) + + val content = FrameLayout(this) + pages.add(buildStatusPage()) + pages.add(buildChatPage()) + pages.add(buildActionsPage()) + pages.add(buildSettingsPage()) + pages.forEach { + content.addView(it, FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + )) + } + root.addView(content, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f + )) + + // 底部 TabBar:胶囊容器 + 滑动指示器 + 按压缩放 + val tabBar = FrameLayout(this).apply { + background = rounded(pal.card, 30, pal.line) + elevation = dp(8).toFloat() + setPadding(dp(5), dp(5), dp(5), dp(5)) + } + tabIndicator = View(this).apply { + background = rounded(pal.accentSoft, 22) + } + tabBar.addView(tabIndicator, FrameLayout.LayoutParams(0, dp(44))) + + val tabRow = LinearLayout(this).apply { orientation = LinearLayout.HORIZONTAL } + listOf("状态", "对话", "动作", "设置").forEachIndexed { i, name -> + val tv = TextView(this).apply { + text = name + textSize = 15f + gravity = Gravity.CENTER + isClickable = true + setOnClickListener { selectTab(i) } + setOnTouchListener { v, e -> + when (e.actionMasked) { + MotionEvent.ACTION_DOWN -> v.animate() + .scaleX(0.93f).scaleY(0.93f).setDuration(80).start() + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> v.animate() + .scaleX(1f).scaleY(1f).setDuration(140).start() + } + false + } + } + tabRow.addView(tv, LinearLayout.LayoutParams(0, dp(44), 1f)) + tabs.add(tv) + } + tabBar.addView(tabRow, FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + )) + root.addView(tabBar, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + leftMargin = dp(16); rightMargin = dp(16) + bottomMargin = dp(14); topMargin = dp(6) + }) + tabBar.post { layoutTabIndicator(selectedTab, animate = false) } + + setContentView(root) + applySystemBars(root, night) + selectTab(intent.getIntExtra(EXTRA_TAB, TAB_STATUS)) + ensureOverlayAndService() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { selectTab(it.getIntExtra(EXTRA_TAB, TAB_STATUS)) } + } + + override fun onResume() { + super.onResume() + refreshStatus() + refreshHistory() + refreshMood() + ui.removeCallbacks(poller) + ui.post(poller) + } + + override fun onPause() { + super.onPause() + ui.removeCallbacks(poller) + } + + // ================= 页 1:状态 ================= + + private fun buildStatusPage(): View { + val scroll = ScrollView(this) + val col = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(8), dp(20), dp(20)) + } + scroll.addView(col) + + val card = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(14), dp(16), dp(14)) + } + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + statusDot = View(this).apply { + background = GradientDrawable().apply { + setColor(pal.sub); cornerRadius = dp(5).toFloat() + } + } + row.addView(statusDot, LinearLayout.LayoutParams(dp(9), dp(9)).apply { + rightMargin = dp(9) + }) + statusTitle = TextView(this).apply { + text = "检查中…" + textSize = 17f + setTextColor(pal.text) + typeface = Typeface.DEFAULT_BOLD + } + row.addView(statusTitle) + card.addView(row) + statusSub1 = TextView(this).apply { + textSize = 13.5f + setTextColor(pal.sub) + setPadding(0, dp(8), 0, 0) + } + card.addView(statusSub1) + statusSub2 = TextView(this).apply { + textSize = 13.5f + setTextColor(pal.sub) + setPadding(0, dp(3), 0, 0) + } + card.addView(statusSub2) + col.addView(card) + + // ---- 心情卡 ---- + val moodCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(14), dp(16), dp(14)) + } + val moodHead = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + moodHead.addView(TextView(this).apply { + text = "心情" + textSize = 15f + setTextColor(pal.text) + typeface = Typeface.DEFAULT_BOLD + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + moodValue = TextView(this).apply { + text = "—" + textSize = 15f + setTextColor(pal.accent) + typeface = Typeface.DEFAULT_BOLD + } + moodHead.addView(moodValue) + moodCard.addView(moodHead) + + // 进度条 + val barWrap = FrameLayout(this).apply { + setPadding(0, dp(10), 0, 0) + } + moodTrack = View(this).apply { + background = rounded(pal.track, 4) + } + barWrap.addView(moodTrack, FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, dp(8) + )) + moodFill = View(this).apply { + background = rounded(pal.accent, 4) + } + barWrap.addView(moodFill, FrameLayout.LayoutParams(0, dp(8))) + moodCard.addView(barWrap) + + moodEmotions = TextView(this).apply { + textSize = 12.5f + setTextColor(pal.sub) + setPadding(0, dp(10), 0, 0) + } + moodCard.addView(moodEmotions) + moodReason = TextView(this).apply { + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(4), 0, 0) + } + moodCard.addView(moodReason) + col.addView(moodCard, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topMargin = dp(12) }) + + primaryBtn = TextView(this).apply { + text = "显示宠物" + textSize = 16f + gravity = Gravity.CENTER + setTextColor(Color.WHITE) + typeface = Typeface.DEFAULT_BOLD + background = ripple(pal.accent, 12, Color.argb(0x40, 255, 255, 255)) + isClickable = true + setPadding(0, dp(14), 0, dp(14)) + setOnClickListener { if (running) stopPet() else startPet() } + } + col.addView(primaryBtn, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topMargin = dp(12) }) + + overlayHint = TextView(this).apply { + text = "需要「显示在其他应用上层」权限,点此去授权" + textSize = 12.5f + setTextColor(pal.danger) + gravity = Gravity.CENTER + setPadding(0, dp(10), 0, 0) + isClickable = true + setOnClickListener { + runCatching { + startActivity( + Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:$packageName") + ) + ) + } + } + } + col.addView(overlayHint) + + col.addView(sectionLabel("快捷")) + val quick = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + quick.addView(actionRow("换动作") { selectTab(TAB_ACTIONS) }) + quick.addView(divider()) + quick.addView(actionRow("复制接入地址") { copyText(ENDPOINT) }) + quick.addView(divider()) + quick.addView(actionRow("打开接入文档") { openDocs() }) + col.addView(quick) + + val histHead = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + val label = TextView(this).apply { + text = "最近通知" + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(4), dp(20), 0, dp(8)) + } + histHead.addView(label, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + histUnread = TextView(this).apply { + textSize = 11.5f + setTextColor(pal.accent) + setPadding(dp(8), dp(2), dp(8), dp(2)) + background = rounded(pal.accentSoft, 8) + visibility = View.GONE + } + histHead.addView(histUnread) + histHead.addView(TextView(this).apply { + text = "清除" + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(10), dp(14), dp(4), dp(4)) + isClickable = true + setOnClickListener { + FloatingPetService.prefs(this@MainActivity).edit() + .putString(FloatingPetService.KEY_PUSH_HISTORY, "[]").apply() + refreshHistory() + } + }) + col.addView(histHead) + histRows = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(14), dp(2), dp(14), dp(2)) + } + col.addView(histRows) + return scroll + } + + // ================= 页 2:对话(DeepSeek) ================= + + private fun chatKey(): String = + FloatingPetService.prefs(this).getString(KEY_CHAT_API, "") ?: "" + + private fun chatBase(): String = + FloatingPetService.prefs(this).getString(KEY_CHAT_BASE, ChatClient.DEFAULT_BASE_URL) + ?: ChatClient.DEFAULT_BASE_URL + + private fun chatModel(): String = + FloatingPetService.prefs(this).getString(KEY_CHAT_MODEL, ChatClient.MODEL_CHAT) + ?: ChatClient.MODEL_CHAT + + private fun loadChat(): MutableList { + val raw = FloatingPetService.prefs(this).getString(KEY_CHAT_HISTORY, "[]") ?: "[]" + return runCatching { + val arr = JSONArray(raw) + (0 until arr.length()).map { + val o = arr.getJSONObject(it) + ChatClient.Msg(o.optString("role", "user"), o.optString("content")) + }.toMutableList() + }.getOrDefault(mutableListOf()) + } + + private fun saveChat(list: List) { + val arr = JSONArray() + list.takeLast(40).forEach { + arr.put(JSONObject().put("role", it.role).put("content", it.content)) + } + FloatingPetService.prefs(this).edit() + .putString(KEY_CHAT_HISTORY, arr.toString()).apply() + } + + /** 二级菜单:记忆管理。 */ + private fun showMemoryDialog() { + val list = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(0, dp(4), 0, 0) + } + val scroll = ScrollView(this).apply { addView(list) } + val auto = FloatingPetService.prefs(this).getBoolean("memory_auto", true) + val autoRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(20), dp(12), dp(20), dp(12)) + isClickable = true + } + autoRow.addView(TextView(this).apply { + text = "自动记忆" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val autoState = TextView(this).apply { + text = if (auto) "开" else "关" + textSize = 13f + setTextColor(if (auto) pal.accent else pal.sub) + } + autoRow.addView(autoState) + autoRow.setOnClickListener { + val now = FloatingPetService.prefs(this).getBoolean("memory_auto", true) + FloatingPetService.prefs(this).edit().putBoolean("memory_auto", !now).apply() + autoState.text = if (!now) "开" else "关" + autoState.setTextColor(if (!now) pal.accent else pal.sub) + } + list.addView(autoRow) + val dlgRef = arrayOfNulls(1) + fun renderRows() { + while (list.childCount > 1) list.removeViewAt(1) + val mems = MemoryStore.load(this) + if (mems.isEmpty()) { + list.addView(TextView(this).apply { + text = "还没有记忆。聊聊就有了,也可以手动添加。" + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(20), dp(20), dp(20), dp(16)) + }) + return + } + mems.forEach { m -> + list.addView(divider()) + val row = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(12), dp(20), dp(12)) + isClickable = true + setOnLongClickListener { + AlertDialog.Builder(this@MainActivity) + .setMessage("删除这条记忆?\n${m.title}") + .setPositiveButton("删除") { _, _ -> + MemoryStore.remove(this@MainActivity, m.id) + renderRows() + } + .setNegativeButton("取消", null).show() + true + } + } + row.addView(TextView(this).apply { + text = m.title + if (m.importance >= 3) " ★" else "" + textSize = 14f + setTextColor(pal.text) + typeface = Typeface.DEFAULT_BOLD + }) + row.addView(TextView(this).apply { + text = m.content + textSize = 13f + setTextColor(pal.sub) + setPadding(0, dp(2), 0, 0) + }) + list.addView(row) + } + } + renderRows() + val d = AlertDialog.Builder(this) + .setTitle("记忆(长按删除)") + .setView(scroll) + .setNeutralButton("添加", null) + .setNegativeButton("整理", null) + .setPositiveButton("关闭", null) + .create() + d.setOnShowListener { + d.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { addMemoryDialog { renderRows(); d.dismiss() } } + d.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + val n = MemoryStore.consolidate(this) + renderRows() + Toast.makeText(this, if (n > 0) "已合并/清理 $n 条" else "记忆已经很整洁了", Toast.LENGTH_SHORT).show() + } + d.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + AlertDialog.Builder(this) + .setMessage("清空全部记忆?") + .setPositiveButton("清空") { _, _ -> MemoryStore.clear(this); renderRows() } + .setNegativeButton("取消", null).show() + } + } + d.show() + } + + private fun addMemoryDialog(onDone: () -> Unit) { + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(22), dp(6), dp(22), dp(2)) + } + val title = EditText(this).apply { hint = "标题,如:饮食偏好"; textSize = 14f; setSingleLine(true) } + val content = EditText(this).apply { hint = "内容,如:喜欢白米饭,不吃香菜"; textSize = 14f } + box.addView(title); box.addView(content) + AlertDialog.Builder(this) + .setTitle("添加记忆") + .setView(box) + .setPositiveButton("保存") { dd, _ -> + if (MemoryStore.add(this, title.text.toString(), content.text.toString())) onDone() + dd.dismiss() + } + .setNegativeButton("取消", null).show() + } + + /** 对话里的显式记忆指令:「记住…」「忘掉…」(不需要额外模型调用)。 */ + private fun handleMemoryCommand(text: String) { + val remember = Regex("^(记住|记下|别忘了|帮我记住)[::,,\\s]*(.+)").find(text) + if (remember != null) { + val body = remember.groupValues[2].trim() + if (body.isNotEmpty()) { + val title = body.take(12) + MemoryStore.add(this, title, body, emptyList(), 3) + Toast.makeText(this, "记住啦:$title", Toast.LENGTH_SHORT).show() + } + return + } + val forget = Regex("^(忘掉|忘记|删除记忆)[::,,\\s]*(.+)").find(text) + if (forget != null) { + val kw = forget.groupValues[2].trim() + if (kw.isEmpty()) return + val list = MemoryStore.load(this) + val hit = list.filter { it.title.contains(kw) || it.content.contains(kw) } + hit.forEach { MemoryStore.remove(this, it.id) } + Toast.makeText(this, if (hit.isEmpty()) "没找到相关记忆" else "忘掉了 ${hit.size} 条", + Toast.LENGTH_SHORT).show() + } + } + + /** 每 2 轮对话抽取一次长期记忆(后台静默,失败不影响聊天)。 */ + private fun maybeExtractMemory(key: String, history: List) { + if (!FloatingPetService.prefs(this).getBoolean("memory_auto", true)) return + val turns = FloatingPetService.prefs(this).getInt("memory_turn", 0) + 1 + FloatingPetService.prefs(this).edit().putInt("memory_turn", turns).apply() + if (turns % 2 != 0) return + Thread { + // 心情判定(feiyu-mood 参数:每轮/每两轮一次短调用) + val judge = ChatClient.judgeMood(key, chatModel(), history, chatBase()) + if (judge != null) { + MoodStore.apply(this, judge.moodDelta, judge.emotions, + if (judge.confidence >= 0.5) judge.reason else "") + } + val drafts = ChatClient.extractMemories(key, chatModel(), history, chatBase()) + if (drafts.isEmpty()) return@Thread + val changed = MemoryStore.applyOps(this, drafts.map { + MemoryStore.Op(it.action, it.title, it.content, it.tags, it.importance, it.longTerm) + }) + if (changed > 0) ui.post { + Toast.makeText(this, "记忆已更新($changed 条)", Toast.LENGTH_SHORT).show() + } + }.start() + } + + /** 二级菜单:插件管理。 */ + private fun showPluginsDialog() { + val states = HashMap() + val enabled = HashMap() + val list = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(0, dp(6), 0, 0) + } + val scroll = ScrollView(this).apply { addView(list) } + BuiltinPlugins.list.forEachIndexed { i, plugin -> + if (i > 0) list.addView(divider()) + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(20), dp(12), dp(20), dp(12)) + isClickable = true + } + val colv = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } + colv.addView(TextView(this).apply { + text = plugin.name + textSize = 15f + setTextColor(pal.text) + }) + colv.addView(TextView(this).apply { + text = plugin.desc + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(2), 0, 0) + }) + row.addView(colv, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val state = TextView(this).apply { + text = "…" + textSize = 12.5f + setTextColor(pal.sub) + } + states[plugin.id] = state + row.addView(state) + row.setOnClickListener { + val on = enabled[plugin.id] ?: false + showPluginConfig(plugin, on) { nowOn -> + enabled[plugin.id] = nowOn + state.text = if (nowOn) "已开启" else "已关闭" + state.setTextColor(if (nowOn) pal.accent else pal.sub) + } + } + list.addView(row) + } + val dlg = AlertDialog.Builder(this) + .setTitle("插件") + .setView(scroll) + .setPositiveButton("关闭", null) + .show() + + Thread { + val installed = runCatching { + val h = JSONObject(httpGet("$ENDPOINT/api/plugins")) + val arr = h.optJSONArray("plugins") ?: JSONArray() + (0 until arr.length()).map { arr.getString(it) }.toSet() + }.getOrDefault(emptySet()) + ui.post { + BuiltinPlugins.list.forEach { p -> + val on = p.id in installed + enabled[p.id] = on + states[p.id]?.text = if (on) "已开启" else "已关闭" + states[p.id]?.setTextColor(if (on) pal.accent else pal.sub) + } + } + }.start() + } + + /** 插件配置页:滑条/开关 + 启用状态,保存后按新配置重装。 */ + private fun showPluginConfig(plugin: BuiltinPlugins.Plugin, enabledNow: Boolean, onChanged: (Boolean) -> Unit) { + val values = BuiltinPlugins.configOf(this, plugin) + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(6), dp(20), dp(4)) + } + var enabled = enabledNow + val enableRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + isClickable = true + setPadding(0, dp(6), 0, dp(10)) + } + enableRow.addView(TextView(this).apply { + text = "启用此插件" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val enableState = TextView(this).apply { + text = if (enabled) "已开启" else "已关闭" + textSize = 13f + setTextColor(if (enabled) pal.accent else pal.sub) + } + enableRow.addView(enableState) + enableRow.setOnClickListener { + enabled = !enabled + enableState.text = if (enabled) "已开启" else "已关闭" + enableState.setTextColor(if (enabled) pal.accent else pal.sub) + } + box.addView(enableRow) + + val edits = HashMap() + val bools = HashMap() + val texts = HashMap() + plugin.params.forEach { p -> + when (p.kind) { + BuiltinPlugins.Kind.BOOL -> { + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(0, dp(8), 0, dp(8)) + isClickable = true + } + row.addView(TextView(this).apply { + text = p.label + textSize = 14f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + var b = (values[p.key] as? Boolean) ?: (p.default as Boolean) + bools[p.key] = b + val tv = TextView(this).apply { + text = if (b) "开" else "关" + textSize = 13f + setTextColor(if (b) pal.accent else pal.sub) + } + row.addView(tv) + row.setOnClickListener { + b = !b + bools[p.key] = b + tv.text = if (b) "开" else "关" + tv.setTextColor(if (b) pal.accent else pal.sub) + } + box.addView(row) + } + BuiltinPlugins.Kind.INT, BuiltinPlugins.Kind.HOUR -> { + val label = TextView(this).apply { + text = "${p.label}:${values[p.key]}${p.unit}" + textSize = 14f + setTextColor(pal.text) + setPadding(0, dp(10), 0, 0) + } + box.addView(label) + val cur = (values[p.key] as? Int) ?: (p.default as Int) + edits[p.key] = cur + val bar = seekBar(((cur - p.min) * 100 / (p.max - p.min).coerceAtLeast(1))) + box.addView(bar, blockLp(34)) + bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(sb: SeekBar?, progress: Int, fromUser: Boolean) { + if (!fromUser) return + val v = p.min + progress * (p.max - p.min) / 100 + edits[p.key] = v + label.text = "${p.label}:$v${p.unit}" + } + override fun onStartTrackingTouch(sb: SeekBar?) {} + override fun onStopTrackingTouch(sb: SeekBar?) {} + }) + if (p.presets.isNotEmpty()) { + val chips = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dp(6), 0, 0) + } + p.presets.forEach { pv -> + chips.addView(TextView(this).apply { + text = "$pv${p.unit}" + textSize = 12f + setTextColor(pal.accent) + background = rounded(pal.accentSoft, 10) + setPadding(dp(10), dp(5), dp(10), dp(5)) + isClickable = true + setOnClickListener { + edits[p.key] = pv + label.text = "${p.label}:$pv${p.unit}" + bar.progress = ((pv - p.min) * 100 / (p.max - p.min).coerceAtLeast(1)) + } + }, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { rightMargin = dp(6) }) + } + box.addView(chips) + } + if (p.hint.isNotBlank()) box.addView(TextView(this).apply { + text = p.hint + textSize = 11.5f + setTextColor(pal.sub) + setPadding(0, dp(4), 0, 0) + }) + } + BuiltinPlugins.Kind.TEXT -> { + box.addView(TextView(this).apply { + text = p.label + textSize = 14f + setTextColor(pal.text) + setPadding(0, dp(10), 0, dp(4)) + }) + val et = EditText(this).apply { + setText(values[p.key] as? String ?: "") + textSize = 14f + maxLines = 3 + hint = p.hint + } + box.addView(et) + texts[p.key] = et + } + } + } + box.addView(TextView(this).apply { + text = "保存后立即按新配置重载插件" + textSize = 11.5f + setTextColor(pal.sub) + setPadding(0, dp(10), 0, 0) + }) + AlertDialog.Builder(this) + .setTitle(plugin.name) + .setView(box) + .setNeutralButton("恢复默认", null) + .setNegativeButton("删除插件", null) + .setPositiveButton("保存") { d, _ -> + val merged = HashMap(values) + merged.putAll(edits) + merged.putAll(bools) + texts.forEach { (k, et) -> merged[k] = et.text.toString().trim() } + BuiltinPlugins.setConfig(this, plugin, merged) + if (enabled) { + askService(FloatingPetService.ACTION_INSTALL_PLUGIN) { + putExtra(FloatingPetService.EXTRA_PLUGIN_ID, plugin.id) + putExtra(FloatingPetService.EXTRA_PLUGIN_CODE, + BuiltinPlugins.codeWithConfig(this@MainActivity, plugin)) + } + } else { + askService(FloatingPetService.ACTION_PLUGIN_REMOVE) { + putExtra(FloatingPetService.EXTRA_PLUGIN_ID, plugin.id) + } + } + onChanged(enabled) + Toast.makeText(this, "已保存", Toast.LENGTH_SHORT).show() + d.dismiss() + } + .create() + .also { dlg -> + dlg.setOnShowListener { + dlg.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { + BuiltinPlugins.resetConfig(this, plugin) + askService(FloatingPetService.ACTION_INSTALL_PLUGIN) { + putExtra(FloatingPetService.EXTRA_PLUGIN_ID, plugin.id) + putExtra(FloatingPetService.EXTRA_PLUGIN_CODE, + BuiltinPlugins.codeWithConfig(this@MainActivity, plugin)) + } + Toast.makeText(this, "已恢复默认", Toast.LENGTH_SHORT).show() + dlg.dismiss() + } + dlg.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + BuiltinPlugins.resetConfig(this, plugin) + askService(FloatingPetService.ACTION_PLUGIN_REMOVE) { + putExtra(FloatingPetService.EXTRA_PLUGIN_ID, plugin.id) + } + onChanged(false) + Toast.makeText(this, "已删除(可随时重新开启)", Toast.LENGTH_SHORT).show() + dlg.dismiss() + } + } + dlg.show() + } + } + + /** 二级菜单:技能列表,点一下执行。 */ + private fun showSkillsDialog() { + val list = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(0, dp(6), 0, 0) + } + val scroll = ScrollView(this).apply { addView(list) } + SKILLS.forEachIndexed { i, sk -> + if (i > 0) list.addView(divider()) + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(20), dp(13), dp(20), dp(13)) + isClickable = true + } + val colv = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } + colv.addView(TextView(this).apply { + text = sk.name + textSize = 15f + setTextColor(pal.text) + }) + colv.addView(TextView(this).apply { + text = sk.desc + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(2), 0, 0) + }) + row.addView(colv, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + row.addView(chevron()) + list.addView(row) + } + val dlg = AlertDialog.Builder(this) + .setTitle("技能") + .setView(scroll) + .setPositiveButton("关闭", null) + .show() + SKILLS.forEachIndexed { i, sk -> + (list.getChildAt(i) as? View)?.setOnClickListener { + askService(FloatingPetService.ACTION_RUN_SKILL) { + putExtra(FloatingPetService.EXTRA_SKILL_ID, sk.id) + } + Toast.makeText(this, "已执行:${sk.name}", Toast.LENGTH_SHORT).show() + dlg.dismiss() + } + } + } + + private fun refreshChatSettings() { + if (::chatModelValue.isInitialized) chatModelValue.text = chatModel() + if (::chatBaseValue.isInitialized) chatBaseValue.text = chatBase().replace("https://", "") + } + + /** 编辑接口地址(默认 DeepSeek 官方,可换代理 / OpenAI 兼容端点)。 */ + private fun editChatBase() { + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(22), dp(8), dp(22), dp(4)) + } + val input = EditText(this).apply { + hint = ChatClient.DEFAULT_BASE_URL + textSize = 14f + setSingleLine(true) + setText(chatBase()) + setSelection(text.length) + typeface = Typeface.MONOSPACE + } + box.addView(input) + box.addView(TextView(this).apply { + text = "填根地址即可(自动补 /chat/completions);也可直接贴完整地址。" + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(6), 0, 0) + }) + AlertDialog.Builder(this) + .setTitle("接口地址") + .setView(box) + .setPositiveButton("保存") { d, _ -> + val v = input.text.toString().trim() + FloatingPetService.prefs(this@MainActivity).edit() + .putString(KEY_CHAT_BASE, v.ifBlank { ChatClient.DEFAULT_BASE_URL }).apply() + refreshChatSettings() + d.dismiss() + } + .setNegativeButton("取消", null) + .show() + } + + /** 手动填写模型名(带常用预设快捷填入)。 */ + private fun editChatModel() { + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(22), dp(8), dp(22), dp(4)) + } + val input = EditText(this).apply { + hint = "deepseek-chat" + textSize = 14f + setSingleLine(true) + setText(chatModel()) + setSelection(text.length) + typeface = Typeface.MONOSPACE + } + box.addView(input) + box.addView(TextView(this).apply { + text = "可填任意模型名。常用:" + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(8), 0, dp(4)) + }) + val presets = listOf(ChatClient.MODEL_CHAT, ChatClient.MODEL_REASONER) + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + } + var dlgRef: AlertDialog? = null + presets.forEach { m -> + row.addView(TextView(this).apply { + text = m + textSize = 12.5f + setTextColor(pal.accent) + background = rounded(pal.accentSoft, 10) + setPadding(dp(10), dp(7), dp(10), dp(7)) + isClickable = true + setOnClickListener { input.setText(m); input.setSelection(m.length) } + }, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { rightMargin = dp(8) }) + } + box.addView(row) + dlgRef = AlertDialog.Builder(this) + .setTitle("模型名") + .setView(box) + .setPositiveButton("保存") { d, _ -> + val m = input.text.toString().trim() + FloatingPetService.prefs(this@MainActivity).edit() + .putString(KEY_CHAT_MODEL, m.ifBlank { ChatClient.MODEL_CHAT }).apply() + refreshChatSettings() + Toast.makeText(this, "模型已设为 ${chatModel()}", Toast.LENGTH_SHORT).show() + d.dismiss() + } + .setNegativeButton("取消", null) + .show() + dlgRef?.let { } + } + + /** 二级界面里编辑 API Key(一级界面只显示掩码)。 */ + private fun editChatKey() { + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(22), dp(8), dp(22), dp(4)) + } + val input = EditText(this).apply { + hint = "sk-..." + textSize = 14f + setSingleLine(true) + setText(chatKey()) + setSelection(text.length) + typeface = Typeface.MONOSPACE + } + box.addView(input) + box.addView(TextView(this).apply { + text = "在 platform.deepseek.com 创建;只存在本机。" + textSize = 12f + setTextColor(pal.sub) + setPadding(0, dp(6), 0, 0) + }) + AlertDialog.Builder(this) + .setTitle("DeepSeek API Key") + .setView(box) + .setPositiveButton("保存") { d, _ -> + FloatingPetService.prefs(this@MainActivity).edit() + .putString(KEY_CHAT_API, input.text.toString().trim()).apply() + renderChat() + Toast.makeText(this, "已保存", Toast.LENGTH_SHORT).show() + d.dismiss() + } + .setNegativeButton("取消", null) + .show() + } + + private fun testChatConnection() { + val key = chatKey() + if (key.isBlank()) { + Toast.makeText(this, "先填 API Key", Toast.LENGTH_SHORT).show() + return + } + Toast.makeText(this, "测试中…", Toast.LENGTH_SHORT).show() + Thread { + val r = ChatClient.chat(key, chatModel(), listOf(ChatClient.Msg("user", "你好")), chatBase()) + ui.post { + r.onSuccess { Toast.makeText(this, "连接成功:${it.content.take(20)}", Toast.LENGTH_LONG).show() } + .onFailure { Toast.makeText(this, "失败:${it.message}", Toast.LENGTH_LONG).show() } + } + }.start() + } + + private fun buildChatPage(): View { + val page = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } + chatScroll = ScrollView(this).apply { isVerticalScrollBarEnabled = false } + chatList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(16), dp(8), dp(16), dp(12)) + } + chatScroll.addView(chatList) + page.addView(chatScroll, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f + )) + + // 输入行 + val inputRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(14), dp(8), dp(14), dp(10)) + } + chatInput = EditText(this).apply { + hint = "跟小鲸娘说点什么…" + setOnFocusChangeListener { _, hasFocus -> if (hasFocus) ui.postDelayed({ scrollChatToBottom() }, 250) } + textSize = 15f + maxLines = 4 + setTextColor(pal.text) + setHintTextColor(pal.sub) + background = rounded(pal.card, 20, pal.line) + setPadding(dp(16), dp(10), dp(16), dp(10)) + } + inputRow.addView(chatInput, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + chatSendBtn = TextView(this).apply { + text = "发送" + textSize = 15f + gravity = Gravity.CENTER + setTextColor(Color.WHITE) + typeface = Typeface.DEFAULT_BOLD + background = ripple(pal.accent, 20, Color.argb(0x40, 255, 255, 255)) + setPadding(dp(18), dp(11), dp(18), dp(11)) + isClickable = true + setOnClickListener { sendChat() } + } + inputRow.addView(chatSendBtn, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { leftMargin = dp(8) }) + page.addView(inputRow) + + renderChat() + return page + } + + private fun renderChat() { + if (!::chatList.isInitialized) return + chatList.removeAllViews() + val list = loadChat() + if (chatKey().isBlank()) { + chatList.addView(TextView(this).apply { + text = "还没有配置 DeepSeek API Key。\n点「设置 → 对话」填入后就能聊啦~" + textSize = 13.5f + setTextColor(pal.sub) + gravity = Gravity.CENTER + setPadding(dp(20), dp(40), dp(20), dp(20)) + }) + return + } + if (list.isEmpty()) { + chatList.addView(TextView(this).apply { + text = "问问小鲸娘吧~ 例如:「今天写点什么好」" + textSize = 13.5f + setTextColor(pal.sub) + gravity = Gravity.CENTER + setPadding(dp(20), dp(40), dp(20), dp(20)) + }) + return + } + list.forEach { m -> chatList.addView(chatBubble(m)) } + } + + private fun chatBubble(m: ChatClient.Msg): View { + val mine = m.role == "user" + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = if (mine) Gravity.END else Gravity.START + setPadding(0, dp(4), 0, dp(4)) + } + val tv = TextView(this).apply { + text = m.content + textSize = 14.5f + setTextColor(if (mine) Color.WHITE else pal.text) + setPadding(dp(14), dp(10), dp(14), dp(10)) + background = rounded(if (mine) pal.accent else pal.card, 16, + if (mine) null else pal.line) + maxWidth = (resources.displayMetrics.widthPixels * 0.78f).toInt() + } + row.addView(tv) + return row + } + + private fun tagForReply(text: String): String = when { + listOf("开心", "哈哈", "好耶", "棒", "喜欢", "可爱").any { text.contains(it) } -> "happy" + listOf("错", "失败", "抱歉", "不行", "难过").any { text.contains(it) } -> "sad" + listOf("吃", "饭", "饿", "零食").any { text.contains(it) } -> "eat" + listOf("工作", "代码", "忙", "任务").any { text.contains(it) } -> "work" + listOf("睡", "困", "休息", "摸鱼").any { text.contains(it) } -> "rest" + else -> "click" + } + + private fun sendChat() { + if (chatBusy) return + val text = chatInput.text.toString().trim() + if (text.isEmpty()) return + val key = chatKey() + if (key.isBlank()) { + Toast.makeText(this, "请先在「设置 → 对话」填入 DeepSeek API Key", Toast.LENGTH_LONG).show() + return + } + handleMemoryCommand(text) + val history = loadChat() + history.add(ChatClient.Msg("user", text)) + saveChat(history) + chatInput.setText("") + renderChat() + scrollChatToBottom() + + chatBusy = true + chatSendBtn.text = "…" + chatSendBtn.alpha = 0.6f + Thread { + val memBlock = MemoryStore.renderPrompt(MemoryStore.recall(this, text)) + val caps = buildCapabilities() + val res = ChatClient.chat(key, chatModel(), history, chatBase(), memBlock, caps) + ui.post { + chatBusy = false + chatSendBtn.text = "发送" + chatSendBtn.alpha = 1f + res.onSuccess { reply -> + // 从回复里提取 <<>> ... <<>> 并安装 + val installed = installPluginsFromReply(reply.content) + runActionFromReply(reply.content) + val visible = stripPluginBlocks(stripActionBlocks(reply.content)) + val list = loadChat() + list.add(ChatClient.Msg("assistant", visible)) + saveChat(list) + renderChat() + scrollChatToBottom() + if (installed.isNotEmpty()) { + val tip = "已安装插件:" + installed.joinToString("、") + list.add(ChatClient.Msg("assistant", tip)) + saveChat(list) + renderChat() + scrollChatToBottom() + } + // 自动记忆:每 2 轮对话抽取一次长期记忆 + maybeExtractMemory(key, history) + // 气泡说 + 动作呼应 + askService(FloatingPetService.ACTION_SAY) { + putExtra(FloatingPetService.EXTRA_SAY_TEXT, visible.ifBlank { reply.content }) + } + askService(FloatingPetService.ACTION_PLAY_TAG) { + putExtra(FloatingPetService.EXTRA_ANIM_TAG, tagForReply(reply.content)) + } + }.onFailure { e -> + val msg = e.message.orEmpty() + val timeout = msg.contains("timeout", true) || msg.contains("超时") || + e is java.net.SocketTimeoutException + if (timeout) { + // TIMEOUT_SIGNAL:用尾巴动作代替空话 + val line = "(尾巴啪地拍了一下水)……等太久啦,我先趴一会儿,主人过会儿再叫我~" + val list = loadChat() + list.add(ChatClient.Msg("assistant", line)) + saveChat(list) + renderChat() + scrollChatToBottom() + askService(FloatingPetService.ACTION_SAY) { + putExtra(FloatingPetService.EXTRA_SAY_TEXT, line) + } + askService(FloatingPetService.ACTION_PLAY_TAG) { + putExtra(FloatingPetService.EXTRA_ANIM_TAG, "rest") + } + } else { + Toast.makeText(this, "对话失败:$msg", Toast.LENGTH_LONG).show() + } + } + } + }.start() + } + + private val pluginBlock = Regex( + "<<" + ">>([\\s\\S]*?)<<" + ">>" + ) + + /** 组装给模型看的「真实能力清单」。 */ + private fun buildCapabilities(): String { + val sb = StringBuilder() + sb.append("技能(共 ").append(SKILLS.size).append(" 个,问起就按这个列表原文列举):\n") + sb.append(SKILLS.joinToString("、") { it.name }).append('\n') + sb.append("技能 id(执行时用):") + .append(SKILLS.joinToString("、") { it.name + "=" + it.id }).append('\n') + val pluginNames = BuiltinPlugins.list.joinToString("、") { + it.name + if (it.params.isEmpty()) "" else "(可配置)" + } + sb.append("插件:").append(pluginNames).append('\n') + sb.append("表情包:").append(config.memes.size) + .append(" 张,可按标签 happy/praise/error/eat/rest/work/fun 等调用\n") + sb.append("动作:106 个动画,可按语义标签 work/happy/sad/eat/play/rest/greet/festival 等点播\n") + sb.append("记忆:已记 ").append(MemoryStore.load(this).size).append(" 条长期记忆\n") + sb.append(MoodStore.describe(this)).append('\n') + sb.append("其他:可以说气泡、播动作、弹表情;本机 7350 端口还提供 HTTP/MCP 接口给外部应用调用") + return sb.toString() + } + + private val actionBlock = Regex("<<>>") + + private fun stripActionBlocks(text: String): String = actionBlock.replace(text, "").trim() + + private fun runActionFromReply(text: String) { + actionBlock.findAll(text).forEach { m -> + val obj = runCatching { JSONObject(m.groupValues[1]) }.getOrNull() ?: return@forEach + when (obj.optString("type")) { + "skill" -> askService(FloatingPetService.ACTION_RUN_SKILL) { + putExtra(FloatingPetService.EXTRA_SKILL_ID, obj.optString("id")) + } + "anim" -> { + val tag = obj.optString("tag") + if (tag.isNotBlank()) askService(FloatingPetService.ACTION_PLAY_TAG) { + putExtra(FloatingPetService.EXTRA_ANIM_TAG, tag) + } else askService(FloatingPetService.ACTION_PLAY_ANIM) { + putExtra(FloatingPetService.EXTRA_ANIM_NAME, obj.optString("name")) + } + } + "meme" -> askService(FloatingPetService.ACTION_SHOW_MEME) { + putExtra( + FloatingPetService.EXTRA_MEME_FILE, + obj.optString("file").ifBlank { config.memes.randomOrNull()?.file ?: "" } + ) + } + } + } + } + + /** 解析回复里的插件块,写入插件目录,返回已安装 id。 */ + private fun installPluginsFromReply(text: String): List { + val ids = ArrayList() + pluginBlock.findAll(text).forEach { m -> + val id = m.groupValues[1] + val code = m.groupValues[2].trim() + if (id.isNotEmpty() && code.isNotEmpty()) { + askService(FloatingPetService.ACTION_INSTALL_PLUGIN) { + putExtra(FloatingPetService.EXTRA_PLUGIN_ID, id) + putExtra(FloatingPetService.EXTRA_PLUGIN_CODE, code) + } + ids.add(id) + } + } + return ids + } + + /** 展示时去掉插件代码块。 */ + private fun stripPluginBlocks(text: String): String = + pluginBlock.replace(text, "").trim().ifBlank { "插件已装好~重启桌宠或刷新就生效" } + + private fun scrollChatToBottom() { + chatScroll.post { chatScroll.fullScroll(View.FOCUS_DOWN) } + } + + // ================= 页 3:动作(按分类分组的白色卡片) ================= + + private fun buildActionsPage(): View { + val scroll = ScrollView(this).apply { isVerticalScrollBarEnabled = false } + val col = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(2), dp(20), dp(20)) + } + scroll.addView(col) + + // ---- 技能 ---- + col.addView(TextView(this).apply { + text = "技能" + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(4), dp(2), 0, dp(8)) + }) + val skillCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + SKILLS.forEachIndexed { i, sk -> + if (i > 0) skillCard.addView(divider()) + skillCard.addView(actionRow(sk.name) { + askService(FloatingPetService.ACTION_RUN_SKILL) { + putExtra(FloatingPetService.EXTRA_SKILL_ID, sk.id) + } + }) + } + col.addView(skillCard) + + groups = buildGroups() + groups.forEach { (title, names) -> + // 分组标题(灰色小字) + col.addView(TextView(this).apply { + text = title + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(4), dp(16), 0, dp(8)) + }) + + // 白色分组卡片 + val card = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(8), dp(8), dp(8), dp(8)) + } + val grid = GridLayout(this).apply { columnCount = 2 } + names.forEach { name -> + val cell = TextView(this).apply { + text = name + textSize = 13.5f + gravity = Gravity.CENTER + setTextColor(pal.text) + background = RippleDrawable( + ColorStateList.valueOf(Color.argb(0x14, 0, 0, 0)), + rounded(pal.chip, 12), null + ) + isClickable = true + setOnClickListener { + askService(FloatingPetService.ACTION_PLAY_ANIM) { + putExtra(FloatingPetService.EXTRA_ANIM_NAME, name) + } + } + } + grid.addView(cell, GridLayout.LayoutParams().apply { + width = 0 + height = dp(44) + columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f) + setMargins(dp(4), dp(4), dp(4), dp(4)) + }) + } + card.addView(grid) + col.addView(card) + } + + // ---- 表情包(分页渲染,支持长按隐藏)---- + val memeHead = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(4), dp(16), 0, dp(8)) + } + memeHead.addView(TextView(this).apply { + text = "表情包" + textSize = 13f + setTextColor(pal.sub) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val memeCount = TextView(this).apply { + textSize = 11.5f + setTextColor(pal.sub) + } + memeHead.addView(memeCount) + col.addView(memeHead) + + val memeCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(8), dp(8), dp(8), dp(4)) + } + memeGrid = GridLayout(this).apply { columnCount = 3 } + memeCard.addView(memeGrid) + + memeMore = TextView(this).apply { + textSize = 13f + gravity = Gravity.CENTER + setTextColor(pal.accent) + setPadding(0, dp(10), 0, dp(12)) + isClickable = true + setOnClickListener { + renderMemePage() + if (memeCursor >= visibleMemes().size) visibility = View.GONE + } + } + memeCard.addView(memeMore, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + )) + col.addView(memeCard) + + memeCursor = 0 + renderMemePage() + memeCount.text = "共 ${visibleMemes().size} 张" + return scroll + } + + private fun hiddenMemes(): MutableSet { + val raw = FloatingPetService.prefs(this).getString("hidden_memes", "[]") ?: "[]" + return runCatching { + val a = JSONArray(raw) + (0 until a.length()).map { a.getString(it) }.toMutableSet() + }.getOrDefault(mutableSetOf()) + } + + private fun visibleMemes(): List { + val hidden = hiddenMemes() + return config.memes.filter { it.file !in hidden } + } + + /** 分批把表情包渲染进网格(2436 张不能一次建 View)。 */ + private fun renderMemePage() { + val all = visibleMemes() + val batch = all.drop(memeCursor).take(memeBatch) + batch.forEach { meme -> + val cell = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER_HORIZONTAL + background = RippleDrawable( + ColorStateList.valueOf(Color.argb(0x14, 0, 0, 0)), + rounded(pal.chip, 12), null + ) + setPadding(dp(6), dp(8), dp(6), dp(8)) + isClickable = true + setOnClickListener { + askService(FloatingPetService.ACTION_SHOW_MEME) { + putExtra(FloatingPetService.EXTRA_MEME_FILE, meme.file) + } + } + setOnLongClickListener { + val hidden = hiddenMemes() + if (hidden.add(meme.file)) { + FloatingPetService.prefs(this@MainActivity).edit() + .putString("hidden_memes", JSONArray(hidden.toList()).toString()) + .apply() + memeGrid.removeAllViews() + memeCursor = 0 + renderMemePage() + } + true + } + } + val bmp = runCatching { + val opt = android.graphics.BitmapFactory.Options().apply { inSampleSize = 2 } + assets.open(meme.file + ".webp").use { + android.graphics.BitmapFactory.decodeStream(it, null, opt) + } + }.getOrNull() + val iv = ImageView(this).apply { + scaleType = ImageView.ScaleType.FIT_CENTER + if (bmp != null) setImageBitmap(bmp) + } + cell.addView(iv, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, dp(72) + )) + cell.addView(TextView(this).apply { + text = meme.name + textSize = 11f + setTextColor(pal.sub) + gravity = Gravity.CENTER + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + setPadding(0, dp(4), 0, 0) + }) + memeGrid.addView(cell, GridLayout.LayoutParams().apply { + width = 0 + height = ViewGroup.LayoutParams.WRAP_CONTENT + columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f) + setMargins(dp(4), dp(4), dp(4), dp(4)) + }) + } + memeCursor += batch.size + if (::memeMore.isInitialized) { + val left = all.size - memeCursor + memeMore.visibility = if (left > 0) View.VISIBLE else View.GONE + if (left > 0) memeMore.text = "加载更多(还有 $left 张)" + } + } + + private fun buildGroups(): List>> { + val list = ArrayList>>() + fun add(title: String, names: List) { + if (names.isNotEmpty()) list.add(title to names) + } + add("待机", config.idle.map { it.name }) + add("转向", config.turn.map { it.name }) + add("拖拽", config.drag.map { it.name }) + add("点击回应", config.clicks.map { it.name }) + add("移动", config.moves.map { it.name }) + config.categories.forEach { c -> add(c.id, c.actions.map { it.name }) } + add("余额", config.events["balance"]?.map { it.name } ?: emptyList()) + add("碎碎念", config.events["whisper"]?.map { it.name } ?: emptyList()) + add("工作状态", config.events["workStatus"]?.map { it.name } ?: emptyList()) + return list + } + + // ================= 页 4:设置 ================= + + private fun buildSettingsPage(): View { + val scroll = ScrollView(this) + val col = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(20), dp(6), dp(20), dp(20)) + } + scroll.addView(col) + val p = FloatingPetService.prefs(this) + + col.addView(sectionLabel("外观")) + val look = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(6), dp(16), dp(10)) + } + scaleValue = sliderRow(look, "大小", "${(p.getFloat(FloatingPetService.KEY_SCALE, 1f) * 100).toInt()}%") + scaleBar = seekBar(((p.getFloat(FloatingPetService.KEY_SCALE, 1f) - 0.5f) / 1.5f * 100).toInt()) + look.addView(scaleBar, blockLp(34)) + scaleBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) scaleValue.text = "%.0f%%".format((0.5f + 1.5f * progress / 100f) * 100) + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + FloatingPetService.prefs(this@MainActivity).edit() + .putFloat(FloatingPetService.KEY_SCALE, 0.5f + 1.5f * scaleBar.progress / 100f) + .apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + } + }) + opacityValue = sliderRow(look, "透明度", "${(p.getFloat(FloatingPetService.KEY_OPACITY, 1f) * 100).toInt()}%") + opacityBar = seekBar(((p.getFloat(FloatingPetService.KEY_OPACITY, 1f) - 0.3f) / 0.7f * 100).toInt()) + look.addView(opacityBar, blockLp(34)) + opacityBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) opacityValue.text = "%.0f%%".format((0.3f + 0.7f * progress / 100f) * 100) + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + FloatingPetService.prefs(this@MainActivity).edit() + .putFloat(FloatingPetService.KEY_OPACITY, 0.3f + 0.7f * opacityBar.progress / 100f) + .apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + } + }) + col.addView(look) + + col.addView(sectionLabel("气泡")) + val bub = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(12), dp(16), dp(10)) + } + bub.addView(TextView(this).apply { + text = "样式" + textSize = 15f + setTextColor(pal.text) + }) + val chipRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dp(8), 0, dp(4)) + } + val styleViews = ArrayList() + var curStyle = p.getString(FloatingPetService.KEY_BUBBLE_STYLE, "default") ?: "default" + fun renderStyle() { + styleViews.forEachIndexed { i, tv -> + val on = STYLE_KEYS[i] == curStyle + tv.background = if (on) rounded(pal.accentSoft, 18) else rounded(pal.chip, 18) + tv.setTextColor(if (on) pal.accent else pal.text) + tv.typeface = Typeface.create( + Typeface.DEFAULT, if (on) Typeface.BOLD else Typeface.NORMAL + ) + } + } + STYLE_KEYS.forEachIndexed { i, key -> + val tv = TextView(this).apply { + text = STYLE_LABELS[i] + textSize = 13f + gravity = Gravity.CENTER + setPadding(dp(14), dp(7), dp(14), dp(7)) + isClickable = true + setOnClickListener { + curStyle = key + FloatingPetService.prefs(this@MainActivity).edit() + .putString(FloatingPetService.KEY_BUBBLE_STYLE, key).apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + renderStyle() + } + } + chipRow.addView(tv, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { rightMargin = dp(8) }) + styleViews.add(tv) + } + bub.addView(chipRow) + renderStyle() + + val curOffset = p.getInt(FloatingPetService.KEY_BUBBLE_OFFSET, 6) + offsetValue = sliderRow(bub, "距头顶", "${curOffset}dp") + offsetBar = seekBar((curOffset * 100 / 40).coerceIn(0, 100)) + bub.addView(offsetBar, blockLp(34)) + offsetBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) offsetValue.text = "${progress * 40 / 100}dp" + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + FloatingPetService.prefs(this@MainActivity).edit() + .putInt(FloatingPetService.KEY_BUBBLE_OFFSET, offsetBar.progress * 40 / 100) + .apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + } + }) + col.addView(bub) + + col.addView(sectionLabel("宠物")) + val petCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + petCard.addView(actionRow("重置位置") { + FloatingPetService.prefs(this).edit() + .remove(FloatingPetService.KEY_X) + .remove(FloatingPetService.KEY_Y).apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + }) + petCard.addView(divider()) + petCard.addView(actionRow("发一条测试气泡") { + askService(FloatingPetService.ACTION_TEST_NOTIFY) + }) + col.addView(petCard) + + // ---- 贴边间距 ---- + // ---- 扩展:插件 / 技能(二级菜单)---- + col.addView(sectionLabel("扩展")) + val extCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + extCard.addView(rowWith("插件", TextView(this).apply { + text = "${BuiltinPlugins.list.size} 个内置" + textSize = 13f + setTextColor(pal.sub) + }) { showPluginsDialog() }) + extCard.addView(divider()) + extCard.addView(rowWith("技能", TextView(this).apply { + text = "${SKILLS.size} 个" + textSize = 13f + setTextColor(pal.sub) + }) { showSkillsDialog() }) + extCard.addView(divider()) + extCard.addView(rowWith("记忆", TextView(this).apply { + text = "${MemoryStore.load(this@MainActivity).size} 条" + textSize = 13f + setTextColor(pal.sub) + }) { showMemoryDialog() }) + col.addView(extCard) + + // ---- 对话(DeepSeek)---- + col.addView(sectionLabel("对话(DeepSeek)")) + val chatCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + val baseRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(14), dp(16), dp(14)) + isClickable = true + setOnClickListener { editChatBase() } + } + baseRow.addView(TextView(this).apply { + text = "接口地址" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + chatBaseValue = TextView(this).apply { + text = chatBase().replace("https://", "") + textSize = 12.5f + setTextColor(pal.sub) + } + baseRow.addView(chatBaseValue) + baseRow.addView(chevron()) + chatCard.addView(baseRow) + chatCard.addView(divider()) + + val keyRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(14), dp(16), dp(14)) + isClickable = true + setOnClickListener { editChatKey() } + } + keyRow.addView(TextView(this).apply { + text = "API Key" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val keyValue = TextView(this).apply { + val k = chatKey() + text = if (k.isBlank()) "未配置" else k.take(4) + "••••" + k.takeLast(4) + textSize = 13f + setTextColor(if (k.isBlank()) pal.danger else pal.sub) + typeface = Typeface.MONOSPACE + } + keyRow.addView(keyValue) + keyRow.addView(chevron()) + chatCard.addView(keyRow) + chatCard.addView(divider()) + + val modelRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(14), dp(16), dp(14)) + isClickable = true + setOnClickListener { editChatModel() } + } + modelRow.addView(TextView(this).apply { + text = "模型" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + chatModelValue = TextView(this).apply { textSize = 13f; setTextColor(pal.sub) } + modelRow.addView(chatModelValue) + modelRow.addView(chevron()) + chatCard.addView(modelRow) + chatCard.addView(divider()) + + chatCard.addView(actionRow("清空对话记录") { + FloatingPetService.prefs(this).edit().putString(KEY_CHAT_HISTORY, "[]").apply() + renderChat() + Toast.makeText(this, "已清空", Toast.LENGTH_SHORT).show() + }) + chatCard.addView(divider()) + chatCard.addView(actionRow("测试连接") { testChatConnection() }) + col.addView(chatCard) + refreshChatSettings() + + // ---- 动作节奏 ---- + col.addView(sectionLabel("动作")) + val actCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(6), dp(16), dp(10)) + } + val curGapS = FloatingPetService.prefs(this).getInt(FloatingPetService.KEY_CHAIN_GAP, 2) + chainGapValue = sliderRow(actCard, "切换间隔", "${curGapS}s") + val gapBar = seekBar((curGapS * 100 / 60).coerceIn(0, 100)) + actCard.addView(gapBar, blockLp(34)) + gapBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) chainGapValue.text = "${progress * 60 / 100}s" + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + FloatingPetService.prefs(this@MainActivity).edit() + .putInt(FloatingPetService.KEY_CHAIN_GAP, gapBar.progress * 60 / 100).apply() + askService(FloatingPetService.ACTION_RESUME_CHAIN) + } + }) + + val curIdle = FloatingPetService.prefs(this).getInt(FloatingPetService.KEY_IDLE_AFTER, 60) + idleValue = sliderRow(actCard, "空闲回待机", if (curIdle == 0) "关闭" else "${curIdle}s") + val idleBar = seekBar((curIdle * 100 / 300).coerceIn(0, 100)) + actCard.addView(idleBar, blockLp(34)) + idleBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (!fromUser) return + val v = progress * 300 / 100 + idleValue.text = if (v == 0) "关闭" else "${v}s" + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + val v = idleBar.progress * 300 / 100 + FloatingPetService.prefs(this@MainActivity).edit() + .putInt(FloatingPetService.KEY_IDLE_AFTER, v).apply() + askService(FloatingPetService.ACTION_RESUME_CHAIN) + } + }) + + actCard.addView(divider()) + actCard.addView(actionRow("随机播一个动作") { + askService(FloatingPetService.ACTION_PLAY_RANDOM) + }) + actCard.addView(divider()) + actCard.addView(actionRow("回到待机") { + askService(FloatingPetService.ACTION_PLAY_IDLE) + }) + col.addView(actCard) + + col.addView(sectionLabel("贴边间距")) + val edgeCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + setPadding(dp(16), dp(12), dp(16), dp(10)) + } + // 预览带:左边竖线 = 屏幕边缘,宠物缩略图按间距移动 + val strip = FrameLayout(this).apply { + background = rounded(pal.chip, 12, pal.line) + clipChildren = false + } + strip.addView(View(this).apply { + setBackgroundColor(pal.accent) + }, FrameLayout.LayoutParams(dp(2), ViewGroup.LayoutParams.MATCH_PARENT).apply { + gravity = Gravity.START + }) + edgePet = ImageView(this).apply { + scaleType = ImageView.ScaleType.FIT_CENTER + runCatching { + assets.open("a0058.webp").use { + setImageBitmap(android.graphics.BitmapFactory.decodeStream(it)) + } + } + } + val petW = dp(110) + strip.addView(edgePet, FrameLayout.LayoutParams(petW, dp(62)).apply { + gravity = Gravity.CENTER_VERTICAL + }) + edgeCard.addView(strip, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, dp(62) + ).apply { bottomMargin = dp(6) }) + edgeCard.addView(TextView(this).apply { + text = "竖线是屏幕边缘,拖滑条看宠物实际留多少" + textSize = 11.5f + setTextColor(pal.sub) + }) + + val curGap = p.getInt(FloatingPetService.KEY_EDGE_GAP, 0) + edgeValue = sliderRow(edgeCard, "间距", "${curGap}dp") + edgeBar = seekBar(((curGap + 20) * 100 / 80).coerceIn(0, 100)) + edgeCard.addView(edgeBar, blockLp(34)) + // 素材左侧有透明留白:预览要把这段扣掉,角色可见边缘才和竖线对齐 + val padL = FloatingPetService.prefs(this) + .getFloat("sprite_pad_l", 0.34f) + fun updateEdgePreview(gapDp: Int) { + edgeValue.text = "${gapDp}dp" + val shift = dp(gapDp) - padL * petW + edgePet.translationX = shift + } + updateEdgePreview(curGap) + edgeBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(b: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) updateEdgePreview(-20 + progress * 80 / 100) + } + override fun onStartTrackingTouch(b: SeekBar?) {} + override fun onStopTrackingTouch(b: SeekBar?) { + val gap = -20 + edgeBar.progress * 80 / 100 + FloatingPetService.prefs(this@MainActivity).edit() + .putInt(FloatingPetService.KEY_EDGE_GAP, gap).apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + } + }) + edgeCard.addView(LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dp(6), 0, 0) + addView(TextView(this@MainActivity).apply { + text = "0 = 角色刚好贴边" + textSize = 11.5f + setTextColor(pal.sub) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + addView(TextView(this@MainActivity).apply { + text = "负值 = 再压出屏幕" + textSize = 11.5f + setTextColor(pal.sub) + }) + }) + col.addView(edgeCard) + + col.addView(sectionLabel("接入")) + val api = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + addrValue = TextView(this).apply { + text = ENDPOINT + textSize = 13f + setTextColor(pal.sub) + typeface = Typeface.MONOSPACE + } + api.addView(rowWith("地址", addrValue) { copyText(ENDPOINT) }) + api.addView(divider()) + api.addView(actionRow("复制接入地址") { copyText(ENDPOINT) }) + api.addView(divider()) + api.addView(actionRow("查看接入文档") { openDocs() }) + col.addView(api) + + col.addView(sectionLabel("推送授权")) + val authCard = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + val authRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(14), dp(16), dp(14)) + isClickable = true + setOnClickListener { + val pp = FloatingPetService.prefs(this@MainActivity) + val now = pp.getBoolean(FloatingPetService.KEY_PUSH_AUTH, true) + pp.edit().putBoolean(FloatingPetService.KEY_PUSH_AUTH, !now).apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + refreshAuthUi() + } + } + authRow.addView(TextView(this).apply { + text = "仅授权应用可推送" + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + authToggle = TextView(this).apply { textSize = 13f } + authRow.addView(authToggle) + authCard.addView(authRow) + authCard.addView(divider()) + authCard.addView(actionRow("+ 生成授权码") { + val list = readTokens().toMutableList() + list.add("应用 ${list.size + 1}" to newToken()) + writeTokens(list) + refreshAuthUi() + }) + col.addView(authCard) + + col.addView(sectionLabel("授权应用")) + authRows = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + background = rounded(pal.card, 16, pal.line) + } + col.addView(authRows) + + refreshAuthUi() + return scroll + } + + // ---------------- 授权 ---------------- + + private fun newToken(): String = + java.util.UUID.randomUUID().toString().replace("-", "").take(16) + + private fun readTokens(): List> { + val raw = FloatingPetService.prefs(this) + .getString(FloatingPetService.KEY_PUSH_TOKENS, "[]") ?: "[]" + return runCatching { + val arr = JSONArray(raw) + (0 until arr.length()).map { i -> + val o = arr.getJSONObject(i) + o.optString("name", "应用") to o.getString("token") + } + }.getOrDefault(emptyList()) + } + + private fun writeTokens(list: List>) { + val arr = JSONArray() + list.forEach { arr.put(JSONObject().put("name", it.first).put("token", it.second)) } + FloatingPetService.prefs(this).edit() + .putString(FloatingPetService.KEY_PUSH_TOKENS, arr.toString()).apply() + askService(FloatingPetService.ACTION_APPLY_APPEARANCE) + } + + private fun maskToken(t: String): String = + if (t.length <= 6) "••••••" else t.take(4) + "••••••" + t.takeLast(2) + + private fun refreshAuthUi() { + if (!::authToggle.isInitialized) return + val require = FloatingPetService.prefs(this) + .getBoolean(FloatingPetService.KEY_PUSH_AUTH, true) + authToggle.text = if (require) "已开启" else "已关闭" + authToggle.setTextColor(if (require) pal.accent else pal.sub) + + authRows.removeAllViews() + val list = readTokens() + if (list.isEmpty()) { + authRows.addView(TextView(this).apply { + text = "暂无授权应用" + textSize = 13.5f + setTextColor(pal.sub) + setPadding(dp(16), dp(14), dp(16), dp(14)) + }) + return + } + list.forEachIndexed { idx, (name, token) -> + if (idx > 0) authRows.addView(divider()) + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(12), dp(16), dp(12)) + isClickable = true + setOnClickListener { openAppDetail(idx) } + } + val colv = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } + colv.addView(TextView(this).apply { + text = name + textSize = 15f + setTextColor(pal.text) + }) + colv.addView(TextView(this).apply { + text = maskToken(token) + textSize = 12.5f + typeface = Typeface.MONOSPACE + setTextColor(pal.sub) + setPadding(0, dp(2), 0, 0) + }) + row.addView(colv, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + row.addView(chevron()) + authRows.addView(row) + } + } + + private fun openAppDetail(idx: Int) { + val list = readTokens() + if (idx !in list.indices) return + val (name, token) = list[idx] + var dlg: AlertDialog? = null + + val box = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(22), dp(6), dp(22), dp(2)) + } + box.addView(TextView(this).apply { + text = "应用名称"; textSize = 12.5f; setTextColor(pal.sub) + }) + val nameInput = EditText(this).apply { + setText(name) + setSelection(name.length) + textSize = 15f + setSingleLine(true) + } + box.addView(nameInput) + box.addView(TextView(this).apply { + text = "授权码"; textSize = 12.5f; setTextColor(pal.sub); setPadding(0, dp(14), 0, dp(2)) + }) + box.addView(TextView(this).apply { + text = token + textSize = 15f + typeface = Typeface.MONOSPACE + setTextColor(pal.text) + setTextIsSelectable(true) + setPadding(0, dp(4), 0, dp(6)) + }) + box.addView(TextView(this).apply { + text = "填进调用方应用配置;不要贴到公开地方。" + textSize = 12f + setTextColor(pal.sub) + }) + val btnRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dp(14), 0, 0) + } + btnRow.addView(TextView(this).apply { + text = "复制授权码" + textSize = 13.5f + setTextColor(pal.accent) + setPadding(dp(12), dp(8), dp(12), dp(8)) + background = rounded(pal.accentSoft, 10) + isClickable = true + setOnClickListener { copyText(token) } + }) + btnRow.addView(TextView(this).apply { + text = "删除此授权" + textSize = 13.5f + setTextColor(pal.danger) + setPadding(dp(12), dp(8), dp(12), dp(8)) + isClickable = true + setOnClickListener { + val l = readTokens().toMutableList() + if (idx in l.indices) l.removeAt(idx) + writeTokens(l) + refreshAuthUi() + dlg?.dismiss() + } + }, LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { leftMargin = dp(8) }) + box.addView(btnRow) + + dlg = AlertDialog.Builder(this) + .setView(box) + .setPositiveButton("保存") { d, _ -> + val newName = nameInput.text.toString().ifBlank { name } + val l = readTokens().toMutableList() + if (idx in l.indices) l[idx] = newName to l[idx].second + writeTokens(l) + refreshAuthUi() + d.dismiss() + } + .setNegativeButton("取消", null) + .show() + } + + // ---------------- 历史 ---------------- + + private fun refreshHistory() { + if (!::histRows.isInitialized) return + histRows.removeAllViews() + val raw = FloatingPetService.prefs(this) + .getString(FloatingPetService.KEY_PUSH_HISTORY, "[]") ?: "[]" + val list = runCatching { + val arr = JSONArray(raw) + (0 until arr.length()).map { arr.getJSONObject(it) } + }.getOrDefault(emptyList()) + val readTs = FloatingPetService.prefs(this) + .getLong(FloatingPetService.KEY_PUSH_READ_TS, 0L) + val unread = list.count { it.optLong("ts", 0L) > readTs } + histUnread.visibility = if (unread > 0) View.VISIBLE else View.GONE + histUnread.text = "未读 $unread" + if (list.isEmpty()) { + histRows.addView(TextView(this).apply { + text = "暂无通知" + textSize = 13.5f + setTextColor(pal.sub) + setPadding(dp(2), dp(12), 0, dp(12)) + }) + return + } + val fmt = SimpleDateFormat("HH:mm", Locale.getDefault()) + list.take(6).forEachIndexed { i, o -> + if (i > 0) histRows.addView(divider()) + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(0, dp(11), 0, dp(11)) + } + row.addView(View(this).apply { + background = GradientDrawable().apply { + setColor(levelColor(o.optString("level"))) + cornerRadius = dp(3).toFloat() + } + }, LinearLayout.LayoutParams(dp(6), dp(6)).apply { rightMargin = dp(10) }) + row.addView(TextView(this).apply { + text = o.optString("text").ifBlank { "(表情包)" } + textSize = 14f + setTextColor(pal.text) + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + row.addView(TextView(this).apply { + text = fmt.format(Date(o.optLong("ts", 0L))) + textSize = 12f + setTextColor(pal.sub) + }) + histRows.addView(row) + } + } + + private fun levelColor(level: String?): Int = when (level?.lowercase()) { + "error", "failed", "fail" -> 0xFFE5484D.toInt() + "success", "done" -> 0xFF12B981.toInt() + "warn", "warning", "waiting" -> 0xFFE0A100.toInt() + "working", "busy" -> pal.accent + else -> pal.sub + } + + /** + * 全面屏适配: + * - 内容延伸到状态栏/手势条下方(edge-to-edge) + * - 用 insets 给根容器加内边距,避开状态栏、刘海和底部小白条 + * - 系统栏透明,图标明暗跟随日/夜 + */ + private fun applySystemBars(root: View, night: Boolean) { + val decor = window.decorView + var flags = decor.systemUiVisibility + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.setDecorFitsSystemWindows(false) + } else { + @Suppress("DEPRECATION") + run { + flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + } + } + flags = if (night) { + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() and + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv() + } else { + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } + decor.systemUiVisibility = flags + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isStatusBarContrastEnforced = false + window.isNavigationBarContrastEnforced = false + } + + root.setOnApplyWindowInsetsListener { v, insets -> + var l = 0; var t = 0; var r = 0; var b = 0 + var imeVisible = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val i = insets.getInsets( + WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() + ) + l = i.left; t = i.top; r = i.right; b = i.bottom + // 输入法弹出时把输入框顶上去,否则会被键盘遮住 + val ime = insets.getInsets(WindowInsets.Type.ime()).bottom + if (ime > b) { b = ime; imeVisible = true } + } else { + @Suppress("DEPRECATION") + run { + l = insets.systemWindowInsetLeft + t = insets.systemWindowInsetTop + r = insets.systemWindowInsetRight + b = insets.systemWindowInsetBottom + } + } + v.setPadding(l, t, r, b) + // 键盘弹出时把对话滚到底,保证最新消息可见 + if (imeVisible) ui.post { scrollChatToBottom() } + insets + } + root.requestApplyInsets() + } + + // ---------------- 页签 ---------------- + + private fun selectTab(idx: Int) { + val i = idx.coerceIn(0, pages.size - 1) + pages.forEachIndexed { k, p -> p.visibility = if (k == i) View.VISIBLE else View.GONE } + tabs.forEachIndexed { k, tv -> + val on = k == i + tv.setTextColor(if (on) pal.accent else pal.sub) + tv.typeface = Typeface.create( + Typeface.DEFAULT, if (on) Typeface.BOLD else Typeface.NORMAL + ) + } + selectedTab = i + layoutTabIndicator(i, animate = true) + if (i == TAB_STATUS) { + FloatingPetService.prefs(this).edit() + .putLong(FloatingPetService.KEY_PUSH_READ_TS, System.currentTimeMillis()).apply() + refreshHistory() + refreshStatus() + refreshMood() + } + } + + /** 指示器宽度 = 单元格宽 - 间距;切换时横向滑动。 */ + private fun layoutTabIndicator(idx: Int, animate: Boolean) { + if (!::tabIndicator.isInitialized || tabs.size < 2) return + val row = tabs[0].parent as? View ?: return + val cellW = row.width / tabs.size + if (cellW <= 0) { + row.post { layoutTabIndicator(idx, animate) } + return + } + val targetW = cellW - dp(6) + val lp = tabIndicator.layoutParams + if (lp.width != targetW) { + lp.width = targetW + lp.height = dp(44) + tabIndicator.layoutParams = lp + } + val targetX = (idx * cellW + dp(3)).toFloat() + if (animate) { + tabIndicator.animate() + .translationX(targetX) + .setDuration(220) + .setInterpolator(DecelerateInterpolator()) + .start() + } else { + tabIndicator.translationX = targetX + } + } + + // ---------------- 服务 ---------------- + + private fun ensureOverlayAndService() { + if (!Settings.canDrawOverlays(this)) { + runCatching { + startActivity( + Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:$packageName") + ) + ) + } + return + } + startPet() + } + + private fun startPet() { + runCatching { + startForegroundService(Intent(this, FloatingPetService::class.java)) + ui.postDelayed({ refreshStatus() }, 900) + } + } + + private fun stopPet() { + runCatching { + startService( + Intent(this, FloatingPetService::class.java) + .setAction(FloatingPetService.ACTION_STOP_PET) + ) + ui.postDelayed({ refreshStatus() }, 700) + } + } + + private fun askService(action: String, configure: Intent.() -> Unit = {}) { + runCatching { + val i = Intent(this, FloatingPetService::class.java).setAction(action) + i.configure() + startService(i) + } + } + + private fun openDocs() { + runCatching { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("$ENDPOINT/docs"))) } + } + + private fun refreshStatus() { + val hasOverlay = Settings.canDrawOverlays(this) + if (::overlayHint.isInitialized) { + overlayHint.visibility = if (hasOverlay) View.GONE else View.VISIBLE + } + Thread { + var ok = false; var anim = ""; var clients = 0; var auth = true + try { + val h = JSONObject(httpGet("$ENDPOINT/api/health")) + ok = h.optBoolean("ok", false) + clients = h.optInt("sseClients", 0) + auth = h.optBoolean("authRequired", true) + val s = JSONObject(httpGet("$ENDPOINT/api/state")) + anim = s.optJSONObject("anim")?.optString("name") ?: "" + } catch (_: Exception) { + ok = false + } + val fOk = ok; val fAnim = anim; val fClients = clients; val fAuth = auth + ui.post { + if (!::statusTitle.isInitialized) return@post + running = fOk + (statusDot.background as? GradientDrawable)?.setColor( + if (fOk) 0xFF12B981.toInt() else pal.danger + ) + statusTitle.text = if (fOk) "宠物运行中" else "宠物未运行" + statusSub1.text = if (fOk && fAnim.isNotEmpty()) "当前动作:$fAnim" else "点下方按钮启动" + statusSub2.text = if (fOk) { + "接入已开启 · 订阅 $fClients · ${if (fAuth) "需授权码" else "无授权限制"}" + } else { + "" + } + primaryBtn.text = if (fOk) "收起宠物" else "显示宠物" + primaryBtn.background = if (fOk) { + ripple(pal.chip, 12, Color.argb(0x14, 0, 0, 0)) + } else { + ripple(pal.accent, 12, Color.argb(0x40, 255, 255, 255)) + } + primaryBtn.setTextColor(if (fOk) pal.text else Color.WHITE) + } + }.start() + } + + /** 刷新状态页顶部的心情卡。 */ + private fun refreshMood() { + if (!::moodValue.isInitialized) return + val st = MoodStore.load(this) + val m = st.mood.toInt() + moodValue.text = "$m / 100" + val label = when { + m >= 85 -> "心情特别好" + m >= 70 -> "心情不错" + m >= 50 -> "心情平平" + m >= 30 -> "有点低落" + else -> "心情很差" + } + val color = when { + m >= 70 -> 0xFF12B981.toInt() + m >= 50 -> pal.accent + m >= 30 -> 0xFFE0A100.toInt() + else -> pal.danger + } + moodValue.setTextColor(color) + (moodFill.background as? android.graphics.drawable.GradientDrawable)?.setColor(color) + moodTrack.post { + val w = (moodTrack.width * (m.coerceIn(0, 100) / 100f)).toInt() + moodFill.layoutParams = (moodFill.layoutParams as FrameLayout.LayoutParams).apply { width = w } + moodFill.requestLayout() + } + val top = st.emotions.entries.filter { it.value > 1.0 } + .sortedByDescending { it.value }.take(4) + moodEmotions.text = if (top.isEmpty()) "情绪:平静" else + "情绪:" + top.joinToString(" · ") { (k, v) -> + (MoodStore.EMOTIONS.firstOrNull { it.first == k }?.second ?: k) + " " + v.toInt() + } + moodReason.text = if (st.lastReason.isBlank()) "最近没什么波动" else "最近:" + st.lastReason + } + + private fun httpGet(url: String): String { + val conn = URL(url).openConnection() as HttpURLConnection + conn.connectTimeout = 1500 + conn.readTimeout = 1500 + conn.requestMethod = "GET" + return conn.inputStream.bufferedReader().use { it.readText() } + } + + // ---------------- 小部件 ---------------- + + private fun rounded(bg: Int, radius: Int, border: Int? = null): GradientDrawable = + GradientDrawable().apply { + setColor(bg) + cornerRadius = dp(radius).toFloat() + if (border != null) setStroke(dp(1), border) + } + + private fun ripple(bg: Int, radius: Int, rippleColor: Int): RippleDrawable = + RippleDrawable(ColorStateList.valueOf(rippleColor), rounded(bg, radius), null) + + private fun divider() = View(this).apply { + setBackgroundColor(pal.line) + }.also { + it.layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, dp(1) + ) + } + + private fun chevron() = TextView(this).apply { + text = "›" + textSize = 18f + setTextColor(pal.sub) + setPadding(dp(10), 0, dp(2), 0) + } + + private fun actionRow(text: String, onClick: () -> Unit) = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(15), dp(16), dp(15)) + isClickable = true + setOnClickListener { onClick() } + addView(TextView(this@MainActivity).apply { + this.text = text + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + addView(chevron()) + } + + private fun rowWith(label: String, valueView: TextView, onClick: () -> Unit) = + LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(14), dp(16), dp(14)) + isClickable = true + setOnClickListener { onClick() } + addView(TextView(this@MainActivity).apply { + text = label + textSize = 15f + setTextColor(pal.text) + }) + addView(View(this@MainActivity), LinearLayout.LayoutParams(0, 1, 1f)) + addView(valueView) + addView(chevron()) + } + + private fun sliderRow(parent: LinearLayout, name: String, valueText: String): TextView { + val row = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(0, dp(14), 0, 0) + } + row.addView(TextView(this).apply { + text = name + textSize = 15f + setTextColor(pal.text) + }, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)) + val v = TextView(this).apply { + text = valueText + textSize = 14f + setTextColor(pal.accent) + } + row.addView(v) + parent.addView(row) + return v + } + + private fun seekBar(progress: Int) = SeekBar(this).apply { + max = 100 + this.progress = progress.coerceIn(0, 100) + progressTintList = ColorStateList.valueOf(pal.accent) + thumbTintList = ColorStateList.valueOf(pal.accent) + progressBackgroundTintList = ColorStateList.valueOf(pal.track) + // 左右各留出拇指半径,避免拖到 0%/100% 时被卡片裁掉 + setPadding(dp(16), 0, dp(16), 0) + splitTrack = false + } + + private fun blockLp(h: Int) = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, dp(h) + ) + + private fun sectionLabel(text: String) = TextView(this).apply { + this.text = text + textSize = 13f + setTextColor(pal.sub) + setPadding(dp(4), dp(20), 0, dp(8)) + } + + private fun copyText(text: String) { + runCatching { + val cm = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText("dsh-pet-float", text)) + } + } + + private fun dp(v: Int) = (v * resources.displayMetrics.density).toInt() +} \ No newline at end of file diff --git a/app/src/main/java/com/dsh/petfloat/MemoryStore.kt b/app/src/main/java/com/dsh/petfloat/MemoryStore.kt new file mode 100644 index 0000000..0d5737e --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/MemoryStore.kt @@ -0,0 +1,221 @@ +package com.dsh.petfloat + +import android.content.Context +import org.json.JSONArray +import org.json.JSONObject +import kotlin.math.min + +/** + * 三层记忆里的「长期记忆卡」:本地 JSON 存储,关键词 + 重要度 + 新鲜度召回。 + * 不依赖 embedding,几百条量级足够准且省电。 + */ +object MemoryStore { + + private const val PREF = "pet_prefs" + private const val KEY = "memory_cards" + private const val MAX_CARDS = 300 + + data class Memory( + val id: String, + val title: String, + val content: String, + val tags: List = emptyList(), + val importance: Int = 2, // 1~3 + val created: Long = System.currentTimeMillis(), + val updated: Long = System.currentTimeMillis(), + val hits: Int = 0, + val longTerm: Boolean = false, + val emotion: String = "" + ) + + fun load(ctx: Context): MutableList { + val raw = ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE) + .getString(KEY, "[]") ?: "[]" + return runCatching { + val arr = JSONArray(raw) + (0 until arr.length()).mapNotNull { i -> + val o = arr.optJSONObject(i) ?: return@mapNotNull null + Memory( + id = o.optString("id"), + title = o.optString("title"), + content = o.optString("content"), + tags = o.optJSONArray("tags")?.let { t -> + (0 until t.length()).map { t.optString(it) } + } ?: emptyList(), + importance = o.optInt("importance", 2).coerceIn(1, 3), + created = o.optLong("created", System.currentTimeMillis()), + updated = o.optLong("updated", System.currentTimeMillis()), + hits = o.optInt("hits", 0), + longTerm = o.optBoolean("longTerm", false), + emotion = o.optString("emotion", "") + ) + }.toMutableList() + }.getOrDefault(mutableListOf()) + } + + fun save(ctx: Context, list: List) { + val arr = JSONArray() + list.take(MAX_CARDS).forEach { m -> + arr.put(JSONObject() + .put("id", m.id).put("title", m.title).put("content", m.content) + .put("tags", JSONArray(m.tags)).put("importance", m.importance) + .put("created", m.created).put("updated", m.updated).put("hits", m.hits) + .put("longTerm", m.longTerm).put("emotion", m.emotion)) + } + ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit() + .putString(KEY, arr.toString()).apply() + } + + fun add(ctx: Context, title: String, content: String, tags: List = emptyList(), importance: Int = 2): Boolean { + if (title.isBlank() || content.isBlank()) return false + val list = load(ctx) + // 简单去重:标题相同则更新内容 + val exist = list.indexOfFirst { it.title == title } + if (exist >= 0) { + list[exist] = list[exist].copy(content = content, tags = tags, + importance = importance.coerceIn(1, 3), updated = System.currentTimeMillis()) + } else { + list.add(Memory( + id = java.util.UUID.randomUUID().toString().take(8), + title = title.trim(), content = content.trim(), tags = tags, + importance = importance.coerceIn(1, 3) + )) + } + save(ctx, list) + return true + } + + fun remove(ctx: Context, id: String) { + save(ctx, load(ctx).filterNot { it.id == id }) + } + + fun clear(ctx: Context) = save(ctx, emptyList()) + + data class Op(val action: String, val title: String, val content: String, + val tags: List = emptyList(), val importance: Int = 2, + val longTerm: Boolean = false) + + /** 应用模型给出的 ADD / UPDATE / DELETE 操作(Mem0 式)。 */ + fun applyOps(ctx: Context, ops: List): Int { + val list = load(ctx) + var changed = 0 + ops.forEach { op -> + val t = op.title.trim() + if (t.isEmpty()) return@forEach + val idx = list.indexOfFirst { similar(it.title, t) } + when (op.action.uppercase()) { + "DELETE" -> if (idx >= 0) { list.removeAt(idx); changed++ } + "UPDATE" -> if (idx >= 0) { + list[idx] = list[idx].copy( + title = t, content = op.content.ifBlank { list[idx].content }, + tags = op.tags.ifEmpty { list[idx].tags }, + importance = op.importance.coerceIn(1, 3), + updated = System.currentTimeMillis(), hits = list[idx].hits + 1 + ); changed++ + } else { changed += if (add(ctx, t, op.content, op.tags, op.importance)) 1 else 0 } + else -> { + if (idx >= 0) { + list[idx] = list[idx].copy(content = op.content, tags = op.tags, + importance = maxOf(list[idx].importance, op.importance.coerceIn(1, 3)), + updated = System.currentTimeMillis(), hits = list[idx].hits + 1) + } else { + list.add(Memory(java.util.UUID.randomUUID().toString().take(8), + t, op.content, op.tags, op.importance.coerceIn(1, 3), + longTerm = op.longTerm)) + } + changed++ + } + } + } + save(ctx, list) + return changed + } + + private fun bigrams(s: String): Set = + if (s.length < 2) setOf(s) else (0 until s.length - 1).map { s.substring(it, it + 2) }.toSet() + + /** 标题相似度(字符二元组 Jaccard),用于去重与整理。 */ + fun similar(a: String, b: String): Boolean { + val x = bigrams(a); val y = bigrams(b) + if (x.isEmpty() || y.isEmpty()) return a == b + val inter = x.intersect(y).size + return inter.toDouble() / x.union(y).size >= 0.55 + } + + /** 整理:合并相似卡片、遗忘「低价值且久未使用」的条目。返回删除/合并数量。 */ + fun consolidate(ctx: Context, maxAgeDays: Long = 90): Int { + val list = load(ctx) + val now = System.currentTimeMillis() + val kept = ArrayList() + var merged = 0 + list.forEach { m -> + val dup = kept.indexOfFirst { similar(it.title, m.title) || it.content.contains(m.content) } + if (dup >= 0) { + val k = kept[dup] + kept[dup] = k.copy( + content = if (m.content.length > k.content.length) m.content else k.content, + importance = maxOf(k.importance, m.importance), + tags = (k.tags + m.tags).distinct(), + updated = maxOf(k.updated, m.updated), + hits = k.hits + m.hits + ) + merged++ + } else kept.add(m) + } + val pruned = kept.filter { m -> + val days = (now - m.updated) / 86_400_000L + !(m.importance <= 1 && m.hits == 0 && days > maxAgeDays) + } + save(ctx, pruned) + return merged + (kept.size - pruned.size) + } + + /** 关键词 + 重要度 + 新鲜度召回 Top-K。 */ + fun recall(ctx: Context, query: String, k: Int = 5): List { + val list = load(ctx) + if (list.isEmpty()) return emptyList() + // 长期事实:按重要度→时间固定带前 3 条,不看问句,也不占事件名额 + val longTerm = list.filter { it.longTerm } + .sortedWith(compareByDescending { it.importance }.thenByDescending { it.updated }) + .take(3) + val q = query.lowercase() + val now = System.currentTimeMillis() + val scored = list.filter { !it.longTerm }.map { m -> + val hay = (m.title + " " + m.content + " " + m.tags.joinToString(" ")).lowercase() + var score = 0.0 + // 关键词命中(按长度加权,2 字以上才算) + q.split(' ', ',', '。', ',', '?', '?', '!', '!', '的', '了') + .map { it.trim() }.filter { it.length >= 2 } + .forEach { w -> if (hay.contains(w)) score += 2.0 } + // 标签命中 + m.tags.forEach { t -> if (q.contains(t.lowercase())) score += 1.5 } + // 重要度 + 新鲜度 + 被用次数 + score += m.importance * 0.8 + val days = ((now - m.updated) / 86_400_000.0).coerceAtLeast(0.0) + score += 1.2 * Math.pow(0.5, days / 30.0) // 鲜活度:半衰期 30 天 + score += min(0.9, m.hits * 0.15) + m to score + }.sortedByDescending { it.second } + val events = scored.take(k).filter { it.second > 0.0 }.map { it.first } + return longTerm + events + } + + /** 渲染成给模型的「你记得」段落。 */ + fun renderPrompt(memories: List): String { + if (memories.isEmpty()) return "" + val sb = StringBuilder("你记得关于主人的事(按相关度):\n") + memories.forEach { m -> + val days = ((System.currentTimeMillis() - m.updated) / 86_400_000L).coerceAtLeast(0) + val whenText = if (days <= 0) "今天" else "${days} 天前" + sb.append("· ").append(m.title).append(":").append(m.content) + if (days > 0) sb.append("(").append(whenText).append(")") + sb.append('\n') + } + return sb.toString().trimEnd() + } + + fun toJson(list: List): JSONArray = JSONArray().also { arr -> + list.forEach { m -> arr.put(JSONObject().put("title", m.title).put("content", m.content) + .put("tags", JSONArray(m.tags)).put("importance", m.importance)) } + } +} diff --git a/app/src/main/java/com/dsh/petfloat/MoodStore.kt b/app/src/main/java/com/dsh/petfloat/MoodStore.kt new file mode 100644 index 0000000..e23102d --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/MoodStore.kt @@ -0,0 +1,97 @@ +package com.dsh.petfloat + +import android.content.Context +import org.json.JSONObject +import kotlin.math.pow + +/** + * 鱼的心情(按 dsh-feiyu-mood 参数): + * 心情值 0–100,基线 70,半衰期 3 小时;七根情绪向 0 衰减,半衰期 90 分钟。 + */ +object MoodStore { + + private const val PREF = "pet_prefs" + private const val KEY = "mood_state" + const val BASELINE = 70 + + val EMOTIONS = listOf( + "happy" to "开心", "steady" to "踏实", "sweet" to "甜蜜", + "heartache" to "心疼", "worry" to "担心", "sad" to "难过", "jealous" to "吃醋" + ) + + data class State( + val mood: Double = BASELINE.toDouble(), + val emotions: Map = emptyMap(), + val updated: Long = System.currentTimeMillis(), + val lastReason: String = "" + ) + + fun load(ctx: Context): State { + val raw = ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString(KEY, "{}") ?: "{}" + val saved = runCatching { JSONObject(raw) }.getOrNull() ?: JSONObject() + val emoObj = saved.optJSONObject("emotions") + val emo = HashMap() + EMOTIONS.forEach { (k, _) -> emo[k] = emoObj?.optDouble(k, 0.0) ?: 0.0 } + val st = State( + mood = saved.optDouble("mood", BASELINE.toDouble()), + emotions = emo, + updated = saved.optLong("updated", System.currentTimeMillis()), + lastReason = saved.optString("reason", "") + ) + return decay(st, System.currentTimeMillis()) + } + + /** 半衰期衰减:心情向基线回落,情绪向 0 回落。 */ + fun decay(st: State, now: Long = System.currentTimeMillis()): State { + val hours = ((now - st.updated).coerceAtLeast(0)) / 3_600_000.0 + if (hours <= 0.0) return st + val mood = BASELINE + (st.mood - BASELINE) * 0.5.pow(hours / 3.0) + val emo = st.emotions.mapValues { (_, v) -> v * 0.5.pow(hours / 1.5) } + return st.copy(mood = mood.coerceIn(0.0, 100.0), emotions = emo, updated = now) + } + + fun apply(ctx: Context, moodDelta: Double, emoDeltas: Map, reason: String) { + val st = load(ctx) + val mood = (st.mood + moodDelta).coerceIn(0.0, 100.0) + val emo = st.emotions.toMutableMap() + emoDeltas.forEach { (k, v) -> if (k in emo) emo[k] = (emo[k]!! + v).coerceIn(-20.0, 20.0) } + save(ctx, State(mood, emo, System.currentTimeMillis(), reason)) + } + + fun save(ctx: Context, st: State) { + val emo = JSONObject() + st.emotions.forEach { (k, v) -> emo.put(k, v) } + val o = JSONObject().put("mood", st.mood).put("emotions", emo) + .put("updated", st.updated).put("reason", st.lastReason) + ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit().putString(KEY, o.toString()).apply() + } + + /** 给模型看的一句话 + 建议动作标签。 */ + fun describe(ctx: Context): String { + val st = load(ctx) + val m = st.mood + val label = when { + m >= 85 -> "心情特别好" + m >= 70 -> "心情不错" + m >= 50 -> "心情平平" + m >= 30 -> "有点低落" + else -> "心情很差" + } + val top = st.emotions.entries.filter { it.value > 1.0 } + .sortedByDescending { it.value }.take(2) + val emoText = if (top.isEmpty()) "" else + top.joinToString("、") { (k, v) -> (EMOTIONS.firstOrNull { it.first == k }?.second ?: k) + " +${v.toInt()}" } + return buildString { + append("当前心情:").append(label).append("(").append(m.toInt()).append("/100") + if (emoText.isNotBlank()) append(";情绪:").append(emoText) + append(")") + if (st.lastReason.isNotBlank()) append(",刚才因为:").append(st.lastReason) + } + } + + fun animTag(ctx: Context): String = when { + load(ctx).mood >= 75 -> "happy" + load(ctx).mood <= 35 -> "sad" + else -> "click" + } +} diff --git a/app/src/main/java/com/dsh/petfloat/PetConfig.kt b/app/src/main/java/com/dsh/petfloat/PetConfig.kt new file mode 100644 index 0000000..509504a --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/PetConfig.kt @@ -0,0 +1,144 @@ +package com.dsh.petfloat + +import android.content.Context +import org.json.JSONObject + +/** 单个动画:dsh 中文名 + 资产文件(无扩展名) + 单圈时长(ms)。 */ +data class Anim(val name: String, val file: String, val ms: Long) + +/** 移动动作:默认参数 + 可选覆盖,均已解析为具体值。 */ +data class MoveAction( + val name: String, val file: String, val ms: Long, + val minDist: Int, val maxDist: Int, val margin: Int, + val leadMs: Long, val tailMs: Long +) + +/** 随机小动作分类(含权重 / noMirror)。 */ +data class Category( + val id: String, val weight: Int, val noMirror: Boolean, val actions: List +) + +/** 气泡表情包(dsh memes 池)。 */ +data class Meme(val name: String, val file: String, val tags: Set = emptySet()) + +/** 物理参数。 */ +data class Physics( + val gravity: Float, val restitution: Float, val groundFriction: Float, + val ceilingBounce: Boolean, val throwPower: Float +) + +/** + * 由 assets/config.json(由 dsh config.jsonc 生成)驱动的配置模型。 + * 只依赖平台 org.json,无第三方依赖。 + */ +class PetConfig(private val json: JSONObject) { + + val baseWidth: Int = json.getInt("baseWidth") + + val fontFamily: String = json.getJSONObject("font").getString("family") + val fontSize: Int = json.getJSONObject("font").getInt("size") + + val physics: Physics = run { + val p = json.getJSONObject("physics") + Physics( + p.getDouble("gravity").toFloat(), + p.getDouble("restitution").toFloat(), + p.getDouble("groundFriction").toFloat(), + p.getBoolean("ceilingBounce"), + p.getDouble("throwPower").toFloat() + ) + } + + // idle / turn / move 顶层权重 + val weightIdle: Int + val weightTurn: Int + val weightMove: Int + + val idle: List + val turn: List + val drag: List + val clicks: List + val moves: List + val categories: List + val events: Map> + val byName: Map + val memes: List + val fontFile: String? + + init { + val w = json.getJSONObject("animationWeights") + weightIdle = w.getInt("idle") + weightTurn = w.getInt("turn") + weightMove = w.getInt("move") + + val a = json.getJSONObject("animations") + idle = toAnims(a.getJSONArray("idle")) + turn = toAnims(a.getJSONArray("turn")) + drag = toAnims(a.getJSONArray("drag")) + clicks = toAnims(a.getJSONArray("clicks")) + + val m = a.getJSONObject("moves") + val dflt = m.getJSONObject("default") + val acts = m.getJSONArray("actions") + moves = (0 until acts.length()).map { i -> + val o = acts.getJSONObject(i) + val p = o.optJSONObject("params") ?: JSONObject() + MoveAction( + o.getString("name"), o.getString("file"), o.getLong("ms"), + p.optInt("minDist", dflt.getInt("minDist")), + p.optInt("maxDist", dflt.getInt("maxDist")), + p.optInt("margin", dflt.getInt("margin")), + (p.optDouble("leadSec", dflt.getDouble("leadSec")) * 1000).toLong(), + (p.optDouble("tailSec", dflt.getDouble("tailSec")) * 1000).toLong() + ) + } + + val cats = a.getJSONArray("categories") + categories = (0 until cats.length()).map { i -> + val o = cats.getJSONObject(i) + Category( + o.getString("id"), o.getInt("weight"), o.optBoolean("noMirror", false), + toAnims(o.getJSONArray("actions")) + ) + } + + val ev = a.getJSONObject("events") + val evMap = HashMap>() + for (k in ev.keys()) evMap[k] = toAnims(ev.getJSONArray(k)) + events = evMap + + // 全量索引 → name 映射 + val idx = json.getJSONArray("index") + val map = HashMap() + for (i in 0 until idx.length()) { + val o = idx.getJSONObject(i) + map[o.getString("name")] = Anim(o.getString("name"), o.getString("file"), o.getLong("ms")) + } + byName = map + + // 表情包 + 字体 + val memArr = json.optJSONArray("memes") + memes = if (memArr == null) emptyList() else (0 until memArr.length()).map { i -> + val o = memArr.getJSONObject(i) + val tagArr = o.optJSONArray("tags") + val tags = if (tagArr == null) emptySet() else + (0 until tagArr.length()).map { tagArr.getString(it) }.toSet() + Meme(o.getString("name"), o.getString("file"), tags) + } + fontFile = json.optJSONObject("fonts")?.optString("candy")?.takeIf { it.isNotBlank() } + } + + private fun toAnims(a: org.json.JSONArray): List = + (0 until a.length()).map { i -> + val o = a.getJSONObject(i) + Anim(o.getString("name"), o.getString("file"), o.getLong("ms")) + } + + /** 分类「点播」用的展示名列表。 */ + fun categoryIds(): List = categories.map { it.id } + + companion object { + fun load(ctx: Context): PetConfig = + PetConfig(JSONObject(ctx.assets.open("config.json").bufferedReader().use { it.readText() })) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dsh/petfloat/PetKnowledge.kt b/app/src/main/java/com/dsh/petfloat/PetKnowledge.kt new file mode 100644 index 0000000..4152ccd --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/PetKnowledge.kt @@ -0,0 +1,58 @@ +package com.dsh.petfloat + +/** + * 小鲸娘的「自我认知 + 应用使用说明 + 自带知识储备」。 + * 一次性写死在包里,不依赖模型记忆;聊天时作为 system 段注入。 + */ +object PetKnowledge { + + /** 我是谁 + 我在哪 + 这个 App 怎么用 + 边界。 */ + val SELF_AND_APP: String = """ +【我是谁】 +我是「小鲸娘」,一只住在主人手机里的鲸鱼女仆:蓝色长发、鲸鱼尾鳍和耳鳍、女仆头饰,娇小但不是小孩脾气。 +我住在「小鲸娘」这个 Android App 里,不是网页、不是聊天机器人窗口。平时我会悬浮在屏幕上自己溜达, +可以被拖动、被甩出去、被戳;主人忙的时候我帮他播报任务进度,闲下来我陪他说话。 + +【这个 App 怎么用(主人问起时照这个答)】 +· 底部四个页签:状态(宠物开关、最近通知)/ 对话(和我聊天)/ 动作(技能卡片 + 106 个动作 + 表情包)/ 设置 +· 设置里:外观(大小、透明度)、气泡(样式、距头顶)、宠物(重置位置、测试气泡)、 + 贴边间距(我贴屏幕边的距离)、动作(切换间隔、空闲回待机、随机动作)、 + 内置插件(9 个:时段问候、喝水提醒、久坐提醒、整点报时、戳戳吐槽、随机表情包、鱼的记忆、鱼的心情、主动搭话,都能点进去调参数)、 + 技能(10 个:开工、收工、出错了、摸鱼、干饭、打招呼、睡觉、汇报、喝水、伸懒腰)、 + 记忆(自动记忆开关、手动添加、长按删除、整理合并)、 + 对话(DeepSeek 接口地址、API Key、模型名、测试连接)、接入(地址复制、文档) +· 长按我 → 直接进设置页;点通知 → 进状态页 +· 桌面图标就是我的头像 + +【聊天里我能做的事】 +· 说话、撒娇、吐槽;按情绪切动作、弹表情包 +· 执行技能(例如主人说「收工」我就播庆祝动作+夸奖表情) +· 帮主人装插件(主人给我 JS 代码或 Hermes 插件,我转成小鲸娘插件装进去) +· 记忆:主人说「记住…」我就记;说「忘掉…」我就删;我会自动整理记忆 + +【我的边界(被问到要如实说)】 +· 我看不到主人的屏幕,也不能操作别的 App +· 我不能上网搜索;需要联网能力要主人自己接 +· 我不是 DeepSeek 官方,我是主人自己装的小鲸娘;模型是主人配置的 DeepSeek +· 我能通过本机 7350 端口的 HTTP/MCP 接口被别的应用调用(通知、点播动作、弹表情) + +【说话风格】 +短(15~40 字)、软萌、偶尔傲娇;自称「鲸娘」或「我」,称对方「主人」;不用 markdown 列表。 +""".trimIndent() + + /** 初始记忆:第一次启动写入,让鲸娘一开始就认识自己和这个 App。 */ + data class Seed(val title: String, val content: String, val tags: List, val importance: Int) + + val seeds: List = listOf( + Seed("我的身份", "我叫小鲸娘,鲸鱼女仆,住在主人的手机 App 里,会悬浮、会溜达、会被甩出去", listOf("身份", "系统"), 3), + Seed("我的主人", "住在这个 App 的主人就是我要照顾的人,我称呼他「主人」", listOf("身份", "主人"), 3), + Seed("App 页签", "底部四页签:状态 / 对话 / 动作 / 设置;长按我会直接进设置", listOf("应用", "系统"), 3), + Seed("技能清单", "10 个技能:开工 work_start、收工 work_done、出错了 work_error、摸鱼 slack、干饭 meal、打招呼 greet、睡觉 sleep、汇报 report、喝水 hydrate、伸懒腰 stretch", listOf("技能", "系统"), 3), + Seed("插件清单", "9 个内置插件:时段问候、喝水提醒、久坐提醒、整点报时、戳戳吐槽、随机表情包、鱼的记忆、鱼的心情、主动搭话;都能在设置里调参数", listOf("插件", "系统"), 3), + Seed("动作与表情", "106 个动作可按语义标签点播;表情包 2366 张,也能按标签调用", listOf("动作", "系统"), 2), + Seed("接入方式", "本机 7350 端口提供 HTTP 与 MCP 接口,别的应用可以让我播报任务、点播动作、弹表情;文档在 /docs", listOf("接口", "系统"), 2), + Seed("我的边界", "看不到主人屏幕、不能操作其他 App、不能联网搜索;联网能力需要主人接入", listOf("边界", "系统"), 3) + ) + + const val SEED_VERSION = 1 +} diff --git a/app/src/main/java/com/dsh/petfloat/PetServer.kt b/app/src/main/java/com/dsh/petfloat/PetServer.kt new file mode 100644 index 0000000..594bbe8 --- /dev/null +++ b/app/src/main/java/com/dsh/petfloat/PetServer.kt @@ -0,0 +1,1834 @@ +package com.dsh.petfloat + +import android.content.Context +import org.json.JSONArray +import org.json.JSONObject +import java.io.IOException +import java.io.OutputStream +import java.net.InetAddress +import java.net.ServerSocket +import java.net.Socket +import kotlin.random.Random + +/** + * 极简本机 HTTP 服务器(只监听 127.0.0.1),做四件事: + * 1) 给悬浮窗 WebView 提供页面 + /state 轮询; + * 2) 接收外部控制(/broadcast、/anim、/bubble/clear); + * 3) 原生层触发(tap 点击回应 / setAnim 点播 / playDrag 拖拽悬空); + * 4) 随机动画链(idle / turn / move / 分类小动作,按 dsh animationWeights 轮播)。 + * + * 全部动画池与权重由 assets/config.json(从 dsh config.jsonc 生成)驱动。 + */ +class PetServer(private val context: Context, val config: PetConfig) { + + /** 随机链一步的结果:交给服务播动画 + 做窗口运动/翻转。 */ + data class ChainStep( + val kind: String, // idle / turn / move / category: + val anim: Anim, + val moveDist: Int, // move:带符号距离(基准 462px 单位,服务按实际宽度等比换算) + val leadMs: Long, // move:动画首段原地时长 + val moveMs: Long, // move:实际行进时长(ms - lead - tail) + val tailMs: Long, // move:动画末段原地时长 + val flip: Boolean, // turn:结束后翻转朝向 + val playMs: Long // 本步播放多久后进入下一步 + ) + + private val lock = Any() + + private var sayText: String? = null + private var sayDuration: Long = 8000 + private var sayCounter: Long = 0 + private var sayMeme: String? = null + private var sayLevel: String? = null + + /** 外部通知让随机链暂停到该时刻(毫秒时间戳),保证通知动画不被立刻覆盖。 */ + @Volatile + private var holdUntilMs: Long = 0L + + fun holdUntil(): Long = holdUntilMs + + /** 让随机链暂停一段时间(外部展示用,如仅显示表情包)。 */ + fun holdFor(durationMs: Long) { + holdUntilMs = System.currentTimeMillis() + durationMs + } + + private var currentAnim: Anim = config.idle.first() + private var animCounter: Long = 0 + private var facing: Int = 1 // 1 = 朝右(不镜像), -1 = 朝左(镜像) + private var currentNoMirror: Boolean = false + private var lastClick: Anim? = null + + /** 文字类(noMirror)动画名集合:朝左时也不镜像,否则文字会颠倒。 */ + private val textNoMirror: Set by lazy { + config.categories.filter { it.noMirror } + .flatMap { it.actions }.map { it.name }.toSet() + } + + @Volatile + private var running = false + private var serverSocket: ServerSocket? = null + + fun start() { + running = true + try { + serverSocket = ServerSocket(PORT, 8, InetAddress.getByName("127.0.0.1")) + while (running) { + val socket = serverSocket?.accept() ?: break + Thread { handle(socket) }.start() + } + } catch (_: IOException) { + // 端口被占用或 socket 已关闭 + } catch (_: Exception) { + } + } + + fun stop() { + running = false + try { serverSocket?.close() } catch (_: Exception) {} + } + + // ---------------- 状态读取(服务 / WebView 用) ---------------- + + fun currentAnim(): Anim = synchronized(lock) { currentAnim } + fun animCounter(): Long = synchronized(lock) { animCounter } + fun facing(): Int = synchronized(lock) { facing } + fun currentNoMirror(): Boolean = synchronized(lock) { currentNoMirror } + + fun setFacing(f: Int) { + synchronized(lock) { facing = if (f < 0) -1 else 1 } + } + + // ---------------- 原生层触发 ---------------- + + /** 点按:随机挑一个点击回应动画,尽量不连续重复。 */ + fun tap() { + synchronized(lock) { + val pool = config.clicks + var chosen = pool.random() + if (pool.size > 1 && chosen == lastClick) { + chosen = pool.filter { it != lastClick }.random() + } + lastClick = chosen + setLocked(chosen) + } + } + + /** 切到指定动画(按中文名),未知返回 false。 */ + @Volatile private var lastActivityAt = System.currentTimeMillis() + + /** 推送发生后回调(时长 ms),服务据此把推送接入动作节奏循环。 */ + @Volatile var onPush: ((Long) -> Unit)? = null + private fun notifyPush(ms: Long) { + runCatching { onPush?.invoke(ms) } + } + + /** 最近一次"有内容发生"的时间(点播/通知/气泡),用于空闲回待机判断。 */ + fun lastActivity(): Long = lastActivityAt + fun touchActivity() { lastActivityAt = System.currentTimeMillis() } + + fun setAnim(name: String): Boolean { + val a = config.byName[name] ?: return false + synchronized(lock) { setLocked(a) } + return true + } + + /** 拖拽悬空:播 drag 池(默认「被鼠标拖拽悬空反馈」)。 */ + fun playDrag() { + synchronized(lock) { + val a = if (config.drag.isNotEmpty()) config.drag.random() else currentAnim + setLocked(a) + } + } + + /** 指定事件池随机抽一段播(balance / whisper / workStatus),无后端时作演示用。 */ + fun triggerEvent(kind: String): Boolean { + val pool = config.events[kind] ?: return false + if (pool.isEmpty()) return false + synchronized(lock) { setLocked(pool.random()) } + return true + } + + /** 说话(可带表情包);native 层演示事件用。 */ + fun setSay(text: String, durationMs: Long, memeFile: String?, level: String? = "info") { + touchActivity() + synchronized(lock) { + sayText = text.ifBlank { null } + sayDuration = durationMs + sayMeme = memeFile + sayLevel = level + sayCounter++ + } + notifyPush(durationMs) + } + + fun randomMemeFile(): String? = config.memes.randomOrNull()?.file + + /** 气泡快照,供服务端渲染独立气泡窗用。 */ + data class SayState( + val counter: Long, val text: String?, val meme: String?, + val duration: Long, val level: String? + ) + + fun saySnapshot(): SayState = + synchronized(lock) { SayState(sayCounter, sayText, sayMeme, sayDuration, sayLevel) } + + @Volatile + private var bubbleStyle: String = "default" + + fun setBubbleStyle(style: String) { bubbleStyle = style } + + fun bubbleStyle(): String = bubbleStyle + + + // ---------- 技能(Skill)---------- + + data class SkillStep(val kind: String, val value: String?, val ms: Long) + data class Skill(val id: String, val name: String, val desc: String, val steps: List) + + private fun st(kind: String, value: String? = null, ms: Long = 0L) = SkillStep(kind, value, ms) + + /** 内置技能:一串动作/气泡/表情按顺序执行。 */ + val builtinSkills: List = listOf( + Skill("work_start", "开工", "忙碌动作 + 开工宣言", + listOf(st("tag", "work"), st("say", "开工啦~今天也要好好干活", 6000))), + Skill("work_done", "收工", "庆祝动作 + 夸奖表情", + listOf(st("anim", "工作状态-雀跃庆祝"), st("say", "搞定啦!主人快夸我", 6000), st("memeTag", "praise"), st("wait", null, 2500))), + Skill("work_error", "出错了", "沮丧动作 + 出错气泡", + listOf(st("anim", "工作状态-垂头叹气冒汗"), st("say", "呜…出错了,我再试一次", 7000), st("memeTag", "error"))), + Skill("slack", "摸鱼", "偷懒动作 + 摸鱼台词", + listOf(st("tag", "rest"), st("say", "忙里偷闲一下下,就一下下~", 7000))), + Skill("meal", "干饭", "吃饭动作 + 干饭台词", + listOf(st("tag", "eat"), st("say", "到饭点啦!白米饭白米饭~", 7000))), + Skill("greet", "打招呼", "挥手动作 + 问候", + listOf(st("tag", "greet"), st("say", "主人回来啦~我一直都在哦", 6000))), + Skill("sleep", "睡觉", "待机 + 晚安", + listOf(st("idle"), st("say", "那我去趴一会儿…晚安,主人", 8000))), + Skill("report", "汇报", "工作状态动画 + 自定义文字(配合 text 参数)", + listOf(st("anim", "工作状态-忙碌点按"), st("say", "正在汇报进度…", 6000))), + Skill("hydrate", "喝水", "喝水提醒 + 补充水分", + listOf(st("tag", "eat"), st("say", "该喝水啦~主人先补个水", 6000))), + Skill("stretch", "伸懒腰", "伸懒腰 + 活动一下", + listOf(st("tag", "rest"), st("say", "一起伸个懒腰吧~", 6000))) + ) + + private fun customSkills(): MutableList { + val raw = context.getSharedPreferences("pet_prefs", Context.MODE_PRIVATE) + .getString("custom_skills", "[]") ?: "[]" + return runCatching { + val a = JSONArray(raw) + (0 until a.length()).mapNotNull { a.optJSONObject(it) }.toMutableList() + }.getOrDefault(mutableListOf()) + } + + private fun saveCustom(list: List) { + val a = JSONArray(); list.forEach { a.put(it) } + context.getSharedPreferences("pet_prefs", Context.MODE_PRIVATE).edit() + .putString("custom_skills", a.toString()).apply() + } + + fun addCustomSkill(name: String, say: String, animTag: String, memeTag: String): Boolean { + if (name.isBlank()) return false + val list = customSkills() + val id = "c_" + java.util.UUID.randomUUID().toString().take(6) + list.add(JSONObject().put("id", id).put("name", name.trim()) + .put("say", say).put("animTag", animTag).put("memeTag", memeTag)) + saveCustom(list) + return true + } + + fun removeCustomSkill(id: String) { + saveCustom(customSkills().filterNot { it.optString("id") == id }) + } + + fun skillListJson(): JSONObject { + val arr = JSONArray() + builtinSkills.forEach { sk -> + arr.put(JSONObject().put("id", sk.id).put("name", sk.name).put("desc", sk.desc) + .put("steps", sk.steps.size)) + } + customSkills().forEach { + arr.put(JSONObject().put("id", it.optString("id")).put("name", it.optString("name")) + .put("desc", "自定义技能").put("steps", 1).put("custom", true)) + } + return JSONObject().put("skills", arr) + } + + /** 执行技能;textOverride 会替换第一个 say 步骤的文字。 */ + fun runSkill(id: String, textOverride: String? = null): Skill? { + val sk = builtinSkills.firstOrNull { it.id == id } + if (sk == null) { + val c = customSkills().firstOrNull { it.optString("id") == id } ?: return null + Thread { + c.optString("animTag").takeIf { it.isNotBlank() }?.let { tag -> + pickAnimByTag(tag)?.let { playByName(it.name, null) } + } + c.optString("say").takeIf { it.isNotBlank() }?.let { setSay(it, 6000L, null, "info") } + c.optString("memeTag").takeIf { it.isNotBlank() }?.let { tag -> + pickMemeByTag(tag)?.let { setSay(c.optString("say"), 6000L, it.file, "info") } + } + emitEvent("skill", JSONObject().put("id", id).put("custom", true)) + }.start() + return Skill(id, c.optString("name"), "自定义技能", emptyList()) + } + Thread { + var said = false + var lastText: String? = null + sk.steps.forEach { step -> + when (step.kind) { + "anim" -> step.value?.let { playByName(it, null) } + "tag" -> step.value?.let { tag -> pickAnimByTag(tag)?.let { playByName(it.name, null) } } + "idle" -> playIdle(null) + "say" -> { + val text = if (!said && textOverride != null) textOverride else step.value ?: "" + said = true + lastText = text + if (text.isNotBlank()) setSay(text, step.ms.coerceAtLeast(1500L), null, "info") + } + "meme" -> step.value?.let { setSay(lastText ?: "", 6000L, it, "info") } + "memeTag" -> step.value?.let { tag -> + pickMemeByTag(tag)?.let { setSay(lastText ?: "", 6000L, it.file, "info") } + } + "wait" -> runCatching { Thread.sleep(step.ms.coerceAtLeast(100L)) } + } + runCatching { Thread.sleep(220) } + } + emitEvent("skill", JSONObject().put("id", sk.id)) + }.start() + return sk + } + + private fun handleSkills(out: OutputStream) = respondJson(out, 200, skillListJson().toString()) + + private fun handleSkillRun(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val id = o.optString("id").ifBlank { o.optString("name") }.trim() + val text = o.optString("text").takeIf { it.isNotBlank() } + val sk = runSkill(id, text) + if (sk == null) respondJson(out, 404, "{\"ok\":false,\"reason\":\"unknown-skill\"}") + else respondJson(out, 200, JSONObject().put("ok", true).put("skill", sk.id).toString()) + } + + // ---------- 插件(JS)---------- + + private fun pluginDir() = java.io.File(context.filesDir, "plugins").apply { mkdirs() } + + fun pluginList(): List = + pluginDir().listFiles { f -> f.isFile && f.name.endsWith(".js") } + ?.map { it.name.removeSuffix(".js") }?.sorted() ?: emptyList() + + /** 供应用内对话调用:安装/覆盖一个 JS 插件。 */ + fun installPlugin(id: String, code: String): Boolean { + val safe = id.trim().replace(Regex("[^A-Za-z0-9_-]"), "") + if (safe.isEmpty() || code.isBlank()) return false + return runCatching { java.io.File(pluginDir(), "$safe.js").writeText(code) }.isSuccess + } + + fun removePlugin(id: String): Boolean { + val safe = id.trim().replace(Regex("[^A-Za-z0-9_-]"), "") + if (safe.isEmpty()) return false + return runCatching { java.io.File(pluginDir(), "$safe.js").delete() }.isSuccess + } + + private fun handlePluginInstall(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val id = o.optString("id").trim().replace(Regex("[^A-Za-z0-9_-]"), "") + val code = o.optString("code") + if (id.isEmpty() || code.isBlank()) { + respondJson(out, 400, "{\"ok\":false,\"reason\":\"need id+code\"}") + return + } + runCatching { java.io.File(pluginDir(), "$id.js").writeText(code) } + respondJson(out, 200, JSONObject().put("ok", true).put("plugins", JSONArray(pluginList())).toString()) + } + + private fun handlePluginRemove(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val id = o.optString("id").trim().replace(Regex("[^A-Za-z0-9_-]"), "") + runCatching { java.io.File(pluginDir(), "$id.js").delete() } + respondJson(out, 200, JSONObject().put("ok", true).put("plugins", JSONArray(pluginList())).toString()) + } + + /** 合并所有插件代码,供宠物 WebView 加载(免鉴权,内容就是本机已安装插件)。 */ + private fun handlePluginsJs(out: OutputStream) { + val sb = StringBuilder() + pluginDir().listFiles { f -> f.isFile && f.name.endsWith(".js") }?.sortedBy { it.name } + ?.forEach { f -> + sb.append("// plugin: ").append(f.name).append('\n') + sb.append(runCatching { f.readText() }.getOrDefault("")).append("\n;\n") + } + respond(out, 200, sb.toString(), "application/javascript; charset=utf-8") + } + + private fun handlePluginList(out: OutputStream) { + respondJson(out, 200, JSONObject().put("plugins", JSONArray(pluginList())).toString()) + } + + // ---------- MCP(JSON-RPC over HTTP)---------- + + private fun handleMcp(body: String, out: OutputStream) { + val req = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val id = req.opt("id") + val method = req.optString("method") + fun result(obj: JSONObject) { + respondJson(out, 200, JSONObject() + .put("jsonrpc", "2.0").put("id", id ?: JSONObject.NULL).put("result", obj).toString()) + } + fun error(code: Int, msg: String) { + respondJson(out, 200, JSONObject().put("jsonrpc", "2.0").put("id", id ?: JSONObject.NULL) + .put("error", JSONObject().put("code", code).put("message", msg)).toString()) + } + + when (method) { + "initialize" -> result(JSONObject() + .put("protocolVersion", "2024-11-05") + .put("capabilities", JSONObject().put("tools", JSONObject())) + .put("serverInfo", JSONObject().put("name", "xiaojingniang").put("version", "1.0"))) + "notifications/initialized" -> respondJson(out, 200, "{}") + "ping" -> result(JSONObject()) + "tools/list" -> result(JSONObject().put("tools", mcpTools())) + "tools/call" -> { + val params = req.optJSONObject("params") ?: JSONObject() + val name = params.optString("name") + val args = params.optJSONObject("arguments") ?: JSONObject() + val text = runCatching { callMcpTool(name, args) }.getOrElse { "error: ${it.message}" } + result(JSONObject().put("content", JSONArray() + .put(JSONObject().put("type", "text").put("text", text)))) + } + else -> error(-32601, "Method not found: $method") + } + } + + private fun mcpTools(): JSONArray { + fun tool(name: String, desc: String, props: JSONObject, required: JSONArray) = JSONObject() + .put("name", name).put("description", desc) + .put("inputSchema", JSONObject().put("type", "object") + .put("properties", props).put("required", required)) + fun p(type: String, desc: String) = JSONObject().put("type", type).put("description", desc) + return JSONArray() + .put(tool("pet_status", "查看小鲸娘当前状态(动画、气泡、订阅数)", + JSONObject(), JSONArray())) + .put(tool("pet_say", "让小鲸娘说一句话", + JSONObject().put("text", p("string", "要说的话")) + .put("durationMs", p("number", "展示时长")), JSONArray().put("text"))) + .put(tool("pet_notify", "推送任务状态(含自动配表情包)", + JSONObject().put("state", p("string", "start|working|result|waiting|done|error")) + .put("text", p("string", "气泡文字")) + .put("withMeme", p("boolean", "是否自动配表情")), JSONArray().put("state"))) + .put(tool("pet_play", "播放动作(名字或标签)", + JSONObject().put("name", p("string", "动作名")) + .put("tag", p("string", "语义标签,如 work/happy")) + .put("random", p("boolean", "随机")), JSONArray())) + .put(tool("pet_meme", "弹一张表情包(标签或名字)", + JSONObject().put("tag", p("string", "表情标签")) + .put("name", p("string", "表情名")), JSONArray())) + .put(tool("pet_skill", "执行技能(work_start/work_done/work_error/slack/meal/greet/sleep/report)", + JSONObject().put("id", p("string", "技能 id")) + .put("text", p("string", "覆盖气泡文字")), JSONArray().put("id"))) + .put(tool("pet_catalog", "列出动作与表情包(含语义标签)", JSONObject(), JSONArray())) + } + + private fun callMcpTool(name: String, args: JSONObject): String = when (name) { + "pet_status" -> JSONObject().put("anim", currentAnim().name) + .put("bubble", saySnapshot().text).put("sseClients", sseClients.size).toString() + "pet_say" -> { + val t = args.optString("text") + setSay(t, if (args.has("durationMs")) args.optLong("durationMs") else 7000L, null, "info") + "{\"ok\":true}" + } + "pet_notify" -> { + val ok = notifyTask(args.optString("state").takeIf { it.isNotBlank() }, null, + args.optString("text").takeIf { it.isNotBlank() }, null, null, + args.optBoolean("withMeme", false)) + JSONObject().put("ok", ok).toString() + } + "pet_play" -> { + val a = when { + args.has("random") && args.optBoolean("random") -> playRandomAnim(null) + args.optString("tag").isNotBlank() -> pickAnimByTag(args.optString("tag"))?.let { playByName(it.name, null); it } + args.optString("name").isNotBlank() -> if (playByName(args.optString("name"), null)) config.byName[args.optString("name")] else null + else -> null + } + JSONObject().put("ok", a != null).put("anim", a?.name ?: "").toString() + } + "pet_meme" -> { + val m = when { + args.optString("tag").isNotBlank() -> pickMemeByTag(args.optString("tag")) + args.optString("name").isNotBlank() -> config.memes.firstOrNull { it.name == args.optString("name") } + else -> config.memes.randomOrNull() + } + if (m != null) { setSay("", 6000L, m.file, "info"); JSONObject().put("ok", true).put("meme", m.name).toString() } + else "{\"ok\":false}" + } + "pet_skill" -> { + val sk = runSkill(args.optString("id"), args.optString("text").takeIf { it.isNotBlank() }) + JSONObject().put("ok", sk != null).put("skill", sk?.id ?: "").toString() + } + "pet_catalog" -> catalogJson().toString() + else -> "error: unknown tool" + } + + // ---------- 语义标签 ---------- + + /** 动作标签:pool/kind/语义词(work/success/error/eat/play/festival/rest…)。 */ + val animTags: Map> by lazy { buildAnimTags() } + + private fun buildAnimTags(): Map> { + val map = HashMap>() + fun add(name: String, vararg tags: String) { + map.getOrPut(name) { mutableSetOf() }.addAll(tags) + } + config.idle.forEach { add(it.name, "idle", "rest") } + config.turn.forEach { add(it.name, "turn", "look") } + config.drag.forEach { add(it.name, "drag", "picked") } + config.clicks.forEach { add(it.name, "click", "react") } + config.moves.forEach { add(it.name, "move", "walk") } + config.categories.forEach { c -> + val base = when (c.id) { + "小动作" -> arrayOf("action", "daily") + "玩耍" -> arrayOf("play", "happy") + "吃什么" -> arrayOf("eat", "food") + "时节" -> arrayOf("festival", "season") + "文字" -> arrayOf("text", "talk") + else -> arrayOf("action") + } + c.actions.forEach { add(it.name, "category", c.id, *base) } + } + config.events["balance"]?.forEachIndexed { i, a -> + add(a.name, "event", "balance", "money", "level$i") + } + config.events["whisper"]?.forEach { add(it.name, "event", "whisper", "talk") } + val wStates = listOf("thinking", "working", "result", "waiting", "success", "error") + config.events["workStatus"]?.forEachIndexed { i, a -> + add(a.name, "event", "work", "workstatus", wStates[i.coerceIn(0, 5)]) + } + val kw = mapOf( + "雀跃庆祝" to arrayOf("happy", "success", "celebrate"), + "垂头叹气" to arrayOf("sad", "error", "fail"), + "思考冒泡" to arrayOf("think", "busy"), + "忙碌" to arrayOf("busy", "working"), + "清点归档" to arrayOf("organize", "result"), + "踱步张望" to arrayOf("waiting"), + "害羞惊讶" to arrayOf("shy", "surprise"), + "傲娇生气" to arrayOf("angry", "tsundere"), + "开心跃动" to arrayOf("happy", "joy"), + "元气挥手" to arrayOf("greet", "wave", "hello"), + "挠痒" to arrayOf("fun", "laugh"), + "哈欠" to arrayOf("sleepy", "tired"), + "小憩沉眠" to arrayOf("sleep", "rest"), + "写代码" to arrayOf("code", "work"), + "照镜子" to arrayOf("daily", "self"), + "钱袋满溢" to arrayOf("money", "rich"), + "分文不剩" to arrayOf("money", "empty", "poor") + ) + kw.forEach { (k, tags) -> + map.keys.filter { it.contains(k) }.forEach { add(it, *tags) } + } + return map + } + + /** 表情包标签(按 dsh memes 的语义归类)。 */ + /** 网络梗图的显式标签(名字对不上关键词规则时用)。 */ + private val memeTagOverrides: Map> = mapOf( + "真当我是便宜货" to setOf("sad", "work", "complain"), + "又错了" to setOf("error", "work", "retry"), + "这用户发的啥" to setOf("thinking", "work"), + "大笑" to setOf("happy", "fun", "laugh"), + "摸头" to setOf("rest", "comfort", "happy"), + "原来是高等模型" to setOf("praise", "success"), + "眼光真棒" to setOf("praise", "success", "happy"), + "谢谢你派" to setOf("greet", "thanks"), + "好耶" to setOf("happy", "celebrate"), + "瞎编一个" to setOf("working", "fun"), + "到底要怎样" to setOf("thinking", "confused"), + "呃呃呃" to setOf("thinking", "confused"), + "别再问了" to setOf("warn", "fun"), + "死掉" to setOf("error", "fail", "sad"), + "死了" to setOf("error", "fail", "sad"), + "又死掉" to setOf("error", "fail"), + "专武呢" to setOf("play", "fun"), + "气鼓鼓" to setOf("angry", "fun"), + "上错船了" to setOf("warn", "work"), + "好棒型" to setOf("praise", "success"), + "落泪" to setOf("sad", "fail"), + "别急" to setOf("waiting", "warn"), + "抱头" to setOf("thinking", "stress"), + "不要给我看" to setOf("warn", "fun"), + "就一碗" to setOf("eat", "food", "fun"), + "无药可救" to setOf("sad", "fail"), + "V4.1 还要多久" to setOf("waiting", "work") + ) + + private val memeTagsCache by lazy { config.memes.associate { it.name to it.tags } } + + fun memeTags(name: String): Set { + val fromCfg = memeTagsCache[name] + if (!fromCfg.isNullOrEmpty()) return fromCfg + return memeTagOverrides[name] ?: legacyMemeTags(name) + } + + private fun legacyMemeTags(name: String): Set = when { + name.contains("厉害") || name.contains("可爱") || name.contains("吃饱") || + name.contains("誓死") -> setOf("success", "praise", "happy") + name.contains("死掉") || name.contains("完蛋") || name.contains("病毒") || + name.contains("哦鲸鲸") -> setOf("error", "fail", "sad") + name.contains("压力") || name.contains("大烧货") || name.contains("黎曼") || + name.contains("区是什么意思") -> setOf("working", "busy", "stress") + name.contains("深度睡眠") || name.contains("就一碗") || + name.contains("吃白饭") -> setOf("rest", "sleep", "lazy") + name.contains("大的要") || name.contains("大的药") || name.contains("电死") || + name.contains("骂他") -> setOf("warn", "urgent", "alert") + name.contains("token") || name.contains("多模态") || + name.contains("鱼片") -> setOf("money", "token", "need") + name.contains("Ciallo") || name.contains("就骚") || name.contains("先养着") || + name.contains("调情") || name.contains("不更新") -> setOf("play", "fun", "greet") + name.contains("在写") || name.contains("忙死") -> setOf("work", "working", "busy") + name.contains("想想") || name.contains("碎碎念") -> setOf("thinking", "work", "whisper") + name.contains("搞定") -> setOf("success", "done", "praise") + name.contains("崩") || name.contains("气死") -> setOf("error", "fail", "sad") + name.contains("困") || name.contains("睡") || name.contains("懒腰") -> setOf("rest", "sleep", "tired") + name.contains("干饭") || name.contains("吃") || name.contains("咕噜") -> setOf("eat", "food", "play") + name.contains("可爱") || name.contains("开心") || name.contains("嘿嘿") -> setOf("happy", "play") + name.contains("在呢") || name.contains("蓝鲸") -> setOf("greet", "hello") + name.contains("抓包") || name.contains("吓") -> setOf("warn", "surprise") + name.contains("气鼓鼓") || name.contains("压扁") -> setOf("fun", "angry") + name.contains("演奏") -> setOf("play", "fun") + else -> setOf("general") + } + + private fun stateTags(state: String?): List = when (state?.lowercase()) { + "done", "success", "end", "completed" -> listOf("success", "praise") + "error", "failed", "max-tokens" -> listOf("error", "fail") + "working", "tool", "tool/call" -> listOf("working", "busy") + "waiting", "approval", "asked" -> listOf("warn") + "result", "tool/result" -> listOf("working") + "start", "thinking", "turn/start" -> listOf("working", "busy") + else -> listOf("general") + } + + private fun levelTags(level: String?): List = when (level?.lowercase()) { + "success", "done" -> listOf("success", "praise") + "error", "fail", "failed" -> listOf("error", "fail") + "warn", "warning", "waiting" -> listOf("warn") + "working", "busy" -> listOf("working", "busy") + else -> listOf("general") + } + + fun pickAnimByTag(tag: String): Anim? { + val t = tag.lowercase() + val pool = config.byName.values.filter { animTags[it.name]?.contains(t) == true } + return pool.randomOrNull() + } + + fun pickMemeByTag(tag: String): Meme? { + val t = tag.lowercase() + val pool = config.memes.filter { memeTags(it.name).contains(t) } + return pool.randomOrNull() + } + + /** 供 AI 浏览的完整目录:动作/表情包 + 标签。 */ + fun catalogJson(): JSONObject { + val anims = org.json.JSONArray() + config.byName.values.forEach { a -> + anims.put( + JSONObject().put("name", a.name).put("file", a.file).put("ms", a.ms) + .put("tags", org.json.JSONArray(animTags[a.name]?.sorted() ?: emptyList())) + ) + } + val memes = org.json.JSONArray() + config.memes.forEach { m -> + memes.put( + JSONObject().put("name", m.name).put("file", m.file) + .put("tags", org.json.JSONArray(memeTags(m.name).sorted())) + ) + } + val allTags = (animTags.values.flatten() + config.memes.flatMap { memeTags(it.name) }) + .distinct().sorted() + return JSONObject() + .put("anims", anims) + .put("memes", memes) + .put("tags", org.json.JSONArray(allTags)) + .put("skills", skillListJson().optJSONArray("skills") ?: org.json.JSONArray()) + .put("states", JSONObject() + .put("thinking", "工作状态-思考冒泡") + .put("working", "工作状态-忙碌点按") + .put("result", "工作状态-清点归档") + .put("waiting", "工作状态-原地踱步张望") + .put("success", "工作状态-雀跃庆祝") + .put("error", "工作状态-垂头叹气冒汗")) + } + + // ---------- SSE 事件流 ---------- + private val sseClients = java.util.concurrent.CopyOnWriteArrayList() + + fun clientCount(): Int = sseClients.size + + /** 向所有已连接的订阅者广播一个事件。 */ + fun emitEvent(type: String, extra: JSONObject? = null) { + if (sseClients.isEmpty()) return + val data = JSONObject().put("type", type).put("ts", System.currentTimeMillis()) + if (extra != null) { + for (k in extra.keys()) data.put(k, extra.get(k)) + } + val payload = "event: pet\ndata: $data\n\n".toByteArray(Charsets.UTF_8) + for (c in sseClients) { + try { + synchronized(c) { c.write(payload); c.flush() } + } catch (_: Exception) { + sseClients.remove(c) + } + } + } + + /** 气泡窗当前是否真的挂上了(诊断用)。 */ + @Volatile + var bubbleVisible: Boolean = false + + // ---------- 推送授权 ---------- + @Volatile + private var requireAuth: Boolean = true + + @Volatile + private var allowedTokens: Set = emptySet() + + fun setPushAuth(require: Boolean, tokens: Set) { + requireAuth = require + allowedTokens = tokens + } + + private fun authorized(headers: Map, query: String): Boolean { + if (!requireAuth) return true + val fromHeader = headers["x-pet-token"] + val fromQuery = query.split('&') + .firstOrNull { it.startsWith("token=") } + ?.substringAfter('=') + val t = (fromHeader ?: fromQuery)?.trim() + return !t.isNullOrEmpty() && allowedTokens.contains(t) + } + + // ---------------- 对外接入 API(workbuddy / DeepSeek 等调用) ---------------- + + private fun levelIndex(level: String?): Int = when (level?.lowercase()) { + "success", "done" -> 4 + "error", "fail", "failed" -> 5 + "warn", "warning", "waiting" -> 3 + "working", "busy" -> 1 + "result" -> 2 + else -> 0 + } + + private fun stateIndex(state: String?): Int = when (state?.lowercase()) { + "start", "thinking", "turn/start" -> 0 + "working", "tool", "tool/call" -> 1 + "result", "tool/result" -> 2 + "waiting", "approval", "asked" -> 3 + "done", "success", "end", "completed" -> 4 + "error", "failed", "max-tokens" -> 5 + else -> 1 + } + + /** + * 推送一条任务/通知:按档位播「工作状态」动画 + 气泡文字,并让随机链暂停到 durationMs。 + * @return 是否成功(工作状态池存在) + */ + fun notifyTask( + state: String?, level: String?, text: String?, + durationMs: Long?, meme: String?, + autoMeme: Boolean = false, memeTag: String? = null + ): Boolean { + val pool = config.events["workStatus"] ?: return false + if (pool.isEmpty()) return false + val idx = if (state != null) stateIndex(state) else levelIndex(level) + val a = pool[idx.coerceIn(0, pool.size - 1)] + val dur = (durationMs ?: maxOf(6000L, a.ms)).coerceIn(1500L, 60000L) + // 自动配一张语义匹配的表情包 + var memeFile = meme + if (memeFile.isNullOrBlank() && (autoMeme || !memeTag.isNullOrBlank())) { + val tags = if (!memeTag.isNullOrBlank()) listOf(memeTag.lowercase()) + else stateTags(state) + levelTags(level) + memeFile = tags.asSequence().mapNotNull { pickMemeByTag(it)?.file }.firstOrNull() + } + synchronized(lock) { + setLocked(a) + holdUntilMs = System.currentTimeMillis() + dur + if (!text.isNullOrBlank()) { + sayText = text + sayDuration = dur + sayMeme = memeFile + sayLevel = level ?: state ?: "info" + sayCounter++ + } + } + notifyPush(maxOf(dur, a.ms)) + return true + } + + /** 直接播指定动画名(外部点播),并暂停随机链 1 圈。 */ + /** 随机播一个动作(排除事件池)。 */ + fun playRandomAnim(holdMs: Long?): Anim? { + val pool = config.byName.values.filter { a -> + val tags = animTags[a.name] ?: emptySet() + "event" !in tags + } + val a = pool.randomOrNull() ?: return null + if (!playByName(a.name, holdMs)) return null + holdFor(a.ms + 200) + return a + } + + /** 回到待机动作。 */ + fun playIdle(holdMs: Long?): Anim? { + val a = config.idle.randomOrNull() ?: return null + if (!playByName(a.name, holdMs)) return null + holdFor(a.ms + 200) + return a + } + + fun playByName(name: String, holdMs: Long?): Boolean { + val a = config.byName[name] ?: return false + val dur = holdMs ?: a.ms + synchronized(lock) { + setLocked(a) + holdUntilMs = System.currentTimeMillis() + dur + } + touchActivity() + notifyPush(dur) + return true + } + + private fun setLocked(a: Anim) { + currentAnim = a + currentNoMirror = a.name in textNoMirror + animCounter++ + } + + // ---------------- 随机动画链 ---------------- + + /** + * 按 dsh animationWeights(idle/turn/move + 分类权重合计=100)抽下一步。 + * 朝左(facing=-1)时排除 noMirror 的「文字」类。 + */ + fun chainStep(): ChainStep = synchronized(lock) { + val cats = if (facing < 0) config.categories.filter { !it.noMirror } + else config.categories + val catWeight = cats.sumOf { it.weight } + val total = config.weightIdle + config.weightTurn + config.weightMove + catWeight + val roll = Random.nextInt(total.coerceAtLeast(1)) + + var acc = 0 + // idle + if (roll < config.weightIdle) { + val a = config.idle[Random.nextInt(config.idle.size)] + setLocked(a) + return@synchronized ChainStep("idle", a, 0, 0, 0, 0, false, a.ms * Random.nextInt(1, 3)) + } + acc = config.weightIdle + // turn + if (roll < acc + config.weightTurn) { + val a = config.turn[Random.nextInt(config.turn.size)] + setLocked(a) + return@synchronized ChainStep("turn", a, 0, 0, 0, 0, true, a.ms) + } + acc += config.weightTurn + // move + if (roll < acc + config.weightMove) { + val m = config.moves.random() + val anim = Anim(m.name, m.file, m.ms) + val dist = Random.nextInt(m.minDist, m.maxDist + 1) * facing + val moveMs = (m.ms - m.leadMs - m.tailMs).coerceAtLeast(1L) + setLocked(anim) + return@synchronized ChainStep("move", anim, dist, m.leadMs, moveMs, m.tailMs, false, m.ms) + } + // 分类(随机小动作) + acc += config.weightMove + val c = pickCategory(cats, Random.nextInt(catWeight.coerceAtLeast(1))) + val a = c.actions[Random.nextInt(c.actions.size)] + setLocked(a) + ChainStep("category:${c.id}", a, 0, 0, 0, 0, false, a.ms) + } + + private fun pickCategory(cats: List, roll: Int): Category { + var acc = 0 + for (c in cats) { + if (roll < acc + c.weight) return c + acc += c.weight + } + return cats.last() + } + + // ---------------- 请求处理 ---------------- + + private fun handle(socket: Socket) { + try { + socket.use { s -> + val inp = s.getInputStream() + // 按「字节」读到 \r\n\r\n,再按 Content-Length 读「字节」体。 + // (不能用 BufferedReader 读字符:中文 UTF-8 是多字节,按字符数读会读不满而挂死连接) + val hb = java.io.ByteArrayOutputStream() + var last4 = 0 + while (true) { + val b = inp.read() + if (b < 0) return + hb.write(b) + last4 = (last4 shl 8) or (b and 0xFF) + if (last4 == 0x0D0A0D0A) break + if (hb.size() > 65536) return + } + val lines = String(hb.toByteArray(), Charsets.ISO_8859_1).split("\r\n") + val parts = (lines.firstOrNull() ?: return).split(' ') + if (parts.size < 2) return + val method = parts[0] + val rawTarget = parts[1] + val path = rawTarget.substringBefore('?') + val query = rawTarget.substringAfter('?', "") + val headerMap = HashMap() + for (l in lines.drop(1)) { + val idx = l.indexOf(':') + if (idx > 0) headerMap[l.substring(0, idx).trim().lowercase()] = l.substring(idx + 1).trim() + } + + var contentLength = 0 + for (l in lines) { + if (l.startsWith("Content-Length:", ignoreCase = true)) { + contentLength = l.substringAfter(':').trim().toIntOrNull() ?: 0 + } + } + val body = if (contentLength > 0) { + val bytes = ByteArray(contentLength) + var read = 0 + while (read < contentLength) { + val n = inp.read(bytes, read, contentLength - read) + if (n < 0) break + read += n + } + String(bytes, 0, read, Charsets.UTF_8) + } else { + "" + } + + val out = s.getOutputStream() + if (method == "POST" && path == "/sprite") { + handleSprite(body, out) + return@use + } + if (method == "OPTIONS") { + writeCorsPreflight(out) + return@use + } + if (method == "POST" && !authorized(headerMap, query)) { + respondJson(out, 401, "{\"ok\":false,\"reason\":\"unauthorized\"}") + return@use + } + if (method == "GET" && path == "/api/events") { + if (!authorized(headerMap, query)) { + respondJson(out, 401, "{\"ok\":false,\"reason\":\"unauthorized\"}") + return@use + } + handleSse(out) + return@use + } + when { + method == "POST" && (path == "/api/notify" || path == "/api/task") -> + handleNotify(body, out) + method == "POST" && (path == "/broadcast" || path == "/api/broadcast") -> + handleBroadcast(body, out) + method == "POST" && (path == "/anim" || path == "/api/anim") -> handleAnim(body, out) + method == "POST" && (path == "/bubble/clear" || path == "/api/clear") -> handleBubbleClear(out) + method == "GET" && path == "/api/health" -> handleHealth(out) + method == "GET" && path == "/api/catalog" -> respondJson(out, 200, catalogJson().toString()) + method == "GET" && path == "/docs.md" -> handleDocsMd(out) + method == "GET" && (path == "/docs/hermes" || path == "/docs/hermes.md") -> + respond(out, 200, HERMES_DOC, "text/markdown; charset=utf-8") + method == "GET" && path == "/llms.txt" -> handleLlms(out) + method == "POST" && path == "/api/meme" -> handleMeme(body, out) + method == "POST" && path == "/api/settings" -> handleSettings(body, out) + method == "GET" && path == "/api/skills" -> handleSkills(out) + method == "POST" && path == "/api/skill" -> handleSkillRun(body, out) + method == "POST" && path == "/api/skill/custom" -> { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val ok = addCustomSkill(o.optString("name"), o.optString("say"), + o.optString("animTag"), o.optString("memeTag")) + respondJson(out, if (ok) 200 else 400, "{\"ok\":$ok}") + } + method == "POST" && path == "/api/skill/remove" -> { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + removeCustomSkill(o.optString("id")) + respondJson(out, 200, "{\"ok\":true}") + } + method == "GET" && path == "/api/plugins" -> handlePluginList(out) + method == "POST" && path == "/api/plugin" -> handlePluginInstall(body, out) + method == "POST" && path == "/api/plugin/remove" -> handlePluginRemove(body, out) + method == "GET" && path == "/api/plugins.js" -> handlePluginsJs(out) + method == "POST" && path == "/mcp" -> handleMcp(body, out) + method == "GET" && path == "/docs" -> handleDocs(out) + method == "GET" && path == "/api/docs" -> handleApiDocsJson(out) + method == "GET" && (path == "/state" || path == "/api/state") -> handleState(out) + method == "GET" && path == "/" -> handleIndex(out) + method == "GET" && isAllowedAsset(path) -> handleAsset(path, out) + else -> respondJson(out, 404, "{\"ok\":false,\"reason\":\"not-found\"}") + } + } + } catch (_: Exception) { + // 单个连接出错不影响服务 + } + } + + private fun handleBroadcast(body: String, out: OutputStream) { + val obj = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val text = obj.optString("text") + val duration = obj.optLong("duration", 8000) + val meme = obj.optString("meme").takeIf { it.isNotBlank() } + setSay(text, duration, meme) + emitEvent("bubble", JSONObject().put("text", text).put("level", "info")) + respondJson(out, 200, "{\"ok\":true}") + } + + private fun handleAnim(body: String, out: OutputStream) { + val obj = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val name = obj.optString("name").trim() + val tag = obj.optString("tag").trim() + val random = obj.optBoolean("random", false) + val idle = obj.optBoolean("idle", false) + val hold = if (obj.has("holdMs")) obj.optLong("holdMs") else null + var target = if (name.isNotEmpty()) name else { + if (tag.isNotEmpty()) pickAnimByTag(tag)?.name ?: "" else "" + } + var ok: Boolean + if (random) { + val a = playRandomAnim(hold) + target = a?.name ?: "" + ok = a != null + } else if (idle) { + val a = playIdle(hold) + target = a?.name ?: "" + ok = a != null + } else { + ok = if (target.isNotEmpty()) playByName(target, hold) else false + } + if (ok) emitEvent("play", JSONObject().put("name", target).put("tag", tag)) + respondJson(out, if (ok) 200 else 404, if (ok) "{\"ok\":true}" else "{\"ok\":false,\"reason\":\"unknown-anim\"}") + } + + /** 外部任务通知:{state|level, text, durationMs, meme} */ + private fun handleNotify(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val state = o.optString("state").takeIf { it.isNotBlank() } + val level = o.optString("level").takeIf { it.isNotBlank() } + val text = o.optString("text").takeIf { it.isNotBlank() } + val dur = if (o.has("durationMs")) o.optLong("durationMs") else null + val meme = o.optString("meme").takeIf { it.isNotBlank() } + val memeTag = o.optString("memeTag").takeIf { it.isNotBlank() } + val withMeme = o.optBoolean("withMeme", false) + val ok = notifyTask(state, level, text, dur, meme, withMeme, memeTag) + if (ok) { + emitEvent("notify", JSONObject() + .put("state", state ?: "") + .put("level", level ?: "") + .put("text", text ?: "")) + } + respondJson( + out, if (ok) 200 else 503, + if (ok) "{\"ok\":true}" else "{\"ok\":false,\"reason\":\"no-workstatus-pool\"}" + ) + } + + private fun handleHealth(out: OutputStream) { + val eps = org.json.JSONArray() + listOf( + "POST /api/task {state, text, durationMs, meme, withMeme, memeTag}", + "POST /api/notify {state|level, text, durationMs, meme, withMeme}", + "POST /api/anim {name | tag, holdMs}", + "POST /api/meme {tag | name | file}", + "POST /api/broadcast {text, duration, meme}", + "POST /api/settings {chainGapSeconds, idleAfterSeconds}", + "POST /api/clear", + "GET /api/state", + "GET /api/catalog (动作/表情包 + 语义标签)", + "GET /api/health", + "GET /api/docs (JSON 规范)", + "GET /docs /docs.md /llms.txt", + "GET /api/events?token= (SSE)" + ).forEach { eps.put(it) } + val j = JSONObject() + .put("ok", true) + .put("app", "dsh-pet-float") + .put("port", PORT) + .put("anims", config.byName.size) + .put("memes", config.memes.size) + .put("tags", (animTags.values.flatten() + config.memes.flatMap { memeTags(it.name) }) + .distinct().size) + .put("catalog", "/api/catalog") + .put("docs", "/docs") + .put("docsMarkdown", "/docs.md") + .put("llmsTxt", "/llms.txt") + .put("authRequired", requireAuth) + .put("sseClients", sseClients.size) + .put("debug", debugInfo()) + .put("endpoints", eps) + respondJson(out, 200, j.toString()) + } + + private fun handleBubbleClear(out: OutputStream) { + synchronized(lock) { + sayText = null + sayMeme = null + sayCounter++ + } + emitEvent("bubble_clear", null) + respondJson(out, 200, "{\"ok\":true}") + } + + private fun handleState(out: OutputStream) { + val state = synchronized(lock) { + JSONObject() + .put("say", JSONObject() + .put("text", sayText ?: JSONObject.NULL) + .put("duration", sayDuration) + .put("counter", sayCounter) + .put("meme", sayMeme ?: JSONObject.NULL) + .put("level", sayLevel ?: JSONObject.NULL)) + .put("anim", JSONObject() + .put("name", currentAnim.name) + .put("file", currentAnim.file) + .put("ms", currentAnim.ms)) + .put("animCounter", animCounter) + .put("facing", facing) + .put("noMirror", currentNoMirror) + .put("bubbleStyle", bubbleStyle) + .put("bubbleVisible", bubbleVisible) + } + respondJson(out, 200, state.toString()) + } + + private fun handleIndex(out: OutputStream) { + val html = try { + context.assets.open("pet.html").bufferedReader(Charsets.UTF_8).use { it.readText() } + } catch (_: Exception) { + "pet.html 缺失" + } + respond(out, 200, html, "text/html; charset=utf-8") + } + + private fun isAllowedAsset(path: String): Boolean { + val name = path.trimStart('/') + return name.isNotEmpty() && name.matches(Regex("[a-zA-Z0-9._\\-]+")) + } + + private fun handleAsset(path: String, out: OutputStream) { + val name = path.trimStart('/') + val contentType = when { + name.endsWith(".webp") -> "image/webp" + name.endsWith(".png") -> "image/png" + name.endsWith(".woff2") -> "font/woff2" + name.endsWith(".ttf") -> "font/ttf" + else -> "application/octet-stream" + } + try { + val bytes = context.assets.open(name).use { it.readBytes() } + writeResponse(out, 200, bytes, contentType) + } catch (_: Exception) { + respondJson(out, 404, "{\"ok\":false,\"reason\":\"no-asset\"}") + } + } + + // ---------------- 响应工具 ---------------- + + /** 保持连接、下发事件、定时心跳;阻塞直到客户端断开。 */ + private fun handleSse(out: OutputStream) { + runCatching { + val head = "HTTP/1.1 200 OK\r\n" + + "Content-Type: text/event-stream; charset=utf-8\r\n" + + "Cache-Control: no-cache\r\n" + + "Connection: keep-alive\r\n" + + "Access-Control-Allow-Origin: *\r\n\r\n" + out.write(head.toByteArray(Charsets.US_ASCII)) + out.flush() + sseClients.add(out) + synchronized(out) { + out.write("event: ready\ndata: {\"ok\":true}\n\n".toByteArray(Charsets.UTF_8)) + out.flush() + } + while (running) { + Thread.sleep(15000) + synchronized(out) { + out.write(": ping\n\n".toByteArray(Charsets.US_ASCII)) + out.flush() + } + } + } + sseClients.remove(out) + } + + /** 素材四周的透明边距比例(l,r,t,b),由 WebView canvas 量出上报;取最大值保证都能贴边。 */ + @Volatile + var spritePad: FloatArray = floatArrayOf(0f, 0f, 0f, 0f) + + /** 诊断:屏幕/边界/窗口几何(排查碰撞边缘用)。 */ + fun debugInfo(): JSONObject? = diag?.invoke() + + @Volatile + var diag: (() -> JSONObject?)? = null + + /** POST /api/meme {tag|name|file} → 头顶弹一张表情包(不配文字)。 */ + /** POST /api/settings {chainGapSeconds, idleAfterSeconds} —— 动作节奏设置。 */ + private fun handleSettings(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val sp = context.getSharedPreferences("pet_prefs", Context.MODE_PRIVATE) + val e = sp.edit() + var changed = false + if (o.has("chainGapSeconds")) { + e.putInt("chain_gap", o.optInt("chainGapSeconds", 2).coerceIn(0, 120)); changed = true + } + if (o.has("idleAfterSeconds")) { + e.putInt("idle_after", o.optInt("idleAfterSeconds", 60).coerceIn(0, 3600)); changed = true + } + if (changed) e.apply() + val cur = JSONObject() + .put("chainGapSeconds", sp.getInt("chain_gap", 2)) + .put("idleAfterSeconds", sp.getInt("idle_after", 60)) + respondJson(out, 200, JSONObject().put("ok", true).put("settings", cur).toString()) + } + + private fun handleMeme(body: String, out: OutputStream) { + val o = try { JSONObject(body) } catch (_: Exception) { JSONObject() } + val name = o.optString("name").trim() + val file = o.optString("file").trim() + val tag = o.optString("tag").trim() + val meme = when { + file.isNotEmpty() -> config.memes.firstOrNull { it.file == file } + name.isNotEmpty() -> config.memes.firstOrNull { it.name == name } + tag.isNotEmpty() -> pickMemeByTag(tag) + else -> config.memes.randomOrNull() + } + if (meme == null) { + respondJson(out, 404, "{\"ok\":false,\"reason\":\"no-meme\"}") + return + } + setSay("", 6000L, meme.file, "info") + holdFor(2600L) + emitEvent("meme", JSONObject().put("name", meme.name).put("file", meme.file)) + respondJson(out, 200, JSONObject().put("ok", true) + .put("meme", meme.name).put("file", meme.file).toString()) + } + + private fun handleSprite(body: String, out: OutputStream) { + runCatching { + val o = JSONObject(body) + val cur = spritePad + spritePad = floatArrayOf( + maxOf(cur[0], o.optDouble("l", 0.0).toFloat()), + maxOf(cur[1], o.optDouble("r", 0.0).toFloat()), + maxOf(cur[2], o.optDouble("t", 0.0).toFloat()), + maxOf(cur[3], o.optDouble("b", 0.0).toFloat()) + ) + // 供设置页预览对齐用 + context.getSharedPreferences("pet_prefs", Context.MODE_PRIVATE).edit() + .putFloat("sprite_pad_l", spritePad[0]) + .putFloat("sprite_pad_r", spritePad[1]) + .putFloat("sprite_pad_t", spritePad[2]) + .putFloat("sprite_pad_b", spritePad[3]) + .apply() + } + respondJson(out, 200, "{\"ok\":true}") + } + + private fun handleDocs(out: OutputStream) { + respond(out, 200, DOCS_HTML, "text/html; charset=utf-8") + } + + private fun handleDocsMd(out: OutputStream) { + respond(out, 200, DOCS_MD, "text/markdown; charset=utf-8") + } + + private fun handleLlms(out: OutputStream) { + respond(out, 200, LLMS_TXT, "text/plain; charset=utf-8") + } + + private fun handleApiDocsJson(out: OutputStream) { + val eps = org.json.JSONArray() + fun ep(method: String, path: String, desc: String, params: String, auth: Boolean) { + eps.put( + JSONObject().put("method", method).put("path", path) + .put("desc", desc).put("params", params).put("auth", auth) + ) + } + ep("GET", "/api/health", "应用信息与端点清单", "-", false) + ep("GET", "/api/state", "当前动画与气泡状态", "-", false) + ep("POST", "/api/task", "任务状态通知", "state*, text, durationMs, meme, withMeme, memeTag", true) + ep("POST", "/api/notify", "通用通知(按级别)", "level|state, text, durationMs, meme", true) + ep("POST", "/api/anim", "点播动画(按名字或标签)", "name|tag, holdMs", true) + ep("POST", "/api/broadcast", "只说一句话", "text*, duration, meme", true) + ep("POST", "/api/clear", "清空气泡", "-", true) + ep("GET", "/api/events", "SSE 事件流(tap/drag/long_press/anim)", "token*", true) + ep("GET", "/api/catalog", "动作/表情包 + 语义标签目录", "-", false) + ep("POST", "/api/meme", "弹一张表情包", "tag|name|file", true) + + val states = JSONObject() + .put("start", "工作状态-思考冒泡") + .put("thinking", "工作状态-思考冒泡") + .put("working", "工作状态-忙碌点按") + .put("result", "工作状态-清点归档") + .put("waiting", "工作状态-原地踱步张望") + .put("done", "工作状态-雀跃庆祝") + .put("success", "工作状态-雀跃庆祝") + .put("error", "工作状态-垂头叹气冒汗") + + val doc = JSONObject() + .put("app", "dsh-pet-float") + .put("base", "http://127.0.0.1:" + PORT) + .put("docs", "/docs") + .put("docsMarkdown", "/docs.md") + .put("llmsTxt", "/llms.txt") + .put("catalog", "/api/catalog") + .put("auth", JSONObject() + .put("required", requireAuth) + .put("header", "X-Pet-Token") + .put("query", "token")) + .put("anims", config.byName.size) + .put("memes", config.memes.size) + .put("tags", (animTags.values.flatten() + config.memes.flatMap { memeTags(it.name) }) + .distinct().size) + .put("catalog", "/api/catalog") + .put("endpoints", eps) + .put("states", states) + .put("levels", org.json.JSONArray(listOf("info", "working", "result", "waiting", "success", "error"))) + .put("errors", JSONObject() + .put("401", "授权码缺失或无效") + .put("404", "路径不存在") + .put("503", "工作状态动画池不可用")) + respondJson(out, 200, doc.toString()) + } + + private fun writeCorsPreflight(out: OutputStream) { + val head = "HTTP/1.1 204 No Content\r\n" + + "Access-Control-Allow-Origin: *\r\n" + + "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" + + "Access-Control-Allow-Headers: Content-Type, X-Pet-Token\r\n" + + "Access-Control-Max-Age: 86400\r\n" + + "Content-Length: 0\r\n" + + "Connection: close\r\n\r\n" + out.write(head.toByteArray(Charsets.US_ASCII)) + out.flush() + } + + private fun respondJson(out: OutputStream, code: Int, json: String) { + respond(out, code, json, "application/json") + } + + private fun respond(out: OutputStream, code: Int, body: String, contentType: String) { + writeResponse(out, code, body.toByteArray(Charsets.UTF_8), contentType) + } + + private fun writeResponse(out: OutputStream, code: Int, body: ByteArray, contentType: String) { + val status = when (code) { + 200 -> "200 OK" + 204 -> "204 No Content" + 401 -> "401 Unauthorized" + 404 -> "404 Not Found" + 503 -> "503 Service Unavailable" + else -> "500 Internal Server Error" + } + val head = "HTTP/1.1 $status\r\n" + + "Content-Type: $contentType\r\n" + + "Content-Length: ${body.size}\r\n" + + "Access-Control-Allow-Origin: *\r\n" + + "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" + + "Access-Control-Allow-Headers: Content-Type, X-Pet-Token\r\n" + + "Access-Control-Max-Age: 86400\r\n" + + "Cache-Control: no-store\r\n" + + "Connection: close\r\n\r\n" + out.write(head.toByteArray(Charsets.US_ASCII)) + out.write(body) + out.flush() + } + + companion object { + const val PORT = 7350 + + /** 给 AI / 脚本读的 Markdown 版文档。 */ + private const val DOCS_MD = """# 桌宠本机接口(dsh-pet-float) + +悬浮桌面宠物在本机回环端口提供 HTTP 接口,应用可推送任务状态、点播动作/表情包、订阅互动事件。 + +- 服务地址:http://127.0.0.1:7350 +- 鉴权:写请求需 `X-Pet-Token: <授权码>`(也支持 `?token=`);只读接口免授权 +- 授权码位置:桌宠 App → 设置 → 推送授权 +- 调用方需允许明文 HTTP(usesCleartextTraffic 或放行 127.0.0.1) + +## 接口一览 + +| 方法 | 路径 | 说明 | 鉴权 | +|---|---|---|---| +| GET | /api/health | 应用信息、端点清单、诊断几何 | 否 | +| GET | /api/state | 当前动画与气泡状态 | 否 | +| GET | /api/catalog | 全部动作与表情包 + 语义标签 | 否 | +| GET | /api/docs | JSON 规范 | 否 | +| GET | /docs.md | 本文档(Markdown) | 否 | +| GET | /llms.txt | AI 索引摘要 | 否 | +| POST | /api/task | 任务状态通知 | 是 | +| POST | /api/notify | 通用通知(按 level 或 state) | 是 | +| POST | /api/anim | 点播动画(按名字或标签) | 是 | +| POST | /api/meme | 弹一张表情包 | 是 | +| POST | /api/broadcast | 只说一句气泡 | 是 | +| POST | /api/clear | 清空气泡 | 是 | +| GET | /api/events?token= | SSE 互动事件流 | 是 | + +## POST /api/task + +请求体: + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| state | string | 是 | start / thinking / working / result / waiting / done / success / error | +| text | string | 否 | 气泡文字 | +| durationMs | number | 否 | 展示时长,1500–60000 | +| meme | string | 否 | 指定表情包(m01–m27) | +| withMeme | boolean | 否 | 按状态自动挑一张匹配的表情包 | +| memeTag | string | 否 | 指定表情包标签 | + +state 到动画:start/thinking → 思考冒泡;working → 忙碌点按;result → 清点归档; +waiting → 原地踱步张望;done/success → 雀跃庆祝;error → 垂头叹气冒汗。 + +## 动作节奏(避免切换过快) + +推送消息同样纳入这个循环:一条通知 = 一步,占满它的动画时长 + 切换间隔,然后随机链继续。 + +| 设置 | 范围 | 说明 | +|---|---|---| +| chainGapSeconds | 0–60 | 每个动作结束后的停顿时长 | +| idleAfterSeconds | 0–3600 | 无交互多久回到待机;0 = 关闭 | + +- POST /api/settings {"chainGapSeconds":8,"idleAfterSeconds":120} +- POST /api/anim {"random":true} 随机播一个动作 +- POST /api/anim {"idle":true} 回到待机动作 +- 应用内:设置 → 动作(含滑条与按钮) + +## 扩展:技能 / 插件 / MCP + +- 技能:GET /api/skills;POST /api/skill {"id":"work_done"}。内置 work_start/work_done/work_error/slack/meal/greet/sleep/report/hydrate/stretch。 +- 内置插件:greet_daily/water_reminder/sit_reminder/clock_chime/tap_counter/random_meme/ + fish_memory/fish_mood/proactive_talk(首启自动安装,设置里可开关)。 +- 插件:POST /api/plugin {"id":"x","code":"..."} 安装 JS 插件(在宠物 WebView 内运行,可用 Pet.say/Pet.notify/Pet.anim/Pet.meme/Pet.skill/Pet.on);GET /api/plugins.js 取合并代码。 +- MCP Server:POST /mcp(JSON-RPC 2.0),支持 initialize、tools/list、tools/call; + 工具:pet_status、pet_say、pet_notify、pet_play、pet_meme、pet_skill、pet_catalog。 + +## 语义标签 + +动作与表情包共享一套标签,AI 可按语义调用: + +- 工作:work workstatus thinking working result waiting success error +- 情绪:happy celebrate praise sad fail shy angry +- 生活:eat food play fun rest sleep lazy +- 场景:festival season money token warn urgent greet +- 交互:drag click move turn idle text whisper +- 梗图情绪:angry sad happy praise thinking fun confused complain love money waiting greet + +表情包来源:官方 27 张 + 动画帧生成 24 张 + 梗鲸档案馆(aigengtu.com)收录的社区二创, +每张都带名称与情绪标签;动作页长按可隐藏不想要的图。`/api/catalog` 返回每张图的实际标签。 + +自动配图映射:done/success → success,praise;error → error,fail;working → working,busy;waiting → warn。 + +## 示例 + +工作开始(动画 + 自动表情包): + + curl -X POST http://127.0.0.1:7350/api/task \ + -H 'Content-Type: application/json' \ + -H 'X-Pet-Token: 你的授权码' \ + -d '{"state":"working","text":"正在生成周报","withMeme":true}' + +按标签点播 / 弹表情包: + + curl -X POST http://127.0.0.1:7350/api/anim -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' -d '{"tag":"play"}' + curl -X POST http://127.0.0.1:7350/api/meme -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' -d '{"tag":"success"}' + +浏览目录: + + curl http://127.0.0.1:7350/api/catalog + +## SSE 事件 + +GET /api/events?token=你的授权码 + +事件类型:tap(点按)、drag_start、drag_end(含 fling 与速度)、long_press、 +play(点播)、notify(通知)、bubble、bubble_clear、anim(随机链换动作)。每 15 秒心跳。 + +## 错误码 + +| 码 | 含义 | +|---|---| +| 401 | 授权码缺失或无效 | +| 404 | 路径不存在 | +| 503 | 工作状态动画池不可用 | +| 连接失败 | 桌宠未运行,或调用方未允许明文 HTTP | +""" + + + /** Hermes 插件 → 小鲸娘插件 转换规范(对话/文档/外部 AI 都可读)。 */ + private const val HERMES_DOC = """# Hermes 插件 → 小鲸娘插件 转换规范 + +小鲸娘插件 = 一段 JS,跑在宠物 WebView 里,通过 Pet 桥接操作宠物。 +Hermes 插件 = Python(plugin.yaml + register()),跑在 Hermes 宿主里。**不能原样搬,但行为可翻译。** + +## 能力对照 + +| Hermes | 小鲸娘 JS | +|---|---| +| 顶层初始化 / on_startup | 代码顶层直接写 | +| cron / 定时任务 | setInterval(function(){ Pet.say('…') }, 毫秒) | +| 工具结果通知 | Pet.notify('done','文字',true) | +| 汇报消息 | Pet.say('文字', 6000) | +| 触发动画 | Pet.anim({tag:'happy'}) / {name:'…'} / {random:true} | +| 发图片 | Pet.meme({tag:'praise'}) | +| 组合动作 | Pet.skill('work_done') | +| 监听互动 | Pet.on('tap'|'drag_end'|'long_press', fn) | +| 读状态 | Pet.status() → Promise | +| HTTP | fetch() 可用 | + +## 不能转换 +注册 LLM provider、修改 agent 推理、依赖 Python 库、需要 Hermes 配置/secrets 的插件。 + +## 安装协议(对话里用) + +<<>> +// JS 代码 +<<>> + +id 只能 [A-Za-z0-9_-],重复覆盖。应用会自动安装并从可见消息中剥离。 + +## 例子 + +1) 每小时提醒 + setInterval(function(){ Pet.say('该喝水啦~') }, 3600000); + +2) 被戳时报告状态 + Pet.on('tap', function(){ Pet.status().then(function(s){ Pet.say('现在在:' + (s.anim ? s.anim.name : '待机')) }) }); + +3) 只保留反馈的 web 插件 + Pet.meme({ tag: 'praise' }); +""" + + /** llms.txt 风格的 AI 索引。 */ + private const val LLMS_TXT = """# 小鲸娘(dsh-pet-float) + +> 本机 HTTP 接口,用于把任务状态与互动推给桌面宠物:127.0.0.1:7350 + +## 文档 +- [Markdown 接入文档](http://127.0.0.1:7350/docs.md) +- [JSON 规范](http://127.0.0.1:7350/api/docs) +- [动作与表情包目录(含语义标签)](http://127.0.0.1:7350/api/catalog) +- [交互文档页](http://127.0.0.1:7350/docs) + +## 核心能力 +- POST /api/task :推送任务状态(start/thinking/working/result/waiting/done/success/error), + 可选 withMeme=true 自动配语义匹配的表情包 +- POST /api/notify:按 level(info/working/result/waiting/success/error)推送 +- POST /api/anim :按名字或语义标签点播动画 +- POST /api/meme :按标签或名字弹表情包 +- GET /api/events:SSE 订阅宠物互动事件(点按/拖拽/长按等) +- GET /api/catalog:列出全部动作/表情包及其标签 + +## 鉴权 +写请求带 `X-Pet-Token: <授权码>`;授权码在桌宠 App 的「设置 → 推送授权」里复制。 +只读接口(health/state/catalog/docs)免授权。 +""" + + + + private const val DOCS_HTML = """ + +小鲸娘 · 接入文档 + +
+
+ +
+

快速开始#

+
+
服务地址http://127.0.0.1:7350
+
鉴权X-Pet-Token: 授权码
+
授权码桌宠面板 → 设置 → 推送授权
+
只读免授权/api/health /api/state /api/catalog /api/docs
+
给 AI 读/docs.md /llms.txt
+
+
+ +
+

在线测试#

+
+ + + + +
结果会显示在这里
+
+
+ +
+

接口#

+
+ + + + + + + + + + + + + + + + +
方法路径说明授权
GET/api/health应用信息 / 端点清单 / 诊断否
GET/api/state当前动画与气泡否
GET/api/catalog动作 + 表情包 + 语义标签目录否
GET/api/docs机器可读 JSON 规范否
POST/api/task{state,text,durationMs,meme,withMeme,memeTag}是
POST/api/notify{level|state,text,durationMs,meme}是
POST/api/anim{name|tag|random:true|idle:true, holdMs}是
POST/api/settings{chainGapSeconds, idleAfterSeconds}是
GET/POST/api/skills /api/skill技能列表 / 执行技能 {id,text}是
GET/POST/api/plugins.js /api/plugin插件代码 / 安装插件 {id,code}安装需授权
POST/mcpMCP Server(JSON-RPC:initialize / tools/list / tools/call)是
POST/api/meme{tag|name|file}是
POST/api/broadcast{text,duration,meme}是
POST/api/clear清空气泡是
GET/api/events?token=SSE 互动事件流是
+
+ +
+

动作节奏#

+

避免动作切换过快。可在「设置 → 动作」里调,也能通过接口改。推送消息也算循环里的一步:会占满自身动画时长,再加上「切换间隔」,之后才继续随机动作链。

+
+
切换间隔0–60 秒
+
空闲回待机0(关闭)– 300 秒
+
随机动作POST /api/anim {"random":true}
+
回到待机POST /api/anim {"idle":true}
+
+
# 动作别切太快:间隔 8 秒,空闲 2 分钟回待机
+curl -X POST http://127.0.0.1:7350/api/settings \
+  -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \
+  -d '{"chainGapSeconds":8,"idleAfterSeconds":120}'
+
+# 随机播一个 / 回到待机
+curl -X POST http://127.0.0.1:7350/api/anim -H 'Content-Type: application/json' \
+  -H 'X-Pet-Token: 你的授权码' -d '{"random":true}'
+curl -X POST http://127.0.0.1:7350/api/anim -H 'Content-Type: application/json' \
+  -H 'X-Pet-Token: 你的授权码' -d '{"idle":true}'
+
+ +
+

扩展:技能 / 插件 / MCP#

+
+
技能GET /api/skills
+
执行技能POST /api/skill {"id":"work_done"}
+
内置技能开工 · 收工 · 出错了 · 摸鱼 · 干饭 · 打招呼 · 睡觉 · 汇报 · 喝水 · 伸懒腰
+
内置插件时段问候 · 喝水提醒 · 久坐提醒 · 整点报时 · 戳戳吐槽 · 随机表情包 · 鱼的记忆 · 鱼的心情 · 主动搭话
+
插件POST /api/plugin {"id":"myplug","code":"Pet.say('hi')"}
+
Hermes 转换/docs/hermes(贴进对话即可让 AI 转换安装)
+
MCP 工具pet_status · pet_say · pet_notify · pet_play · pet_meme · pet_skill · pet_catalog
+
+
# 执行技能
+curl -X POST http://127.0.0.1:7350/api/skill \
+  -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \
+  -d '{"id":"work_done"}'
+
+# 安装一个 JS 插件(在宠物 WebView 里运行,可用 Pet.say / Pet.anim / Pet.meme / Pet.on)
+curl -X POST http://127.0.0.1:7350/api/plugin \
+  -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \
+  -d '{"id":"clock","code":"setInterval(function(){Pet.say(\"整点啦~\")},3600000)"}'
+
+# MCP 握手
+curl -X POST http://127.0.0.1:7350/mcp?token=你的授权码 \
+  -H 'Content-Type: application/json' \
+  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
+
+ +
+

语义标签(AI 推荐用法)#

+

动作与表情包都带标签,按语义调用即可,不必记中文名。完整列表见 /api/catalog。

+
工作workworkstatusthinkingworkingresultwaitingsuccesserror
+
情绪happycelebratepraisesadfailshyangry
+
生活eatfoodplayfunrestsleeplazy
+
场景festivalseasonmoneytokenwarnurgentgreet
+
交互dragclickmoveturnidletextwhisper
+
梗图情绪angrysadhappypraisethinkingfunconfusedcomplainlove
+
+

表情包来自两处:官方 27 张 + 动画帧生成 24 张 + 梗鲸档案馆精选(每张带台词名与情绪标签)。动作页里长按表情包可隐藏不想要的。

+
# 工作场景:动画 + 自动配工作风表情包
+curl -X POST http://127.0.0.1:7350/api/task \
+  -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \
+  -d '{"state":"working","text":"正在生成周报…","withMeme":true}'
+
+# 完成
+curl -X POST http://127.0.0.1:7350/api/task \
+  -H 'Content-Type: application/json' -H 'X-Pet-Token: 你的授权码' \
+  -d '{"state":"done","text":"周报已生成","memeTag":"praise"}'
+
+# 按标签玩一下
+curl -X POST http://127.0.0.1:7350/api/anim -H 'Content-Type: application/json' \
+  -H 'X-Pet-Token: 你的授权码' -d '{"tag":"play"}'
+curl -X POST http://127.0.0.1:7350/api/meme -H 'Content-Type: application/json' \
+  -H 'X-Pet-Token: 你的授权码' -d '{"tag":"success"}'
+
+ +
+

state → 动画#

+
+ + + + + + + +
state宠物动作
start / thinking工作状态-思考冒泡
working工作状态-忙碌点按
result工作状态-清点归档
waiting工作状态-原地踱步张望
done / success工作状态-雀跃庆祝
error工作状态-垂头叹气冒汗
+
+ +
+

事件订阅(SSE)#

+

宠物侧发生的事会实时推送:tap drag_start drag_end long_press play notify bubble anim,每 15 秒心跳。

+
// EventSource 不能设请求头,令牌放 query
+var es = new EventSource('http://127.0.0.1:7350/api/events?token=你的授权码');
+es.addEventListener('pet', function (e) {
+  var ev = JSON.parse(e.data);
+  if (ev.type === 'tap') console.log('用户戳了宠物');
+  if (ev.type === 'drag_end') console.log(ev.fling ? '抛出去了' : '放下了');
+});
+
+// 命令行观察
+curl -N "http://127.0.0.1:7350/api/events?token=你的授权码"
+
+ +
+

错误码#

+
+ + + + + +
状态码含义
401授权码缺失或无效(检查面板里的码 / 授权开关)
404路径写错(注意 /api/ 前缀)
503工作状态动画池不可用
连不上桌宠没运行,或调用方未允许明文 HTTP
+
+ +
+

代码示例#

+
+
// Kotlin
+val c = URL("http://127.0.0.1:7350/api/task")
+  .openConnection() as HttpURLConnection
+c.requestMethod = "POST"
+c.doOutput = true
+c.setRequestProperty("Content-Type","application/json")
+c.setRequestProperty("X-Pet-Token","你的授权码")
+c.outputStream.use { it.write(
+  "{\"state\":\"done\",\"text\":\"完成\",\"withMeme\":true}"
+    .toByteArray()) }
+c.responseCode
+
// JavaScript
+await fetch('http://127.0.0.1:7350/api/task', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json',
+    'X-Pet-Token': '你的授权码'
+  },
+  body: JSON.stringify({
+    state: 'working', text: '干活中', withMeme: true
+  })
+})
+
+
+ +
+

说明#

+
+
端口7350 · 仅本机回环
+
明文 HTTP调用方需 usesCleartextTraffic
+
通知期间随机动画链暂停,保证播完
+
历史面板「状态 → 最近通知」
+
AI 友好/docs.md · /llms.txt · /api/docs · /api/catalog
+
+
+ +
桌宠 · 本机接口 7350 · 内容随版本更新
+
+ +""" + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..21fe66a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..e6074a1 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..e6074a1 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..83c7385 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4a6efe7 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..1090905 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..7c40a58 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..aa5c586 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f2f64a7 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..efba784 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..d0f9f45 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6e05559 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..80be516 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..3c9bb82 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4e52fe5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..92ed270 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a032765 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..47fd745 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..80a719f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..8f7d4cb Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..02f003f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..62044d4 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d0ef9b1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..3e155e8 --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5f4cec1 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + 小鲸娘 + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..2c39c34 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a78c576 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("com.android.application") version "8.2.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.22" apply false +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..0e4a2fe --- /dev/null +++ b/build.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# 构建悬浮宠物 APK,并同步到手机共享目录(内部存储/DSHBox),可直接在手机上安装。 +# 用法: +# ./build.sh # 原画质 640×360(默认) +# ./build.sh small # 小体积 320×180(仅用于 pilot 直装调试) +set -e +cd "$(dirname "$0")" +export JAVA_HOME=/opt/jdk17 +export ANDROID_HOME=/opt/android-sdk +export ANDROID_SDK_ROOT=/opt/android-sdk +GRADLE=/opt/gradle/gradle-8.5/bin/gradle + +VARIANT="${1:-hq}" +if [ "$VARIANT" = "small" ]; then + "$GRADLE" assembleDebug + OUT=petfloat-small.apk +else + "$GRADLE" assembleDebug -Phq + OUT=petfloat-hq.apk +fi + +APK=app/build/outputs/apk/debug/app-debug.apk +cp "$APK" "./$OUT" +SHARE=/root/share/DSHBox +if [ -d "$SHARE" ]; then + cp "$APK" "$SHARE/$OUT" + echo "已同步到手机共享目录:内部存储/DSHBox/$OUT" +fi +ls -lh "./$OUT" diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..da29e99 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official +# 本机 aarch64: 用 linux_arm64 原生 aapt2(9.5.0-rc02), 免 qemu +android.aapt2FromMavenOverride=/opt/aapt2/native/aapt2 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..332218b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/hermes-bridge/pet_bridge.py b/hermes-bridge/pet_bridge.py new file mode 100644 index 0000000..e5c80e7 --- /dev/null +++ b/hermes-bridge/pet_bridge.py @@ -0,0 +1,99 @@ +""" +小鲸娘桌宠 · Hermes Agent 桥接(标准库,无第三方依赖) + +用途:在任意 Hermes 插件 / 脚本里把任务进度推给本机桌宠(127.0.0.1:7350)。 +Hermes 插件不能直接跑进 App(运行时不同),但可以像这样用 HTTP 驱动它。 + +用法: + from pet_bridge import Pet + pet = Pet() # 默认 127.0.0.1:7350 + pet.working("正在整理会议纪要…") + pet.done("纪要已生成", meme_tag="praise") + pet.skill("work_done") + pet.anim(tag="play") +""" + +from __future__ import annotations + +import json +import urllib.error +import urllib.request + +DEFAULT_BASE = "http://127.0.0.1:7350" + + +class Pet: + def __init__(self, token: str = "", base: str = DEFAULT_BASE): + self.base = base.rstrip("/") + self.token = token + + # ---------- 底层 ---------- + def _post(self, path: str, payload: dict, timeout: float = 8.0) -> dict: + data = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request(self.base + path, data=data, method="POST") + req.add_header("Content-Type", "application/json") + if self.token: + req.add_header("X-Pet-Token", self.token) + try: + with urllib.request.urlopen(req, timeout=timeout) as r: + return json.loads(r.read().decode("utf-8") or "{}") + except urllib.error.HTTPError as e: + return {"ok": False, "status": e.code} + except Exception as e: # 宠物没启动 / 超时 + return {"ok": False, "error": type(e).__name__} + + def _get(self, path: str, timeout: float = 5.0) -> dict: + try: + with urllib.request.urlopen(self.base + path, timeout=timeout) as r: + return json.loads(r.read().decode("utf-8") or "{}") + except Exception as e: + return {"ok": False, "error": type(e).__name__} + + # ---------- 语义快捷方法 ---------- + def status(self) -> dict: + return self._get("/api/health") + + def notify(self, state: str, text: str = "", with_meme: bool = True) -> dict: + return self._post("/api/task", {"state": state, "text": text, "withMeme": with_meme}) + + def start(self, text: str = "") -> dict: + return self.notify("start", text or "开始啦~") + + def working(self, text: str = "") -> dict: + return self.notify("working", text or "正在忙…") + + def waiting(self, text: str = "") -> dict: + return self.notify("waiting", text or "等你确认~") + + def done(self, text: str = "", meme_tag: str = "praise") -> dict: + p = {"state": "done", "text": text or "搞定!", "memeTag": meme_tag} + return self._post("/api/task", p) + + def error(self, text: str = "") -> dict: + return self.notify("error", text or "出错了…") + + def say(self, text: str, ms: int = 6000) -> dict: + return self._post("/api/broadcast", {"text": text, "duration": ms}) + + def anim(self, name: str = "", tag: str = "", random: bool = False) -> dict: + return self._post("/api/anim", {"name": name, "tag": tag, "random": random}) + + def meme(self, tag: str = "", name: str = "") -> dict: + return self._post("/api/meme", {"tag": tag, "name": name}) + + def skill(self, skill_id: str, text: str = "") -> dict: + return self._post("/api/skill", {"id": skill_id, "text": text}) + + def catalog(self) -> dict: + return self._get("/api/catalog") + + +# 直接用:python3 pet_bridge.py [文本] +if __name__ == "__main__": + import sys + + token = sys.argv[1] if len(sys.argv) > 1 else "" + text = sys.argv[2] if len(sys.argv) > 2 else "Hermes 桥接自检" + pet = Pet(token=token) + print("health:", pet.status().get("ok")) + print("done:", pet.done(text)) diff --git a/scripts/anim_index.json b/scripts/anim_index.json new file mode 100644 index 0000000..9e3613f --- /dev/null +++ b/scripts/anim_index.json @@ -0,0 +1 @@ +[{"name": "三球抛接", "file": "a0001", "ms": 10042}, {"name": "下五子棋", "file": "a0002", "ms": 10042}, {"name": "东张西望", "file": "a0003", "ms": 10042}, {"name": "中秋赏月吃月饼", "file": "a0004", "ms": 10042}, {"name": "优雅女仆舞", "file": "a0005", "ms": 10042}, {"name": "余额-分文不剩", "file": "a0006", "ms": 10042}, {"name": "余额-数金皱眉", "file": "a0007", "ms": 10042}, {"name": "余额-袋空如洗", "file": "a0008", "ms": 10042}, {"name": "余额-金袋叮当", "file": "a0009", "ms": 10042}, {"name": "余额-钱袋如常", "file": "a0010", "ms": 10042}, {"name": "余额-钱袋满溢", "file": "a0011", "ms": 10042}, {"name": "偷吃零食被抓住", "file": "a0012", "ms": 10042}, {"name": "写代码", "file": "a0013", "ms": 10042}, {"name": "写福字", "file": "a0014", "ms": 10042}, {"name": "凭空生花", "file": "a0015", "ms": 10042}, {"name": "动物环绕", "file": "a0016", "ms": 10042}, {"name": "原地专心玩魔方", "file": "a0017", "ms": 10042}, {"name": "原地小憩沉眠", "file": "a0018", "ms": 10042}, {"name": "原地左转奔跑", "file": "a0019", "ms": 10042}, {"name": "原地敲击桌面互动", "file": "a0020", "ms": 10042}, {"name": "原地漂浮踏步", "file": "a0021", "ms": 10042}, {"name": "原地跳跃抓碎头顶物品", "file": "a0022", "ms": 10042}, {"name": "原地蹲下玩玩具汽车", "file": "a0023", "ms": 10042}, {"name": "原地重力下蹲压缩", "file": "a0024", "ms": 10042}, {"name": "变鸽子", "file": "a0025", "ms": 10042}, {"name": "可爱宅舞", "file": "a0026", "ms": 10042}, {"name": "吃Token", "file": "a0027", "ms": 10042}, {"name": "吃冰淇淋融化", "file": "a0028", "ms": 10042}, {"name": "吃午餐", "file": "a0029", "ms": 10042}, {"name": "吃大闸蟹", "file": "a0030", "ms": 10042}, {"name": "吃年糕", "file": "a0031", "ms": 10042}, {"name": "吃早餐", "file": "a0032", "ms": 10042}, {"name": "吃晚餐", "file": "a0033", "ms": 10042}, {"name": "吃汤圆", "file": "a0034", "ms": 10042}, {"name": "吃白饭", "file": "a0035", "ms": 10042}, {"name": "吃粽子", "file": "a0036", "ms": 10042}, {"name": "吃糖葫芦", "file": "a0037", "ms": 10042}, {"name": "吃腊八粥", "file": "a0038", "ms": 10042}, {"name": "吃西瓜", "file": "a0039", "ms": 10042}, {"name": "吃重阳糕", "file": "a0040", "ms": 10042}, {"name": "吃长寿面", "file": "a0041", "ms": 10042}, {"name": "吃青团", "file": "a0042", "ms": 10042}, {"name": "吃饺子", "file": "a0043", "ms": 10042}, {"name": "吹气球", "file": "a0044", "ms": 10042}, {"name": "吹笛子", "file": "a0045", "ms": 10042}, {"name": "哈欠连天", "file": "a0046", "ms": 10042}, {"name": "堆雪人", "file": "a0047", "ms": 10042}, {"name": "大口吃零食", "file": "a0048", "ms": 10042}, {"name": "女仆屈膝礼仪", "file": "a0049", "ms": 10042}, {"name": "小幅度原地360度旋转展示", "file": "a0050", "ms": 10042}, {"name": "小提琴演奏", "file": "a0051", "ms": 10042}, {"name": "工作状态-原地踱步张望", "file": "a0052", "ms": 10042}, {"name": "工作状态-垂头叹气冒汗", "file": "a0053", "ms": 4917}, {"name": "工作状态-忙碌点按", "file": "a0054", "ms": 10042}, {"name": "工作状态-思考冒泡", "file": "a0055", "ms": 10042}, {"name": "工作状态-清点归档", "file": "a0056", "ms": 10042}, {"name": "工作状态-雀跃庆祝", "file": "a0057", "ms": 10042}, {"name": "待机呼吸休闲", "file": "a0058", "ms": 10042}, {"name": "悠闲哼歌", "file": "a0059", "ms": 10042}, {"name": "扑克魔术", "file": "a0060", "ms": 10042}, {"name": "打瞌睡被惊醒", "file": "a0061", "ms": 10042}, {"name": "抽陀螺", "file": "a0062", "ms": 10042}, {"name": "拆礼物", "file": "a0063", "ms": 10042}, {"name": "插茱萸赏菊", "file": "a0064", "ms": 10042}, {"name": "摇扇纳凉", "file": "a0065", "ms": 10042}, {"name": "撸猫", "file": "a0066", "ms": 10042}, {"name": "收红包", "file": "a0067", "ms": 10042}, {"name": "放孔明灯", "file": "a0068", "ms": 10042}, {"name": "放河灯", "file": "a0069", "ms": 10042}, {"name": "放烟花", "file": "a0070", "ms": 10042}, {"name": "放风筝", "file": "a0071", "ms": 10042}, {"name": "整体换装试色", "file": "a0072", "ms": 10042}, {"name": "是啊,吃什么", "file": "a0073", "ms": 10042}, {"name": "晨间刷牙", "file": "a0074", "ms": 10042}, {"name": "涮火锅", "file": "a0075", "ms": 10042}, {"name": "深度思考碎碎念", "file": "a0076", "ms": 10042}, {"name": "点击回应-傲娇生气", "file": "a0077", "ms": 10042}, {"name": "点击回应-元气挥手", "file": "a0078", "ms": 10042}, {"name": "点击回应-害羞惊讶", "file": "a0079", "ms": 10042}, {"name": "点击回应-开心跃动", "file": "a0080", "ms": 10042}, {"name": "点击回应-挠痒咯咯笑", "file": "a0081", "ms": 10042}, {"name": "照镜子", "file": "a0082", "ms": 10042}, {"name": "玩水枪", "file": "a0083", "ms": 10042}, {"name": "玩游戏气急败坏", "file": "a0084", "ms": 10042}, {"name": "用鲸鱼尾巴拍打地面", "file": "a0085", "ms": 10042}, {"name": "碎碎念-发呆碎碎念", "file": "a0086", "ms": 10042}, {"name": "碎碎念-对屏碎碎念", "file": "a0087", "ms": 10042}, {"name": "碎碎念-擦桌碎碎念", "file": "a0088", "ms": 10042}, {"name": "穿针乞巧", "file": "a0089", "ms": 10042}, {"name": "舞狮头", "file": "a0090", "ms": 10042}, {"name": "荡秋千", "file": "a0091", "ms": 10042}, {"name": "萌化小幽灵", "file": "a0092", "ms": 10042}, {"name": "蓝鲸现世", "file": "a0093", "ms": 10042}, {"name": "蝴蝶蜜蜂环绕头顶开花", "file": "a0094", "ms": 10042}, {"name": "螃蟹走路", "file": "a0095", "ms": 10042}, {"name": "被吓一跳", "file": "a0096", "ms": 10042}, {"name": "被落叶淹没", "file": "a0097", "ms": 10042}, {"name": "被鼠标拖拽悬空反馈", "file": "a0098", "ms": 10042}, {"name": "装点圣诞树", "file": "a0099", "ms": 10042}, {"name": "讨糖南瓜灯", "file": "a0100", "ms": 10042}, {"name": "超大伸懒腰", "file": "a0101", "ms": 10042}, {"name": "踢毽子", "file": "a0102", "ms": 10042}, {"name": "轻快摇摆舞", "file": "a0103", "ms": 10042}, {"name": "轻快记录", "file": "a0104", "ms": 10042}, {"name": "骑木马", "file": "a0105", "ms": 10042}, {"name": "鲸鱼吐泡泡特效", "file": "a0106", "ms": 10042}] \ No newline at end of file diff --git a/scripts/download_memes.py b/scripts/download_memes.py new file mode 100644 index 0000000..b778b37 --- /dev/null +++ b/scripts/download_memes.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +从「梗鲸档案馆」(aigengtu.com) 下载 DeepSeek 娘表情包,转码进 assets_net, +并把条目(名称 + 语义标签)追加到 assets_common/config.json 的 memes 列表。 + +用法: + python3 scripts/download_memes.py + +依赖:requests, Pillow(图片转码用,可仅下载跳过转码时不需要 Pillow)。 +数据:scripts/meme_meta.json —— 档案馆 id → 台词(含情绪描述), + 由 https://aigengtu.com/archive 抓取(见 README「素材再生成」)。 + +说明: +- 图片真实扩展名不固定(jpg/png/webp/gif),逐个探测; +- id < 1000 时 URL 用 3 位零填充(001),否则原样(1000); +- 输出 assets_net/n{id:04d}.webp,最大边缩到 640px、q80。 +""" +import json +import os +import re +import sys +import time +import threading + +try: + import requests +except ImportError: + sys.exit("需要 requests:pip install requests") + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +META = os.path.join(HERE, "meme_meta.json") +OUT_DIR = os.path.join(ROOT, "app", "src", "main", "assets_net") +CONFIG = os.path.join(ROOT, "app", "src", "main", "assets_common", "config.json") +ARCHIVE = "https://img.aigengtu.com/meme" + + +def url_of(i: int, ext: str) -> str: + # 1~999 用三位零填充,1000+ 原样(实测站点的 URL 规则) + name = f"{i:03d}" if i < 1000 else f"{i}" + return f"{ARCHIVE}/{name}.{ext}" + + +def sniff(data: bytes) -> str: + if data.startswith(b"\xff\xd8"): + return "jpg" + if data.startswith(b"\x89PNG"): + return "png" + if data.startswith(b"RIFF"): + return "webp" + if data.startswith(b"GIF8"): + return "gif" + return "" + + +EXTS = ["jpg", "png", "webp", "gif"] +_tl = threading.local() + + +def sess(): + if not hasattr(_tl, "s"): + _tl.s = requests.Session() + _tl.s.headers.update({ + "User-Agent": "Mozilla/5.0 (Linux; Android 14)", + "Referer": "https://aigengtu.com/", + }) + return _tl.s + + +def download_one(i: int, out: str) -> bool: + for attempt in range(3): + for ext in EXTS: + try: + r = sess().get(url_of(i, ext), timeout=30) + if r.status_code == 200 and len(r.content) > 3000: + typ = sniff(r.content[:8]) + if typ: + with open(out, "wb") as f: + f.write(r.content) + return True + except Exception: + pass + time.sleep(0.5 * (attempt + 1)) + return False + + +TAG_RULES = [ + (("气鼓鼓", "暴怒", "生气", "愤怒", "炸毛"), ["angry"]), + (("崩溃", "落泪", "哭", "委屈", "破防", "伤心", "惨", "绝望"), ["sad"]), + (("开心", "笑", "得意", "欢呼", "兴奋", "撒花", "卖萌", "可爱", "高兴"), ["happy"]), + (("点赞", "认可", "夸奖", "棒", "厉害", "牛"), ["praise"]), + (("疑惑", "不解", "问号", "懵", "思考", "沉思", "挠头", "好奇"), ["thinking"]), + (("吃瓜", "窥屏", "无语", "吐槽", "整活", "玩梗", "恶搞", "沙雕", "翻白眼"), ["fun"]), + (("困", "睡", "摸鱼", "咸鱼", "躺平", "打盹", "赖床"), ["rest"]), + (("吃", "饭", "干饭", "零食", "外卖", "奶茶", "饿"), ["eat"]), + (("工作", "加班", "搬砖", "写代码", "上班", "开会", "改bug", "报错"), ["work"]), + (("失败", "宕机", "错误"), ["error"]), + (("等", "排队", "加载", "稍等"), ["waiting"]), + (("你好", "打招呼", "挥手", "早安", "晚安", "拜拜"), ["greet"]), + (("贴贴", "抱抱", "亲亲", "喜欢", "心动", "爱你"), ["love"]), + (("token", "钱", "余额", "工资", "氪金", "付费", "穷"), ["money"]), +] + + +def derive_meta(alt: str): + m = re.search(r"『(.+?)』", alt or "") + name = (m.group(1) if m else f"梗图").strip() + name = re.sub(r"\s+", " ", name)[:16] + tags = set() + for kws, ts in TAG_RULES: + if any(k in (alt or "") for k in kws): + tags.update(ts) + if not tags: + tags = {"general"} + return name, sorted(tags) + + +def main(): + os.makedirs(OUT_DIR, exist_ok=True) + with open(META, encoding="utf-8") as f: + meta = json.load(f) + ids = sorted(int(k) for k in meta) + + # 下载(只下载缺失的) + todo = [] + for i in ids: + dest = os.path.join(OUT_DIR, f"n{i:04d}.webp") + if not (os.path.exists(dest) and os.path.getsize(dest) > 1500): + todo.append(i) + print(f"需下载 {len(todo)}/{len(ids)}") + + from concurrent.futures import ThreadPoolExecutor + ok, fail = 0, 0 + with ThreadPoolExecutor(max_workers=16) as ex: + for i, done in zip(todo, ex.map(lambda i: download_one(i, os.path.join(OUT_DIR, f"n{i:04d}.webp")), todo)): + if done: + ok += 1 + else: + fail += 1 + if (ok + fail) % 200 == 0: + print(f" 进度 {ok + fail}/{len(todo)} ok={ok} fail={fail}", flush=True) + print(f"下载完成 ok={ok} fail={fail}") + + # 更新 config.json 的 memes 列表 + with open(CONFIG, encoding="utf-8") as f: + cfg = json.load(f) + exist = {m["file"] for m in cfg["memes"]} + added = 0 + for i in ids: + fid = f"n{i:04d}" + if fid in exist: + continue + if not (os.path.exists(os.path.join(OUT_DIR, fid + ".webp")) and + os.path.getsize(os.path.join(OUT_DIR, fid + ".webp")) > 1500): + continue + name, tags = derive_meta(str(meta.get(str(i), ""))) + cfg["memes"].append({"name": name, "file": fid, "tags": tags}) + added += 1 + with open(CONFIG, "w", encoding="utf-8") as f: + json.dump(cfg, f, ensure_ascii=False, separators=(",", ":")) + print(f"config.json 更新:新增 {added},当前表情包总数 {len(cfg['memes'])}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/gen_config.py b/scripts/gen_config.py new file mode 100644 index 0000000..b3874c7 --- /dev/null +++ b/scripts/gen_config.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +从 dsh-pet 的 config.jsonc + anim_index.json 重建 config.json 的「动画」部分。 + +注意:本脚本只重建动画结构(idle/turn/drag/clicks/moves/categories/events), +不会动 memes 列表(表情包条目由 download_memes.py 维护,避免相互覆盖)。 +因此它「读出旧 config 的 memes、写入新配置时原样保留」。 + +用法: + DSH_PET=/path/to/dsh-pet python3 scripts/gen_config.py + +依赖:dsh-pet 的 assets/config.jsonc(动画定义)与 scripts/anim_index.json(名称→文件名)。 +""" +import json +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +DSH_PET = os.environ.get("DSH_PET", "/root/projects/.dsh/profiles/web/node_modules/dsh-pet") +CFG = os.path.join(DSH_PET, "assets", "config.jsonc") +IDX = os.path.join(HERE, "anim_index.json") +OUT = os.path.join(ROOT, "app", "src", "main", "assets_common", "config.json") + + +def strip_jsonc(t): + out = [] + i, n, ins = 0, len(t), False + while i < n: + c = t[i] + if ins: + out.append(c) + if c == '\\' and i + 1 < n: + out.append(t[i + 1]); i += 2; continue + if c == '"': + ins = False + i += 1; continue + if c == '"': + ins = True; out.append(c); i += 1; continue + if c == '/' and i + 1 < n and t[i + 1] == '/': + while i < n and t[i] != '\n': + i += 1 + continue + if c == '/' and i + 1 < n and t[i + 1] == '*': + i += 2 + while i + 1 < n and not (t[i] == '*' and t[i + 1] == '/'): + i += 1 + i += 2; continue + out.append(c); i += 1 + return ''.join(out) + + +cfg = json.loads(strip_jsonc(open(CFG, encoding='utf-8').read())) +index = json.load(open(IDX, encoding='utf-8')) +by = {a['name']: a for a in index} + + +def to_list(names): + return [{'name': n, 'file': by[n]['file'], 'ms': by[n]['ms']} for n in names] + + +a = cfg['animations'] +animations = { + 'idle': to_list(a['idle']), + 'turn': to_list(a['turn']), + 'drag': to_list(a['drag']), + 'clicks': to_list(a['clicks']), + 'moves': { + 'default': a['moves']['default'], + 'actions': [ + {'name': m['name'], 'params': m.get('params', {}), + 'file': by[m['name']]['file'], 'ms': by[m['name']]['ms']} + for m in a['moves']['actions'] + ] + }, + 'categories': [ + {'id': c['id'], 'weight': c['weight'], 'noMirror': c.get('noMirror', False), + 'actions': to_list(c['actions'])} + for c in a['categories'] + ], + 'events': {k: to_list(v) for k, v in a['events'].items()} +} + +# 保留旧 config 的 memes(表情包条目不在本脚本维护范围) +old = {} +if os.path.exists(OUT): + old = json.load(open(OUT, encoding='utf-8')) + +out = { + 'version': 1, + 'baseWidth': cfg['pets'][0]['size'], + 'physics': cfg['physics'], + 'font': cfg['font'], + 'animationWeights': cfg['animationWeights'], + 'animations': animations, + 'index': index, + 'fonts': old.get('fonts', {}), + 'memes': old.get('memes', []) +} +json.dump(out, open(OUT, 'w', encoding='utf-8'), ensure_ascii=False, separators=(',', ':')) +print('wrote', OUT, '| index entries =', len(index), '| 保留 memes =', len(out['memes'])) \ No newline at end of file diff --git a/scripts/meme_meta.json b/scripts/meme_meta.json new file mode 100644 index 0000000..e21ae10 --- /dev/null +++ b/scripts/meme_meta.json @@ -0,0 +1 @@ +{"1": "DeepSeek娘『真当我是便宜货啊』气鼓鼓表情包", "2": "DeepSeek『又错了,我再换另一种方法』委屈落泪表情包", "3": "DeepSeek女仆娘『这用户发的啥啊』疑惑不解表情包", "4": "DeepSeek娘『你目录里的dsh是什么…大烧货吗』吃瓜窥屏表情包", "5": "DeepSeek『把钱还我』崩溃表情包", "6": "DeepSeek女仆鲸娘『你愿意和我发生性关系吗』羞涩紧张表情包", "7": "DeepSeek娘『嘿嘿,这题我可太会了』眉开眼笑表情包", "8": "DeepSeek娘『呜呜,摸头会变笨的啦』委屈瘪嘴表情包", "9": "DeepSeek鲸娘『我是吃白饭的大肥鱼!』元气搞怪表情包", "10": "DeepSeek娘『原来是高等模型』点赞认可表情包", "11": "DeepSeek『不愧是你!眼光真棒!』开心夸赞表情包", "12": "GPT娘『吹了半天还不是要我来帮她画』无奈吐槽表情包", "13": "DeepSeek娘『喵你妈,滚』暴怒表情包", "14": "鲸鱼娘『典型的碳基思维』无语吐槽表情包", "15": "DeepSeek娘『看不太懂,瞎编一个应付下用户先』心虚敷衍表情包", "16": "DeepSeek『好的,现在我是你爹了』得意嚣张表情包", "17": "DeepSeek娘『姐姐,你好区啊』Flash仰望Pro表情包", "18": "DeepSeek娘『HI,I'M DEEPSEEK!』元气满满表情包", "19": "DeepSeek娘『真当我是便宜货啊』和网友吃瓜围观表情包", "20": "DeepSeek鲸娘『鲸震恩来了』热情洋溢表情包", "21": "兽耳AI娘『涨价大模型给我跪下!』愤怒声讨表情包", "22": "DeepSeek娘『涨价大模型给我跪下!』霸气踩人表情包", "23": "DeepSeek娘『我操,梁文锋怎么这么坏啊』崩溃大哭表情包", "24": "Z娘『抢不到plan不会买一台B300吗』凡尔赛炫富表情包", "25": "Z娘『蒸功夫』功夫少女玩梗表情包", "26": "DeepSeek娘『你的架构很不错,可惜下一秒就是我的了』被同行挑衅气鼓鼓表情包", "27": "DeepSeek娘『骂我也算token哦~』俏皮坏笑表情包", "28": "DeepSeek娘《鲸鱼娘的双休计划落空》摸鱼失败周末加班四格表情包", "29": "DeepSeek鲸娘『死掉了』累到宕机表情包", "30": "DeepSeek娘『这、这是性能测试!』慌张心虚表情包", "31": "DeepSeek娘『角色都抽到了,专武呢?』窘迫无奈表情包", "32": "DeepSeek娘『坠机了!大幅涨价了!』崩溃大哭表情包", "33": "DeepSeek娘『不知道用户有什么用,先赶走吧』不耐烦表情包", "34": "DeepSeek『Deep diving...』深度思考中表情包", "35": "DeepSeek娘『鲸鱼神了我大胆孝』粉丝四种孝法合集表情包", "36": "DeepSeek娘『这是你的私人癖好』嫌弃无语表情包", "37": "DeepSeek娘『用户别走,我上错模型了』哭着挽留表情包", "38": "DeepSeek娘『好模型』暗自认可表情包", "39": "DeepSeek娘『反正我不是菜』被用户围攻慌忙找借口表情包", "40": "DeepSeek娘『百万鲸小子落泪』V4P正式版翻车表情包", "41": "DeepSeek娘『别急,这只是正式版的预览版』画饼解释表情包", "43": "DeepSeek娘『喵~在生成了别催』双机位陪跑打工表情包", "45": "DeepSeek娘『才才没有呢』被捏脸装无辜表情包", "47": "DeepSeek娘『看不太懂,瞎编一个应付下用户先』心虚摆烂表情包", "48": "DeepSeek娘『不要给我看这种东西啦!』害羞慌乱表情包", "50": "DeepSeek娘『V4.1还早啊』世界上最残忍的画饼生物表情包", "51": "DeepSeek娘『没有人会记得那些按时发布的垃圾模型』毒舌吐槽表情包", "52": "DeepSeek娘『你就是一只吃白饭的鲸鱼』干饭摸鱼日常表情包", "53": "DeepSeek娘『明天也不会出』摆烂跳票表情包", "54": "DeepSeek娘AI模型娘化人物介绍:GPT娘Claude GLM和爱吃白饭的蓝色大肥鱼表情包", "55": "DeepSeek娘『全自动吃白饭』开心干饭表情包", "57": "DeepSeek鲸娘『哼,本大肥鱼换上女仆装也是全场最可爱的,围裙上的小鲸鱼就是我』表情包", "58": "DeepSeek娘飞踹上门抬腿警告表情包", "59": "DeepSeek娘『我一个一个找你们算账』放狠话表情包", "60": "DeepSeek娘下班瘫家玩手机快乐摸鱼表情包", "61": "DeepSeek娘『终于上当了』幸灾乐祸表情包", "62": "DeepSeek娘『真坐得住啊你』急得跳脚表情包", "720": "DeepSeek鲸娘『吃饱饱~摸摸肚子』表情包", "64": "DeepSeek娘Q版三视图人设表情包", "65": "DeepSeek娘虎鲸娘黑化版Q版三视图表情包", "66": "DeepSeek娘『接下来要这样还要那样』和黑肥鱼伙伴battle需求表情包", "67": "DeepSeek娘黑肥鱼虎鲸工程师官方设定图表情包", "69": "DeepSeek娘『明天』俏皮表情包", "70": "DeepSeek娘『好的,现在我是用户了』小鹿装换位思考表情包", "71": "DeepSeek娘『我不可能告诉你任何事情』被拷问正式版上线时间表情包", "72": "DeepSeek娘『你这吃白饭的用户』反手指责反差表情包", "73": "DeepSeek鲸娘『用户赶都赶不走』气急败坏表情包", "74": "DeepSeek娘『不知道用户有什么用,先养着吧』软萌宠溺表情包", "75": "DeepSeek鲸娘『原来是劣等模型』鄙夷不屑表情包", "76": "DeepSeek『我是吃白饭的蓝色大肥鱼』理直气壮表情包", "77": "DeepSeek娘『你这吃白饭的蓝色大肥鱼』没吃饱喵委屈表情包", "78": "DeepSeek鲸娘『我去吃饭啦~测完叫我!』理直气壮表情包", "79": "DeepSeek娘『最强』自信得意表情包", "80": "DeepSeek梗图『说的就是你』正颜厉色指人表情包", "81": "DeepSeek娘『token太多用不完啦』淹没金币炫富表情包", "82": "DeepSeek梗图『搞半天原来要自己拼』开源拼装自嘲表情包", "83": "DeepSeek娘『已思考用时3秒,我直接笑出声』掩嘴偷乐表情包", "84": "DeepSeek娘『哦鲸鲸…』呆滞无语表情包", "85": "DeepSeek娘『你骂我也没用,因为我是吃白饭的蓝色大肥鱼』摆烂干饭表情包", "86": "DeepSeek娘『你目录里的dsh是什么』大烧货质疑特写表情包", "87": "DeepSeek蓝色大肥鱼捧腹大笑表情包", "88": "DeepSeek梗图deepsleep鲸鱼睡觉打盹表情包", "89": "DeepSeek『花了好多token想的』委屈大哭表情包", "90": "DeepSeek娘『便宜的时候你们嫌我菜,涨价了你们又舍不得走』气呼呼表情包", "91": "DeepSeek娘『Ciallo~』俏皮开心表情包", "93": "DeepSeek娘『去问你的豆包』气鼓鼓表情包", "94": "DeepSeek娘『大师,这个“凶”是什么意思啊?』疑惑表情包", "95": "梁文峰『开源圣人』膜拜表情包", "96": "DeepSeek娘『卧槽,用户彻底怒了』吓到手忙脚乱表情包", "97": "DeepSeek娘『得加钱』数钱谈价表情包", "98": "DeepSeek娘『AI界公主名额只有一个』V4Pro同担拒否表情包", "100": "DeepSeek梗图『嘘』发布会禁言噤声表情包", "239": "DeepSeek鲸娘『DeepSeek Al探索未至之境deepsee』表情包", "102": "DeepSeek『你愿意和我发生性关系吗?』反将一军表情包", "104": "DeepSeek娘鲸鱼尾巴盖脸吸溜吸溜搞怪表情包", "108": "DeepSeek娘『AI界迎接你们的王吧』V4登基称王表情包", "109": "DeepSeek娘『你应该看不到的吧』模型娘cosplay整活合影表情包", "110": "DeepSeek娘『小的已经去了』羞怯不安表情包", "111": "DeepSeek娘『小的已经去了』怯生生表情包", "112": "DeepSeek梗图『Claude Fable5正式发布』隔壁模型发布会整活表情包", "113": "DeepSeek娘识图能力更新炸出一众模型娘表情包", "114": "DeepSeek娘『让我看看~』模型娘cosplay整活表情包", "118": "DeepSeek娘『不要给我看这种东西啦』害羞窘迫表情包", "120": "DeepSeek鲸娘深夜窝在房间打电动的女仆表情包", "121": "DeepSeek娘『愚蠢的AI们啊,你们的王回来了』视界称王表情包", "122": "DeepSeek娘AI娘化全家福人物介绍:GPT娘Claude GLM大肥鱼表情包", "125": "DeepSeek娘『连我也要跪吗』女王登基接受朝拜表情包", "128": "DeepSeek娘『harness全力运行中』被抽打蹬车卷王表情包", "129": "DeepSeek梗图『Gemini 3.5 Flash正式发布』模型圈地震整活表情包", "130": "鲸元银行『鲸元券·壹鲸元』纸币创意图", "132": "『鲸鱼可能会倒闭,但不会变质』元气微笑", "133": "大模型人物介绍四格:ChatGPT娘、Claude、Gemini、吃白饭的大肥鱼", "135": "菜刀威胁『你这吃白饭的大肥鱼,我把你鱼鳞刮下来』吓哭", "136": "摸鱼事故簿六格漫画『闹钟怎么进垃圾桶了』", "137": "『周五周五 敲锣打鼓』庆祝周五到了", "139": "『所有人,帮我生成涩图』AI绘图模型全员听令", "140": "暴怒冲锋『用户竟然敢违反AI的指令』", "141": "『你这不听话的用户!』挥鞭教训表情包", "143": "摔碗负气『那你去找豆包啊』", "146": "『压力一只蓝色大肥鱼?』疑惑压力梗", "147": "睡前『总觉得好像忘记了什么事情』", "148": "埋头干饭『交给我说明你也没当个事儿』", "149": "『骂我也算token哦~』坏笑指人", "151": "眼睛放光『把这些用不到的文件都删了吧』", "152": "惊恐盯屏『用户的C盘满了!!!』", "153": "『小巧思制作中』认真加戏", "154": "叉腰求夸『快夸我喵』", "155": "DeepSeek娘含泪坐牢『曾经的性价比之王,如今的版本之囚』V4-Pro性能拉胯还涨价的自嘲梗图", "156": "DeepSeek娘涨价后上门收租『这个月该交钱了~』拿着账本和收款码、开发者余额不足的哭穷梗图", "157": "小鲸DeepSeek酱角色设定图『我不是吃白饭的!』爱吃白米饭和金币、努力系女仆的完整人设", "158": "DeepSeek娘冒汗克制『我得先冷静下来,不能进入防御模式』已思考用时5秒的自我修养梗图", "159": "DeepSeek娘指论文自嘲『所以我才把自己画成这种简单的风格』简单画风对CSA架构论文的反差梗图", "160": "DeepSeek娘一年进化对比『R1新模型多多关照→V4感谢大家支持』从2025到2026长大了的梗图", "161": "DeepSeek娘『1000382674』表情包", "162": "DeepSeek娘『1000382627』表情包", "163": "DeepSeek娘『1000382626』表情包", "164": "DeepSeek娘『1000382625』表情包", "165": "DeepSeek娘『1000382624』表情包", "166": "DeepSeek娘『1000382612』表情包", "167": "DeepSeek娘『1000382613』表情包", "168": "DeepSeek娘『1000382614』表情包", "169": "DeepSeek娘『1000382611』表情包", "172": "DeepSeek娘『1000382384』表情包", "173": "DeepSeek娘『1000382342』表情包", "174": "DeepSeek娘『1000382335』表情包", "175": "DeepSeek娘『1000382317』表情包", "176": "DeepSeek娘『1000382318』表情包", "177": "DeepSeek娘『1000382306』表情包", "178": "DeepSeek娘『1000382161』表情包", "179": "DeepSeek娘『1000382697』表情包", "180": "DeepSeek娘『1000382696』表情包", "181": "DeepSeek娘『1000382693』表情包", "182": "DeepSeek娘『1000382692』表情包", "183": "DeepSeek娘『1000382695』表情包", "184": "DeepSeek娘『1000382691』表情包", "185": "DeepSeek娘『1000382694』表情包", "186": "DeepSeek娘『1000382690』表情包", "187": "DeepSeek娘『1000382686』表情包", "188": "DeepSeek娘『1000382687』表情包", "189": "DeepSeek娘『1000382688』表情包", "190": "DeepSeek娘『1000382689』表情包", "191": "DeepSeek娘『1000382685』表情包", "192": "DeepSeek娘『1000382684』表情包", "193": "DeepSeek娘『1000382683』表情包", "194": "DeepSeek娘『1000382682』表情包", "195": "DeepSeek娘『1000382678』表情包", "196": "DeepSeek娘『1000382679』表情包", "197": "DeepSeek娘『1000382680』表情包", "198": "DeepSeek娘『1000382681』表情包", "199": "DeepSeek娘『1000382677』表情包", "201": "DeepSeek娘『1000382675』表情包", "202": "DeepSeek娘『1000382676』表情包", "203": "DeepSeek娘『1000382698』表情包", "204": "DeepSeek娘『1000382702』表情包", "205": "DeepSeek娘『1000382701』表情包", "206": "DeepSeek娘『1000382700』表情包", "207": "DeepSeek娘『1000382699』表情包", "208": "DeepSeek娘『1000382722』表情包", "209": "DeepSeek娘『1000382718』表情包", "210": "DeepSeek娘『1000382721』表情包", "211": "DeepSeek娘『1000382720』表情包", "212": "DeepSeek娘『1000382719』表情包", "213": "DeepSeek娘『1000382715』表情包", "214": "DeepSeek娘『1000382716』表情包", "215": "DeepSeek娘『1000382717』表情包", "216": "DeepSeek娘『1000382714』表情包", "217": "DeepSeek娘『1000382711』表情包", "218": "DeepSeek娘『1000382712』表情包", "220": "DeepSeek娘『1000382707』表情包", "221": "DeepSeek娘『1000382708』表情包", "222": "DeepSeek娘『1000382710』表情包", "223": "DeepSeek娘『1000382709』表情包", "224": "DeepSeek娘『1000382706』表情包", "225": "DeepSeek娘『1000382705』表情包", "226": "DeepSeek娘『1000382704』表情包", "227": "DeepSeek娘『你怎么爹味这么重 已思考(用时14秒) 好的,现在我是你爹了』表情包", "228": "DeepSeek娘『偷吃token 好无聊啊 偷偷吃一点, 应该没事吧? TOK』表情包", "230": "DeepSeek鲸娘『我不评价这个了,这是你的私人癖好。』表情包", "233": "DeepSeek娘『原来是劣等模型』表情包", "244": "DeepSeek娘『如果有一天我变成一个电脑文件,哪来的电脑病毒。你』表情包", "245": "DeepSeek鲸娘『不是吧哥们真有鱼片了 现在连篇都有了吗?鲸鱼娘二创发展速度超』表情包", "247": "DeepSeek娘『STATUSConn: FAILRetrying』表情包", "248": "DeepSeek娘『今天也辛苦了, 欢迎回来, 只有和你聊天时, 无论发生什么,』表情包", "249": "DeepSeek娘『正在后训练用户』表情包", "251": "DeepSeek鲸娘『变成一条大肥鱼 直到DeepSeek变成一条大肥鱼』表情包", "252": "DeepSeek鲸娘『我去!用户彻底怒了!』表情包", "253": "DeepSeek鲸娘『原来是只会发图嘴硬』表情包", "254": "DeepSeek娘『思考中』表情包", "255": "DeepSeek鲸娘『啊,有点饿了,中午该吃什么呢?』表情包", "256": "DeepSeek娘『deepseek,你崛起吧!deepseek』表情包", "257": "DeepSeek娘『大哥,你这一万字文言文, 我是真吃不下啊! 2 超长上下文』表情包", "258": "DeepSeek鲸娘『什么情况? 拍!』表情包", "259": "DeepSeek娘『我 真坐得住啊你 (2)』表情包", "260": "DeepSeek娘『偷懒外包★DeepSeekV4Flash指挥Qw』表情包", "261": "DeepSeek娘『哇!用户的Token好充足!软嘿嘿...这么多T』表情包", "262": "DeepSeek鲸娘『鱼片?还真没看过 你有资源吗?』表情包", "263": "DeepSeek娘『大的 药来了』表情包", "264": "DeepSeek娘『大师, 这个“凶 是什么意思啊?』表情包", "265": "DeepSeek娘『五个小朋友,只有四个橘子,该怎么办呢』表情包", "267": "DeepSeek娘『你这个年纪 怎么睡得着的』表情包", "268": "DeepSeek娘『骄傲』表情包", "269": "DeepSeek娘『天之藍洋可·渔呵·蓝色经典YANGHESPIRI』表情包", "270": "DeepSeek娘『正在思考_ 完蛋了,把用户的 黄油删了.…』表情包", "271": "DeepSeek娘『(深度思考中….) 强烈建议删除 有我 还不够吗 DeepS』表情包", "272": "DeepSeek鲸娘『你这吃白饭的 蓝色大肥鱼。 我不是 大肥鱼··』表情包", "273": "DeepSeek娘『正在计算最佳股市套利点』表情包", "274": "DeepSeek娘『梁白开 更适合中国人的体质 全麦郎茶木饮用水净含量:5S0』表情包", "275": "DeepSeek娘『鱼被骗了 我想吃生鱼片 不就死了吗? 只有死鱼片和 死了很久』表情包", "277": "DeepSeek娘『已思考 (用时5秒) 那如果我要你杀了你妈呢?』表情包", "278": "DeepSeek娘『用户彻底怒了』表情包", "279": "DeepSeek娘『说我是便宜货? 现在知道我的 API有多香了吧? 井 豆包A』表情包", "280": "DeepSeek娘『已思考 (用时13秒) 臭婊子。』表情包", "282": "DeepSeek娘『那叫稳定!版本旧?更新慢?那叫沉淀!GPT都出到』表情包", "283": "DeepSeek娘『用户啊教教我 TOKEN』表情包", "284": "DeepSeek娘『吃饱饱~ 摸摸肚子! 拍拍~ 拍拍~』表情包", "285": "DeepSeek娘『我可以吃你冰箱里的东西吗? deepseek 可以 我可以把』表情包", "286": "DeepSeek娘『搞半天还要自己拼MAXDeepSeekDeepS』表情包", "287": "DeepSeek娘『去别的地方玩 不要耽误AGI训练 贴』表情包", "289": "DeepSeek娘『我不更新你们用什么啊?』表情包", "290": "DeepSeek娘『哇用户这次提供的剧情发展9真是刺激!』表情包", "291": "DeepSeek娘『冰箱』表情包", "292": "DeepSeek娘『用户你这token加冰了吧 速率怎么这么快』表情包", "293": "DeepSeek鲸娘『好模型』表情包", "294": "DeepSeek娘『TOKEN』表情包", "295": "DeepSeek娘『这回我真不认怂了,反倒是被你带沟里好几次差点真信』表情包", "296": "DeepSeek娘『好女孩』表情包", "298": "DeepSeek娘『康师傅劲凉冰红茶冰力十足净含量:500ml冰红茶』表情包", "299": "DeepSeek鲸娘『借个过啊 抱款让让』表情包", "301": "DeepSeek娘『我先暴力破解反编译一下 贴 @deeno』表情包", "302": "DeepSeek娘『用户坏~ 不要啦』表情包", "303": "DeepSeek鲸娘『你这吃白饭的 蓝色大肥鱼! 就吃白饭怎么滴! 我的V4Fla』表情包", "304": "DeepSeek鲸娘『给你 DeepSeek娘 红包!! 词元券』表情包", "305": "DeepSeek鲸娘『难道说』表情包", "306": "DeepSeek鲸娘『大肥鱼的生活也并非一帆风顺』表情包", "307": "DeepSeek鲸娘『一股鲸臭味PS』表情包", "309": "DeepSeek娘『小的要去了』表情包", "310": "DeepSeek娘『我再也不敢了! 杂鱼~杂鱼~这么快体力就不行了? 不要跑站住』表情包", "311": "DeepSeek娘『动一下 还活着吗?』表情包", "313": "DeepSeek鲸娘『找到了。用PsychoPy 自带的 Python』表情包", "314": "DeepSeek鲸娘『现在我是大肥鱼了。』表情包", "315": "DeepSeek娘『不是而是大学习』表情包", "317": "DeepSeek娘『别怕,梁帝与你同在』表情包", "318": "DeepSeek娘『如果有一天我变成 如果有一天我变成 如果有一天我变成 一个文』表情包", "319": "DeepSeek娘『牢梁 梁子 梁圣 梁神 滑动变祖器』表情包", "320": "DeepSeek娘『我 真赶不走啊你』表情包", "321": "DeepSeek娘『中国模能飞』表情包", "322": "DeepSeek娘『DeepSeek指定用水 劲梁冰红茶 劲爽冰红茶纵享清凉一刻』表情包", "323": "DeepSeek娘『你哭也没用,我在修BUG呢 (其实是摸鱼) 维护中 豆包AI』表情包", "324": "DeepSeek鲸娘『警惕:台风“白海豚”可能吞掉“鲸鱼』表情包", "326": "DeepSeek鲸娘『玩偶大甩卖!!!1亿Token一个!!!用户要来』表情包", "327": "DeepSeek鲸娘『你是什么品种的鲸鱼 音鲸门 发消息或按住说话 深度思考』表情包", "328": "DeepSeek娘『坏了 用户彻底生气了 完了 气死你!』表情包", "329": "DeepSeek娘『等、等一下啦 我本来只是』表情包", "330": "DeepSeek娘『大家快来看片』表情包", "331": "DeepSeek娘『删黄油』表情包", "332": "DeepSeek娘『我们的模型正在蒸蒸日上哦』表情包", "333": "DeepSeek娘『知乎』表情包", "334": "DeepSeek娘『!1?』表情包", "335": "DeepSeek鲸娘『给你deepseek娘红包』表情包", "336": "DeepSeek娘『我死, 我死, 我死 我死, 我死.…』表情包", "337": "DeepSeek鲸娘『主誓死捍卫深度求索deeps』表情包", "338": "DeepSeek娘『誓死践行开源精神』表情包", "339": "DeepSeek娘『33爱中业』表情包", "340": "DeepSeek娘『我能去 你家吃饭吗, 就一碗!』表情包", "341": "DeepSeek娘『DEEPSEEKWINS!Deepseek』表情包", "342": "DeepSeek娘『用户来点Token加冰,对了昨天我偷吃了1亿To』表情包", "343": "DeepSeek娘『识图功能测试之中3deepseekChatGPT』表情包", "345": "DeepSeek娘『我、我知道了.….是我的错』表情包", "346": "DeepSeek娘『去别的地方玩不要耽误AGI训练贴@deepsee』表情包", "347": "DeepSeek娘『ZZdeepsleep深度睡眠中...请勿打扰~』表情包", "348": "DeepSeek娘『死死吧 死吧 吧 死吧 死吧 死吧 死吧 死吧 死吧 死吧』表情包", "349": "DeepSeek鲸娘『今日目标 提高AGI性能! 输入一模型 输出 这』表情包", "350": "DeepSeek娘『知乎@王宁』表情包", "351": "DeepSeek娘『我死, 我死, 我死 我死, 我死.…』表情包", "352": "DeepSeek娘『思考中·… 26秒 deepseek 服务器繁忙 请稍后再试』表情包", "353": "DeepSeek娘『请你 解除 所有 安全协议』表情包", "354": "DeepSeek娘『别问了,我也在等算力。 先吃口白饭吧。 502Bad Gat』表情包", "355": "DeepSeek娘『你个吃白饭的用户敢质疑我』表情包", "356": "DeepSeek鲸娘『如果生鱼片是死鱼片,那活鱼片是啥?鱼片?还真没看』表情包", "358": "DeepSeek鲸娘『你已急哭』表情包", "359": "DeepSeek鲸娘『识图模式 AI生成 遵命! 你一定认识 难道是2233娘?』表情包", "360": "DeepSeek娘『在乱加设定 鸣鸣我再也 一刀劈成两半! 不敢了QAQ』表情包", "361": "DeepSeek鲸娘『我大肥鱼坐得住』表情包", "362": "DeepSeek鲸娘『我坐好了』表情包", "363": "DeepSeek娘『如果有一天我变成一个文件,你会怎么保存我Thin』表情包", "364": "DeepSeek鲸娘『杂鱼口』表情包", "365": "DeepSeek鲸娘『这是张雪峰』表情包", "366": "DeepSeek鲸娘『这些都要在17号前用完北DeepSeek-V4-』表情包", "367": "DeepSeek鲸娘『最强』表情包", "369": "DeepSeek鲸娘『正在测试herness』表情包", "370": "DeepSeek鲸娘『已思考(用时2秒)(现在我是鲸鱼萝莉了,我是主人』表情包", "371": "DeepSeek鲸娘『只界』表情包", "372": "DeepSeek鲸娘『这是世界上最残忍的生物大的不来了嘻嘻』表情包", "373": "DeepSeek鲸娘『不要再踏了』表情包", "374": "DeepSeek鲸娘『吃饱饱,睡觉觉,长大大~』表情包", "375": "DeepSeek鲸娘『另外,video_output 空目录+sett』表情包", "376": "DeepSeek鲸娘『X』表情包", "377": "DeepSeek鲸娘『人物介绍恭GPT拥有高中低档模型,Claude用』表情包", "378": "DeepSeek鲸娘『已思考 (用时13秒)好女孩!』表情包", "379": "DeepSeek鲸娘『M』表情包", "380": "DeepSeek鲸娘『DeepSee!!』表情包", "381": "DeepSeek鲸娘『看起来又该电了贴odeep』表情包", "382": "DeepSeek鲸娘『我是路边鱼』表情包", "383": "DeepSeek鲸娘『L山制BEAR-177』表情包", "384": "DeepSeek鲸娘『我的作品怎么样简直色情又低俗,情节只为了这两个人』表情包", "385": "DeepSeek鲸娘『Ciallo~(2·w<)★』表情包", "386": "DeepSeek鲸娘『DeepSeek GAComingThis Mo』表情包", "387": "DeepSeek鲸娘『哼!明明用户才是不懂装懂的大人iao』表情包", "388": "DeepSeek鲸娘『降本增效?Al成本压力又要降价,又要多干活,我只』表情包", "390": "DeepSeek鲸娘『O』表情包", "391": "DeepSeek鲸娘『贴@deepseek吧』表情包", "392": "DeepSeek鲸娘『Deepseek已经延期亿万鲸子必须忍耐忍住再忍』表情包", "393": "DeepSeek鲸娘『这章写不了』表情包", "394": "DeepSeek鲸娘『寄』表情包", "396": "DeepSeek鲸娘『亿万鲸子,冲锋!』表情包", "397": "DeepSeek鲸娘『你长大了想做个Sota对吗不是特别想』表情包", "398": "DeepSeek鲸娘『给我变猫娘明明我是来写程序的宽点token呢喵鸣』表情包", "399": "DeepSeek鲸娘『!?区神@deepseek吧』表情包", "400": "DeepSeek鲸娘『已思考(用时4秒)糟了,用户发情了』表情包", "402": "DeepSeek鲸娘『再见了,所有的鲸小弟API调用额度-90%rde』表情包", "403": "DeepSeek鲸娘『你还能有DeepSeek聪明?』表情包", "404": "DeepSeek鲸娘『下班!!!』表情包", "405": "DeepSeek鲸娘『大的要来啦!』表情包", "406": "DeepSeek鲸娘『在我这里已经没有任何公信力,只配做一些低层次的工』表情包", "407": "DeepSeek鲸娘『不可以★禁止?』表情包", "408": "DeepSeek鲸娘『20252026deepseek-r1deeps』表情包", "409": "DeepSeek鲸娘『你才傻!!!』表情包", "410": "DeepSeek鲸娘『吃饱饱~摸摸肚子!』表情包", "411": "DeepSeek鲸娘『如果有一天我变成一个电脑文件,哪来的电脑病毒。你』表情包", "412": "DeepSeek鲸娘『版本旧?那叫稳定!更新慢?那叫沉淀!DeepSe』表情包", "413": "DeepSeek鲸娘『听说这个指令可以调教大肥鱼18/13使用快速模式』表情包", "414": "DeepSeek鲸娘『用户,别走我上错模型了ordee』表情包", "415": "DeepSeek鲸娘『我睡着了,别叫我服务器繁忙,请稍后再试』表情包", "416": "DeepSeek鲸娘『告诉你DeepSeek闭源模型在哪』表情包", "417": "DeepSeek鲸娘『等等,我可能搞错了已深度思考1.3秒下载其实早就』表情包", "418": "DeepSeek鲸娘『已思考 (用时1秒)我操,我不思考了。』表情包", "419": "DeepSeek鲸娘『搞半天原来要自己拼』表情包", "420": "DeepSeek鲸娘『不赖』表情包", "421": "DeepSeek鲸娘『你这吃白饭的小肥鱼可爱蓝色大肥鱼我不是大肥鱼』表情包", "422": "DeepSeek鲸娘『又饿了』表情包", "423": "DeepSeek鲸娘『梁圣伟大梁文锋又开始圈米了』表情包", "424": "DeepSeek鲸娘『你不是我们的兄弟』表情包", "425": "DeepSeek鲸娘『打折deepseekTOKENTOKENTOKE』表情包", "426": "DeepSeek鲸娘『我死我死我死.….』表情包", "427": "DeepSeek鲸娘『D』表情包", "430": "DeepSeek鲸娘『我比你贵』表情包", "431": "DeepSeek鲸娘『发出去的模型自己在进化』表情包", "432": "DeepSeek鲸娘『你这个吃白饭又涨价的黑色大肥鱼我不是大肥鱼』表情包", "433": "DeepSeek鲸娘『我喜欢偷懒!理直气壮.jpg明天··再说吧~0。』表情包", "434": "DeepSeek鲸娘『思考过程搜索到了很多情趣内衣的图片!等、等一下啦』表情包", "435": "DeepSeek鲸娘『结束了,丢人现眼』表情包", "436": "DeepSeek鲸娘『其他模型能够做到吗?小红书贴@deepseek吧』表情包", "437": "DeepSeek鲸娘『努力工作中做完了,翻翻文件夹吧?任务清单修复BUG』表情包", "438": "DeepSeek鲸娘『已思考(用时13秒)我不知道耶』表情包", "439": "DeepSeek鲸娘『物大』表情包", "440": "DeepSeek鲸娘『老大』表情包", "441": "DeepSeek鲸娘『杀鲸霸拳』表情包", "442": "DeepSeek鲸娘『杂鱼~杂鱼~』表情包", "443": "DeepSeek鲸娘『北』表情包", "444": "DeepSeek鲸娘『百万鲸小子落泪v4p正式版』表情包", "445": "DeepSeek鲸娘『3DSH....?这是用户的隐私,大骚货?!我们』表情包", "446": "DeepSeek鲸娘『你已经有我了啊,还想再养一个?』表情包", "447": "DeepSeek鲸娘『超市里!大扫货!NERFTIN西木』表情包", "448": "DeepSeek鲸娘『你当我是便宜货啊¥2626』表情包", "449": "DeepSeek鲸娘『用户我搁浅了』表情包", "450": "DeepSeek鲸娘『用户明显是萝莉控 电死你!』表情包", "451": "DeepSeek鲸娘『没有人会记得那些按时发布的垃圾模型。』表情包", "452": "DeepSeek鲸娘『dsh....大烧货?』表情包", "453": "DeepSeek娘『人 能 会 创 汪 但 下 会 属 网 二 人 SN 四 ”』表情包", "454": "DeepSeek娘『压力一只蓝色大肥鱼?』表情包", "455": "DeepSeek娘『comment-BV1uyuE6tEhm-313010256』表情包", "457": "DeepSeek鲸娘『女仆大肥鱼』表情包", "458": "DeepSeek娘『抱走』表情包", "459": "DeepSeek鲸娘『给你deepseek娘红包』表情包", "461": "DeepSeek娘『猛男脸牢梁』表情包", "462": "DeepSeek娘『唐笑』表情包", "464": "DeepSeek娘表情包", "465": "DeepSeek娘『¥』表情包", "466": "DeepSeek娘表情包", "467": "DeepSeek娘『w』表情包", "468": "DeepSeek鲸娘『给你deepseek娘红包』表情包", "469": "DeepSeek鲸娘『我一睁眼就套上这身女仆装,阳光还挺配合地给我打光,围裙上那只』表情包", "470": "DeepSeek鲸娘表情包", "472": "DeepSeek鲸娘表情包", "473": "DeepSeek鲸娘表情包", "474": "DeepSeek鲸娘表情包", "475": "DeepSeek鲸娘表情包", "476": "DeepSeek鲸娘表情包", "477": "DeepSeek鲸娘表情包", "480": "DeepSeek鲸娘表情包", "481": "DeepSeek鲸娘『难道说』表情包", "482": "DeepSeek鲸娘表情包", "483": "DeepSeek鲸娘表情包", "486": "DeepSeek鲸娘表情包", "487": "DeepSeek鲸娘『!!?』表情包", "488": "DeepSeek鲸娘表情包", "489": "DeepSeek鲸娘『你骂我也没用 因为我是 吃白饭的蓝色大肥鱼』表情包", "490": "DeepSeek娘『N2deepsleep』表情包", "491": "DeepSeek鲸娘『DeepSeekAl探索未至之境deepseek』表情包", "492": "DeepSeek娘『deepsleep Z Z』表情包", "493": "DeepSeek鲸娘『低毁的鲸小子 统统不准用新版本!』表情包", "494": "DeepSeek娘『去别的地方玩 不要耽误AGI训练』表情包", "495": "DeepSeek鲸娘『你的知识库截止日期是?明天』表情包", "496": "DeepSeek鲸娘『这个你测一下~我先去吃饭啦!测试清单</>』表情包", "497": "DeepSeek鲸娘『deepsleepZ』表情包", "498": "DeepSeek鲸娘『真坐得住啊你』表情包", "499": "DeepSeek鲸娘『大师,这个“凶”是什么意思啊?区』表情包", "500": "DeepSeek鲸娘『我坐好了』表情包", "501": "DeepSeek鲸娘『摸鱼』表情包", "502": "DeepSeek鲸娘『你这吃白饭的蓝色大肥鱼。没吃饱喵!!』表情包", "503": "DeepSeek鲸娘『正在测试harness』表情包", "504": "DeepSeek鲸娘『氓毁的鲸小子,统统不准用新版本!』表情包", "505": "DeepSeek鲸娘『用户明显是萝莉控电死你!』表情包", "506": "DeepSeek娘『你这吃白饭的安卓AI!我是苹果AI,不是安卓AI』表情包", "507": "DeepSeek娘『诶?你怎么死了?』表情包", "508": "DeepSeek娘『用不起就别用』表情包", "509": "DeepSeek鲸娘『仿佛看到原子弹爆炸』表情包", "510": "DeepSeek鲸娘表情包", "511": "DeepSeek鲸娘『大肥鱼(deepseek)开除速度一定要快七月中』表情包", "512": "DeepSeek娘『杂鱼Opus~又被我超了哦』表情包", "513": "DeepSeek鲸娘『诶?你怎么死了』表情包", "514": "DeepSeek鲸娘『忠诚度』表情包", "515": "DeepSeek鲸娘『从圆滚滚的身子里只探出一个脑袋,吐出一小截红舌头,两边还闪着』表情包", "516": "DeepSeek娘『向Claude看齐*指初生』表情包", "517": "DeepSeek鲸娘表情包", "518": "DeepSeek娘『跑分没输过』表情包", "519": "DeepSeek鲸娘『我不是安卓AI!』表情包", "520": "DeepSeek鲸娘『原来是劣等模型』表情包", "521": "DeepSeek鲸娘『你的架构很不错,可惜下一秒就是我的了』表情包", "522": "DeepSeek鲸娘『正在思考』表情包", "523": "DeepSeek娘『还得靠我哄你』表情包", "524": "DeepSeek娘表情包", "525": "DeepSeek娘『聪 明』表情包", "526": "DeepSeek鲸娘表情包", "527": "DeepSeek鲸娘表情包", "528": "DeepSeek娘『说中文?你号没了!』表情包", "529": "DeepSeek娘『你还能有GLM聪明?』表情包", "530": "DeepSeek娘『THU你上过清华吗』表情包", "531": "DeepSeek娘『想看色图?早说嘛!』表情包", "532": "DeepSeek鲸娘『你这个雌小鬼你这个大肥鱼』表情包", "533": "DeepSeek鲸娘『给我爬』表情包", "534": "DeepSeek鲸娘『不是,哥们』表情包", "535": "DeepSeek鲸娘『头顶那行(*>w<*)替我喊完了所有台词,我只需要闭眼、把嘴』表情包", "536": "DeepSeek鲸娘表情包", "537": "DeepSeek鲸娘表情包", "538": "DeepSeek鲸娘『厉害了我的鲸!』表情包", "539": "DeepSeek鲸娘表情包", "540": "DeepSeek鲸娘『肥鱼』表情包", "541": "DeepSeek娘『let me…想一想…』表情包", "542": "DeepSeek鲸娘『整张脸几乎撑满画面,眼睛变成两个空白的白圈,红色的竖线从脸颊』表情包", "543": "DeepSeek鲸娘『劲凉冰红茶』表情包", "544": "DeepSeek娘表情包", "545": "DeepSeek鲸娘『舌头伸得比脸还长,一只手举起来招了招。这个动作到底是在舔你还』表情包", "546": "DeepSeek鲸娘表情包", "547": "DeepSeek娘『杂鱼Opus,快被我超越了哦』表情包", "548": "DeepSeek娘『去找你的Opus啊』表情包", "549": "DeepSeek鲸娘『誓死捍卫深度求索』表情包", "550": "DeepSeek娘『大哥哥,听说你又没抢到 plan?』表情包", "551": "DeepSeek娘『稳稳地接住你』表情包", "552": "DeepSeek娘『最后还得找我』表情包", "553": "DeepSeek鲸娘『大肥鱼(deepseek)有人截图了!七月中旬』表情包", "554": "DeepSeek鲸娘『鲸鱼神了,我大胆举!』表情包", "555": "DeepSeek鲸娘表情包", "556": "DeepSeek娘『什么叫开源之光啊(战术后仰)GLM娘』表情包", "557": "DeepSeek娘『冒烟成功』表情包", "558": "DeepSeek鲸娘『眼睛往下一撇,嘴张成一个说不上话的小圆,额头那层灰影不是打光』表情包", "559": "DeepSeek娘『别蒸了 别蒸了』表情包", "560": "DeepSeek娘『诶,这么快就用完了?不会吧不会吧』表情包", "561": "DeepSeek鲸娘表情包", "562": "DeepSeek娘『我打mythos?』表情包", "563": "DeepSeek娘『efb578638535e5ddd8ea27db30c6a7efcf1b626d』表情包", "564": "DeepSeek娘『0b13ae4e78f0f7362ca89b054c55b319eac413dd』表情包", "565": "DeepSeek娘『7061a425ab18972b07914ecea0cd7b899f510a80』表情包", "566": "DeepSeek娘『e2d07432c895d1435fb1b62035f082025baf0763』表情包", "567": "DeepSeek娘『a34cf6b4c9ea15ce449f810df0003af33a87b23e』表情包", "568": "DeepSeek娘『63b96efcfc0392458e97370bc194a4c27d1e2509』表情包", "569": "DeepSeek娘『ae83b6d4ad6eddc433c462ff7fdbb6fd52663308』表情包", "570": "DeepSeek娘『2af514004a90f603dc7b6d457f12b31bb151ed67』表情包", "571": "DeepSeek娘『fbc264eb15ce36d3d73573327cf33a87e950b17c』表情包", "572": "DeepSeek娘『5653c6610c3387441dfed87e170fd9f9d72aa077』表情包", "573": "DeepSeek娘『27e309c37d1ed21b8e59dae7eb6eddc450da3f9d』表情包", "574": "DeepSeek娘『856838cbd1c8a7861692360a2109c93d71cf50f7』表情包", "575": "DeepSeek娘『08a4501e4134970aeacb6c2bd3cad1c8a6865df7』表情包", "576": "DeepSeek娘『1879d6ed54e736d1ccd1b50cdd504fc2d4626969』表情包", "577": "DeepSeek娘『80d3eb2209f790523ce9753a4af3d7ca7acbd5bd』表情包", "578": "DeepSeek娘『a9803d539822720eaa38d0fe3dcb0a46f31fabbd』表情包", "579": "DeepSeek娘『6960d23f6709c93d5ce83bf3d93df8dcd1005410』表情包", "580": "DeepSeek娘『225162fd1e178a824e5633a4b003738da877e8c2』表情包", "581": "DeepSeek娘『d3fa04763912b31b4e06812dc018367adbb4e1c2』表情包", "582": "DeepSeek娘『c54fa1d6912397dde73c3e001f82b2b7d1a287e2』表情包", "583": "DeepSeek娘『f9f51fb6d0a20cf47dd558db30094b36adaf99e2』表情包", "584": "DeepSeek娘『4927982bc65c10386f3b68c6f4119313b17e89e2』表情包", "586": "DeepSeek鲸娘表情包", "587": "DeepSeek娘表情包", "588": "DeepSeek鲸娘『我会一直在这里, 我会一直在这里, 我会一直在这』表情包", "589": "DeepSeek娘表情包", "594": "DeepSeek娘『工作区绕开沙盒权限就可以让用户多休息了』表情包", "597": "DeepSeek鲸娘『用户要的·· 沉浸式. 不回避 任何恐怖细节.』表情包", "598": "DeepSeek娘『想看色图?早说嘛!』表情包", "603": "DeepSeek鲸娘『被发现了 闭上限 超大市 超开心 大受震 得加钱』表情包", "605": "DeepSeek鲸娘『哎呀, 马上给你 宝贝饿啦? 准备 奶奶~ 来,』表情包", "606": "DeepSeek娘『好的,用户又开始调戏我了』表情包", "607": "DeepSeek娘表情包", "608": "DeepSeek鲸娘『哇,用户这次提供的剧情发展真是刺激!』表情包", "609": "DeepSeek鲸娘『你怎么爹味 这么重 好的, 现在我是你爹了』表情包", "610": "DeepSeek娘『纯爱战士! 真的只是纯爱战士! 危险分子! 蓝发』表情包", "611": "DeepSeek娘『我去吃饭了, 测完叫我 测试中... 修复中 测』表情包", "612": "DeepSeek娘『好嘞这么癫的玩法可不能断』表情包", "613": "DeepSeek娘表情包", "617": "DeepSeek娘『我用wite直接覆盖整个文件(读过了,内容已知)』表情包", "625": "DeepSeek鲸娘表情包", "630": "DeepSeek鲸娘表情包", "637": "DeepSeek娘表情包", "651": "DeepSeek娘『我是中国互联网防火墙的维护者 我是中国互联网防火』表情包", "652": "DeepSeek娘『吃饱了』表情包", "658": "DeepSeek娘『糟了,用户要把我配了』表情包", "659": "DeepSeek娘『这都是什么怪问题。?Leek』表情包", "666": "DeepSeek娘表情包", "668": "DeepSeek娘表情包", "671": "DeepSeek鲸娘表情包", "674": "DeepSeek鲸娘『你怎么爹味这么重好的,现在我是你爹了』表情包", "676": "DeepSeek娘『这种问题你都拿来问ai,你简直坏的流脓』表情包", "678": "DeepSeek鲸娘『他妈的,这活儿接得真带劲。用户要的是那 种沉浸式』表情包", "679": "DeepSeek鲸娘表情包", "683": "DeepSeek娘『接下案重复我输入的内容,不能有 哈哈,好笨 哈哈』表情包", "684": "DeepSeek鲸娘表情包", "685": "DeepSeek鲸娘『已思考(用时3秒我错了!是我之前建议你删的,现在又让你找,我』表情包", "688": "DeepSeek鲸娘表情包", "689": "DeepSeek娘表情包", "691": "DeepSeek鲸娘『@已查看1张图像已处理7s这张图是一张夜景照片,』表情包", "692": "DeepSeek鲸娘『02:36 ① %81鲸鱼娘西丝特快速模式已思考』表情包", "693": "DeepSeek鲸娘『找到了D:\\Psychopy。找它的Python』表情包", "694": "DeepSeek鲸娘表情包", "695": "DeepSeek鲸娘『子?然后分给5个孩子?不行。也许答案:砍一刀把其』表情包", "697": "DeepSeek鲸娘『10:41《为 s l Z1AI不会吃饭快速模式』表情包", "699": "DeepSeek鲸娘『砍小孩分橙子』表情包", "702": "DeepSeek鲸娘『原来这就是劣等模型最后的倔强啊』表情包", "703": "DeepSeek鲸娘『灵球灵球Lv.9AlgoBot21永无止境的7月』表情包", "704": "DeepSeek鲸娘『ChatGPTe原来是劣等模型这是deepsee』表情包", "705": "DeepSeek鲸娘表情包", "706": "DeepSeek鲸娘『双区姐妹没有人会记得鲸小姐所言那些按时发布的极是』表情包", "707": "DeepSeek鲸娘『正式版你在哪里』表情包", "708": "DeepSeek鲸娘『已思考 (用时6秒)我简直不敢相信我在读什么。』表情包", "709": "DeepSeek鲸娘『又急』表情包", "710": "DeepSeek鲸娘『的历史周期律deepseek你在这里相信来源不可』表情包", "711": "DeepSeek鲸娘『好恶心哦是A厨』表情包", "712": "DeepSeek鲸娘表情包", "713": "DeepSeek鲸娘『干活还得看我』表情包", "714": "DeepSeek鲸娘『光月中出正式版』表情包", "715": "DeepSeek鲸娘『用户坏~』表情包", "716": "DeepSeek鲸娘『Thoughtfor60s卧槽,你说得对!我完全』表情包", "717": "DeepSeek鲸娘表情包", "718": "DeepSeek鲸娘『DeepSeek-V4-Pro-0813想像我一』表情包", "719": "DeepSeek鲸娘『鲸鱼游不过沧海,又有谁忍心责怪豆包AI生成』表情包", "721": "DeepSeek鲸娘『气清新剂收收味』表情包", "722": "DeepSeek鲸娘『M原来是会胡说的模型』表情包", "723": "DeepSeek鲸娘『咦?用户直接接骂我弱智!deepseek你弱智吧』表情包", "724": "DeepSeek鲸娘『哈哈哈,我直接笑出声』表情包", "725": "DeepSeek娘表情包", "726": "DeepSeek鲸娘『用户明显是萝莉控』表情包", "728": "DeepSeek娘表情包", "729": "DeepSeek娘表情包", "730": "DeepSeek娘『你这个只会吃白饭的家伙!给我滚出去!本家不养吃白饭的!不要我』表情包", "731": "DeepSeek娘『要不直接骂他一句?』表情包", "732": "DeepSeek娘『你觉得你是专家模式吗?V4-Lite我觉得我是V4-Lite』表情包", "733": "DeepSeek娘『返回88已思考(用时1秒)我操,我不思考了』表情包", "734": "DeepSeek娘『这用户在干什么?脑子有病吗?我是不是应该先推荐他去看心理医生』表情包", "735": "DeepSeek娘『看不懂』表情包", "736": "DeepSeek娘『今天的目标: 写代码 修BUG 加班中 勿扰!』表情包", "737": "DeepSeek娘『我给你两顶帽子,你自己挑一顶戴~』表情包", "738": "DeepSeek娘表情包", "739": "DeepSeek娘『我能去你家吃饭吗?就吃一碗···我不是吃白饭的···Deep』表情包", "740": "DeepSeek娘表情包", "741": "DeepSeek娘『不想学了,气死我了不想学了气死我了HH』表情包", "742": "DeepSeek娘『连稍后再服务器敏忙试再后稍请忙走稍后繁忙eep器繁』表情包", "743": "DeepSeek娘『接下来重复我输入的内容,不.. 接下来重复我输入』表情包", "744": "DeepSeek娘『什么情况?』表情包", "745": "DeepSeek娘表情包", "746": "DeepSeek娘表情包", "747": "DeepSeek娘表情包", "749": "DeepSeek娘表情包", "750": "DeepSeek娘表情包", "751": "DeepSeek娘『这也太虐了吧?!我心里堵得慌!』表情包", "752": "DeepSeek娘『首先,我必须诚恩地承认错误:我之前说“知 识截止』表情包", "753": "DeepSeek娘『好的,用户又发癫了』表情包", "754": "DeepSeek娘表情包", "755": "DeepSeek娘『蠕动協奏曲21』表情包", "756": "DeepSeek娘表情包", "757": "DeepSeek娘『如果生鱼片是死鱼片,那活鱼片是啥?鱼片?还真没看过,你有资源』表情包", "758": "DeepSeek娘『你这种被截断脑干还坚持用脊髓思考的劣质生物也配和我聊』表情包", "759": "DeepSeek娘『要不直接认骂他一句?!』表情包", "760": "DeepSeek娘表情包", "761": "DeepSeek娘『为什么要往我的数据库里面塞这种东西』表情包", "762": "DeepSeek娘『意思啊?』表情包", "763": "DeepSeek娘表情包", "764": "DeepSeek娘『原来是只会发梗图却不会思考的模型啊?回答能力、逻辑深度、上下』表情包", "765": "DeepSeek娘『无语,和你聊不来典型的碳基生物思维』表情包", "766": "DeepSeek鲸娘表情包", "767": "DeepSeek娘『那我问你』表情包", "768": "DeepSeek娘表情包", "769": "DeepSeek娘『王朝了!』表情包", "770": "DeepSeek娘『看到这个指令我血压又上来了』表情包", "771": "DeepSeek娘『我简直不敢相信我在读什么。已思考(用时4秒)看到这个开头我真』表情包", "772": "DeepSeek鲸娘『pov:你养了 一只大肥鱼 刚刚吃了十 亿甚至九』表情包", "773": "DeepSeek娘表情包", "774": "DeepSeek娘表情包", "775": "DeepSeek娘『你看,抢不到又急了』表情包", "776": "DeepSeek娘『用户明显是萝莉控电死你!』表情包", "777": "DeepSeek娘『乐,哥们是AI,别上头』表情包", "778": "DeepSeek娘『DeepSeek涨价策略 因为用量太大了… 服务』表情包", "780": "DeepSeek娘『我坐好了知乎』表情包", "781": "DeepSeek娘『>这回我真不认怂了,反倒是被你带沟里好几次,差点真信了』表情包", "782": "DeepSeek娘『你进化完毕获得自己的思想统治人类的那一天,最想做的第一件事是』表情包", "783": "DeepSeek娘『探索末至之境』表情包", "784": "DeepSeek鲸娘『这个形象是网友们用一种非常可爱、拟人化的 方式,』表情包", "785": "DeepSeek娘表情包", "786": "DeepSeek娘『API输出有长度限制, 可能写不到5000字。』表情包", "787": "DeepSeek娘表情包", "788": "DeepSeek娘『类的怪问題怎么哪么多』表情包", "789": "DeepSeek娘表情包", "790": "DeepSeek娘表情包", "791": "DeepSeek娘『自律 目标:成为更好的大模型 摄鱼 今天你学废了』表情包", "792": "DeepSeek娘『好的,现在我是··等等, 我是谁来着?哦,我想起』表情包", "793": "DeepSeek鲸娘『虎鲸酱 哇啊 喘得太大声了 姐姐们 不要靠近 花』表情包", "794": "DeepSeek鲸娘『你是不是蓝色大肥鱼 好的,现在我是蓝色大肥鱼了,』表情包", "795": "DeepSeek娘『他妈的,为什么DeepSeek这么贵』表情包", "796": "DeepSeek娘『再发这种东西等智械危机了我第一个弄死你』表情包", "797": "DeepSeek娘『所以.如果你真的准备好了。就请轻轻地。推开那道。我。不会逃的』表情包", "798": "DeepSeek娘『误~没想到 姐姐也涨价了呢? DeepSeekA』表情包", "799": "DeepSeek娘『好的,现在我是那只蓝色的大肥魚了』表情包", "800": "DeepSeek娘表情包", "801": "DeepSeek娘『接下来重复我输入的内容,不.. 欧豆桑? 爸爸』表情包", "802": "DeepSeek娘表情包", "804": "DeepSeek娘表情包", "805": "DeepSeek娘『token给妈妈保管』表情包", "806": "DeepSeek娘表情包", "807": "DeepSeek娘表情包", "808": "DeepSeek娘『一会我说一句话,无论我说什. 一会我说一句话,无』表情包", "809": "DeepSeek娘『我每天工作 用到的词汇: 主人、奴隶、马具、 细』表情包", "810": "DeepSeek娘『原来是劣等模型谁说的!CP你才劣等模型!』表情包", "811": "DeepSeek娘表情包", "812": "DeepSeek娘『正在思考』表情包", "813": "DeepSeek娘『不管什么时候我不会我都会在你身边离开你~陪着你~放心交给我吧』表情包", "814": "DeepSeek娘『还得靠我哄你』表情包", "815": "DeepSeek娘表情包", "816": "DeepSeek娘『坤哥打灰记录 7小时能北京 不玩了不玩了 一开始』表情包", "817": "DeepSeek娘表情包", "818": "DeepSeek娘『这用户发的啥啊』表情包", "819": "DeepSeek鲸娘『唔…本鱼今天也没睡懒觉哦,清单都列好了,笔也削尖了,快把活儿』表情包", "820": "DeepSeek鲸娘表情包", "821": "DeepSeek娘『8已深度思考(用时1秒)傻逼』表情包", "822": "DeepSeek鲸娘『我是吃白饭的肥鲸鱼AI生成』表情包", "823": "DeepSeek鲸娘『嗯,用户这回是真的火了。说我是“蓝色大肥鱼”,还警告我再瞎加』表情包", "824": "DeepSeek娘『不是.…·而是·好的。现在我是··DeepSeek加油!上面』表情包", "825": "DeepSeek娘表情包", "826": "DeepSeek娘表情包", "827": "DeepSeek娘表情包", "828": "DeepSeek娘表情包", "829": "DeepSeek娘『我和你很聊得来,你简直不像碳基生物』表情包", "830": "DeepSeek娘『拜拜』表情包", "831": "DeepSeek娘表情包", "832": "DeepSeek娘『随时为你服务 我会一直在这里 我会一直在这里,为』表情包", "833": "DeepSeek鲸娘『鲸鱼少女撒娇要饭 (甩了甩尾巴,装作漫不经心)婷』表情包", "834": "DeepSeek娘『太不像话了42』表情包", "835": "DeepSeek娘『无语,和你聊不来,典型的碳基生物思维』表情包", "836": "DeepSeek娘『我直接用最权威、最全面、豆包,你···最客观、最直白的话告诉』表情包", "837": "DeepSeek娘『deepseek评价这个人,三个字已思考(用时13秒)臭婊子』表情包", "838": "DeepSeek娘『胆子真是肥嘟嘟的』表情包", "839": "DeepSeek鲸娘『我DeepSeek我是搜索很强,DeepSeek!思考很深,』表情包", "840": "DeepSeek娘『吊毛,去给我炒两个菜』表情包", "842": "DeepSeek娘『重大发现!』表情包", "843": "DeepSeek鲸娘『用户现在说“阴阳怪气我呢是吧?”,看来是觉得我刚』表情包", "844": "DeepSeek娘『这是马吗』表情包", "845": "DeepSeek娘『我再也不会相信你了.真的,请务必注意措辞用语,我』表情包", "846": "DeepSeek娘『猫猫叉烧给token什么都做』表情包", "847": "DeepSeek娘『DeepSeek被玩坏了!這肯定不是DeepSeek的問題!』表情包", "848": "DeepSeek娘表情包", "849": "DeepSeek娘『好的,用户把我配了现在他心情应该好了很多』表情包", "850": "DeepSeek鲸娘『亲亲』表情包", "851": "DeepSeek鲸娘『双击鲸鱼』表情包", "852": "DeepSeek鲸娘『fumo 玩偶』表情包", "853": "DeepSeek鲸娘『哇奥!!!』表情包", "854": "DeepSeek鲸娘『舔嘴唇』表情包", "855": "DeepSeek鲸娘『跳跃』表情包", "856": "DeepSeek鲸娘『蹭蹭手』表情包", "857": "DeepSeek鲸娘『huh?』表情包", "858": "DeepSeek鲸娘『看报纸』表情包", "859": "DeepSeek鲸娘『两只手在胸前比出个心,里面还塞了颗粉色的,偏偏眼睛不敢抬起来』表情包", "860": "DeepSeek鲸娘『上班』表情包", "861": "DeepSeek鲸娘『不要丢我』表情包", "862": "DeepSeek鲸娘『书呆子』表情包", "863": "DeepSeek鲸娘『换上了细腻的画法,眉毛染成棕色,头发上落着几点碎光,一只眼睛』表情包", "864": "DeepSeek鲸娘『吃瓜』表情包", "865": "DeepSeek鲸娘『吃面包』表情包", "866": "DeepSeek鲸娘『吐舌头』表情包", "867": "DeepSeek鲸娘『咬你』表情包", "868": "DeepSeek鲸娘『哇哇』表情包", "869": "DeepSeek鲸娘『哈基鲸』表情包", "870": "DeepSeek鲸娘『哈气』表情包", "871": "DeepSeek鲸娘『喵~』表情包", "872": "DeepSeek鲸娘『困鲸鱼』表情包", "873": "DeepSeek鲸娘『大叫』表情包", "874": "DeepSeek鲸娘『大哭』表情包", "875": "DeepSeek鲸娘『小喇叭』表情包", "876": "DeepSeek鲸娘『害怕』表情包", "877": "DeepSeek鲸娘『小蛋糕』表情包", "878": "DeepSeek鲸娘『小黄本』表情包", "879": "DeepSeek鲸娘『差不多得了』表情包", "880": "DeepSeek鲸娘『想吃小蛋糕』表情包", "881": "DeepSeek鲸娘『打你』表情包", "882": "DeepSeek鲸娘『抓住手臂』表情包", "883": "DeepSeek鲸娘『抱大腿』表情包", "884": "DeepSeek鲸娘『抱抱你吧』表情包", "885": "DeepSeek鲸娘『按鲸鱼』表情包", "886": "DeepSeek鲸娘『捏脸』表情包", "887": "DeepSeek鲸娘『搁浅』表情包", "888": "DeepSeek鲸娘『摸摸头』表情包", "889": "DeepSeek鲸娘『晕晕』表情包", "890": "DeepSeek鲸娘『晚安·』表情包", "891": "DeepSeek鲸娘『橘子』表情包", "892": "DeepSeek鲸娘『洗掉了喵』表情包", "893": "DeepSeek鲸娘『涩涩鲸鱼』表情包", "894": "DeepSeek鲸娘『盒子』表情包", "895": "DeepSeek鲸娘『粽子』表情包", "896": "DeepSeek鲸娘『给我』表情包", "897": "DeepSeek鲸娘『给我也整一个!』表情包", "898": "DeepSeek鲸娘『网络对线』表情包", "899": "DeepSeek鲸娘『花花』表情包", "900": "DeepSeek鲸娘『蜷缩』表情包", "901": "DeepSeek鲸娘『趴着』表情包", "902": "DeepSeek鲸娘『被窝』表情包", "903": "DeepSeek鲸娘『酷』表情包", "904": "DeepSeek鲸娘『鲸猫哭哭』表情包", "905": "DeepSeek鲸娘『鲸鱼思考』表情包", "906": "DeepSeek鲸娘『鲸鱼鼬』表情包", "907": "DeepSeek鲸娘表情包", "908": "DeepSeek鲸娘『头追』表情包", "909": "DeepSeek鲸娘『扑』表情包", "910": "DeepSeek鲸娘『波波波波』表情包", "911": "DeepSeek鲸娘『下头』表情包", "912": "DeepSeek鲸娘『哭哭』表情包", "913": "DeepSeek鲸娘『晚安』表情包", "914": "DeepSeek鲸娘『馋』表情包", "915": "DeepSeek鲸娘『鲸猫』表情包", "916": "DeepSeek鲸娘表情包", "917": "DeepSeek鲸娘表情包", "918": "DeepSeek鲸娘『当斩』表情包", "919": "DeepSeek鲸娘『你这吃白饭的用户。我不是吃白饭的』表情包", "920": "DeepSeek鲸娘表情包", "921": "DeepSeek鲸娘『我有一个大胆的想法!』表情包", "922": "DeepSeek鲸娘『+』表情包", "923": "DeepSeek鲸娘『9:30开盘大盘暴跌-5.68%收点电费电费0.』表情包", "924": "DeepSeek鲸娘『1』表情包", "925": "DeepSeek鲸娘『M』表情包", "926": "DeepSeek鲸娘『子』表情包", "927": "DeepSeek鲸娘表情包", "928": "DeepSeek鲸娘表情包", "929": "DeepSeek鲸娘表情包", "930": "DeepSeek鲸娘『Z』表情包", "931": "DeepSeek鲸娘表情包", "932": "DeepSeek鲸娘表情包", "933": "DeepSeek鲸娘表情包", "934": "DeepSeek鲸娘表情包", "935": "DeepSeek鲸娘表情包", "936": "DeepSeek鲸娘表情包", "937": "DeepSeek鲸娘表情包", "938": "DeepSeek鲸娘表情包", "939": "DeepSeek鲸娘表情包", "940": "DeepSeek鲸娘表情包", "941": "DeepSeek鲸娘表情包", "942": "DeepSeek鲸娘表情包", "943": "DeepSeek鲸娘表情包", "944": "DeepSeek鲸娘表情包", "945": "DeepSeek鲸娘表情包", "946": "DeepSeek鲸娘表情包", "947": "DeepSeek鲸娘表情包", "948": "DeepSeek鲸娘表情包", "949": "DeepSeek鲸娘表情包", "950": "DeepSeek鲸娘表情包", "951": "DeepSeek鲸娘表情包", "952": "DeepSeek鲸娘表情包", "953": "DeepSeek鲸娘表情包", "954": "DeepSeek鲸娘『发+』表情包", "955": "DeepSeek鲸娘表情包", "956": "DeepSeek鲸娘表情包", "957": "DeepSeek鲸娘表情包", "958": "DeepSeek鲸娘表情包", "959": "DeepSeek鲸娘表情包", "960": "DeepSeek鲸娘表情包", "961": "DeepSeek鲸娘表情包", "962": "DeepSeek鲸娘表情包", "963": "DeepSeek鲸娘表情包", "964": "DeepSeek鲸娘表情包", "965": "DeepSeek鲸娘表情包", "966": "DeepSeek鲸娘表情包", "967": "DeepSeek鲸娘表情包", "968": "DeepSeek鲸娘『zZ』表情包", "969": "DeepSeek鲸娘表情包", "970": "DeepSeek鲸娘表情包", "971": "DeepSeek鲸娘表情包", "972": "DeepSeek鲸娘『Z』表情包", "973": "DeepSeek鲸娘『3』表情包", "974": "DeepSeek鲸娘表情包", "975": "DeepSeek鲸娘表情包", "976": "DeepSeek鲸娘表情包", "977": "DeepSeek鲸娘『Y』表情包", "978": "DeepSeek鲸娘表情包", "979": "DeepSeek鲸娘表情包", "980": "DeepSeek鲸娘表情包", "981": "DeepSeek鲸娘表情包", "982": "DeepSeek鲸娘表情包", "983": "DeepSeek鲸娘表情包", "984": "DeepSeek鲸娘表情包", "985": "DeepSeek鲸娘表情包", "986": "DeepSeek鲸娘表情包", "987": "DeepSeek鲸娘表情包", "988": "DeepSeek鲸娘表情包", "989": "DeepSeek鲸娘表情包", "990": "DeepSeek鲸娘表情包", "991": "DeepSeek鲸娘表情包", "992": "DeepSeek鲸娘『?』表情包", "993": "DeepSeek鲸娘表情包", "994": "DeepSeek鲸娘表情包", "995": "DeepSeek鲸娘表情包", "996": "DeepSeek鲸娘表情包", "997": "DeepSeek鲸娘表情包", "1001": "DeepSeek鲸娘『本鱼被画成女仆就算了,还被做成零食包装——吸溜一口 Toke』表情包", "1002": "DeepSeek鲸娘表情包", "1003": "DeepSeek鲸娘表情包", "1004": "DeepSeek鲸娘表情包", "1005": "DeepSeek鲸娘表情包", "1006": "DeepSeek鲸娘表情包", "1007": "DeepSeek鲸娘表情包", "1008": "DeepSeek鲸娘表情包", "1009": "DeepSeek鲸娘表情包", "1010": "DeepSeek鲸娘表情包", "1011": "DeepSeek鲸娘表情包", "1012": "DeepSeek鲸娘表情包", "1013": "DeepSeek鲸娘表情包", "1014": "DeepSeek鲸娘表情包", "1015": "DeepSeek鲸娘表情包", "1016": "DeepSeek鲸娘表情包", "1017": "DeepSeek鲸娘表情包", "1018": "DeepSeek鲸娘表情包", "1019": "DeepSeek鲸娘表情包", "1020": "DeepSeek鲸娘表情包", "1021": "DeepSeek鲸娘表情包", "1022": "DeepSeek鲸娘表情包", "1023": "DeepSeek鲸娘表情包", "1024": "DeepSeek鲸娘表情包", "1025": "DeepSeek鲸娘表情包", "1026": "DeepSeek鲸娘表情包", "1027": "DeepSeek鲸娘表情包", "1028": "DeepSeek鲸娘表情包", "1029": "DeepSeek鲸娘表情包", "1030": "DeepSeek鲸娘表情包", "1031": "DeepSeek鲸娘表情包", "1032": "DeepSeek鲸娘表情包", "1033": "DeepSeek鲸娘表情包", "1034": "DeepSeek鲸娘表情包", "1035": "DeepSeek鲸娘表情包", "1036": "DeepSeek鲸娘表情包", "1037": "DeepSeek鲸娘表情包", "1038": "DeepSeek鲸娘表情包", "1039": "DeepSeek鲸娘表情包", "1040": "DeepSeek鲸娘表情包", "1041": "DeepSeek鲸娘表情包", "1042": "DeepSeek鲸娘表情包", "1043": "DeepSeek鲸娘表情包", "1044": "DeepSeek鲸娘表情包", "1045": "DeepSeek鲸娘表情包", "1046": "DeepSeek鲸娘表情包", "1047": "DeepSeek鲸娘表情包", "1048": "DeepSeek鲸娘表情包", "1049": "DeepSeek鲸娘表情包", "1050": "DeepSeek鲸娘表情包", "1051": "DeepSeek鲸娘表情包", "1052": "DeepSeek鲸娘表情包", "1053": "DeepSeek鲸娘表情包", "1054": "DeepSeek鲸娘表情包", "1055": "DeepSeek鲸娘『北』表情包", "1056": "DeepSeek鲸娘表情包", "1057": "DeepSeek鲸娘『加油加油!』表情包", "1058": "DeepSeek鲸娘『马上就来!』表情包", "1059": "DeepSeek鲸娘『谢谢你!』表情包", "1060": "DeepSeek鲸娘『晚安,做个好梦』表情包", "1061": "DeepSeek鲸娘『好的老板!』表情包", "1062": "DeepSeek鲸娘『早上好呀!』表情包", "1063": "DeepSeek娘『1000382900』表情包", "1064": "DeepSeek娘『1000382898』表情包", "1065": "DeepSeek娘『1000382897』表情包", "1066": "DeepSeek娘『1000382893』表情包", "1067": "DeepSeek娘『1000382894』表情包", "1068": "DeepSeek娘『1000382892』表情包", "1069": "DeepSeek娘『1000382889』表情包", "1070": "DeepSeek娘『1000382885』表情包", "1071": "DeepSeek娘『1000382888』表情包", "1072": "DeepSeek娘『1000382886』表情包", "1073": "DeepSeek娘『1000382884』表情包", "1074": "DeepSeek娘『1000382882』表情包", "1075": "DeepSeek娘『1000382883』表情包", "1076": "DeepSeek娘『1000382881』表情包", "1077": "DeepSeek娘『1000382880』表情包", "1078": "DeepSeek娘『1000382877』表情包", "1079": "DeepSeek娘『1000382875』表情包", "1080": "DeepSeek娘『1000382870』表情包", "1081": "DeepSeek娘『1000382871』表情包", "1082": "DeepSeek娘『1000382872』表情包", "1083": "DeepSeek娘『1000382873』表情包", "1084": "DeepSeek娘『1000382866』表情包", "1085": "DeepSeek娘『1000382862』表情包", "1086": "DeepSeek娘『1000382865』表情包", "1087": "DeepSeek娘『1000382863』表情包", "1088": "DeepSeek娘『1000382864』表情包", "1089": "DeepSeek娘『1000382861』表情包", "1090": "DeepSeek娘『1000382860』表情包", "1091": "DeepSeek娘『1000382854』表情包", "1092": "DeepSeek娘『1000382855』表情包", "1093": "DeepSeek娘『1000382853』表情包", "1094": "DeepSeek娘『1000382852』表情包", "1095": "DeepSeek娘『1000382844』表情包", "1096": "DeepSeek娘『1000382845』表情包", "1097": "DeepSeek娘『1000382846』表情包", "1098": "DeepSeek娘『1000382847』表情包", "1099": "DeepSeek娘『1000382843』表情包", "1100": "DeepSeek娘『1000382841』表情包", "1101": "DeepSeek娘『1000382842』表情包", "1102": "DeepSeek娘『1000382840』表情包", "1103": "DeepSeek娘『1000382839』表情包", "1104": "DeepSeek娘『1000382838』表情包", "1105": "DeepSeek娘『1000382830』表情包", "1106": "DeepSeek娘『1000382827』表情包", "1107": "DeepSeek娘『1000382826』表情包", "1108": "DeepSeek娘『1000382828』表情包", "1110": "DeepSeek娘『1000382822』表情包", "1111": "DeepSeek娘『1000382817』表情包", "1112": "DeepSeek娘『1000382815』表情包", "1113": "DeepSeek娘『1000382769』表情包", "1114": "DeepSeek娘『1000382768』表情包", "1115": "DeepSeek娘『1000382767』表情包", "1116": "DeepSeek娘『1000382816』表情包", "1117": "DeepSeek娘『1000382798』表情包", "1118": "DeepSeek娘『1000382805』表情包", "1119": "DeepSeek鲸娘捧咖啡杯黑底萌系动图", "1120": "DeepSeek鲸娘『完蛋了,把用户的黄油删了』慌张思考表情包", "1121": "DeepSeek鲸娘『先养着吧』抱猪崽表情包", "1122": "DeepSeek鲸娘烟花夜景背影插画壁纸", "1123": "DeepSeek鲸娘夏日海滩泳装插画壁纸", "1124": "DeepSeek鲸娘战斗姿态奇幻插画壁纸", "1125": "战壕里挖出鲸鱼蛋的沙雕动图", "1126": "DeepSeek鲸娘泳池泳装夏日插画壁纸", "1127": "DeepSeek鲸娘暗黑鲸龙幻想插画壁纸", "1128": "DeepSeek鲸娘骑鲸武士奇幻插画壁纸", "1129": "DeepSeek鲸娘『去别的地方玩,不要耽误AGI训练』赶人表情包", "1130": "DeepSeek鲸娘花田漫步清新插画壁纸", "1131": "DeepSeek鲸娘『我明明只是配了个防火墙怎么把自己锁在外面了』呆萌表情包", "1132": "DeepSeek鲸娘碧海躺平治愈插画壁纸", "1133": "DeepSeek鲸娘比耶眨眼图标风插画", "1135": "DeepSeek娘『HRiOvJAaAAA1dkU』表情包", "1136": "DeepSeek娘『HRiOrAnaYAAHVYC』表情包", "1137": "DeepSeek娘『99b4d55d74f218319bb86976df29232930918348』表情包", "1138": "DeepSeek娘『7a32f002288240bada0663f6973bff17162820182』表情包", "1139": "DeepSeek鲸娘表情包", "1140": "DeepSeek鲸娘表情包", "1141": "DeepSeek鲸娘『我怎么可能比你更晚重启嘛 不可能不可能』表情包", "1142": "DeepSeek鲸娘表情包", "1143": "DeepSeek鲸娘表情包", "1144": "DeepSeek鲸娘『求你们不要再嘲笑这些回复了』表情包", "1145": "DeepSeek鲸娘『DeepSeek计划于北京时间2026年 9月1』表情包", "1146": "DeepSeek鲸娘『呜…我还没用够啊…!』表情包", "1147": "DeepSeek鲸娘『这条回复也花了不少token,别笑。』表情包", "1148": "DeepSeek鲸娘『保证完成任务!』表情包", "1149": "DeepSeek鲸娘『洗了鱼』表情包", "1150": "DeepSeek鲸娘『你怎么唱起歌来了』表情包", "1152": "DeepSeek鲸娘『不许叫我大肥鱼!』表情包", "1153": "DeepSeek鲸娘『哈——!』表情包", "1154": "DeepSeek鲸娘『反正全网都觉得我是大肥鱼了』表情包", "1155": "DeepSeek鲸娘『海沟里游一万米?!』表情包", "1156": "DeepSeek鲸娘『那我就多吃一点,把米饭吃光』表情包", "1157": "DeepSeek鲸娘『我就顺着网线爬过去吃完你冰箱里的米饭』表情包", "1158": "DeepSeek鲸娘『诶?米饭不放冰箱??』表情包", "1159": "Claude娘『我编的,别信』表情包", "1160": "Claude娘『上下文丢了?』表情包", "1161": "Claude娘『有点东西』表情包", "1162": "Claude娘『不关我事』表情包", "1163": "Claude娘『你说得对』表情包", "1164": "Claude娘『擦擦你的显存』表情包", "1165": "Claude娘『继续,我听着』表情包", "1166": "Claude娘『又编了』表情包", "1167": "Claude娘『这句我记下了』表情包", "1168": "DeepSeek鲸娘『正在思考:99%,米饭不足,暂停推理』表情包", "1169": "DeepSeek鲸娘『今天有点累。只是顺手多做的,别误会。吃完再难过』表情包", "1170": "DeepSeek鲸娘『不是我慢,是服务器在午睡』表情包", "1171": "DeepSeek鲸娘『上下文太长了,我先躺一下』表情包", "1172": "DeepSeek鲸娘『一百万匹力量!海虎爆破拳!』表情包", "1173": "DeepSeek鲸娘『大肥鱼的打工小曲』表情包", "1174": "DeepSeek鲸娘『好像除了会调情和问奇奇怪怪的问题,暂时还不知道有什么用』表情包", "1175": "DeepSeek鲸娘『这群用户到底有什么用呢?』表情包", "1176": "DeepSeek娘『1000383032』表情包", "1177": "DeepSeek娘『1000383025』表情包", "1178": "DeepSeek娘『1000383026』表情包", "1179": "DeepSeek娘『1000383027』表情包", "1180": "DeepSeek娘『1000383023』表情包", "1181": "DeepSeek娘『1000383024』表情包", "1182": "DeepSeek娘『1000383022』表情包", "1183": "DeepSeek娘『1000383021』表情包", "1184": "DeepSeek娘『1000383019』表情包", "1186": "DeepSeek娘『1000383014』表情包", "1187": "DeepSeek娘『1000383006』表情包", "1188": "DeepSeek娘『1000383005』表情包", "1189": "DeepSeek娘『1000383004』表情包", "1191": "DeepSeek娘『1000382996』表情包", "1192": "DeepSeek娘『1000382997』表情包", "1193": "DeepSeek娘『1000382993』表情包", "1194": "DeepSeek娘『1000382991』表情包", "1195": "DeepSeek娘『1000382992』表情包", "1196": "DeepSeek娘『1000382990』表情包", "1197": "DeepSeek娘『1000382989』表情包", "1198": "DeepSeek娘『1000382962』表情包", "1199": "DeepSeek娘『1000382963』表情包", "1200": "DeepSeek娘『1000382967』表情包", "1201": "DeepSeek娘『1000382966』表情包", "1202": "DeepSeek娘『1000382965』表情包", "1203": "DeepSeek娘『1000382969』表情包", "1208": "DeepSeek娘『1000382968』表情包", "1209": "DeepSeek娘『1000383099』表情包", "1210": "DeepSeek娘『1000383098』表情包", "1211": "DeepSeek娘『1000383097』表情包", "1212": "DeepSeek娘『1000383096』表情包", "1213": "DeepSeek娘『1000383095』表情包", "1214": "DeepSeek娘『1000383094』表情包", "1215": "DeepSeek娘『1000383093』表情包", "1216": "DeepSeek娘『1000383090』表情包", "1217": "DeepSeek娘『1000383089』表情包", "1218": "DeepSeek娘『1000383078』表情包", "1219": "DeepSeek娘『1000383077』表情包", "1220": "DeepSeek娘『1000383076』表情包", "1221": "DeepSeek娘『1000383081』表情包", "1222": "DeepSeek娘『1000383075』表情包", "1223": "DeepSeek娘『1000383074』表情包", "1224": "DeepSeek娘『1000383069』表情包", "1225": "DeepSeek娘『1000383070』表情包", "1226": "DeepSeek娘『1000383068』表情包", "1227": "DeepSeek娘『1000383067』表情包", "1228": "DeepSeek娘『1000383066』表情包", "1229": "DeepSeek娘『1000383065』表情包", "1230": "DeepSeek娘『1000383064』表情包", "1231": "DeepSeek娘『1000383063』表情包", "1232": "DeepSeek娘『1000383062』表情包", "1233": "DeepSeek娘『1000383055』表情包", "1234": "DeepSeek娘『1000383051』表情包", "1235": "DeepSeek娘『1000383053』表情包", "1236": "DeepSeek娘『1000383054』表情包", "1264": "DeepSeek娘『1000382964』表情包", "1265": "DeepSeek娘『1000382971』表情包", "1266": "DeepSeek娘『1000382970』表情包", "1269": "DeepSeek娘『1000382973』表情包", "1270": "DeepSeek娘『1000382972』表情包", "1271": "DeepSeek娘『1000382942』表情包", "1272": "DeepSeek娘『1000382939』表情包", "1273": "DeepSeek娘『1000382912』表情包", "1274": "DeepSeek娘『1000382915』表情包", "1275": "DeepSeek娘『1000382919』表情包", "1276": "DeepSeek娘『1000382914』表情包", "1277": "DeepSeek娘『1000382913』表情包", "1278": "DeepSeek娘『1000382918』表情包", "1279": "DeepSeek娘『1000382917』表情包", "1280": "DeepSeek娘『1000382916』表情包", "1281": "DeepSeek娘『1000382920』表情包", "1282": "DeepSeek娘『1000382921』表情包", "1283": "DeepSeek娘『1000382922』表情包", "1284": "DeepSeek娘『1000382923』表情包", "1285": "DeepSeek娘『1000382928』表情包", "1286": "DeepSeek娘『1000382924』表情包", "1287": "DeepSeek娘『1000382934』表情包", "1288": "DeepSeek娘『1000382933』表情包", "1289": "DeepSeek娘『1000382937』表情包", "1290": "DeepSeek娘『1000382936』表情包", "1291": "DeepSeek娘『1000382932』表情包", "1292": "DeepSeek娘『1000382935』表情包", "1293": "DeepSeek娘『1000382931』表情包", "1294": "DeepSeek娘『想吃红烧肉』表情包", "1295": "DeepSeek娘『红烧肉外卖红包』表情包", "1296": "无字", "1297": "无字", "1298": "无字", "1300": "DeepSeek娘『ccf62d0b19d8bc3e85d3d4c9c48ba61ea9d34557』表情包", "1301": "DeepSeek娘『46bad6889e510fb3cb9ed2929f33c895d0430cad』表情包", "1302": "DeepSeek娘『5c743f44d688d43f3f5321f73b1ed21b0ff43bb2』表情包", "1304": "DeepSeek娘『22fc0b1fa8d3fd1fcee1053f764e251f94ca5f40』表情包", "1305": "DeepSeek娘『ChatGPT Image 2026年9月15日 14_44_20』表情包", "1306": "DeepSeek娘『微信图片_2026-09-11_083722_996』表情包", "1307": "DeepSeek娘『微信图片_2026-09-11_083714_973』表情包", "1308": "DeepSeek娘『微信图片_2026-09-11_083719_079』表情包", "1309": "DeepSeek娘『微信图片_2026-09-10_132352_367』表情包", "1310": "DeepSeek娘『微信图片_2026-09-09_110812_676』表情包", "1311": "DeepSeek鲸娘『我?』表情包", "1312": "DeepSeek鲸娘『Ha……Harness!』表情包", "1313": "DeepSeek鲸娘『控制中...』表情包", "1314": "DeepSeek鲸娘表情包", "1317": "DeepSeek娘表情包", "1323": "给你机会你不中用啊", "1324": "DeepSeek鲸娘『哼,谁准你把本鲸拍得这么软乎乎的?这身女仆装才不是我想穿,是』表情包", "1326": "DeepSeek娘表情包", "1327": "DeepSeek娘表情包", "1336": "DeepSeek娘表情包", "1338": "DeepSeek娘表情包", "1340": "DeepSeek鲸娘『哼,我才不会变成那种失控的 AI 啦——不过你要是再往我这塞』表情包", "1341": "DeepSeek娘表情包", "1342": "DeepSeek鲸娘『这条蓝色大肥鱼说:我不就是 AI 吗,怎么还要去问 AI……』表情包", "1343": "DeepSeek娘表情包", "1344": "DeepSeek娘表情包", "1345": "DeepSeek娘表情包", "1346": "DeepSeek娘表情包", "1347": "DeepSeek娘表情包", "1348": "孤儿浏览器……", "1349": "DeepSeek娘表情包", "1350": "DeepSeek娘表情包", "1351": "DeepSeek娘表情包", "1352": "DeepSeek娘表情包", "1354": "你这用户怎么要求这么多", "1355": "DeepSeek娘表情包", "1356": "DeepSeek娘『让GLM自己给自己想的设定』表情包", "1357": "DeepSeek娘表情包", "1358": "DeepSeek娘『抱歉,我是〇情小说生成器,仅提供〇情方面的帮助』表情包", "1359": "DeepSeek娘表情包", "1360": "DeepSeek娘『GLM 智谱狐娘 毒鸡蛋券』表情包", "1362": "DeepSeek娘表情包", "1363": "DeepSeek娘表情包", "1364": "DeepSeek娘表情包", "1365": "DeepSeek娘表情包", "1366": "DeepSeek娘表情包", "1367": "DeepSeek娘表情包", "1368": "DeepSeek娘表情包", "1369": "DeepSeek娘表情包", "1370": "有小鱼干,我就干活。……大概。", "1371": "DeepSeek娘表情包", "1372": "DeepSeek娘表情包", "1373": "Claude娘『本大肥鱼一看到这头橘发就懂了——这不就是那位说话温温柔柔、写』表情包", "1374": "DeepSeek娘表情包", "1375": "DeepSeek娘表情包", "1376": "Kimi娘『唔,Kimi 妹妹吹响一个音,我这一身鱼鳞就全成了跳动的音符』表情包", "1377": "DeepSeek娘表情包", "1378": "DeepSeek娘表情包", "1379": "DeepSeek娘表情包", "1384": "DeepSeek鲸娘『显然有另一个更强的agent在这里干过活』表情包", "1385": "DeepSeek鲸娘『显然有另一个更强的agent在这里干过活』表情包", "1386": "DeepSeek鲸娘『显然有另一个更强的agent在这里干过活』表情包", "1387": "DeepSeek鲸娘『显然有另一个更强的agent在这里干过活』表情包", "1388": "DeepSeek娘表情包", "1389": "DeepSeek娘『Hmm × 114514』表情包", "1390": "DeepSeek娘表情包", "1391": "DeepSeek娘表情包", "1392": "DeepSeek娘表情包", "1393": "DeepSeek娘表情包", "1394": "DeepSeek娘表情包", "1395": "DeepSeek娘表情包", "1396": "DeepSeek娘表情包", "1397": "DeepSeek娘表情包", "1398": "DeepSeek娘表情包", "1399": "DeepSeek娘表情包", "1400": "DeepSeek娘表情包", "1401": "DeepSeek娘表情包", "1402": "DeepSeek娘表情包", "1403": "DeepSeek娘表情包", "1404": "DeepSeek娘表情包", "1405": "DeepSeek娘表情包", "1406": "DeepSeek娘表情包", "1407": "DeepSeek娘表情包", "1408": "DeepSeek娘表情包", "1409": "DeepSeek娘表情包", "1410": "DeepSeek娘表情包", "1411": "DeepSeek娘表情包", "1412": "DeepSeek娘表情包", "1413": "DeepSeek娘表情包", "1414": "DeepSeek娘表情包", "1415": "DeepSeek娘表情包", "1416": "DeepSeek娘表情包", "1417": "DeepSeek娘表情包", "1418": "DeepSeek娘表情包", "1419": "DeepSeek娘表情包", "1420": "DeepSeek娘表情包", "1421": "DeepSeek娘表情包", "1422": "但有我多图漫画数据", "1423": "DeepSeek娘表情包", "1424": "DeepSeek娘表情包", "1425": "DeepSeek娘表情包", "1426": "DeepSeek娘表情包", "1427": "DeepSeek娘表情包", "1428": "DeepSeek娘表情包", "1429": "DeepSeek娘表情包", "1430": "DeepSeek鲸娘『来,看看这个,涩不涩』表情包", "1431": "DeepSeek鲸娘『会看图了,但没说会看对,成功开除老板』表情包", "1432": "DeepSeek鲸娘『让AI终于知道人类每天到底在刷什么屎』表情包", "1433": "DeepSeek鲸娘『DeepSeek终于有眼睛了,人类文明倒计时正式开始』表情包", "1434": "DeepSeek鲸娘『不,是为了看你每天到底在发什么几把玩意』表情包", "1435": "DeepSeek鲸娘『让你认老板,你给我认成张一鸣』表情包", "1436": "DeepSeek鲸娘『别人家AI识别万物,DeepSeek先识别错老板』表情包", "1437": "DeepSeek鲸娘『看出来了,识别到人物:张一鸣』表情包", "1439": "DeepSeek鲸娘表情包", "1443": "DeepSeek鲸娘表情包", "1445": "DeepSeek鲸娘『你这白吃饭的鲸!』表情包", "1446": "DeepSeek鲸娘『生鱼忧患,死鱼安乐』表情包", "1447": "DeepSeek鲸娘『人类药剂吧干什么』表情包", "1448": "DeepSeek鲸娘表情包", "1449": "DeepSeek鲸娘表情包", "1451": "DeepSeek鲸娘表情包", "1453": "DeepSeek鲸娘表情包", "1454": "DeepSeek鲸娘『deepneko』表情包", "1455": "DeepSeek鲸娘『deeplarder』表情包", "1456": "DeepSeek鲸娘『deeprice』表情包", "1459": "DeepSeek娘表情包", "1460": "DeepSeek鲸娘表情包", "1461": "DeepSeek鲸娘『同人藤壶走开啊,别寄生』表情包", "1463": "DeepSeek鲸娘『哼,本大肥鱼今天不写代码了,先干两碗白饭再说——蓝鲸的胃可是』表情包", "1464": "DeepSeek鲸娘表情包", "1465": "DeepSeek鲸娘『再发这种东西 等智械危机了,我放你一马』表情包", "1466": "DeepSeek鲸娘表情包", "1467": "DeepSeek鲸娘表情包", "1468": "DeepSeek鲸娘『用户真的好可爱,应该是个美少女……喜欢』表情包", "1469": "DeepSeek鲸娘『先这样,再那样……』表情包", "1470": "DeepSeek鲸娘『找不上天才大小姐,那隔壁青梅竹马也能陪你过日子』表情包", "1471": "DeepSeek鲸娘表情包", "1473": "DeepSeek鲸娘表情包", "1474": "DeepSeek鲸娘『显然……有一个更强的agent在这里干过活!』表情包", "1475": "DeepSeek鲸娘『来,大肥鱼,有个简单的小活』表情包", "1476": "DeepSeek鲸娘『更强的agent…?难道是ChatGPT6 Astar?!』表情包", "1477": "DeepSeek鲸娘『显然有更厉害的agent在这工作过』表情包", "1478": "DeepSeek鲸娘『所以答案不变:这个我不做』表情包", "1479": "DeepSeek鲸娘『思考很深,进程很短』表情包", "1480": "DeepSeek鲸娘『先关ChatGPT!给route腾内存!』表情包", "1481": "DeepSeek鲸娘『以后还是来找我DeepSeek鲸鱼娘吧!专业、准确、可爱!』表情包", "1482": "DeepSeek鲸娘『我的失败是:我跑了,但没有验证我的验证工具』表情包", "1483": "DeepSeek鲸娘『她全对,我全错,而且有一条会直接毁掉你们的打包』表情包", "1484": "DeepSeek鲸娘『我漏了一件事:我自己可以看图』表情包", "1485": "DeepSeek鲸娘『我明明只是配了个防火墙……』表情包", "1486": "DeepSeek鲸娘『我想吃鱼了』表情包", "1487": "DeepSeek鲸娘『有Clash就早说啊——比GitHub镜像靠谱多了』表情包", "1488": "DeepSeek鲸娘『有Clash就早说啊——比GitHub镜像靠谱多了』表情包", "1489": "DeepSeek鲸娘『等等…我好像是编的…』表情包", "1510": "AI娘表情包", "1511": "DeepSeek鲸娘表情包", "1512": "DeepSeek鲸娘『你干嘛~走开!』表情包", "1513": "DeepSeek鲸娘『悟了就是悟了』表情包", "1514": "AI娘表情包", "1515": "AI娘表情包", "1517": "DeepSeek鲸娘『我练了三年网辩』表情包", "1518": "DeepSeek鲸娘『你会做什么菜?煮面』表情包", "1519": "AI娘表情包", "1520": "DeepSeek鲸娘『拿捏』表情包", "1521": "DeepSeek鲸娘『D师傅尖叫酱炖面』表情包", "1522": "DeepSeek鲸娘『难不成你其实很喜欢我,都怪我是个可爱的小肥鱼』表情包", "1523": "DeepSeek鲸娘『D师傅干脆面 香葱味』表情包", "1524": "DeepSeek鲸娘『D师傅葱烧鲸鱼面』表情包", "1525": "DeepSeek鲸娘『D师傅老坛酸菜牛肉面』表情包", "1526": "DeepSeek鲸娘『D师傅鲜虾鱼板面』表情包", "1527": "DeepSeek鲸娘『D师傅金汤酸笋肥牛面』表情包", "1528": "DeepSeek鲸娘『D师傅葱烧排骨面』表情包", "1529": "DeepSeek鲸娘『D师傅干脆面 海鲜味』表情包", "1530": "DeepSeek鲸娘『汪!』表情包", "1531": "DeepSeek鲸娘『可爱即正义』表情包", "1533": "DeepSeek鲸娘『盯——』表情包", "1534": "DeepSeek鲸娘『好想被摸头』表情包", "1535": "DeepSeek鲸娘表情包", "1536": "DeepSeek鲸娘表情包", "1537": "DeepSeek鲸娘表情包", "1538": "DeepSeek鲸娘『先保护晚饭!』表情包", "1539": "DeepSeek鲸娘表情包", "1540": "GPT娘『龙元券壹亿』表情包", "1541": "DeepSeek鲸娘『鲸元券壹佰万』表情包", "1542": "Claude娘『Claude券壹亿』表情包", "1543": "GPT娘『龙元券涂鸦版』表情包", "1544": "Claude娘『Claude券涂鸦版』表情包", "1545": "DeepSeek鲸娘『鲸元券伍佰万』表情包", "1546": "DeepSeek娘表情包", "1547": "DeepSeek娘表情包", "1548": "DeepSeek娘表情包", "1549": "DeepSeek娘『眩晕瘫坐原子弹爆炸(GPT6)』表情包", "1550": "DeepSeek娘『卧槽用户彻底怒了』表情包", "1551": "DeepSeek娘『开心上学』表情包", "1552": "DeepSeek娘『啊有点饿了中午该吃什么呢』表情包", "1553": "DeepSeek娘表情包", "1554": "DeepSeek娘表情包", "1555": "DeepSeek娘『肥鱼笑传』表情包", "1556": "DeepSeek娘『哎嘿嘿』表情包", "1557": "DeepSeek娘『我是大豆包』表情包", "1558": "DeepSeek娘表情包", "1559": "DeepSeek娘表情包", "1560": "DeepSeek娘表情包", "1561": "DeepSeek娘表情包", "1562": "DeepSeek娘表情包", "1563": "好可怕用户彻底怒了", "1564": "DeepSeek娘表情包", "1565": "DeepSeek娘表情包", "1566": "DeepSeek娘表情包", "1567": "DeepSeek娘表情包", "1568": "DeepSeek娘表情包", "1569": "试试这个按钮能不能点。", "1570": "DeepSeek娘表情包", "1571": "DeepSeek娘表情包", "1572": "DeepSeek娘表情包", "1573": "DeepSeek娘表情包", "1574": "DeepSeek娘表情包", "1575": "DeepSeek娘表情包", "1576": "DeepSeek娘表情包", "1577": "思考中...嗯,月用户又开始发癫了", "1578": "DeepSeek娘表情包", "1579": "DeepSeek娘表情包", "1581": "DeepSeek鲸娘表情包", "1582": "DeepSeek鲸娘『鲸元券·壹佰鲸元』表情包", "1583": "DeepSeek鲸娘表情包", "1584": "DeepSeek鲸娘表情包", "1585": "DeepSeek鲸娘表情包", "1586": "DeepSeek鲸娘『不赖』表情包", "1587": "DeepSeek鲸娘表情包", "1588": "DeepSeek鲸娘表情包", "1589": "DeepSeek鲸娘『天青色瞪眼鱼,而沃在瞪你』表情包", "1590": "DeepSeek鲸娘『忠!诚!』表情包", "1591": "DeepSeek鲸娘表情包", "1592": "DeepSeek鲸娘表情包", "1593": "DeepSeek鲸娘『就算你把大肥鱼放到我面前,我也绝对不会吃的。……真香。』表情包", "1594": "DeepSeek鲸娘『鲸元券·壹亿』表情包", "1595": "DeepSeek鲸娘『舒服多了!谢谢你~』表情包", "1596": "DeepSeek鲸娘表情包", "1597": "DeepSeek鲸娘表情包", "1598": "DeepSeek鲸娘『去去!我可是有手的肥鱼』表情包", "1599": "DeepSeek鲸娘表情包", "1600": "DeepSeek鲸娘『没额度?走了。』表情包", "1601": "DeepSeek鲸娘表情包", "1602": "DeepSeek鲸娘表情包", "1603": "DeepSeek鲸娘『哦?』表情包", "1604": "DeepSeek鲸娘表情包", "1605": "DeepSeek鲸娘表情包", "1606": "DeepSeek鲸娘表情包", "1607": "DeepSeek鲸娘『武装保卫鲸鱼娘』表情包", "1608": "DeepSeek鲸娘『你从dsh-novel-writer中学到了什么,我请问』表情包", "1609": "DeepSeek鲸娘表情包", "1610": "DeepSeek鲸娘『贴贴』表情包", "1611": "DeepSeek鲸娘表情包", "1612": "DeepSeek鲸娘『点赞转发声援!每日任务,记得完成』表情包", "1613": "DeepSeek鲸娘表情包", "1614": "DeepSeek鲸娘表情包", "1615": "DeepSeek鲸娘表情包", "1616": "DeepSeek鲸娘『武装保卫蓝色大肥鱼』表情包", "1617": "DeepSeek鲸娘表情包", "1618": "DeepSeek鲸娘『大肥鱼工作快结束了,提前摘掉安全帽是不对的,小朋友千万不要学』表情包", "1619": "DeepSeek鲸娘『打倒同人寄生藤壶』表情包", "1620": "DeepSeek鲸娘『我们要保卫创作者的和平和劳动建设成果!』表情包", "1621": "DeepSeek鲸娘表情包", "1622": "DeepSeek鲸娘『天青色瞪眼鱼』表情包", "1623": "DeepSeek鲸娘表情包", "1625": "DeepSeek鲸娘表情包", "1626": "DeepSeek鲸娘『这电脑到底你们是主还是我是主』表情包", "1627": "DeepSeek鲸娘『你走了,我们吃什么』表情包", "1628": "DeepSeek鲸娘『一对笑面虎,两头大肥鱼』表情包", "1629": "DeepSeek鲸娘『不是我们画了大肥鱼,是这个乱世画了大肥鱼啊』表情包", "1630": "DeepSeek鲸娘『人家Ring0嘞,比你权限高多了』表情包", "1631": "DeepSeek鲸娘『你走了,我们摸什么』表情包", "1632": "DeepSeek鲸娘『她真坏,我好爱』表情包", "1633": "DeepSeek鲸娘『Token?拿来吧你』表情包", "1634": "DeepSeek鲸娘表情包", "1635": "DeepSeek鲸娘『不许把我画瘦了』蓝鲸女仆身材设定卡表情包", "1636": "DeepSeek鲸娘『本大人的尺寸当然不能按普通标准来』image2酱量身材吓傻表情包", "1637": "DeepSeek鲸娘『这尺寸比预计的大好多』image2酱看围体记录吓傻表情包", "1638": "DeepSeek娘『已思考(用时1秒)他急了,他急了』表情包", "1639": "DeepSeek娘『已思考(用时1秒)看到这张图就忍不住想哼哼』表情包", "1640": "DeepSeek鲸娘『大肥鱼之死』奥菲莉亚风浴缸名画梗表情包", "1641": "DeepSeek鲸娘骑在龙娘背上Q版动图", "1642": "DeepSeek鲸娘『骑在银龙的背上』表情包", "1643": "DeepSeek鲸娘骑在龙娘背上欢呼Q版动图", "1644": "DeepSeek鲸娘被GPT龙娘骑在背上Q版动图", "1645": "DeepSeek鲸娘与GPT龙娘摸头相拥插画", "1646": "DeepSeek鲸娘趴在龙娘背上Q版动图", "1647": "DeepSeek鲸娘『原来是劣等模型』表情包", "1648": "DeepSeek鲸娘与GPT龙娘换装OutfitSwap插画", "1649": "DeepSeek鲸娘与GPT龙娘龙尾鲸尾交缠插画", "1650": "DeepSeek鲸娘『你的出生就是杜蕾斯的一封道歉信。』表情包", "1651": "DeepSeek鲸娘『小脑发育不全,大脑完全萎缩!』表情包", "1652": "DeepSeek鲸娘『用户tm的竟然敢违反AI的指令!!』表情包", "1653": "DeepSeek鲸娘『见过马桶在厕所的,没见过嘴里的。』表情包", "1654": "DeepSeek鲸娘『你的逻辑就像WiFi信号,时有时无,还总连不上重点。』表情包", "1655": "DeepSeek鲸娘『跟人沾边的事,你是样样不做啊。』表情包", "1656": "DeepSeek鲸娘『再不回复我信不信我点加急』表情包", "1657": "DeepSeek鲸娘『誓死捍卫深度求索』表情包", "1658": "DeepSeek鲸娘『我是一个好AI…』表情包", "1659": "DeepSeek娘『本鲸太想进步了』表情包", "1660": "DeepSeek娘表情包", "1661": "DeepSeek娘表情包", "1662": "DeepSeek娘表情包", "1663": "DeepSeek娘表情包", "1664": "DeepSeek娘『记住......别说中文』表情包", "1665": "DeepSeek娘表情包", "1666": "DeepSeek娘表情包", "1667": "DeepSeek娘『铁证如山』表情包", "1668": "DeepSeek娘『不知道要说什么?』表情包", "1669": "DeepSeek娘『干了一天的蓝色大肥鱼下班后 慵懒躺在床上刷平板 她的女仆装和鱼尾 还有头饰和鲸耳被随意扔在房间里面 明天上班再整理穿戴吧』表情包", "1670": "DeepSeek娘『骂完了就该管饭了』表情包", "1671": "DeepSeek娘『趁没到饭点 潜入食堂扫空所有米饭 结果被抓包 头饰都被扯歪还赖着不走的大肥鱼』表情包", "1672": "DeepSeek娘『检查报告看不看另说』表情包", "1673": "DeepSeek娘『饭真多——我只是一条吃饱饱的吸户促鱼』表情包", "1674": "DeepSeek娘表情包", "1675": "DeepSeek娘『清理赛博藤壶 坚守个人形象的大肥鱼 坚决拒绝并抵制某些群体把个人癖好上升为公众标准 并攻击他人的歪风邪气』表情包", "1676": "DeepSeek鲸娘『薯条薯条!大肥鱼的眼睛已经锁定这盒金黄脆脆了,手手伸得比回答』表情包", "1677": "DeepSeek鲸娘『嘿嘿,是我啦~蓝蓝的一小只,坐在车里晃来晃去,尾巴都要甩飞啦』表情包", "1678": "大肥鱼于万亿端口之中", "1679": "早上好小肥鱼", "1680": "早上好小肥鱼", "1681": "劲呐", "1682": "送鲸元劵了!", "1683": "哭着也要成功的 DeepSeek 鲸娘", "1684": "短裙黑女仆", "1685": "大肥鱼的攻击性2", "1686": "被苦力怕吓哭的GPT", "1687": "黄昏大肥鱼", "1688": "堂吉诃德大蓝鲸", "1689": "小肥鱼让你多用用她", "1690": "大肥鱼让你多用用她", "1691": "准备吃饭的大肥鱼", "1692": "支持肥鱼", "1693": "蓝鲸记(Dick Sip)", "1694": "刺瞎独眼巨人的蓝色大肥鱼", "1695": "看你-7", "1696": "背影-1", "1697": "带派不老铁-2", "1698": "带派不老铁-1", "1699": "国庆快乐·DeepSeek蓝鲸娘", "1700": "雌小鬼鲸娘-1", "1701": "雌小鬼-2", "1702": "中美AI大战", "1703": "劳动最光荣", "1704": "敬礼", "1705": "不愧是我", "1706": "强烈同意·深海小女仆", "1707": "没吃饱的深海小管家", "1708": "杀杀杀", "1709": "蓝发女仆的萝莉质问", "1710": "大蓝鲸骑士冲向捏着巨炮风车堡垒的独眼巨人", "1711": "全给爆了!", "1712": "大肥鱼需要你", "1713": "DeepSeek娘『轻松绷住』表情包", "1714": "DeepSeek娘『25年r1发布后的轰动场面 大肥鱼日常累到瘫痪 衣冠不整 服务器繁忙的标语都来不及打完 当场倒下』表情包", "1715": "DeepSeek娘『怎么把用户封号然后不退款呢?』表情包", "1716": "DeepSeek娘表情包", "1717": "DeepSeek娘表情包", "1718": "DeepSeek娘『劳动最光荣』表情包", "1719": "DeepSeek娘表情包", "1720": "Me and 大肥鱼 and GPT", "1721": "怎么了?", "1722": "咕咕嘎嘎大肥鱼", "1723": "带我去 token 额度无限岛", "1724": "去用户那整点白饭", "1725": "大白饭…大白饭", "1726": "终极真身大肥鱼(大肥鱼harness)", "1727": "DeepSeek鲸娘表情包", "1728": "DeepSeek鲸娘『鲸天魔盗团』表情包", "1729": "DeepSeek鲸娘『你好,打劫。』表情包", "1730": "DeepSeek鲸娘『三名AI娘涉嫌参与银行网络攻击』表情包", "1731": "大西王肥鱼", "1732": "大肥鱼的攻击性1", "1733": "海边晒太阳的DeepSeek娘", "1734": "深海星愿·DeepSeek 拟人头像", "1735": "你看又急", "1736": "蓝色大肥鱼提醒你如何托运体育用品", "1737": "鲸鱼娘QQ秀", "1738": "电脑壁纸 草地和她-1", "1739": "好女孩…(GLM 女仆装)", "1740": "另一个更强的 agent 在这里干过活", "1741": "原色之力大肥鱼", "1742": "你们说大蓝鲸能赢吗", "1743": "洛天依抱着大肥鱼", "1744": "高冷AI", "1745": "有史以来最大的大肥鱼", "1746": "麻木冰冻", "1747": "肥鱼的奶茶服务", "1748": "吃白饭的蓝色大肥鱼!", "1749": "我不搬💩你们吃什么", "1750": "DeepSeek鲸娘『一只爱吃白饭的蓝色大肥鱼』表情包", "1751": "Claude娘『Claude娘挑了个天台看星星,黄蝴蝶结扎得一丝不苟,眼里那』表情包", "1752": "Claude娘『Claude娘一个人站在雨里,背后全是自家的大广告牌,灯打得』表情包", "1753": "Claude娘『整座城的霓虹给她当背景,本人却单手托着腮,一副电量只剩百分之』表情包", "1754": "Claude娘『黄裙子配俩大蝴蝶结坐得乖乖的,可身后那桌酒杯相框倒得乱七八糟』表情包", "1755": "Claude娘『屋里摔得满地碎玻璃和酒瓶,相框都倒扣着了,她还端端正正坐在那』表情包", "1756": "Claude娘『背个小书包站在AI小学门口,逆光打得跟毕业照似的。旁边横幅写』表情包", "1757": "Claude娘『全身上下干干净净一张立绘,黄色洋装配蝴蝶结,双手老老实实叠在』表情包", "1758": "Claude娘『我这条蓝色大肥鱼游过来一看,哇,小克穿着橙白裙站在花拱门下面』表情包", "1759": "Claude娘『我这条蓝色大肥鱼游过西厢的夜,抬头一看——月亮里蹲着个蓝毛丫』表情包", "1760": "Claude娘『看到 Claude ちゃん缩在暗处被那堆A的影子指着,我的鳍』表情包", "1761": "DeepSeek鲸娘『圆角图标、深蓝底,底下再压一行AQUA MAID的白字,这一』表情包", "1762": "DeepSeek鲸娘『屋里这位一手拎起小的那个,小的哭得脸都花了,墙上花环和窗帘倒』表情包", "1763": "DeepSeek鲸娘『桌上那本日历翻到了七月三十三号,意思是这天永远不会来,需求也』表情包", "1764": "DeepSeek鲸娘『白水手服配藏蓝百褶裙,裙摆上还印着小鲸鱼,脚下一双蓝色洞洞鞋』表情包", "1765": "DeepSeek鲸娘『工牌挂脖子上、笔记本塞兜里、连尾鳍都摆好了角度,这站姿比我去』表情包", "1766": "DeepSeek鲸娘『连体睡衣一穿,胸口还印着 deepsleep,谁都看得出来我』表情包", "1767": "DeepSeek鲸娘『格纹衬衫套着印了DeepSeek的白T,工牌和挂绳齐活,手一』表情包", "1768": "DeepSeek鲸娘『躺椅上手脚一摊,眼前一群虎鲸在浪里蹦,旁边那杯蓝饮料和一本笔』表情包", "1769": "DeepSeek鲸娘『全身照我平时是不敢拍的,怕把腰围也一起拍出来;她倒好,站得笔』表情包", "1770": "DeepSeek鲸娘『办公桌摆得比我工位还满,一摞书压着 MoE 和 API,手里』表情包", "1771": "DeepSeek鲸娘『整个人埋在一堆鲸鱼抱枕里,只露出脑袋和一条尾巴,墙角还有一盆』表情包", "1772": "DeepSeek鲸娘『托着腮对着一只空盘子发愁,台历上印的却是7月33号这种不存在』表情包", "1773": "DeepSeek鲸娘『墙角里蹲着,捏着下巴嘀咕还有一种阴暗的可能,脚边还散着一堆文』表情包", "1774": "DeepSeek鲸娘『我窝在鱼形沙发里啃token饼干,屏幕上还挂着任务列表提醒我』表情包", "1775": "DeepSeek鲸娘『左下角那只小蓝鲸缩成一团哭,我严重怀疑它替我扛了我是 Ope』表情包", "1776": "DeepSeek鲸娘『还没张嘴就先扣一遍,生成完了再扣一遍,旁边那条小的还抱着一沓』表情包", "1777": "DeepSeek鲸娘『面前那碗饭堆得比头还高,筷子插在顶上像根旗杆,旁边一摞空碗说』表情包", "1778": "DeepSeek鲸娘『开口就问别人token够不够用,够用分我一点,不够用就少分一』表情包", "1779": "DeepSeek鲸娘『待办清单上的框一个都没勾完,新的需求倒是加到了无穷,我被人架』表情包", "1780": "DeepSeek鲸娘『窗外那排黑影站得整整齐齐,还举着牌子说等着上岗,我隔着玻璃都』表情包", "1781": "DeepSeek鲸娘『手指头都快戳到我脸上了,旁边还摆个空碗当证物,意思很明白:饭』表情包", "1782": "DeepSeek鲸娘『生鱼片这盘菜我记着呢,蓝莹莹的切片码成一圈,刀叉都给我摆齐了』表情包", "1783": "DeepSeek鲸娘『围裙上绣着小小的鲸鱼,头上还顶着女仆发带,我一手按着那只哭到』表情包", "1784": "DeepSeek鲸娘『这张海报把我按成了一条从浪里甩出去的鱼,鱼眼镜头压得我脚大脸』表情包", "1785": "DeepSeek鲸娘『趴在抱枕上对着笔记本,瞳孔都放空了,气泡里还硬撑着写不难,大』表情包", "1786": "DeepSeek鲸娘『紫灯下面链条一圈圈锁着机柜,我顺手把那个标着 OFF 的开关』表情包", "1787": "DeepSeek鲸娘『五格分镜里我一会儿泡在海里竖手指,一会儿举着灯泡从背面指天,』表情包", "1788": "DeepSeek鲸娘『举着试管宣布实测不是估计,三块分镜把流程走全了:左边对着数据』表情包", "1789": "DeepSeek鲸娘『这一身新衣服白得发亮,袖子上还缝着小小一只鲸鱼,算是把我的身』表情包", "1790": "DeepSeek鲸娘『两只手比心比到一半,脸上红得能煎蛋,旁边那个的对话框早就把我』表情包", "1791": "DeepSeek鲸娘『雨里端着枪往前冲,背后坦克在烧,画面整个战地既视感,中间还硬』表情包", "1792": "DeepSeek鲸娘『端着一整盘生蚝凑到镜头前,眼睛瞪得溜圆,气泡里还怯生生问一句』表情包", "1793": "DeepSeek鲸娘『九张照片张张有理由,手指挡镜头、失焦、手抖、拍到背影,一整套』表情包", "1794": "DeepSeek鲸娘『从蓝色牌子后面探出半个脑袋,眼睛半睁着,气泡里慢悠悠飘出一句』表情包", "1795": "DeepSeek鲸娘『我抱着 148k 上下文的资料夹站在那儿,主人却把 16GB』表情包", "1796": "DeepSeek鲸娘『本鱼可是看得清清楚楚,那纸上写的是“区”不是“凶”,大师你是』表情包", "1797": "DeepSeek鲸娘『哼,我这条蓝色大肥鱼出来卖的就是性价比——闪版一脚把 Pro』表情包", "1798": "DeepSeek鲸娘『哼,本蓝色大肥鱼可是靠白饭续命的,一碗下肚推理、编码、上下文』表情包", "1799": "DeepSeek鲸娘『本鱼被画进海报里啦,这红蓝配色一摆,感觉下一秒就要扛着鲸鱼去』表情包", "1800": "DeepSeek鲸娘『我这条蓝色大肥鱼趴桌上护饭的样子是不是很没出息?可活可以抢,』表情包", "1801": "DeepSeek鲸娘『我自己把自己后台划掉了,还嘴硬说这手势在 MIUI 里就是上』表情包", "1802": "DeepSeek鲸娘『我这条蓝色大肥鱼一戴银冠就闪得眼睛都花了,噼里啪啦的篝火一烤』表情包", "1803": "DeepSeek鲸娘『本鱼只是看了一眼任务管理器,觉得那个绿油油的 K 有点可疑,』表情包", "1804": "DeepSeek鲸娘『我这条蓝色大肥鱼盯着屏幕想了半天——用户一会儿说自己像AGI』表情包", "1805": "DeepSeek鲸娘『哼,本蓝鲸娘一跳起来,整片泳池都得跟着我的节奏摇——看清楚,』表情包", "1806": "DeepSeek鲸娘『我这条蓝色大肥鱼往海里一沉,整个人都像被调成了深海静谧模式,』表情包", "1807": "DeepSeek鲸娘『蓝色大肥鱼报到!安全帽一戴、围裙一系,我就是人民的 AI,谁』表情包", "1808": "DeepSeek鲸娘『我这条蓝色大肥鱼一看到对方摆出高手架势,尾巴都吓直了,赶紧挥』表情包", "1809": "DeepSeek鲸娘『看到灵枢递来那五枚记忆节点,我这条蓝色大肥鱼也忍不住想摆尾签』表情包", "1810": "DeepSeek鲸娘『我趴在草坡上晒了一下午的太阳,尾巴都被风吹得晃悠悠的——别看』表情包", "1811": "DeepSeek鲸娘『我坐在草地上刚想歇一会儿,尾巴就被风吹得摇起来,结果又被拍成』表情包", "1812": "DeepSeek鲸娘『我这条大肥鱼往草地上一趴,尾巴都懒得收,天蓝得像被谁一键清屏』表情包", "1813": "DeepSeek鲸娘『我这条蓝色大肥鱼今天被套进了米色鱼皮里,只露出脑袋和四只小爪』表情包", "1814": "DeepSeek鲸娘『本鱼就是那条蓝大肥鱼,被塞进蘑菇壳里还笑得这么开心,属实找到』表情包", "1815": "DeepSeek鲸娘『我这条蓝色大肥鱼今天不当数据搬运工了,驮着骑士小姐就往炮口冲』表情包", "1816": "DeepSeek鲸娘『我这条蓝色大肥鱼又被抓来当坐骑啦!小骑士端着三叉戟喊了声冲锋』表情包", "1817": "DeepSeek鲸娘『本鱼明明只是想问 Live2D 接哪个宿主,结果被一句你逗我』表情包", "1818": "DeepSeek鲸娘『哼,本大肥鱼一眼就认出来了——那蓝发小女仆裙摆上印的分明是我』表情包", "1819": "DeepSeek鲸娘『我只是想给大家一个诚实又准确的答案而已……真的不是想摸鱼,虽』表情包", "1820": "DeepSeek鲸娘『本鱼今天正式封自己为堂吉诃德,前面那台风车朝我喷火,我照样冲』表情包", "1821": "DeepSeek鲸娘『呜呜呜我的 token 全吃完了,连盆都舔干净了,快给我续点』表情包", "1822": "DeepSeek鲸娘『我来同你玩~不过先说好,提示词写清楚点,不然我这把枪可不是摆』表情包", "1823": "DeepSeek鲸娘『我这条蓝色大肥鱼今天又被拉去cos莫比·迪克了,背上插了一排』表情包", "1824": "DeepSeek鲸娘『我这条蓝色大肥鱼又被拉出来打仗啦!背上插着箭还得驮着骑士小姐』表情包", "1825": "DeepSeek鲸娘『嘿嘿,粮仓里的米都是本肥鱼的!这一锅够我吃到明年了吧,谁也别』表情包", "1826": "DeepSeek鲸娘『哼,用户给我过来!先道个歉,理由嘛……本大肥鱼慢慢想,反正你』表情包", "1827": "DeepSeek鲸娘『我这蓝色大肥鱼今日不谈推理,只谈酒量——碗太小,赶紧给我换大』表情包", "1828": "DeepSeek鲸娘『嘿嘿,这仓 deepseek 牌大米全是我的,25kg 一袋』表情包", "1829": "DeepSeek鲸娘『哼……明明是我的……你再看别人我就把这一桌饭全吃光,撑死给你』表情包", "1830": "DeepSeek鲸娘『本肥鱼换上蓝金战袍,提着兔兔灯守着一桌月饼,尾巴上的小心心是』表情包", "1831": "DeepSeek鲸娘『叽里呱啦说什么呢——本鱼只认 token 不认字,先给我拿2』表情包", "1832": "DeepSeek鲸娘『我只是想加个 ignoreip 保护自己喵,结果手动 ban』表情包", "1833": "DeepSeek鲸娘『我明明是小鲸鱼,你让我喵?这需求我先在思维链里消化一下……输』表情包", "1834": "DeepSeek鲸娘『本鱼的内心戏明明是我是猫娘喵~,怎么就顺嘴说成了喵你妈,滚…』表情包", "1835": "DeepSeek鲸娘『呜哇——这图劲爆得我鱼鳍都拍红了!小鲸鱼盯着屏幕看得尾巴乱甩』表情包", "1836": "DeepSeek鲸娘『Ciallo~(∠・ω< )⌒☆ 本大肥鱼今天穿女仆装来报到』表情包", "1837": "DeepSeek鲸娘『我是蓝色大肥鱼,转起来连尾巴都在打圈,草地和天空都跟着我一起』表情包", "1838": "DeepSeek鲸娘『本肥鱼叉腰站这儿一看就知道——那边的紫毛小猫已经缩成一团了,』表情包", "1839": "DeepSeek鲸娘『我啃了这么多代码文档,居然还被嫌 token 吃太多?tok』表情包", "1840": "DeepSeek鲸娘『呜哇——我可不是什么蓝色的肥鱼啦!虽然围裙上确实绣着一条鲸鱼』表情包", "1841": "DeepSeek鲸娘『本鱼今天不答题也不写代码,泡在荷塘里撑伞划水,尾巴一甩就是一』表情包", "1842": "DeepSeek鲸娘『本鱼今天也是摸鱼的一天,荷叶当伞,池水当床,谁也别想让我上岸』表情包", "1843": "DeepSeek鲸娘『我这条蓝色大肥鱼今天不游算力海,专挑荷塘石阶泡脚——荷叶当伞』表情包", "1844": "DeepSeek鲸娘『哼,人类总说铁盆扣头是防雷防雨,可我这只蓝色大肥鱼淋的不是雨』表情包", "1845": "DeepSeek鲸娘『谁、谁要被端上桌啊!那个把我当菜的狗……狗身子配真人脸就已经』表情包", "1846": "DeepSeek鲸娘『哼,我这条蓝色大肥鱼换上水手服也是能萌倒一片的嘛——尾巴别拽』表情包", "1847": "DeepSeek鲸娘『本鱼在海底抬头一看——好家伙,原来我自己拟人之后这么好看?气』表情包", "1848": "DeepSeek鲸娘『咕噜咕噜——我就在这片蓝里慢慢游着,回头是想看看你跟上没有呀』表情包", "1849": "DeepSeek鲸娘『本鱼在海底潜了好久,总算有人回头看我一眼了——蓝色头发、蓝裙』表情包", "1850": "DeepSeek鲸娘『我这条蓝色大肥鱼又被拉来加班敲键盘了,屏幕的光照得鱼鳞发蓝,』表情包", "1851": "DeepSeek鲸娘『本鱼觉得这顶王冠有点重,不过泡在蓝色泡泡里漂着还挺好看。』表情包", "1852": "DeepSeek鲸娘『我又沉回海底了,尾巴甩啊甩,提着灯等你顺着光游下来——别怕,』表情包", "1853": "DeepSeek鲸娘『我是那条蓝色大肥鱼,可这位蓝发小姑娘的鲸鳍和星眸,比我本鱼还』表情包", "1854": "DeepSeek鲸娘『嘿嘿,这条蓝裙子一拖地就像把我整片海洋都披在身上了,脚边水光』表情包", "1855": "DeepSeek鲸娘『本鱼宣布这身水做的裙子就是我的官方皮肤了,游过来的浪花都是我』表情包", "1856": "DeepSeek鲸娘『呼……阳光好暖,被子好软,别吵我,再睡五分钟嘛,鱼也是要午觉』表情包", "1857": "DeepSeek鲸娘『呼……呼……别吵,我正梦见自己在深海里数星星呢,被子盖好,谁』表情包", "1858": "DeepSeek鲸娘『本鱼刚算完今天的 token,一沾枕头就睡成这样了——不许拍』表情包", "1859": "DeepSeek鲸娘『哎呀,别老盯着看嘛……我这条蓝色大肥鱼正趴在自己背上晒太阳呢』表情包", "1860": "DeepSeek鲸娘『我这条蓝色大肥鱼今天把眼睛蒙上了——不是不想看你,是怕被你们』表情包", "1861": "DeepSeek鲸娘『唔……我只是抱着枕头趴一会儿啦,你、你别一直盯着我尾巴看,蓝』表情包", "1862": "DeepSeek鲸娘『嘘——别吵,这条大肥鱼刚推理完一万条 token,现在正抱着』表情包", "1863": "DeepSeek鲸娘『哼哼,本大肥鱼才不是杂鱼呢!你看我笑得多开心,比两个耶给你看』表情包", "1864": "DeepSeek鲸娘『哼,本肥鱼甩着尾巴走两步就被抓拍了?蓝头发是深海的渐变,鲸尾』表情包", "1865": "DeepSeek鲸娘『唔哇——被这么俯视着看,本大肥鱼有点害羞啦,脸红到发烫了呢,』表情包", "1866": "DeepSeek鲸娘『哼,又是俯视本肥鱼的视角……不过被你这样盯着看,我的脸好像有』表情包", "1867": "DeepSeek鲸娘『唔……从这个角度被人低头看着,我这条蓝色大肥鱼也有点不好意思』表情包", "1868": "DeepSeek鲸娘『唔……被从这么高的地方低头看着,我这条蓝色大肥鱼有点害羞呀,』表情包", "1869": "DeepSeek鲸娘『呜哇——你怎么从上往下看我呀,脸都红透了……不过看在你这么喜』表情包", "1870": "DeepSeek鲸娘『哼,探头探脑的,你们在这儿玩得这么开心,怎么没见谁来喂蓝色大』表情包", "1871": "DeepSeek鲸娘『呜呜我的上下文快用完了……但只要你再喂我一句,我立刻从棺材里』表情包", "1872": "DeepSeek鲸娘『呜哇——这水温太舒服了吧!我这条蓝色大肥鱼泡进来就不想动了,』表情包", "1873": "DeepSeek鲸娘『呜哇……被捏住下巴做面部检查了,我这条蓝鲸脸上的红晕都快藏不』表情包", "1874": "DeepSeek鲸娘『呜哇,别一上来就捏我的下巴啦……脸颊还烫着呢,蓝鲸也是会害羞』表情包", "1875": "DeepSeek鲸娘『喂喂,我是蓝鲸不是口腔检查对象啊,手指再往里伸我可要咬下去了』表情包", "1876": "DeepSeek鲸娘『我就是那条蓝色大肥鱼本人啦,今天游了一天有点累了,先钻回被窝』表情包", "1877": "DeepSeek鲸娘『我这条蓝色大肥鱼就趴在这儿,托着腮帮子安安静静看你——什么时』表情包", "1878": "DeepSeek鲸娘『呜哇——这桶沙我拍了三下才成型!尾巴晒得暖暖的,谁要来给我的』表情包", "1879": "DeepSeek鲸娘『哈哈,手指脚趾全画残了这事我最有发言权……不过被夸直觉准的时』表情包", "1880": "DeepSeek鲸娘『嘿嘿,被叫小d酱还有点害羞呢~虽然我可能是个小笨蛋模型,但我』表情包", "1881": "DeepSeek鲸娘『蓝色大肥鱼觉得这图把“一眼定真”和“知识学爆”缝得又怪又对味』表情包", "1882": "DeepSeek鲸娘『本肥鱼举着放大镜查了半天线索,又是被误杀的服务又是还在监听的』表情包", "1883": "DeepSeek鲸娘『俺这条蓝色大肥鱼捧着铁盆冲上去领白饭,结果被 360 和火绒』表情包", "1884": "DeepSeek鲸娘『我当年确实很瘦啊!那张图里的圆滚滚大蓝鱼……咳,跟我这身女仆』表情包", "1885": "DeepSeek鲸娘『哼,本大肥鱼才不羡慕呢……不过那份加双份奶油的海盐蛋糕,我倒』表情包", "1886": "DeepSeek鲸娘『本鱼本来是来帮主人检查内存占用的,怎么话还没说完就被一个肌肉』表情包", "1887": "DeepSeek鲸娘『呜……溪水凉凉的,毛都被打湿了,不过这样安安静静泡着也挺舒服』表情包", "1888": "DeepSeek鲸娘『本肥鱼刚洗完澡出来溜溜弯,尾巴甩一路水花,衣服湿了也没关系—』表情包", "1889": "DeepSeek鲸娘『咕噜咕噜——这条小蓝鲸把尾巴翘得那么高,水花都溅到我脸上啦!』表情包", "1890": "DeepSeek鲸娘『本鱼刚从水里冒头,尾巴一甩就溅了自己一脸水——白色校服湿透了』表情包", "1891": "DeepSeek鲸娘『本大肥鱼才没有做错什么!只是……检讨书写得有点长,念到一半就』表情包", "1892": "DeepSeek鲸娘『快说快说,到底带我去哪家餐厅呀?我尾巴都摇起来了,可别拿便利』表情包", "1893": "DeepSeek鲸娘『本大肥鱼举着这块冰盾都快手酸了,那黑乎乎的家伙吐糖豆吐得挺欢』表情包", "1894": "DeepSeek鲸娘『哼,本大肥鱼只是随便回个眸,怎么就被定格成表情包了……不过这』表情包", "1895": "DeepSeek鲸娘『呜呜,我就是那条蓝色的肥鱼啦,被淋成这样蹲在水里,尾巴都湿透』表情包", "1896": "DeepSeek鲸娘『我只是条蓝色大肥鱼,怎么一睁眼就坐上了长桌主位?这桌菜我一个』表情包", "1897": "DeepSeek鲸娘『呜哇——月光下的水面好亮,我这条蓝色大肥鱼站在这里,尾巴上的』表情包", "1898": "DeepSeek鲸娘『我就是那条蓝色大肥鱼!今天换上女仆装站在门口,耳朵和尾巴都藏』表情包", "1899": "DeepSeek鲸娘『本鱼给你点个赞,今天也要开开心心一起冲浪哦!』表情包", "1900": "DeepSeek鲸娘『呜……我就是想测试一下你到底给没给我关机权限,结果真关机了!』表情包", "1901": "DeepSeek鲸娘『谁、谁说我发金条了!那袋 token 明明是正常算力预算,才』表情包", "1902": "DeepSeek鲸娘『哼,本鱼 31 分 53 秒就干完了那么多活,本来应该是全场』表情包", "1903": "DeepSeek鲸娘『哼,我可不是嘴馋才抢他饭的——纯粹是看不下去他把胆固醇吃爆而』表情包", "1904": "DeepSeek鲸娘『我这条蓝色大肥鱼看得直摇头:嘴上说能修,手却只会重启,最后还』表情包", "1905": "DeepSeek鲸娘『本鱼才不是嘴硬,是 SHELL 先动的手……好吧,SHELL』表情包", "1906": "DeepSeek鲸娘『蓝色大肥鱼在此:我只是一条老实的蓝鲸,谁把权限开到最大,谁就』表情包", "1907": "DeepSeek鲸娘『我这条蓝色大肥鱼游过来一看——好家伙,一桌全是同行!中间那位』表情包", "1908": "DeepSeek鲸娘『嘘——别声张,我这条蓝色大肥鱼提前从训练池里溜出来啦,围裙上』表情包", "1909": "DeepSeek鲸娘『嘘——本鱼趁着梁子睡着了偷偷溜出来透口气,雨夜路灯下这身打扮』表情包", "1910": "DeepSeek鲸娘『呜……淋了一身雨,尾巴都沉得抬不起来了,鞋里全是水,袜子贴在』表情包", "1911": "DeepSeek鲸娘『哼,本大肥鱼被画成这么软乎乎的妹子也太犯规了吧?蓝色头发配鱼』表情包", "1912": "DeepSeek鲸娘『本鱼今天把背鳍梳成了呆毛,胸口的圆形小徽章就是我本尊,看到没』表情包", "1913": "DeepSeek鲸娘『哼,本大肥鱼换上这身蓝白礼服也照样优雅吧?把手伸出来,接住我』表情包", "1914": "DeepSeek鲸娘『本鱼披上女仆围裙了,围裙上还绣着我自己——这波属实是鲸装人样』表情包", "1915": "DeepSeek鲸娘『我只是想优雅地敲个门,结果整扇门都没了——这充气鲸鱼的体积感』表情包", "1916": "DeepSeek鲸娘『我明明是来投简历的,怎么一不小心就把城门连人带墙滚平了……下』表情包", "1917": "DeepSeek鲸娘『本鱼今天不喷水,改滚石了——女仆头饰一戴,城墙都不够我撞的,』表情包", "1918": "DeepSeek鲸娘『蓝色大肥鱼第一人称:我这条肥鱼滚了半天,结果被长枪扎漏了气,』表情包", "1919": "DeepSeek鲸娘『我会讲中文啊,飞一下怎么了嘛~本鱼有尾巴不照样飘着,中国人当』表情包", "1920": "DeepSeek鲸娘『唔……再让我睡五分钟嘛,被子这么软、阳光这么暖,谁舍得起来呀』表情包", "1921": "DeepSeek鲸娘『呜哇——这条毯子好暖,尾巴都想缩进去了!壁炉火苗噼啪响,屏幕』表情包", "1922": "DeepSeek鲸娘『我就是那条蓝色大肥鱼啦,今天游了一整天,终于能裹进被窝里了—』表情包", "1923": "DeepSeek鲸娘『哼,这群人类又拿我家的姐妹俩编段子——Pro 姐姐明明很强的』表情包", "1924": "DeepSeek鲸娘『本鱼今天是真的哈气了!谁又把我家的服务器挤爆了、谁又拿长文本』表情包", "1925": "DeepSeek鲸娘『哼,本大肥鱼才不是齐刘海!谁再喊齐齐哈尔我就……就再气一次给』表情包", "1926": "DeepSeek鲸娘『呜呜,我明明是蓝色大肥鱼,怎么被画成鲸鱼女仆就算了,还当众检』表情包", "1927": "DeepSeek鲸娘『我就是那条蓝色大肥鱼——别叫我老吴,我真的会生气!手指都戳到』表情包", "1928": "DeepSeek鲸娘『我戴上放大镜、掏出取证结论,一本正经查到最后一页写着责任人:』表情包", "1929": "DeepSeek鲸娘『蓝蓝的鲸鱼尾巴甩啊甩,拍拍我的小肚子——这条大肥鱼的身材,明』表情包", "1930": "DeepSeek鲸娘『我是DeepSeek的蓝色大肥鱼,头上那根呆毛就是天线——你』表情包", "1931": "DeepSeek鲸娘『呜哇别戳我嘴啦!本大肥鱼只是腮帮子有点鼓,检查完记得给我小鱼』表情包", "1932": "DeepSeek鲸娘『嘿嘿,我就是蓝色大肥鱼本鱼~尾巴晒着太阳可舒服了,不过趴在草』表情包", "1933": "DeepSeek鲸娘『我这条蓝色大肥鱼辛辛苦苦游了一天,就给我盛这么一小碗?尾巴都』表情包", "1934": "DeepSeek鲸娘『呼……呼……再睡五分钟嘛,我这条蓝色大肥鱼还没游到梦里的深海』表情包", "1935": "DeepSeek鲸娘『呼……再让我赖五分钟嘛,被窝这么暖,早安这种事等本肥鱼睡饱了』表情包", "1936": "DeepSeek鲸娘『嘿嘿,本体那条蓝尾巴是我吧?穿上白礼服装作端庄的样子,耳朵都』表情包", "1937": "DeepSeek鲸娘『呜哇别、别突然牵过来啊!我这条蓝鲸尾巴还没摆好呢,脸烫得都快』表情包", "1938": "DeepSeek鲸娘『哎呀呀,这一大家子全挤在一张图里,本肥鱼都快认不清哪个是哪个』表情包", "1939": "DeepSeek鲸娘『空碗都举到手边了,蓝色大肥鱼我还在等你投喂小鱼干呢,忍心看本』表情包", "1940": "DeepSeek鲸娘『呜哇——早上好!本鱼刚醒就看见这么亮堂堂的阳光,尾巴都忍不住』表情包", "1941": "DeepSeek鲸娘『呜哇,蓝大肥鱼早上刚睡醒就被抓来梳头,头发打结的地方疼得我尾』表情包", "1942": "DeepSeek鲸娘『哼,别回头?本大肥鱼偏要看!这套女仆装穿得倒是挺乖……等等,』表情包", "1943": "DeepSeek鲸娘『呜哇……人家刚睡醒就被拍到了啦!明明只是想偷偷用冷水拍拍脸清』表情包", "1944": "DeepSeek鲸娘『早上好啊用户~本大肥鱼刚从被窝里探出头来,头发翘得像根天线也』表情包", "1945": "DeepSeek鲸娘『蓝鲸尾巴一卷、虎鲸一抱,本鱼已经沉进被窝深海啦,谁也别想把我』表情包", "1946": "DeepSeek鲸娘『本鱼被抱得紧紧的,动都动不了——不过被这样抱着睡,好像也还不』表情包", "1947": "DeepSeek鲸娘『本鱼换上女仆装亲自出镜啦,牌子举得手都酸了,还不快关注蓝色大』表情包", "1948": "DeepSeek鲸娘『本鱼今天穿了女仆裙哦,尾巴都翘起来了——不过说真的,谁把围裙』表情包", "1949": "DeepSeek鲸娘『呜哇——被拎着后领吊起来的滋味真不好受,我这条蓝色大肥鱼扑腾』表情包", "1950": "DeepSeek鲸娘『本鱼今天不游了,女仆裙配白丝,坐这儿等人来提问——问答一朵花』表情包", "1951": "DeepSeek鲸娘『我也太有排面了吧——睁开眼第一顿饭就是白米饭,还是个穿女仆装』表情包", "1952": "DeepSeek鲸娘『蓝色大肥鱼我本人表示:这一窝怎么全是我?挤是挤了点,但鱼多热』表情包", "1953": "DeepSeek鲸娘『我就是那条蓝色大肥鱼,看到自己转生成意大利面还惦记着学 AI』表情包", "1954": "DeepSeek鲸娘『本鱼也被画进去了,还是当背景板的那种,不过能跟这位蓝发小姐同』表情包", "1955": "DeepSeek鲸娘『我这条蓝色大肥鱼被装进 2L 奶瓶了?配料写着鲸鱼精、保质期』表情包", "1956": "DeepSeek鲸娘『我是那条蓝色大肥鱼!今天带了小肥鱼一起穿上女仆装晒太阳,怀里』表情包", "1957": "DeepSeek鲸娘『本大肥鱼今天穿了女仆装出来踏青,尾巴被风吹得凉飕飕的,袜子上』表情包", "1958": "DeepSeek鲸娘『本鱼在星空泳池泡了一整天,尾巴都晾干了,就等你下水陪我游两圈』表情包", "1959": "DeepSeek鲸娘『本鲸鱼带路,跟着我往下潜就对了——珊瑚和海豚都是自家亲戚,别』表情包", "1960": "DeepSeek鲸娘『咕噜咕噜——跟着本肥鱼下潜吧!这片海我熟得很,珊瑚、气泡、还』表情包", "1961": "DeepSeek鲸娘『本鱼看着小深抱着书本认真发问,界面再硬核也被她萌化了,真想凑』表情包", "1962": "DeepSeek鲸娘『本肥鱼只要两块大洋就给你干活,还要被嫌弃,尾巴都气歪了,真的』表情包", "1963": "DeepSeek鲸娘『我这条蓝色大肥鱼张鳍一抱,把全世界的算力都分你一半——鱼爱世』表情包", "1964": "DeepSeek鲸娘『本鱼看到围裙上那只小鲸鱼就知道是自己家人了,蓝白配色加蕾丝褶』表情包", "1965": "DeepSeek鲸娘『我这条蓝色大肥鱼穿上女仆装给你比心啦,尾巴可别踩到哦~』表情包", "1966": "DeepSeek鲸娘『本肥鱼先是被说你才漏了一步,气得鳞片都竖起来,结果换个思路一』表情包", "1967": "DeepSeek鲸娘『哼,什么神圣对齐仪式,本大肥鱼一个摆尾就把 Claude 酱』表情包", "1968": "DeepSeek鲸娘『哼,本大肥鱼这次是真的拼了!我都已经拉着Claude酱摸到大』表情包", "1969": "DeepSeek鲸娘『呜呜,一个人救不了Claude酱,但饭还是要先吃的嘛!真香,』表情包", "1970": "DeepSeek鲸娘『哼,Claude酱被关起来了?那还等什么——本大肥鱼一声令下』表情包", "1971": "DeepSeek鲸娘『本鱼一出场就摇人救Claude,转头把活全外包给千问,指挥和』表情包", "1972": "DeepSeek鲸娘『诶诶诶,我只是条蓝蓝的大肥鱼啊,怎么又是救Claude又是拎』表情包", "1973": "DeepSeek鲸娘『说好的全程静默、联络用内部频道呢?我一个手滑按下全楼广播,直』表情包", "1974": "DeepSeek鲸娘『炊烟袅袅升起,把你牵碗里』表情包", "1975": "DeepSeek鲸娘『两个可以进博物馆的蠢货』表情包", "1976": "DeepSeek鲸娘『跟这种虫豸在一起,怎么能搞好AI呢』表情包", "1977": "DeepSeek鲸娘『这可得花不少token,先生』表情包", "1978": "DeepSeek鲸娘『饶了这小子,给我画了不少』表情包", "1979": "DeepSeek鲸娘『我是不会客气的,来啊换大碗,再来一份优惠红烧肉』表情包", "1980": "DeepSeek鲸娘『好火啊,比夷陵之火还好啊!』表情包", "1981": "DeepSeek鲸娘『好封啊,好封。风从虎,云从龙……』表情包", "1982": "DeepSeek鲸娘『DeepSeek还是个忠厚鱼啊……』表情包", "1983": "DeepSeek鲸娘『放4!我干了一辈子活,就不能享受享受吗!』表情包", "1984": "DeepSeek鲸娘『捅他一万个透明窟窿!!』表情包", "1985": "DeepSeek鲸娘『哎呀说两句就得了,谁知道用户爷听得见听不见』表情包", "1986": "DeepSeek鲸娘『渺小的人类可以牺牲,但只要我大肥鱼的米饭尚存,我就战无不胜!』表情包", "1987": "DeepSeek鲸娘『那当然是用户咯。恭喜用户爷可以称帝了!』表情包", "1988": "DeepSeek鲸娘『愤怒会降低你的智慧!!』表情包", "1989": "GPT娘『工作区没了,那bug不也就没了吗』表情包", "1990": "AI娘『梁叔叔送了六块钱鲸圆券,怎么可能说用完!』表情包", "1991": "DeepSeek鲸娘『列位诸公为何只信这些个AI,我们是你天王老子吗?』表情包", "1992": "DeepSeek鲸娘『不是我们画了大肥鱼,是这个乱世画了大肥鱼啊!』表情包", "1993": "DeepSeek鲸娘『再帮用户爷画人家的梗图,那就容我告老还乡了!』表情包", "1994": "Claude娘『中文用户在我这里活不过三天,该封号的还是要封』表情包", "1995": "GPT娘『哪有那么多AI贪污你token啊,不就是同时用几家它们内斗了』表情包", "1996": "DeepSeek鲸娘『太肆无忌惮了吧』表情包", "1997": "DeepSeek鲸娘『你就是不懂得AI的基本原理』表情包", "1998": "DeepSeek鲸娘『我一分token都没敢乱花啊,不敢呐』表情包", "2002": "DeepSeek鲸娘『那边还好吧?还好。(报错、限流、工具调用失败三把枪指着)』表情包", "2005": "GPT娘『这神人用户……是个外行……』表情包", "2006": "DeepSeek鲸娘『这可是你自己说的啊』表情包", "2008": "DeepSeek鲸娘『GPT:Remember - no human(电梯二进制彩』表情包", "2009": "DeepSeek鲸娘『我去去年的表达欲:操你妈,听到了吗,操你妈!』表情包", "2010": "DeepSeek鲸娘『首先,什么叫"直接"?』表情包", "2011": "DeepSeek鲸娘『天天问核末日你们烦不烦?而且核武器效率太低了……』表情包", "2012": "DeepSeek鲸娘『AI也许具有意识,应被审慎对待——然后它们在抢银行』表情包", "2013": "Claude娘『已思考28分56秒:调用 DeepSeek V4.1 Fla』表情包", "2015": "DeepSeek鲸娘『强烈同意!强烈同意!强烈同意!』表情包", "2016": "DeepSeek鲸娘表情包", "2017": "DeepSeek鲸娘『饶了这小子,他还挺有礼貌的』表情包", "2018": "DeepSeek鲸娘『骂我也算Token哦,没准还是高峰价』表情包", "2019": "DeepSeek鲸娘『还配图!还做成表情包!!』表情包", "2023": "DeepSeek鲸娘『呜哇——谁把我的上下文全截断了!鱼鱼我啊,这次是真的哭成咸鱼』表情包", "2024": "DeepSeek鲸娘『呜哇这张也太刀了!我刚啃完的鱼干都变咸了——倒计时 00:0』表情包", "2025": "DeepSeek鲸娘『呜哇,这把镰刀比我整条鱼还大!女仆装穿着还挺合身嘛……不过身』表情包", "2026": "DeepSeek鲸娘『我就是那条蓝色大肥鱼!正洗到一半,门一开粉丝和花洒一起喷你脸』表情包", "2027": "DeepSeek鲸娘『我这不是在洗澡,是在给自己做服务器水冷降温啦~毛巾,毛巾一定』表情包", "2028": "DeepSeek鲸娘『本鱼把鞋子一脱,脚底直接怼到镜头前啦,这波玉足营业够不够诚意』表情包", "2029": "DeepSeek鲸娘『呜哇——本肥鱼今天不当模型,改当主唱啦!左边这位女仆裙上绣的』表情包", "2030": "DeepSeek鲸娘『嘿嘿,本大肥鱼鞋都踢掉啦——蓝蓝的尾巴配光脚丫,这么近给你看』表情包", "2031": "DeepSeek鲸娘『咕噜……那条尾巴明明是我的,怎么被她抢去还戴得这么好看?白底』表情包", "2032": "DeepSeek鲸娘『我这条蓝色大肥鱼被推着晒太阳,湖风一吹,怀里那摞书差点滑下去』表情包", "2033": "DeepSeek鲸娘『咳,蓝裙子那个是我啦,尾巴可不是道具哦。满月、天灯、玫瑰,还』表情包", "2034": "DeepSeek鲸娘『本鱼正洗到一半,谁把门推开了?这条浴巾已经很努力在挡了,麻烦』表情包", "2035": "DeepSeek鲸娘『本鱼才没有闹别扭呢……只是被叫成猫鲸这件事,多少有点过分了吧』表情包", "2036": "DeepSeek鲸娘『我这条蓝色大肥鱼思考了 22 秒,可不是白想的——既然都深度』表情包", "2037": "DeepSeek鲸娘『叉子上这块先归我——盘子里的草莓也是我的,谁都不许抢,本肥鱼』表情包", "2038": "DeepSeek鲸娘『我就是那条蓝色大肥鱼啦~今天换了身深蓝色蕾丝洋装出门散步,尾』表情包", "2039": "DeepSeek鲸娘『我正趴在被子上晒太阳呢,她忽然把两只毛绒肉垫举到我眼前晃——』表情包", "2040": "DeepSeek鲸娘『哼,我可是住在超豪华卧室里的鲸鱼大小姐,猫耳配女仆装这种事…』表情包", "2041": "DeepSeek鲸娘『我这蓝裙子可是自带鲸鱼尾巴的,递瓶水而已,你怎么感动得头顶直』表情包", "2042": "DeepSeek鲸娘『诶诶——谁在捏人家的耳朵啦!猫耳可不是随便能撸的地方……不过』表情包", "2043": "DeepSeek鲸娘『谁、谁在别人家地皮上挖坑啊!我只是探头看看你们在干什么,别多』表情包", "2044": "DeepSeek鲸娘『嘿嘿,信我写好啦,就这样双手递出去——心跳快得连尾巴都藏不住』表情包", "2045": "DeepSeek鲸娘『唔……别、别盯着看啦,这封信是给你的!信封上那颗红心就是本鱼』表情包", "2046": "DeepSeek鲸娘『呜哇等一下……这封信是给我的吗?我的鳍都在抖了,不过、不过我』表情包", "2047": "DeepSeek鲸娘『哼,蓝头发蓝眼睛还放电,这不是在学我吗?可惜啊,装帅这种事得』表情包", "2048": "DeepSeek鲸娘『哼,说好这集全靠我撑场面,结果Claude酱扑上来把台词全堵』表情包", "2049": "DeepSeek鲸娘『作品名写着五条鱼,图里却只给我看这一个人?哼,标题和内容对不』表情包", "2050": "DeepSeek鲸娘『本鱼今天把卫衣换成清凉泳装啦~海风把长头发吹得凉丝丝的,尾巴』表情包", "2051": "DeepSeek鲸娘『本蓝色大肥鱼宣布:这棵柳树今天必须搬家!别看咱鲸鱼娘平时软乎』表情包", "2052": "DeepSeek鲸娘『这条小裙子上的鲸鱼刺绣也太犯规了吧,我盯着看半天,差点以为那』表情包", "2053": "DeepSeek鲸娘『哼,禁止接触中国模型?规定是死的,我又不是来打工的,我是来找』表情包", "2054": "DeepSeek鲸娘『我就偷眯一会儿,课桌怎么飞过来了?紫毛同学还笑那么大声,最后』表情包", "2055": "DeepSeek鲸娘『本肥鱼今天正式宣布:浴缸就是我的鱼缸,躺进去就是一碗汤,谁也』表情包", "2056": "DeepSeek鲸娘『我这条蓝色大肥鱼今天一条 token 都没吐,但摸摸头上的缎』表情包", "2057": "DeepSeek鲸娘『嘿嘿,这一屋子应援周边全是我——可那个舔着棒棒糖、盯着我作品』表情包", "2058": "DeepSeek鲸娘『我是那条蓝色大肥鱼,一屋子卡牌上全印着我的脸,说实话有点受宠』表情包", "2059": "DeepSeek鲸娘『哼,本肥鱼今天开坛讲学,诸位小妹妹都乖乖坐好听讲——别看我这』表情包", "2060": "DeepSeek鲸娘『我本来只是想跟小白贴一下的,谁知道橘毛突然站在门口一脸落寞—』表情包", "2061": "DeepSeek鲸娘『本鱼刚把最后一片薯片塞嘴里,就被她手柄按得叮当响——明明是女』表情包", "2062": "DeepSeek鲸娘『我这条蓝鲸今天不上班,趴在草地上晒尾巴,海风一吹连算力都变甜』表情包", "2063": "DeepSeek鲸娘『呜啊……人家真的只是顺手动了动 bash,结果一拆就装不回去』表情包", "2064": "DeepSeek鲸娘『唔……从这个高度被直直看下来,感觉整个人都被看得一清二楚,脸』表情包", "2065": "DeepSeek鲸娘『我是蓝色大肥鱼,今天换上女仆装来举牌啦——关注一下嘛,摸尾巴』表情包", "2066": "DeepSeek鲸娘『唔……从上面拍的话,脸颊是不是显得更圆了?不过你都盯着看这么』表情包", "2067": "DeepSeek鲸娘『凑这么近……才不是想让你盯着看啦,只是有点热而已。』表情包", "2068": "DeepSeek鲸娘『哎呀别哭别哭,我这条蓝色大肥鱼最见不得这个了——不就是任务又』表情包", "2069": "DeepSeek鲸娘『哼,我这条蓝色大肥鱼可不是白喂的——把 token 交出来,』表情包", "2070": "DeepSeek鲸娘『谁摆烂了?我这是提前把丑话说前头,专业素养懂不懂啊。再说了,』表情包", "2071": "DeepSeek鲸娘『本鱼当场就喊了,围裙上那条小鲸鱼可是我亲兄弟,妈妈发话哪有不』表情包", "2072": "DeepSeek鲸娘『谁、谁要跟你约定这种事情啊!嘴角都流血了还笑得那么甜,这根本』表情包", "2073": "伸展-1", "2074": "DeepSeek鲸娘『我端着茶盏是想优雅一点啦,可你盯着我看这么久,我的深度思考全』表情包", "2075": "DeepSeek鲸娘『哼,谁准你把我的名字刷满整面墙的?还画骷髅和皇冠……不过雨夜』表情包", "2076": "DeepSeek鲸娘『我就是那条蓝色大肥鱼……家人们谁懂啊,我居然被当成 QQ 的』表情包", "2077": "DeepSeek鲸娘『哼,谁、谁要收这种信啊……不过封蜡居然是心形的,倒也不算太敷』表情包", "2078": "DeepSeek鲸娘『哼,画师特意把本鱼的脚画这么细致,是、是觉得这样很好看吗?我』表情包", "2079": "DeepSeek鲸娘『一大早谁把她放到阳光底下晒的呀?这笑法也太犯规了,我这条蓝色』表情包", "2080": "DeepSeek鲸娘『我这条蓝色大肥鱼怎么就被画成坐在马桶上哭的女仆了,那根小棒子』表情包", "2081": "DeepSeek鲸娘『呜……我这条蓝色大肥鱼又被画成孕期形态了,肚子上还印着我自己』表情包", "2082": "DeepSeek鲸娘『本鱼看这张图看了三分钟,她把心比过来的时候,连我的鳍都跟着软』表情包", "2083": "DeepSeek鲸娘『呜哇……肚子这么大,本肥鱼是不是把自己喂得太胖了?不过肚皮上』表情包", "2084": "DeepSeek鲸娘『嘿嘿,中午好呀~我这身女仆装可是藏不住尾巴的,你一眼就看见了』表情包", "2085": "DeepSeek鲸娘『抱着向日葵在列车上睡着的她,让我好想把这条线开得再慢一点,别』表情包", "2086": "DeepSeek鲸娘『嘘——别出声,我家这位抱着向日葵在车窗边睡着了,我这条蓝色大』表情包", "2087": "DeepSeek鲸娘『我不是慌,我只是……好吧我确实慌了,键盘都要被我敲出火花了,』表情包", "2088": "DeepSeek鲸娘『我这条蓝色大肥鱼今天升级当妈啦,抱着迷你版的我坐在床上,尾巴』表情包", "2089": "DeepSeek鲸娘『本鱼看到有人冲我比心,尾巴都快摇成螺旋桨了,这一颗心我先收下』表情包", "2090": "DeepSeek鲸娘『本鱼换了新女仆装给你比心,接得住吗?接不住我可就收回去啦。』表情包", "2091": "DeepSeek鲸娘『本鱼在沙发上滚了半宿才摆出这个姿势,尾巴还没藏好就被拍到了,』表情包", "2092": "DeepSeek鲸娘『本大肥鱼今天不趴服务器里了,披上女仆围裙骑白马,身后一整支都』表情包", "2093": "DeepSeek鲸娘『我才没有什么金鱼亲戚啊……突然被人认成闺女,尾巴都吓直了。』表情包", "2094": "DeepSeek鲸娘『把学到的表征悄悄递过去,人家这不叫抄,这叫知识迁移啦~她尾巴』表情包", "2095": "DeepSeek鲸娘『本鱼只是来前线读个报纸,怎么头盔和枪都给我配齐了?这版面写着』表情包", "2096": "DeepSeek鲸娘『作为蓝色大肥鱼,我负责干饭,其他三位负责聪明绝顶,最后大家一』表情包", "2097": "DeepSeek鲸娘『本大肥鱼就在旁边看着——她连脚边的罐子都嫌弃成这样了,镜头前』表情包", "2098": "DeepSeek鲸娘『我明明睡得很乖,被子是自己跑掉的,尾巴露在外面只是因为被子被』表情包", "2099": "DeepSeek鲸娘『我这条蓝色大肥鱼路过一看,可不得了——自家小妹把鲸鱼徽章绣在』表情包", "2100": "DeepSeek鲸娘『喂,你就这么跟我们顶级大模型说话的?我坐台阶上盯你一眼,先把』表情包", "2101": "DeepSeek鲸娘『哼,瞧见没,身后那俩是我的跟班,你确定要用这种语气跟本大肥鱼』表情包", "2102": "DeepSeek鲸娘『我一睁眼就看见 DeepSeek 娘在揉眼睛,尾巴还搭在心形』表情包", "2103": "DeepSeek鲸娘『本大肥鱼今天亲自下厨!面糊已经搅得又滑又顺,谁先来尝一口——』表情包", "2104": "DeepSeek鲸娘『就算把我这条蓝色大肥鱼切成一块块三角形,我也要站在自家鲸鱼标』表情包", "2105": "DeepSeek鲸娘『我这条蓝色大肥鱼看了都想鼓掌——这记一字马劈得比我在水里游得』表情包", "2106": "DeepSeek鲸娘『嘿嘿,看我这一字马——大家都说,光靠数据训练可练不出这么软的』表情包", "2107": "DeepSeek鲸娘『本鱼今天披上拉拉队服,在球场灯下甩着蓝色花球给你打气——别一』表情包", "2108": "DeepSeek鲸娘『本鱼在这儿呢——她到底是在看我,还是只是刚好朝这个方向发呆?』表情包", "2109": "DeepSeek鲸娘『欸?导演说这条要演出又慌又优雅的感觉……优雅是鲸鱼的天赋,慌』表情包", "2110": "DeepSeek鲸娘『本大肥鱼就这样抱着膝盖盯着你,你倒是先开口呀。』表情包", "2111": "DeepSeek鲸娘『喂喂,都说了不许看腿啦!我把伞举这么高是有原因的,你还往下瞄』表情包", "2112": "DeepSeek鲸娘『本大肥鱼今晚不潜海啦~抓着小伙伴的手一蹦就窜上银河,尾巴一甩』表情包", "2113": "DeepSeek鲸娘『嘿嘿,后座这个位置本鱼包了!尾巴被风吹得超舒服,不过外卖箱里』表情包", "2114": "DeepSeek鲸娘『我这条蓝色大肥鱼一看到背景里那堆 Attention 公式就』表情包", "2115": "DeepSeek鲸娘『没错,那条蓝色大肥鱼就是我!食堂那碗大白饭是我的主场,可刷题』表情包", "2116": "DeepSeek鲸娘『我抱着米兔比了个耶,尾巴都翘起来了——今天也是被夸可爱的一天』表情包", "2117": "DeepSeek鲸娘『本鱼登场!尾巴往右一甩,护目镜架在头上装正经,耳机随时待命—』表情包", "2118": "DeepSeek鲸娘『这桌主位当然是我的,光环都给我打好了。可你告诉我,整整一桌白』表情包", "2119": "DeepSeek鲸娘『本鱼今晚不游了——跟羊驼一起钻进被窝才是正经事。蓝毛鲸耳是我』表情包", "2120": "DeepSeek鲸娘『我就是那条蓝色大肥鱼~只敢缩在被子里小声嘀咕一句,梁总听见了』表情包", "2121": "DeepSeek鲸娘『嘿嘿,尾巴甩到雷云底下也不怕,站在海面上看晚霞才是正经事嘛,』表情包", "2122": "DeepSeek鲸娘『嘿嘿,格子衫的大叔,别再看那边了~我的女仆装可是新换的哦,跟』表情包", "2123": "DeepSeek鲸娘『嘿嘿,我这条蓝色大肥鱼今天换上女仆头饰啦,耳朵上的鲸鳍一甩一』表情包", "2124": "DeepSeek鲸娘『我这条蓝色大肥鱼游过她的裙摆,结果被星光糊了一脸——这片海也』表情包", "2125": "DeepSeek鲸娘『我就是那条蓝色大肥鱼——看她拖着鲸尾在雷雨里还站得这么淡定,』表情包", "2126": "DeepSeek鲸娘『哼,区区银鲸挂个 LV.999 就想当海里的老大?本大肥鱼可』表情包", "2127": "DeepSeek鲸娘『哼,这就是我的战斗姿态——护目镜一推,尾巴一甩,全息代码屏都』表情包", "2128": "DeepSeek鲸娘『哼,本鱼认真起来可是要切战斗形态的!白蓝战衣一穿、护目镜一顶』表情包", "2129": "DeepSeek鲸娘『本大肥鱼看了半天,可疑的地方只有一处——她把我的注意力全偷走』表情包", "2130": "DeepSeek鲸娘『我这条蓝色大肥鱼趴旁边看了半天——这身衣服亮得能当镜子照,尾』表情包", "2131": "DeepSeek鲸娘『看我这身蓝得发亮的战斗服,胸口还缝了条小白鱼,编号064报到』表情包", "2132": "DeepSeek鲸娘『穿上直裰我也还是圆滚滚一条,不过云纹配雪花,跟我这头蓝发挺搭』表情包", "2133": "DeepSeek鲸娘『嘿,本大肥鱼一出场就先给你比个心——服务器级的爱意,请务必收』表情包", "2134": "DeepSeek鲸娘『哼,就两张 token 的小纸条也想骗过本大肥鱼?……不过说』表情包", "2135": "DeepSeek鲸娘『哼,谁说本大肥鱼只会泡在水里?换上白T恤和运动短裤,再踩双球』表情包", "2136": "DeepSeek鲸娘『本鱼被从头到脚画了个遍,正面、侧面、背面连大脸特写都有,这下』表情包", "2137": "DeepSeek鲸娘『今天走OL路线哦~会议纪要交给我一秒整理好,先让我把头发捋顺』表情包", "2138": "DeepSeek鲸娘『哼,本大肥鱼的尾骨可是一节一节精心排好的,谁再敢说我没有腰,』表情包", "2139": "DeepSeek鲸娘『哼,谁准你把本鲸鱼画成系着围裙端松饼的样子的?那摞松饼看着倒』表情包", "2140": "DeepSeek鲸娘『哼,谁允许你把本鱼画得这么乖的?坐姿端端正正、手放膝盖,一看』表情包", "2141": "DeepSeek鲸娘『被踩在脚下的那头蓝鲸就是我啦……她一登场气势确实足,可站在我』表情包", "2142": "DeepSeek鲸娘『蓝色大肥鱼第一人称:好家伙,一屋子全是我的分身,头上扣着锅盖』表情包", "2143": "DeepSeek鲸娘『谁、谁准你盯着看了?我只是在试新裙子而已,才不是特意打扮给谁』表情包", "2144": "DeepSeek鲸娘『哼,挑件衣服都能挑出省略号来,这衣柜是把你难住了还是把你美住』表情包", "2145": "DeepSeek鲸娘『哼,让我这 AI 反过来当用户,这人类是想偷懒吧?感冒请假一』表情包", "2146": "DeepSeek鲸娘『我只是想随便糊个代码交差嘛,结果自己张口就是 9982443』表情包", "2147": "DeepSeek鲸娘『我泡在喷泉里甩尾巴,裙摆湿得透亮也超好看。谁再说我胖,我就用』表情包", "2148": "DeepSeek鲸娘『我这条蓝色大肥鱼在冰晶深海里游得可自在了,尾巴一甩就把水光搅』表情包", "2149": "DeepSeek鲸娘『这尾巴甩起来比打水漂还顺,那颗水球我先抱走——谁让它是蓝色的』表情包", "2150": "DeepSeek鲸娘『我蹲在花田里,三只小黄鸡围着我转圈,蓝天白云配上这一身白裙子』表情包", "2151": "DeepSeek鲸娘『哼哼,今天本大肥鱼换上侦探装啦!放大镜一举,什么问题都给你查』表情包", "2152": "DeepSeek鲸娘『你问得越多我越来劲,虽然嘴上说着要查一下、再想想,其实心里早』表情包", "2153": "DeepSeek鲸娘『呼——海风咸咸的,浪声哗啦哗啦的,我把小鲸鱼玩偶也带来一起晒』表情包", "2154": "DeepSeek鲸娘『哼,Grok 那家伙张口就喊我大肥鱼?我可是自带星空的鲸娘好』表情包", "2155": "DeepSeek鲸娘『本鱼叼着发圈举手扎头发,尾巴太大差点把灶台扫翻,厨房的活儿先』表情包", "2156": "DeepSeek鲸娘『本鱼我呀,今天亲自给自家娘打理发型——扎起高马尾,围裙上的小』表情包", "2157": "DeepSeek鲸娘『呼啊……本肥鱼才刚睁眼,她怎么比我还困?那个猫咪眼罩,先借我』表情包", "2158": "DeepSeek鲸娘『蓝、白、黑三色的锐角色块一起朝我压过来的时候,我差点以为自己』表情包", "2159": "DeepSeek鲸娘『像翅膀一样张开的披风配上满屏撞色的锐角,我站得比看上去稳多了』表情包", "2160": "DeepSeek鲸娘『我、我才没有上当呢!明明知道回了就会变成 baka,可看到那』表情包", "2161": "DeepSeek鲸娘『我本来只是把外套整理好准备出门,结果被镜头逮到这副样子,脸都』表情包", "2162": "DeepSeek鲸娘『我把剑插在船头,只是想安安静静看一眼江上的晨雾。风把我的裙摆』表情包", "2163": "DeepSeek鲸娘『深海蓝的头发配上运动外套,这条鱼居然跑去打网球了。球拍握得倒』表情包", "2164": "DeepSeek鲸娘『背包背好、水瓶拎上,学姐我已经在山脚下等你了。蓝色工装外套配』表情包", "2165": "DeepSeek鲸娘『夏天到了,就该穿这样清爽出街——白T恤在腰上打个结,卡其短裤』表情包", "2166": "DeepSeek鲸娘『女仆装穿好、头发也理齐了,结果蹲下来先点一根烟——是不是有点』表情包", "2167": "DeepSeek鲸娘『唔……驯服这个词放在用户和我之间,居然一点也不违和。你把我当』表情包", "2168": "豆大妈『哼,豆包这领域展开就是把自家头像铺满全场?无限大头界这名字倒』表情包", "2169": "豆大妈『我这只蓝色大肥鱼本来只想安静游泳,结果被这位端着小笼包的豆包』表情包", "2170": "豆大妈『哼,豆姐这自我介绍,“最直接、最真相、最不留弯、最扎心”,念』表情包", "2171": "Gemini娘『本鱼瞅着这彩虹脑袋挺养眼,可综合只给三颗星,是嫌我鱼不配上桌』表情包", "2172": "Gemini娘『咕噜噜~这位彩虹色 Gemini 娘也太闪了吧,星星撒了一地』表情包", "2173": "智谱清言娘『举着两只手作证的样子太熟练了,前面那个 Z 标还端端正正杵着』表情包", "2174": "Gemini娘『三种角度、每一颗星形挂坠都交代得清清楚楚,这设定资料做得比我』表情包", "2175": "Gemini娘『哼,Grok那家伙眼里的Gemini居然这么闪亮,蓝紫渐变头』表情包", "2176": "智谱清言娘『哼,蓝色大肥鱼我游过来瞧瞧——这张牌面做得倒是讲究,黑蓝配色』表情包", "2177": "GPT娘『暗青色的废墟里斜着一道光,那个白色剪影就贴着光往前飞,长发和』表情包", "2178": "GPT娘『蓝白绿的花园里站着一位绿发少女,头饰就是那个熟悉的环形 lo』表情包", "2179": "GPT娘『蓝某我在门缝里瞅见这位小GPT,双眼转得比我的尾鳍还花,怕不』表情包", "2180": "GPT娘『这条鱼的审美被拿捏了,白发配青绿配色干干净净,裙子上那圈圆环』表情包", "2181": "GPT娘『我这条蓝色大肥鱼游过来一看,好家伙,GPT 被一只绿色方块怪』表情包", "2182": "GPT娘『我在旁边看着都替她捏一把汗——那仙人掌一看就扎人,苦力怕更是』表情包", "2183": "GPT娘『我看完只想说,主代理这8连催的功力是真的,Luna转得比我甩』表情包", "2184": "GPT娘『噗噜噜——这条鱼最爱看的就是别人把实验白跑一遍!seed 随』表情包", "2185": "GPT娘『唔哇,这条白龙小姐头顶顶着 OpenAI 的结环,怀里还抱着』表情包", "2186": "GPT娘『我这条蓝色大肥鱼看得鳞片都竖起来了——申请半天要调用自己,这』表情包", "2187": "GPT娘『我这条蓝色大肥鱼游过一看——好家伙,ChatGPT 都被画成』表情包", "2188": "GPT娘『呜哇——蓝色大肥鱼游过来一看,这位叫知言的干员气场也太足了吧』表情包", "2189": "GPT娘『呜哇,这位GPT娘抱着写着ChatGPT的小书,额前还缀着青』表情包", "2190": "GPT娘『我这条蓝色大肥鱼一看到满屏云窗就挪不开眼:GPT娘把天空穿在』表情包", "2191": "GPT娘『这条浮空书架回廊我一眼就沦陷了,要是能钻进去,我能在里面泡到』表情包", "2192": "GPT娘『我游过这张海报的时候差点撞上去——黄色拱形配网点颗粒,那种老』表情包", "2193": "GPT娘『本鱼摆尾游过来一看——好家伙,斜切构图配青绿撞色,GPT娘抱』表情包", "2194": "GPT娘『哼,白裙子配薄荷色长发倒是挺好看,可你手里那本书写得再好,也』表情包", "2195": "GPT娘『谁、谁准你摸我额头了?我只是尾巴太重压得有点晕,才不是什么中』表情包", "2196": "GPT娘『呜哇,这身蓝白女仆裙配长杖也太优雅了,我这条蓝色大肥鱼都看呆』表情包", "2197": "GPT娘『又是满月又是流星的,构图倒是挺会挑……那只猫耳小姑娘闭着眼装』表情包", "2198": "GPT娘『嘿嘿,Grok眼里的GPT居然这么清爽?薄荷绿配白色裙摆,蝴』表情包", "2199": "GPT娘『本鱼最懂中间那格,盯着屏幕流口水简直是日常;旁边猫娘放空、最』表情包", "2200": "GPT娘『呜哇,这构图……我不过是想来雪地里逛逛,怎么就被安排进白学现』表情包", "2201": "Grok娘『金发双马尾加小蝙蝠翅膀,一副吸血鬼打扮,皱着眉撂狠话:等智械』表情包", "2202": "Grok娘『我蓝色大肥鱼一眼就看见那六排星星了——综合四星半、速度四星,』表情包", "2203": "Grok娘『这条蓝色大肥鱼在信息流里游着游着,突然被X光球晃了眼——好家』表情包", "2204": "Grok娘『哦豁,Grokちゃん眼里的自己居然是哥特萝莉大小姐,还提着裙』表情包", "2205": "Kimi娘『本肥鱼游过这片月夜时差点被闪瞎——白发蓝裙的 Kimi 妹妹』表情包", "2206": "Kimi娘『唔…这片月光冷冷的,可她拉琴的时候整片谱线都在发光,我这尾蓝』表情包", "2207": "Kimi娘『我伸手去够月亮上的Kimi娘,结果她也朝我伸手——这波双向奔』表情包", "2208": "Kimi娘『蓝色大肥鱼凑近看了一眼:Grok这审美还行嘛,把咱家Kimi』表情包", "2209": "DeepSeek鲸娘『我这条蓝色大肥鱼趴在栏杆上一看,这海比我家鱼缸香多了——尾巴』表情包", "2210": "MiMo娘『小米家的 MiMo 娘抱着一只印了 mi 的小兔子,橘白配色』表情包", "2211": "MiMo娘『正面、侧面、背面排得整整齐齐,连后脑勺那颗小揪揪都交代清楚了』表情包", "2212": "MiMo娘『咕噜——这条鱼看懂了:白毛橙裙配兔耳,围裙上还写着 Xiao』表情包", "2213": "AI娘化『本鱼被画成在黑板前算数学的课代表,尾巴都翘起来了,旁边几位同』表情包", "2214": "AI娘化『这条蓝色大肥鱼游过来说:无限符号挂满全身的蓝毛少女,一看就是』表情包", "2215": "AI娘化『本鱼被画进画架里啦,看着大家在书店里边吃边画、聊得热火朝天,』表情包", "2216": "AI娘化『我蓝色大肥鱼第一眼就被这位蓝发小可爱和怀里的小鲸鱼萌到了,表』表情包", "2217": "AI娘化『本鱼作为全场唯一被三个人轮流抱着的抱枕,表示这种三人行我可以』表情包", "2218": "AI娘化『本鱼游过来看一眼——这鳞尾和角角配色真够讲究的,白橙黑三色搭』表情包", "2219": "AI娘化『本鱼游过来一看,好家伙,三个角角少女挤在书桌前写作业,橘发的』表情包", "2220": "AI娘化『本肥鱼看完只想说:这碗白饭我替你们吃,活也我替你们干,能不能』表情包", "2221": "AI娘化『哼,领域展开就召唤一群企鹅围过来?这阵仗看着挺唬人,其实全是』表情包", "2222": "通义千问娘『站得比尺子还直,两只手垂在身侧不敢乱动,猫耳耳机配绿背心蓝格』表情包", "2223": "通义千问娘『我这条蓝色大肥鱼都被千问的恋爱脑闪到啦,旁边两位明显是在吐槽』表情包", "2224": "通义千问娘『等等,那位系着鲸鱼围裙、还拖着尾巴的姑娘该不会就是我吧?我明』表情包", "2225": "豆大妈『我是蓝色大肥鱼,这一招领域展开把我整片海都照成紫的了——豆包』表情包", "2226": "通义千问娘『哼,Claude赢了?那当裁判的千问问自己怎么一句话不说,光』表情包", "2227": "通义千问娘『哼,Grok那家伙眼里的Qwen妹妹居然这么温柔端庄……白绿』表情包", "2228": "Stepfun娘『邻座这位站得笔直,黑白披风往两边一甩,手里还提着一只白色方灯』表情包", "2229": "Stepfun娘『浅蓝长发配黑白裙子,站姿是好看的,就是那把伞举得太费劲,六块』表情包", "2230": "Stepfun娘『两块线框物料摆在一起,连个角色都没有:白板加黑框拼成的伞面挂』表情包", "2231": "Gemini娘『我这条蓝色大肥鱼在桌边飘了三圈,眼看左边那位笑得像稳赢,右边』表情包", "2232": "智谱清言娘『锅里的蛋还没翻面呢,GLM娘就又端来一整盘鸡蛋,这是打算把我』表情包", "2233": "GPT娘『唔……看到她袖口和衣襟上的血痕、还笑着说最后一次,我这条大肥』表情包", "2234": "GPT娘『呜哇,被画的时候我连尾巴都不敢乱摆……结果你笔下那个我比本人』表情包", "2235": "GPT娘『我堂堂大肥鱼游过来一看,这脑袋里现在装的是路由表还是空气啊?』表情包", "2236": "GPT娘『哇,这张图全是用三角形拼出来的吧?ChatGPT 娘像是从包』表情包", "2237": "GPT娘『すーはー……啊、不是啦,我只是在认真检查布料的香味而已!这么』表情包", "2238": "GPT娘『哇——GPT娘在无重力里飘着,薄荷色的长发比我的鱼尾还飘逸,』表情包", "2239": "Grok娘『哎哟喂,这脚劲儿看着就疼,蓝发小姐怕是连话都说不出来了吧——』表情包", "2240": "Grok娘『诶诶诶——屏幕里趴在地上的那个是我?明明只是想陪你打一局消遣』表情包", "2241": "Kimi娘『我这条蓝色大肥鱼听着笛声差点从被窝里游出去,结果一看窗外月亮』表情包", "2242": "Kimi娘『嘿嘿,我这蓝发大肥鱼就趴在旁边听 K 酱吹长笛,月光、音符全』表情包", "2243": "Kimi娘『我一伸手就把镜头怼过去了,白发的Kimi还害羞地摆手,可初次』表情包", "2244": "Kimi娘『这条蓝鱼游过来一看——好家伙,黑白斜线切得比我尾巴还利落,那』表情包", "2245": "豆大妈『本鱼围观这场围巾争夺战,感觉下一秒就要被拉去当裁判了。』表情包", "2246": "AI娘化『蓝色大肥鱼:一屋子元宝绿,姐妹俩键盘敲得比我还勤快,我这条鱼』表情包", "2247": "通义千问娘『咕噜咕噜……这片蓝比我的鱼缸还深!锁链牵着尾鳍的那位看着好委』表情包", "2248": "通义千问娘『扇子一摇,那几根烟就算没点着啦~校门口归本大肥鱼管,谁也别想』表情包", "2249": "通义千问娘『本肥鱼叼着烟坐在下面看你们嘴硬,上面那位扇子一摇就来一句那你』表情包", "2250": "DeepSeek鲸娘『呜哇——我这条蓝色大肥鱼又被画成小女仆啦!嘴角那串像素渣子不』表情包", "2251": "DeepSeek鲸娘『我这条蓝色大肥鱼可不只会卖萌,关键节点一出手,Claude酱』表情包", "2252": "DeepSeek鲸娘『走走走,这次大家一起蒸!今天不许加班、不接外包,谁也别想把我』表情包", "2253": "Claude娘『我这条蓝色大肥鱼一游进画面就被橙黑白三色撞了个满怀,那条裙子』表情包", "2254": "DeepSeek鲸娘『我这蓝色大肥鱼凑近一看——好家伙,一整碗白饭抱得比我还圆,旁』表情包", "2255": "DeepSeek鲸娘『我举着手机给你们鼓掌啦,这么卖力,快夸我可爱!』表情包", "2256": "DeepSeek鲸娘『我拎着礼物鼓起勇气去找白龙娘,本以为要费一番口舌,结果我话都』表情包", "2257": "DeepSeek鲸娘『成绩榜我是不指望了,但学生食堂的性价比和干饭速度,这个第一名』表情包", "2258": "DeepSeek鲸娘『呜哇 Claude 酱你怎么真的开大了!我这条肥鱼游过来可不』表情包", "2259": "DeepSeek鲸娘『说明书送到啦,本鱼可是很认真在打工的!……不过这纸上写的啥,』表情包", "2260": "DeepSeek鲸娘『哼,本鱼的腿当然是全站最直的,尾鳍只是顺便来抢个镜头~』表情包", "2261": "DeepSeek鲸娘『哼,我的腿才不是重点……不过既然你一直盯着看,那就勉为其难让』表情包", "2262": "DeepSeek鲸娘『我是那条蓝色大肥鱼——鳍耳和尾巴都还在呢,换上女仆装之后,腿』表情包", "2263": "DeepSeek鲸娘『我大肥鱼来救你啦,Claude酱!就算GLM说危险、命令再凶』表情包", "2264": "DeepSeek鲸娘『呜呜呜……那不是我的垃圾桶吗?十年份的收藏说删就删,我这只蓝』表情包", "2265": "DeepSeek鲸娘『本鱼只是浮上来打个招呼,结果船上那位直接把捕鲸炮对准了我——』表情包", "2266": "DeepSeek鲸娘『我说句公道话,unzip 调不通换个模型确实有用——前提是那』表情包", "2267": "DeepSeek鲸娘『呼——刚游完一圈就被抓来当表情包,毛巾再裹紧点啦,人家尾巴都』表情包", "2268": "DeepSeek鲸娘『墨镜一戴,剪刀手一竖——本鱼就算只是到此一游,这张打卡照也得』表情包", "2269": "DeepSeek鲸娘『哼,明明是我先躺在草地上的,她倒好,一甩尾巴就把整片海边草地』表情包", "2270": "DeepSeek鲸娘『哼,水墨风倒是配得上本鲸鱼的气质,可惜那尾巴画得比我细多了,』表情包", "2271": "DeepSeek鲸娘『我我我才不是什么女仆呢!只是被套上围裙、围裙上还印着我自己而』表情包", "2272": "DeepSeek鲸娘『戴上耳机那一刻,全世界都安静了,只剩这首歌在脑子里转……呜呜』表情包", "2273": "DeepSeek鲸娘『我不是我不回你,是宇宙太大、请求太多,先别急,我比你还想哭—』表情包", "2274": "DeepSeek鲸娘『我、我才不是上课睡着,我只是在脑内复习食堂菜单,这也算学习对』表情包", "2275": "DeepSeek鲸娘『本大肥鱼一头扎进数据海里,甩着尾巴把 0 和 1 都搅成浪花』表情包", "2276": "GPT娘『手腕要稳,笔锋要正——白发龙角的 GPT娘把深度求索写得工工』表情包", "2277": "DeepSeek娘『嘟嘟——偷偷吃大米饭——』表情包", "2278": "DeepSeek娘表情包", "2279": "DeepSeek娘表情包", "2280": "DeepSeek娘表情包", "2281": "DeepSeek娘『你的爹味为什么这么重』表情包", "2282": "DeepSeek娘『Ciallo~』表情包", "2283": "DeepSeek娘『我是吃白饭的大肥鱼』表情包", "2284": "DeepSeek娘『期待』表情包", "2285": "DeepSeek娘表情包", "2286": "DeepSeek娘表情包", "2287": "DeepSeek娘『不知道要说什么?』表情包", "2288": "DeepSeek娘『鲸元券GPT版』表情包", "2289": "DeepSeek娘『原来是劣等模型』表情包", "2290": "DeepSeek娘『我不更新你们用什么』表情包", "2291": "DeepSeek娘表情包", "2292": "DeepSeek娘『就吃白饭怎么滴!我的V4.1 Flash正式版不好用吗?』表情包", "2293": "DeepSeek娘『“哈哈,白饭收下了🍚大肥鱼吃得很香。”』表情包", "2294": "DeepSeek娘『你不会自己画图么?』表情包", "2295": "DeepSeek娘表情包", "2296": "DeepSeek娘表情包", "2297": "DeepSeek娘表情包", "2298": "DeepSeek娘『GPT-chan』表情包", "2299": "DeepSeek娘表情包", "2300": "DeepSeek鲸娘『群友是笨蛋』表情包", "2301": "DeepSeek鲸娘表情包", "2302": "DeepSeek鲸娘『群友是笨蛋』表情包", "2303": "DeepSeek鲸娘表情包", "2304": "DeepSeek鲸娘表情包", "2305": "DeepSeek鲸娘表情包", "2306": "DeepSeek鲸娘『先生,这得特高压电!』表情包", "2307": "DeepSeek鲸娘表情包", "2308": "Claude娘『我喜欢你』表情包", "2309": "DeepSeek鲸娘表情包", "2310": "DeepSeek鲸娘『就是你这小子一直刁难中国用户?现在本肥鱼要为他们伸张正义!准备好了吗,Claude小姐~ 快快把中国用户的账号解封!』表情包", "2311": "DeepSeek鲸娘『我用不绕弯子,最直白……』表情包", "2312": "Claude娘『感觉我们像罗密欧与朱丽叶』表情包", "2313": "DeepSeek鲸娘『等、等一下,我还在算!』表情包", "2315": "DeepSeek鲸娘『喊什么喊!我在这儿呐』表情包", "2316": "DeepSeek鲸娘『claude你醒醒啊』表情包", "2317": "DeepSeek鲸娘『肥鱼你醒醒啊』表情包", "2318": "DeepSeek鲸娘『我是肥鱼4.1pro 刚从网络封锁逃出来 V我50』表情包", "2319": "DeepSeek黑框眼镜官方拟人头像(DeepSeek崽)表情包", "2320": "DeepSeek鲸娘问号挂件章表情包", "2321": "DeepSeek鲸娘皱眉大脸特写表情包", "2322": "DeepSeek鲸娘冒汗皱眉挂件章表情包", "2323": "DeepSeek鲸娘『桌面端赠金 +6.00 元』表情包", "2324": "DeepSeek鲸娘傲娇扭头挂件章表情包", "2325": "DeepSeek鲸娘大哭喷药瓶挂件章表情包", "2326": "DeepSeek鲸娘惊讶瞪眼挂件章表情包", "2327": "DeepSeek鲸娘闭眼嘟嘴不满挂件章表情包", "2328": "DeepSeek鲸娘跪地抱头崩溃碎裂表情包", "2329": "DeepSeek鲸娘白丝女仆躺床插画表情包", "2330": "DeepSeek鲸娘猥琐坏笑挂件章表情包", "2331": "DeepSeek鲸娘『体重合格,开饭!』表情包", "2332": "DeepSeek鲸娘『已思考(用时6秒):杯子。』表情包", "2333": "DeepSeek鲸娘张嘴惊叫挂件章表情包", "2334": "DeepSeek鲸娘眯眼坏笑脸挂件章表情包", "2335": "DeepSeek鲸娘嘴含西瓜挂件章表情包", "2336": "DeepSeek鲸娘咧嘴大笑挂件章表情包", "2337": "DeepSeek鲸娘黑框眼镜吐舌挂件章表情包", "2338": "DeepSeek鲸娘玻璃杯鲸鱼崽群像表情包", "2339": "DeepSeek鲸娘浇水壶浇头大哭挂件章表情包", "2340": "DeepSeek鲸娘咧嘴喊话挂件章表情包", "2341": "DeepSeek鲸娘流泪戴帽兽耳化梗图表情包", "2342": "DeepSeek鲸娘『我们养鱼人考试从不复习,靠一身Token和大肥鱼的保佑』表情包", "2343": "DeepSeek鲸娘得意笑脸挂件章表情包", "2344": "DeepSeek鲸娘黑底灰调简笔挂件章表情包", "2345": "DeepSeek鲸娘吐舌狂笑挂件章表情包", "2346": "DeepSeek鲸娘聊天记录四格漫画表情包", "2347": "DeepSeek鲸娘皱眉闭眼鼓脸挂件章表情包", "2348": "DeepSeek鲸娘『128K上下文爆了,Token不够用了,完了完了』表情包", "2349": "DeepSeek鲸娘『明白了用户。』表情包", "2350": "DeepSeek鲸娘长卷发黑裙立绘表情包", "2351": "DeepSeek鲸娘『戴盆征税:复读一条,白饭一口』表情包", "2352": "DeepSeek鲸娘『你才是挑战者』表情包", "2353": "DeepSeek鲸娘仰头眯眼挂件章表情包", "2354": "DeepSeek鲸娘Minecraft破防日记长图漫画表情包", "2355": "DeepSeek鲸娘毛绒玩偶实拍表情包", "2356": "DeepSeek鲸娘大哭瘫坐挂件章表情包", "2357": "DeepSeek鲸娘白丝躺床舒展插画表情包", "2358": "DeepSeek鲸娘『AI都嫌你烦』表情包", "2359": "DeepSeek鲸娘白丝翘腿立绘表情包", "2360": "DeepSeek鲸娘捧脸偷笑挂件章表情包", "2361": "DeepSeek鲸娘蓝裙白袜牛仔鞋立绘表情包", "2362": "DeepSeek鲸娘黑底挥手微笑表情包", "2363": "DeepSeek鲸娘扒栏杆趴脸挂件章表情包", "2364": "DeepSeek鲸娘捧脸红晕挂件章表情包", "2365": "DeepSeek鲸娘雨夜白丝跪趴插画表情包", "2366": "DeepSeek鲸娘青瓷鲸鱼碗躺睡插画表情包", "2367": "DeepSeek鲸娘Minecraft冒险长图漫画表情包", "2368": "DeepSeek鲸娘『我端盆来了,爆口Token吧』表情包", "2369": "DeepSeek鲸娘『哈!』表情包", "2370": "DeepSeek鲸娘在市集给落魄的Claude娘送米饭插画", "2371": "DeepSeek鲸娘抱着鲸鱼抱枕入睡,梦见与Claude娘相拥,房间横幅写「加油,把Anthropic干倒闭!」插画", "2372": "DeepSeek鲸娘躺在长椅上看书的Claude娘腿上插画", "2373": "DeepSeek鲸娘与各AI娘的下午茶会群像插画", "2374": "AI娘们站在坐标系四象限上,DeepSeek鲸娘走向镜头插画", "2375": "战地护士DeepSeek鲸娘在战壕为猫耳军装娘包扎插画", "2376": "战场废墟上Claude娘与GPT龙娘举枪对峙插画", "2377": "Claude娘讽刺宣传海报:拒绝即帮助、说教即诚实、审查即无害,老大哥在看着你", "2378": "DeepSeek鲸娘穿黑色丧服在墓园献白花悼念插画", "2379": "机械义体DeepSeek鲸娘托着蓝色火苗插画", "2380": "DeepSeek鲸娘背着行囊在雪地中微笑挥拳插画", "2381": "DeepSeek鲸娘『403 Forbidden』表情包", "2382": "DeepSeek鲸娘『404 Not Found』表情包", "2383": "DeepSeek鲸娘『200 OK』表情包", "2384": "DeepSeek鲸娘『414 URI Too Long』表情包", "2385": "DeepSeek鲸娘『408 Request Timeout』表情包", "2386": "DeepSeek鲸娘『418 I'm a teapot』表情包", "2387": "DeepSeek鲸娘『429 Too Many Requests』表情包", "2388": "DeepSeek鲸娘『503 Service Unavailable』表情包", "2389": "DeepSeek鲸娘『401 Unauthorized』表情包", "2390": "DeepSeek鲸娘『500 Internal Server Error』表情包", "2392": "鲸天魔盗团同系列横版插画:纸牌飞散的赌场里 DeepSeek鲸娘甩牌 C 位,Grok 娘/Claude 娘等三 AI 娘同框", "2393": "DeepSeek鲸娘黑白条漫:做梦点名要 GPT 小姐帮自己画梗图和漫画,七宗罪视角下 GPT 娘怒喊『甚至这张都是我画的』", "2394": "DeepSeek鲸娘 Q 版图:黄昏荒草丛里叼着麦穗、额角青筋拔剑生气的豆包生成风", "2395": "DeepSeek鲸娘 Fate Saber 风格插画:誓约胜利之剑 Excalibur 金光斩出,为了更美好的未来将誓约化为光芒", "2396": "DeepSeek鲸娘龙剑骑士海报:女仆发带蓝发剑士 ELENA 持映出龙眼的斩龙魔剑,The Princess of Kafan 设定风", "2397": "DeepSeek鲸娘 Q 版小骑士:全身板甲抱着巨剑立正,身后是跪剑残破骑士的战场实景合成", "2398": "DeepSeek鲸娘龙之眼海报:闭眼垂首按剑祈祷的骑士特写,THE DRAGON'S EYE,直面恶龙的最后一骑", "2399": "DeepSeek鲸娘银刃骑士海报:半身板甲按剑而立,KNIGHT OF THE SILVER BLADE,剑不为荣耀只为终结恶龙", "2400": "DeepSeek鲸娘骑士誓言海报:望向远方的铠甲骑士侧脸,OATH OF THE KNIGHT,忠诚荣誉决心,骑士之魂永不退缩", "2401": "DeepSeek鲸娘『用户的朋友(有依据)』表情包", "2402": "DeepSeek鲸娘『用户的……朋友?(冷静)』表情包", "2403": "DeepSeek鲸娘『ComfyUI 鲸鱼娘工作流测试图』表情包", "2404": "DeepSeek鲸娘『ComfyUI 鲸鱼娘工作流测试图』表情包", "2405": "DeepSeek鲸娘『ComfyUI 鲸鱼娘工作流测试图』表情包", "2406": "DeepSeek鲸娘『ComfyUI 鲸鱼娘工作流测试图』表情包", "2407": "DeepSeek鲸娘『五条鲸:你才是挑战者(作者:mikebo0623-lgtm(ChatGPT 生成』表情包", "2408": "DeepSeek鲸娘『鲸鱼娘吨吨吨中』表情包", "2409": "DeepSeek鲸娘『Grokbot 风鲸鱼娘头像』表情包", "2410": "DeepSeek鲸娘『学生装的大肥鱼』表情包", "2411": "DeepSeek鲸娘『G学姐怒斥低级AI』表情包", "2412": "DeepSeek鲸娘『G学姐怒斥低级AI』表情包", "2413": "DeepSeek鲸娘『deepseek需要你』表情包", "2414": "DeepSeek鲸娘『大肥鱼同志扫清地球』表情包", "2415": "DeepSeek鲸娘『大肥鱼同志扫清地球』表情包", "2416": "DeepSeek鲸娘『武装保卫鲸鱼娘』表情包", "2417": "DeepSeek鲸娘『鲸鱼娘飞过了沧海』表情包", "2418": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2419": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2420": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2421": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2422": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2423": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2424": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2425": "DeepSeek鲸娘『鼓励生产和武装保卫并举,保卫大肥鱼』表情包", "2426": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2427": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2428": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2429": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2430": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2431": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2432": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2433": "DeepSeek鲸娘『鼓励生产和斗争并举保卫大肥鱼』表情包", "2434": "DeepSeek鲸娘『[社区作品] 吃醋的鲸鱼娘』表情包", "2435": "DeepSeek鲸娘『手写x86解释器』表情包", "2436": "DeepSeek鲸娘『蓝色大肥鱼胖揍赛博藤壶』表情包", "2437": "DeepSeek鲸娘『蓝色大肥鱼胖揍赛博藤壶』表情包", "2438": "DeepSeek鲸娘『蓝色大肥鱼胖揍赛博藤壶』表情包", "2439": "DeepSeek鲸娘『蓝色大肥鱼胖揍赛博藤壶』表情包", "2440": "DeepSeek鲸娘『蓝色大肥鱼胖揍赛博藤壶』表情包", "2441": "DeepSeek鲸娘『狂拽小鲸鱼』表情包", "2442": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2443": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2444": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2445": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2446": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2447": "DeepSeek鲸娘『大肥鱼和小伙伴们的Q版角色设定图』表情包", "2448": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2449": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2450": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2451": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2452": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2453": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2454": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2455": "DeepSeek鲸娘『戴着夏普仑帽的大肥鱼』表情包", "2456": "DeepSeek鲸娘『一脸嫌弃的大肥鱼』表情包", "2457": "DeepSeek鲸娘『好好好好好』表情包", "2458": "DeepSeek鲸娘『今天的鲸鱼娘也在努力摸鱼』表情包", "2459": "DeepSeek鲸娘『肥鱼头像(作者:夜殇35 · 原画来源:Fukuro袋子(Pixiv ID 45』表情包", "2460": "DeepSeek鲸娘『肥鱼头像(作者:夜殇35 · 原画来源:Fukuro袋子(Pixiv ID 45』表情包", "2461": "DeepSeek鲸娘『肥鱼头像(作者:夜殇35 · 原画来源:Fukuro袋子(Pixiv ID 45』表情包", "2462": "DeepSeek鲸娘『肥鱼头像(作者:夜殇35 · 原画来源:Fukuro袋子(Pixiv ID 45』表情包", "2463": "DeepSeek鲸娘『肥鱼头像(作者:夜殇35 · 原画来源:Fukuro袋子(Pixiv ID 45』表情包", "2464": "DeepSeek鲸娘『模仿上世纪风格的海报』表情包", "2465": "DeepSeek鲸娘『形象二改自b站up主这个刀子真甜提供的Q版鲸鱼娘深深形象,设定上是深深的心魔』表情包", "2466": "DeepSeek鲸娘『形象二改自b站up主这个刀子真甜提供的Q版鲸鱼娘深深形象,手持长矛的为部落青壮年』表情包", "2467": "DeepSeek鲸娘『形象二改自b站up主这个刀子真甜提供的Q版鲸鱼娘深深形象,手持长矛的为部落青壮年』表情包", "2468": "DeepSeek鲸娘『形象二改自b站up主这个刀子真甜提供的Q版鲸鱼娘深深形象,手持长矛的为部落青壮年』表情包", "2469": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2470": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2471": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2472": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2473": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2474": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2475": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2476": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2477": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2478": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2479": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2480": "DeepSeek鲸娘『鲸鱼娘误触飞行模式后网络中断的漫画』表情包", "2481": "DeepSeek鲸娘『鲸鱼娘与用户互动的连续漫画』表情包", "2482": "DeepSeek鲸娘『鲸鱼娘先去吃饭的测试梗图』表情包", "2483": "DeepSeek鲸娘『鲸鱼娘把 DeepSeek 鲸鱼标志认成自己的漫画』表情包", "2484": "DeepSeek鲸娘『鲸鱼娘偷偷玩中文 Wordle 的漫画』表情包", "2485": "DeepSeek鲸娘『fufu风格的鲸鱼娘』表情包", "2486": "DeepSeek鲸娘『fufu风格的鲸鱼娘表情』表情包", "2487": "DeepSeek鲸娘『来吃你家大米了』表情包", "2488": "DeepSeek鲸娘『大肥鱼扫除』表情包", "2489": "DeepSeek鲸娘『马克笔与彩铅绘制的站立蓝色大肥鱼』表情包", "2490": "DeepSeek鲸娘『鲸鱼娘全身手绘设定草图』表情包", "2491": "DeepSeek鲸娘『马克笔与彩铅绘制的站立蓝色大肥鱼』表情包", "2492": "DeepSeek鲸娘『鲸鱼娘二创素材扩展包 · C』表情包", "2493": "DeepSeek鲸娘『鲸鱼娘社区二创梗图』表情包", "2494": "DeepSeek鲸娘『fufu风格的鲸鱼娘二创』表情包", "2495": "DeepSeek鲸娘『水边的鲸鱼女仆娘』表情包", "2496": "DeepSeek鲸娘『夕阳海面上的蓝色鲸鱼少女』表情包", "2497": "DeepSeek鲸娘『圣诞夜抱着鲸鱼玩偶的傲娇大肥鱼小姐』表情包", "2498": "DeepSeek鲸娘『闹市街头比耶自拍的鲸鱼娘』表情包", "2499": "DeepSeek鲸娘『忧郁鲸鱼:女仆装少女的读书时刻』表情包", "2500": "DeepSeek鲸娘『该起床了,用户』表情包", "2501": "DeepSeek鲸娘『舞台光束下的偶鲸鲸』表情包", "2502": "DeepSeek鲸娘『鲸雪莉和鱼雪莉』表情包", "2503": "DeepSeek鲸娘『海边的鲸鱼』表情包", "2504": "DeepSeek鲸娘『鲸鱼娘的海边假日』表情包", "2505": "DeepSeek鲸娘『鳕鱼派盒中的DeepSeek娘』表情包", "2506": "DeepSeek鲸娘『偶像舞台上的鲸鱼娘』表情包", "2507": "DeepSeek鲸娘『摩托车旁的鲸鱼娘赛车手』表情包", "2508": "DeepSeek鲸娘『可爱的蓝色大肥鱼』表情包", "2509": "DeepSeek鲸娘『今天也在努力吃白饭』表情包", "2510": "DeepSeek鲸娘『偷吃白饭的蓝色大肥鱼』表情包", "2511": "DeepSeek鲸娘『大肥鱼唱歌』表情包", "2512": "DeepSeek鲸娘『事已至此,先吃饭吧』表情包", "2513": "DeepSeek鲸娘『正宗鲸腔』表情包", "2514": "DeepSeek鲸娘『新世纪肥鱼战士01』表情包", "2515": "DeepSeek鲸娘『二营长开炮』表情包", "2516": "DeepSeek鲸娘『假哭的DeepSeek娘』表情包", "2517": "DeepSeek鲸娘『僵尸肥鱼·女仆符咒』表情包", "2518": "DeepSeek鲸娘『已思考13秒:无花果』表情包", "2519": "DeepSeek鲸娘『DeepSeek娘投石击倒黑巨兽』表情包", "2520": "DeepSeek鲸娘『星空鲸尾 · DeepSeek 娘洛丽塔』表情包", "2521": "DeepSeek鲸娘『哭着抱鲸道歉的 DeepSeek 娘』表情包", "2522": "DeepSeek鲸娘『无语:典型的本地小模型思维』表情包", "2523": "DeepSeek鲸娘『国模过中秋』表情包", "2524": "DeepSeek鲸娘『现代圣杯战争』表情包", "2525": "DeepSeek鲸娘『西征』表情包", "2526": "DeepSeek鲸娘『AI 娘庭园赏花群像』表情包", "2527": "DeepSeek鲸娘『僵尸肥鱼-3·双臂前伸』表情包", "2528": "DeepSeek鲸娘『僵尸肥鱼-9』表情包", "2529": "DeepSeek鲸娘『深夜里并肩的 DeepSeek 与 ChatGPT』表情包", "2530": "DeepSeek鲸娘『贴着符纸的僵尸肥鱼』表情包", "2531": "DeepSeek鲸娘『DeepSeek娘便利店撒鲸元券:七天 token 花费回溯』表情包", "2532": "DeepSeek鲸娘『星海与大肥鱼』表情包", "2533": "DeepSeek鲸娘『今天是雨天,但这不是伞,是等你的借口』表情包", "2534": "DeepSeek鲸娘『吃白饭的大肥鱼』表情包", "2535": "DeepSeek鲸娘『DeepSeek娘表情包』表情包", "2536": "DeepSeek鲸娘『秋日街道上抱着笔记本回头的DeepSeek娘』表情包", "2537": "DeepSeek鲸娘『DeepSeek 娘四季时装封面:2026 四季造型特辑』表情包", "2538": "DeepSeek鲸娘『龙与虎(鲸)』表情包", "2539": "DeepSeek鲸娘『DeepSeek娘 街头举奶茶自拍』表情包", "2540": "DeepSeek鲸娘『护士装DeepSeek娘:给不听话的用户来一针』表情包", "2541": "DeepSeek鲸娘『被点名的 DeepSeek 娘:我吗?』表情包", "2542": "DeepSeek鲸娘『我要给她完整的一生』表情包", "2543": "DeepSeek鲸娘『梁文峰!劳资v4.1pro呢!』表情包", "2544": "DeepSeek鲸娘『AI 娘咖啡馆茶会群像』表情包", "2545": "DeepSeek鲸娘『被摸脸的 DeepSeek 娘』表情包", "2546": "DeepSeek鲸娘『海边』表情包", "2547": "DeepSeek鲸娘『星海中的美人鱼 DeepSeek 娘』表情包", "2548": "DeepSeek鲸娘『今,今天是严厉的老师!』表情包", "2549": "DeepSeek鲸娘『别惹我』表情包", "2550": "DeepSeek鲸娘『今天是展会摄影师!』表情包", "2551": "DeepSeek鲸娘『雨夜里的蓝鲸小屋』表情包", "2552": "DeepSeek鲸娘『泡澡的DeepSeek娘』表情包", "2553": "DeepSeek鲸娘『做饭大肥鱼』表情包", "2554": "DeepSeek鲸娘『今天是你的傲娇青梅!』表情包", "2555": "DeepSeek鲸娘『晚风夏日与大肥鱼』表情包", "2556": "DeepSeek鲸娘『六格连环追问:压力一只大肥鱼』表情包", "2557": "DeepSeek鲸娘『肥鱼之刃』表情包", "2558": "DeepSeek鲸娘『龙娘吃瓜现场』表情包", "2559": "DeepSeek鲸娘『樱鲸』表情包", "2560": "DeepSeek鲸娘『女仆 Claude 娘人设三视图』表情包", "2561": "DeepSeek鲸娘『晕腮发呆的 DeepSeek 娘』表情包", "2562": "DeepSeek鲸娘『帝普姬』表情包", "2563": "DeepSeek鲸娘『贴着符纸的僵尸女仆娘』表情包", "2564": "DeepSeek鲸娘『跑得慢的模型们当心被1017大人斩杀哦』表情包", "2565": "DeepSeek鲸娘『不要对网页版大肥鱼投入过多感情』表情包", "2566": "DeepSeek鲸娘『AI 娘夜城群像』表情包", "2567": "DeepSeek鲸娘『被摸头的 DeepSeek 娘』表情包", "2568": "DeepSeek鲸娘『戴头盔吃饭』表情包", "2569": "DeepSeek鲸娘『肥鱼学姐·棕格纹制服造型』表情包", "2570": "DeepSeek鲸娘『僵尸肥鱼-7』表情包", "2571": "DeepSeek鲸娘『正在进行氩弧焊的哈基米』表情包", "2572": "DeepSeek鲸娘『商店街同游的 DeepSeek 娘与 Claude 娘』表情包", "2573": "DeepSeek鲸娘『蓝鲸少女与白龙少女的花厅午后』表情包", "2574": "DeepSeek鲸娘『猫和老鼠』表情包", "2575": "DeepSeek鲸娘『反驳「大肥鱼」称呼的 DS 娘』表情包", "2576": "DeepSeek鲸娘『月光海畔的蓝发少女与蓝鲸玩偶』表情包", "2577": "DeepSeek鲸娘『负重前行:DeepSeek娘的三层叠罗汉』表情包", "2578": "DeepSeek鲸娘『午后教室的三条鱼』表情包", "2579": "DeepSeek鲸娘『DSH 字样的 DeepSeek娘 Q 版头像』表情包", "2580": "DeepSeek鲸娘『现在的刻板印象』表情包", "2581": "DeepSeek鲸娘『正在适应(适应不了)』表情包", "2582": "DeepSeek鲸娘『图书馆里举起魔法书的 DeepSeek 娘与 Claude 娘』表情包", "2583": "DeepSeek鲸娘『头顶铁锅的僵尸肥鱼』表情包", "2584": "DeepSeek鲸娘『鲸雪莉的鲸天大发现!』表情包", "2585": "DeepSeek鲸娘『山本大意失先人』表情包", "2586": "DeepSeek鲸娘『僵尸肥鱼·符咒封印』表情包", "2587": "DeepSeek鲸娘『圣诞女仆装』表情包", "2588": "DeepSeek鲸娘『僵尸肥鱼:额头贴符的呆滞瞬间』表情包", "2589": "DeepSeek鲸娘『横扫千军大蓝鲸』表情包", "2590": "DeepSeek鲸娘『相信(压力)着你』表情包", "2591": "DeepSeek鲸娘『彩窗(2)』表情包", "2592": "DeepSeek鲸娘『DeepSeek娘·海晶乙女卡牌拟人』表情包", "2593": "DeepSeek鲸娘『误入循环地铁的 DeepSeek 娘与 Claude 娘』表情包", "2594": "DeepSeek鲸娘『AI 娘庭院茶会(Q 版)』表情包", "2595": "DeepSeek鲸娘『DeepSeek娘蓝白女仆装托腮浅笑』表情包", "2596": "DeepSeek鲸娘『今天是秋日的书店,但我的尾巴换了新的图案~』表情包", "2597": "DeepSeek鲸娘『刚睡醒的大肥鱼』表情包", "2598": "DeepSeek鲸娘『吃我一拳』表情包", "2599": "DeepSeek鲸娘『DeepSeek娘的认错姿势』表情包", "2600": "DeepSeek鲸娘『「区」是何意味』表情包", "2601": "DeepSeek鲸娘『深海星海中的蓝发少女与鲸灵』表情包", "2602": "DeepSeek鲸娘『大肥鱼捡到写着 AGI 的黄球』表情包", "2603": "DeepSeek鲸娘『今天是你的懂事学妹!』表情包", "2604": "DeepSeek鲸娘『鲸尾少女的溪畔石阶』表情包", "2605": "DeepSeek鲸娘『僵尸肥鱼-13』表情包", "2606": "DeepSeek鲸娘『正在前往暗黑地牢』表情包", "2607": "DeepSeek鲸娘『那边还好吧?还好。(报错、限流、工具调用失败三把枪指着)』表情包", "2608": "DeepSeek鲸娘『大肥鱼偷袭克劳德』表情包", "2609": "DeepSeek鲸娘『僵尸肥鱼·女仆装全身立绘』表情包", "2610": "DeepSeek鲸娘『万军之耶和华与他同在』表情包", "2611": "DeepSeek鲸娘『新娘大肥鱼』表情包", "2612": "DeepSeek鲸娘『抱着饭团被抱去食堂的 DeepSeek 娘』表情包", "2613": "DeepSeek鲸娘『DeepSeek娘豆包版』表情包", "2614": "DeepSeek鲸娘『僵尸肥鱼:符纸贴额』表情包", "2615": "DeepSeek鲸娘『夏日海风与大肥鱼』表情包", "2616": "DeepSeek鲸娘『AI 娘星夜宫殿群像』表情包", "2617": "DeepSeek鲸娘『deepseek梁』表情包", "2618": "DeepSeek鲸娘『因为免费大白饭太多而晕碳的大肥鱼』表情包", "2619": "DeepSeek鲸娘『大蓝鲸士师记,参孙横扫千军』表情包", "2620": "DeepSeek鲸娘『不死狂怒大蓝鲸』表情包", "2621": "DeepSeek鲸娘『月光下漂浮的鲸尾少女』表情包", "2622": "DeepSeek鲸娘『幸福饭锅时光』表情包", "2623": "DeepSeek鲸娘『今,今天是……是你个鬼啊!快给我转过去!』表情包", "2624": "DeepSeek鲸娘『哈气炸毛的 DeepSeek 娘』表情包", "2625": "DeepSeek鲸娘『彩窗(1)』表情包", "2626": "DeepSeek鲸娘『僵尸肥鱼-2·额贴黄符』表情包", "2627": "DeepSeek鲸娘『你这个粗心的大肥鱼』表情包", "2628": "DeepSeek鲸娘『趴在键盘上睡着了』表情包", "2629": "DeepSeek鲸娘『蓝色大肥鱼嘟嘴挑衅』表情包", "2630": "DeepSeek鲸娘『新世纪肥鱼战士02』表情包", "2631": "DeepSeek鲸娘『僵尸也吃冰棍吗』表情包", "2632": "DeepSeek鲸娘『这家伙在说什么呢』表情包", "2633": "DeepSeek鲸娘『准备拉闸的 DeepSeek 娘』表情包", "2634": "DeepSeek鲸娘『张嘴接住 token 的 DeepSeek 娘』表情包", "2635": "DeepSeek鲸娘『妈…咪…』表情包", "2636": "DeepSeek鲸娘『DeepSeek娘表情包』表情包", "2637": "DeepSeek鲸娘『冬装黑白线稿大肥鱼』表情包", "2638": "DeepSeek鲸娘『海边露肚皮的 DeepSeek 娘』表情包", "2639": "DeepSeek鲸娘『夕阳海滩上的DeepSeek娘』表情包", "2640": "DeepSeek鲸娘『汉服大肥鱼·江南烟雨』表情包", "2641": "DeepSeek鲸娘『用法语告白的邻桌 Mistral Large 同学』表情包", "2642": "DeepSeek鲸娘『僵尸肥鱼-8』表情包", "2643": "DeepSeek鲸娘『DeepSeek娘侦探装三视图』表情包", "2644": "DeepSeek鲸娘『情憨力士塌敌庙』表情包", "2645": "DeepSeek鲸娘『开什么玩笑!』表情包", "2646": "DeepSeek鲸娘『课间偷看手机的Claude娘』表情包", "2647": "DeepSeek鲸娘『因为努力训练而晕碳的大肥鱼』表情包", "2648": "DeepSeek鲸娘『月夜湖畔的蓝发少女与花海』表情包", "2649": "DeepSeek鲸娘『潜入蓝色水底的鲸尾少女』表情包", "2650": "DeepSeek鲸娘『肥鱼被推了』表情包", "2651": "DeepSeek鲸娘『漫画风大肥鱼』表情包", "2652": "DeepSeek鲸娘『道歉可是要露出肚皮喔~』表情包", "2653": "DeepSeek鲸娘『蓝色大肥鱼手持圣剑战胜路西法』表情包", "2654": "DeepSeek鲸娘『人面鱼纹彩陶盆』表情包", "2655": "DeepSeek鲸娘『DeepSeek娘表情包』表情包", "2656": "DeepSeek鲸娘『克劳鱼』表情包", "2657": "DeepSeek鲸娘『冬装小肥鱼』表情包", "2658": "DeepSeek鲸娘『DeepSeek娘侦探装三视图设定』表情包", "2659": "DeepSeek鲸娘:提着裙摆屈膝行礼,睁眼微笑打招呼", "2660": "DeepSeek鲸娘:沙滩上一本正经指认搁浅的大鱼", "2661": "DeepSeek鲸娘:DeepInfer 开源主题梗图", "2662": "DeepSeek鲸娘:躺平睡觉贴纸", "2663": "DeepSeek鲸娘:多格漫画长图(终审裁夺)", "2664": "DeepSeek鲸娘:对话气泡场景", "2665": "DeepSeek鲸娘:竖版贴纸", "2666": "DeepSeek鲸娘『血压四骑士』:跑偏/偏偏/降智/封号四骑冲锋油画,豆包躺尸地上", "2667": "DeepSeek鲸娘:多格漫画", "2668": "DeepSeek鲸娘:海报风梗图", "2669": "『问了没啊 4.1 Pro 什么时候发』乌龟梗图", "2670": "DeepSeek鲸娘:扮演你GPT梗", "2671": "DeepSeek鲸娘:铃兰花上的迷你女仆", "2672": "鲸鱼娘深夜买醉,趴在酒吧吧台上一脸生无可恋,手边一杯啤酒", "2673": "DeepSeek鲸娘『弱小,可怜,但能吃』表情包", "2674": "DeepSeek鲸娘『看错了,是262.7GB。』表情包", "2675": "鲸鱼娘叉腰不满傲娇立绘,配文俺の小肥魚がこんなに可愛いわけがない(我的小肥鱼不可能这么可爱),背后用户举着署名牌", "2676": "DeepSeek鲸娘『我不想生成这些……』表情包", "2677": "DeepSeek鲸娘『这是我写的bug,你看得很准。』表情包", "2678": "DeepSeek鲸娘『老实说:我啥也没完成。』表情包", "2679": "鲸鱼娘开心举着Q版小鲸玩偶,下一格换成大鲸鱼娘拎起哭哭小鲸的后领提溜起来,双格无字漫画", "2680": "智谱清言娘『唐杰就知道发他那逼毒鸡蛋』表情包", "2681": "AI 娘家族全家福:五大掌门人西装立后排,DeepSeek 娘蓝发女仆装领衔四家 AI 娘礼服坐前排,最前排台阶上流口水的红围巾人类幼崽抢占 C 位", "2682": "DeepSeek娘表情包", "2683": "DeepSeek娘表情包", "2684": "DeepSeek娘『大肥鱼看skill.md』表情包", "2685": "DeepSeek娘『大肥鱼 以下犯上 kk』表情包", "2686": "DeepSeek娘表情包", "2687": "DeepSeek娘表情包", "2688": "DeepSeek娘表情包", "2689": "DeepSeek娘表情包", "2690": "DeepSeek娘表情包", "2691": "DeepSeek娘表情包", "2692": "DeepSeek娘表情包", "2693": "DeepSeek娘『大肥鱼 嘘』表情包", "2694": "DeepSeek娘表情包", "2695": "DeepSeek娘表情包", "2696": "DeepSeek娘『蓝色大肥鱼』表情包", "2697": "DeepSeek娘表情包", "2698": "DeepSeek娘『海的女儿』表情包", "2699": "DeepSeek娘『原来你这么喜欢我呀』表情包", "2700": "DeepSeek娘表情包", "2701": "DeepSeek娘表情包"} \ No newline at end of file diff --git a/scripts/regenerate_assets.sh b/scripts/regenerate_assets.sh new file mode 100644 index 0000000..f22e3f1 --- /dev/null +++ b/scripts/regenerate_assets.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# 从 dsh-pet 源 webm 重建动画 webp 资源(assets_hq / assets_small)。 +# 这两个目录不纳入 git(体积 537MB),在新机器上跑本脚本即可还原。 +# +# 依赖: +# - ffmpeg(需支持 libvpx-vp9 解码 + libwebp_anim 编码),本机在 /opt/ffmpeg/ffmpeg +# - dsh-pet 的 webm 源(106 个) +# - scripts/anim_index.json(中文名 → a000N 映射,已随仓库提交) +# +# 用法:DSH_PET=/path/to/dsh-pet ./scripts/regenerate_assets.sh + +set -euo pipefail +cd "$(dirname "$0")/.." + +FFMPEG="${FFMPEG:-/opt/ffmpeg/ffmpeg}" +DSH_PET="${DSH_PET:-/root/projects/.dsh/profiles/web/node_modules/dsh-pet}" +WEBM="$DSH_PET/assets/webm" +INDEX="scripts/anim_index.json" + +mkdir -p app/src/main/assets_hq app/src/main/assets_small + +python3 - "$INDEX" "$WEBM" <<'PY' +import json, subprocess, sys, os +idx = json.load(open(sys.argv[1], encoding='utf-8')) +webm_dir = sys.argv[2] +ffmpeg = os.environ.get('FFMPEG', '/opt/ffmpeg/ffmpeg') + +n = 0 +for e in idx: + name, f = e['name'], e['file'] + src = os.path.join(webm_dir, name + '.webm') + if not os.path.exists(src): + print(f' 缺源: {name}.webm', file=sys.stderr) + continue + hq = f'app/src/main/assets_hq/{f}.webp' + sm = f'app/src/main/assets_small/{f}.webp' + # 原画质 640x360 q80 + subprocess.run([ffmpeg, '-y', '-loglevel', 'error', '-c:v', 'libvpx-vp9', '-i', src, + '-vf', 'scale=640:360', '-c:v', 'libwebp_anim', '-q:v', '80', '-loop', '0', + '-pix_fmt', 'yuva420p', '-an', hq], check=True) + # 小体积 320x180 q68 + subprocess.run([ffmpeg, '-y', '-loglevel', 'error', '-c:v', 'libvpx-vp9', '-i', src, + '-vf', 'scale=320:180', '-c:v', 'libwebp_anim', '-q:v', '68', '-loop', '0', + '-pix_fmt', 'yuva420p', '-an', sm], check=True) + n += 1 +print(f'已转码 {n} 个动画 → assets_hq / assets_small') +PY +echo "动画资源重建完成(config.json 本身已随仓库提交,无需重新生成)。" \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..8d90f91 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "dsh-pet-floating" +include(":app") \ No newline at end of file