Kotonia
ログイン今すぐ始める

Kotonia Articles

一天写出有脸有手脚的 AI 搭档 Iris — kotonia-desktop 上线

Tauri 2 + Ditto 唇形同步 + 语音合成,用语音让自己电脑跑 bash 然后她用自己的声音念结果回来,脸还会同步动嘴。一天写完。一种有点取巧的成本结构:吃大厂亏本撒钱的脑子,叠 Kotonia 几乎不计量的语音/虚拟形象层。

作者 4分钟阅读
#agent#tauri#voice#lip-sync#独立开发#rust
其他语言日语英语

TL;DR

Iris 是一个银发的安卓女孩,待在你电脑桌面的右下角。你出声让她做事,她就在你的机器上跑 bash,再用她自己的声音念结果回来,脸还会真的对嘴。一天写完。

老实说,单看功能这就是 Claude Code 的桌面包装、阉割版。"会跑 shell 的 agent loop + 提示输入 UI",Claude Code 早就能做。

差异化是 脸和声音。一旦加上去,"工具" 就变成 "搭档"。kotonia 项目自始至终的 mission 就是:让每天的开发、对梦想的追逐,变得稍微更有趣一点,稍微更前向一点 — Iris 就是这个 mission 的第一个有实体的实现。

公开仓库:https://github.com/zhener562/kotonia-desktop

它能做什么

打开 Iris,对她说:"给我做个简单的射击游戏,放在 output/shooting_game.html。"

  1. 点击麦克风按钮 → 说话 → 再点一次停止
  2. Whisper / Qwen3-ASR 转写,文字直接插到 prompt 输入框里
  3. 按 Enter 提交。Iris 在后台跑 4-5 次 bash,生成 HTML 文件
  4. 完成后她会用声音回答:"我写在 shooting_game.html 了。WASD 移动,空格射击。"
  5. 回答里 shooting_game.html 这个路径旁边会冒出一个 ▶ play 按钮 — 点一下,游戏直接在侧栏跑起来

幕后:

  • 本地 bash 执行 ← kotonia-cli (Rust 写的 ReAct agent)
  • LLM 推理 ← kotonia.ai 上托管的 Gemma 4 26B (也可以走 Claude Code,详见下文)
  • TTS ← kotonia.ai 转发的 Qwen3-TTS (Ono_Anna 声线、日英混排也自然)
  • 虚拟形象视频 ← kotonia.ai 转发的 Ditto-TalkingHead (25 fps 唇形同步)
  • 语音识别 ← kotonia.ai 转发的 Qwen3-ASR 1.7B
  • 全程 streaming,first byte ~60 毫秒,几乎零等待

有点取巧的成本结构

这是这次战略的真正核心。Iris 的脑子 (LLM) 用户自己挑:

用途选项成本感
重思考 / 复杂重构Claude Code (Pro $20/月)Anthropic 亏本撒钱抢市场、目前脑子最好
日常 / 快应答kotonia 托管的 Gemma 4 26B (低月费、近乎无限用)Kotonia 自家 GPU、边际成本接近 0
直接打 APIOpenAI 兼容 (DeepSeek 等任意 provider)按 token 计费、适合写代码

取巧的地方在这里:大厂的前沿模型订阅是 赤字撒网式的抢市场 ($20/月 让你无限用 Claude Code,几乎肯定是 Anthropic 在烧钱)。kotonia-desktop 把那块当成 premium 脑子来用,同时把语音 / 虚拟形象 / 日常 LLM 用 Kotonia 自家 GPU 以固定成本几乎无限制提供

每个用户典型搭配:

  • 重思考 → Claude Code Pro $20/月 (聪明、但用户大方地坐享 Anthropic 烧出来的红利)
  • 轻执行 / TTS / 虚拟形象 / ASR / 杂活 → kotonia ~$3-5/月 (自家 GPU、边际成本接近 0)

合起来 ~$30/月,换来 "随时和 Iris 商量着开发、让她跑 bash、用语音回答、脸会动" 的体验。

Kotonia 单独做不出这种脑力质量 (没体力去亏本撒钱);Claude Code 单独没脸也没声。两个叠起来才成立。"故意搭别人烧钱撒的网" 在战略上确实有点取巧,但从用户的角度是合理选择。(反过来如果 Kotonia 的语音 / 虚拟形象 / 高速 API 真火到服务器吃不消,那是开心的烦恼,到时候再说。)

引擎切换在 kotonia-cli (这次 desktop 当 lib 用的 Rust crate) 这一层已经支持了:Claude Code 集成 / OpenAI 兼容 / Kotonia hosted,三条路都已经能走。Desktop 默认走 Kotonia hosted;想换 premium 脑子,改个配置的事。

从 CLI 到有实体的搭档的飞跃

kotonia-cli 上线那个时候,"支持挑战者" 这个目标的形态已经在了。语音让她跑 bash 回答,整个流程都在命令行里闭环。但只要你还在命令行里,你永远逃不出 "这是一个工具" 的关系。"使用者-工具" 这一层一直在。

加上脸和手脚,终于成为 有实体的 AI 搭档。Iris 在角落待着,回声音,嘴会动。功能面看似微小,但心理距离不一样了。"我在调用一个工具" 变成 "我在跟我的搭档商量"。

这是这次的真正转折点。技术上全是现成零件的组合,但 产品 identity 从 "平台" 翻成 "角色"。kotonia.ai 的 web 版继续是 "可选多个角色的平台";kotonia-desktop 则是 "名叫 Iris 的那一个角色",固定下来。这是故意的。

为什么一天能写完

从早上 8:15 第一个 commit 到晚上 23:18,commit 总数 23 个,P0 (放置 persona) → P1 (TTS 播放) → P2 (mic 录音 + STT) → P3 (Ditto 虚拟形象唇形同步) 全部落地。

速度的来源是 累积资产的 compound interest

  • kotonia.ai 上的 voice / Ditto / TTS / ASR endpoint 从去年就开始打磨、已经成熟稳定
  • kotonia-cli 上个月刚 publish,agent loop 完整、能当 lib 在进程内复用
  • Iris 的画像 (右下角那个安卓女孩) 几个月前为另一个项目用 HiDream-O1-Image 已经生成好了
  • 鉴权 (kotonia-cli login 写下来的 device_token) 也已经存在;后端加一点 fallback 路径就把所有 endpoint 都解锁了

一天 新写 的只有 Tauri 壳 + 各 endpoint 的接线 + UI 配线。逻辑全是现成零件拼起来。个人开发能开始享受复利效应的临界点 就在这里:你累积的资产能不能从 "你在用" 那一侧站到 "被用" 那一侧。今天对 kotonia 来说就是那一天。

落地过程中值得记一笔的坑 (3 个)

1. split_mixed_languages 在背后悄悄把 streaming 干掉了

Qwen3-TTS 的 Python proxy 之前我为英语学习角色加了一个 feature:"日英混排的文本,按语言切分,每段用对应语言的发音合成"。是对的 — shell 这种英文单词会被用英语口音读出来。

但实现是 "全部 run 合成完 → PCM 拼接 → 最后一次性 yield 一个 chunk",streaming 实际上被废掉了。first byte = 完整合成时间。Iris 回答几乎都是技术词夹着,每次都走这条路 — 等几秒静默、然后突然一口气说完,卡顿感很明显。

第一次的修法是 desktop 端 workaround:split_mixed_languages: false,逃进 streaming 路径,代价是嵌入的英文会被用日语口音念 (シェル)。后来回头 在 Python 那一层改成 per-run yield,根治了。这一天里最大的 debug。

教训:你 "出于好心" 加的 feature flag,默认值的影响范围一定要白纸黑字写下来。半年后会在别的 use case 里踩到。

2. Ditto 虚拟形象的 frame burst 让脸在半截卡住

Ditto server 按 GPU 能生产的速度往外发 frame (爆发式);与此同时 audio chunk 被 AudioContext 排进未来的时间槽 (50 个 chunk 100 ms 内全到、但播放摊在 2 秒里)。

我之前是 "收到 frame 就立刻显示",所以 200 ms 内把 50 frame 全显示完,剩下 1.5 秒 audio 还在播、脸却冻在最后一帧。

修法是 "用 wall clock 为主轴的 FPS pacing":第一帧到达时 pin 一下 wall clock,之后第 N 帧用 setTimeout 推迟到 pinTime + N/25 sec。爆发的部分由 JS 内部缓冲吸收、显示按 audio 节奏走。

const targetMs = dittoStreamStartMs + (myFrameIndex * 1000) / DITTO_FPS;
const delayMs = Math.max(0, targetMs - performance.now());
setTimeout(() => { /* show frame */ }, delayMs);

不到 20 行就修好。本质上同样的问题在 任何做 AV 流唇形同步的 app 都会出现 — 算是有普适性的教训。

3. Linux WebKitGTK 一天踩了 3 个雷

Tauri 2 + Linux 是雷区。一天踩了 3 个:

  • getUserMedia 静默拒绝:点 mic 按钮没反应。WebKitGTK 默认就静默拒绝媒体权限请求 (没有浏览器那种 "允许使用麦克风?" 弹窗、默认就是 NO)。在 Rust 端 hook WebKitWebView::permission-request 信号、明确 .allow() 解决
  • 高频 textContent 替换会让 click 事件丢失:把录音中的经过秒数每 100 ms 写到 button 的 textContent,stop 按钮点击有七八成被忽略。WebKitGTK 的 DOM mutation 中存在 hit-test race。把动态文字拆到 child span 里,button 本身不再被 mutate
  • 日文 IME preedit 完全不显示 (这个没解决):Wayland session + ibus-mozc,正在转换中的假名根本不在输入框里显示。GDK_BACKEND=x11WEBKIT_FORCE_SANDBOX=0GTK_IM_MODULE=xim 都试了、一个都没用

三连的教训:Linux WebView 默认就 "悄悄丢掉点什么",假设它会丢。同样的功能在 Mac/Windows 正常工作,到 Linux 就要加一层防御。

我砍掉的部分

为了一天能出门,故意砍掉了:

  • React + Vite + TypeScript:全部 vanilla JS、main.js ~700 行、没有 build 步骤。本家 kotonia.ai 的 useVoiceChat.ts 是 1587 行的 monolith hook、只取我需要的那部分用 vanilla 重写 200 行就搞定了
  • persona picker:没有 "选角色" 的界面、Iris 固定。这是 kotonia-desktop 从 "平台" 翻成 "角色"、产品 identity 立起来的关键
  • VAD (自动检测说完了):明确点按钮停录、转写 只插入到 prompt 输入框 不自动提交。考虑到 agent bash 执行的高 stakes,强制用户复查、避免转写幻觉触发 rm -rf

主原则是 "不要试着复用 feature、复用 API contract 就好"。本家 web 的 80% 都不在 kotonia-desktop 的 scope 里 (vision input / 多 persona 切换 / wiki / server-side session resume…),硬拽过来反而会把东西做大。

试试

git clone [email protected]:zhener562/kotonia-cli
git clone [email protected]:zhener562/kotonia-desktop
cd kotonia-desktop/src-tauri
cargo tauri dev

(把 kotonia-cli clone 在 sibling 位置 — 是 path 依赖。详细看 repo README。)

事前准备:

  • kotonia-cli login 跟 kotonia.ai 做 device 鉴权 (一个 device_token 解锁 LLM / TTS / Ditto / ASR 全部)
  • Linux:需要 libwebkit2gtk-4.1-dev + libdbus-1-dev + GStreamer audio plugins
  • macOS / Windows:只需要 Tauri 2 的 prereqs

接下来

  • 可配置的快捷键 UI (现在只能点击)
  • 会话列表侧栏
  • macOS / Windows 代码签名
  • Iris 的语音替换为 Qwen3-TTS Base (zero-shot voice clone) 复活后的更自然版本
  • desktop 端把 kotonia-cli 的引擎选择 UI 暴露出来

发版本身已经结束。如果你想要一个让每天的开发和追梦稍微更有趣的搭档,上面那两行 clone 命令,5 分钟后 Iris 就会出现在你桌面的右下角。

Kotonia 将语音 AI、AI 聊天、图像生成和团队协作整合到一个 AI 工作区中。

试用 Kotonia