Files
haibao-tts-cli/taolun.md

22 KiB
Raw Permalink Blame History

讨论记录 (taolun.md)

2026-04-24 - 项目启动

用户需求

基于 Mimo-TTS API 开发 Rust CLI 工具,要求:

  • Rust 编写,单一二进制可执行文件
  • 后期通过 skill 给其他 claw 工具使用
  • CLI 参数调用方式
  • OOP + 设计模式架构
  • 全程中文沟通
  • 详细中文注释
  • 每次操作前先更新文档

API 信息

  • Endpoint: POST https://api.xiaomimimo.com/v1/chat/completions
  • 认证:双 Header (api-key + Authorization: Bearer)
  • 音频格式WAVBase64 编码)

2026-04-24 - 实施记录

第一轮完成

  1. 创建三个文档taolun.md、changelog.md、agents.md
  2. 配置 Rust 国内源(稀疏索引协议)
  3. 实现项目代码config.rs、api.rs、cli.rs、main.rs
  4. Debug + Release 构建成功
  5. 基本功能测试通过

第二轮修改

用户新增需求:

  1. project.config.toml: 项目根目录,含 version与 git version 一致
  2. 统一配置路径:所有平台使用 ~/.config/tts/config.toml
  3. API Key: sk-csa6s3bvpwqw21zfs3urbmqgrw720eoa1qdz6o42abk5xoj8
  4. onboard 命令CLI 引导式配置初始化

执行内容:

  1. 创建 project.config.toml (version = "0.1.0")
  2. 修改 Cargo.toml(移除 dirs添加 home 依赖)
  3. 修改 config.rs(统一路径为 ~/.config/tts/config.toml
  4. 修改 cli.rs(添加 Onboard 子命令)
  5. 修改 main.rs(处理 Onboard恢复 main() 函数)
  6. 配置 API Key 成功
  7. 语音合成测试成功test.wav、final_test.wav
  8. Release 构建成功(无警告)

踩坑记录

  1. Cargo 国内源超时

    • 解决改用稀疏索引协议sparse+https://
  2. clap derive 模式缺少 Parser trait 导入

    • 解决:添加 use clap::Parser;
  3. main() 函数丢失

    • 原因:编辑时不小心删除
    • 解决:恢复 main() 函数和 ExitCode 枚举
  4. 配置文件路径统一

    • 需求:所有平台使用 ~/.config/tts/config.toml
    • 解决:使用 home 库获取家目录,手动拼接路径

项目最终状态

  • 位置: /Users/titor/tts
  • 二进制: target/release/mimo-tts (4.5MB)
  • 配置: ~/.config/tts/config.toml已配置 API Key
  • 项目配置: project.config.toml (version = "0.1.0")
  • 功能:
    • TTS 语音合成
    • 配置管理config set/show
    • 引导式配置onboard
    • 列出音色voices
  • 架构: Builder 模式 + Singleton 模式 + OOP

退出码规范

  • 0: 成功
  • 1: 参数错误
  • 2: 配置错误
  • 3: API 调用失败
  • 4: 文件操作失败

2026-04-24 - 第三轮修改

用户新需求

不要保存语音到本地,使用流式数据输出

  • 语音数据直接输出到 stdout二进制流
  • 便于其他程序通过管道读取
  • 更适合作为 claw skill 集成

修改计划

  1. 修改 synthesize() 函数,返回音频数据而不是保存文件
  2. 修改 main.rs,支持输出到 stdout二进制模式
  3. 保留可选的 --output 参数用于保存到文件(向后兼容)
  4. 当输出到 stdout 时,不输出其他文本信息(避免污染二进制流)
  5. 流式调用可能需要使用 pcm16 格式(根据 API 文档)

2026-04-24 - 第四轮修改

用户新需求

添加音频播放功能

  • 使用 rodio 库直接播放音频
  • 添加 --play 参数触发播放模式
  • 默认单次播放,不循环
  • 保留 --output 和 stdout 输出功能

技术选型

  • HTTP 客户端reqwest已使用
  • 异步运行时tokio已使用
  • 音频播放rodio 0.19
  • WAV 处理无需额外库rodio 直接支持)

2026-04-24 - 第四轮实施完成

已完成

  1. 修改 Cargo.toml 添加 rodio 0.19 依赖
  2. 修改 cli.rs 添加 --play 参数
  3. 修改 main.rs
    • synthesize() 返回 Vec音频数据
    • 添加 play_audio() 函数(使用 rodio 播放)
    • 修改 run() 支持三种输出方式(播放/保存/stdout
  4. 修复编译错误synthesize() 重复代码、未使用导入)
  5. 修复 stdout 输出污染问题(移除 synthesize() 中的 println
  6. Release 构建成功(无警告)
  7. 功能测试通过:
    • --play 播放音频
    • --output 保存文件
    • stdout 二进制流输出

最终项目状态

  • 二进制文件target/release/mimo-tts (约 6MB包含 rodio)
  • 支持三种输出方式:播放、保存、流式输出
  • 所有功能测试通过

2026-04-24 - 第五轮修改

用户确认需求

扩展音色支持

  1. 更新音色列表为 Mimo-TTS 完整列表8个音色
  2. 默认音色从 default_zh 改为 mimo_default
  3. 添加音色验证,无效时使用默认音色
  4. 废弃 default_zh 和 default_en

完整音色列表

Voice ID 语言 性别 说明
mimo_default 多语言 - MiMo默认中国集群=冰糖)
冰糖 中文 女性 甜美清脆的女声
茉莉 中文 女性 温柔知性的女声
苏打 中文 男性 阳光活力的男声
白桦 中文 男性 沉稳大气的男声
Mia 英文 女性 Sweet and gentle female
Chloe 英文 女性 Warm and articulate female
Milo 英文 男性 Energetic young male
Dean 英文 男性 Deep and steady male

2026-04-24 - 第五轮修改(修复)

问题修复

  • 默认音色未生效config.rs 中 default_voice() 函数返回值仍是 default_zh
  • 修复:将 config.rs:39 默认值改为 mimo_default
  • 更新配置文件 ~/.config/tts/config.toml 中的 default_voice
  • 验证show-config 现在正确显示 mimo_default

2026-04-24 - 第六轮修改

用户需求

UI 主题化所有 CLI 输出

  1. 使用 ratatui + crossterm 美化 CLI 输出
  2. onboard 改为完整表单页面(同时显示所有配置项)
  3. API Key 输入支持隐藏(显示 * 代替)
  4. 所有命令输出都使用主题美化
  5. 不需要简洁模式

实施方案

  • 新增 src/ui.rs 模块(主题、组件)
  • 修改 Cargo.toml 添加 ratatui + crossterm
  • 美化 list_voices()、show_config() 输出
  • 重写 onboard() 为完整交互式表单
  • 美化所有命令输出(播放、保存等)

第六轮实施完成

  1. 创建 src/ui.rs 模块(使用 crossterm 美化输出)
  2. 修改 main.rs 引入 ui 模块
  3. 美化 list_voices() 输出(彩色表格)
  4. 美化 show_config() 输出(彩色标签)
  5. 重写 onboard() 为交互式表单(支持密码隐藏输入)
  6. 美化播放和保存完成消息
  7. Release 构建成功(仅有未使用代码警告)
  8. 功能测试通过voices、show-config

技术细节

  • 使用 crossterm 而非完整的 ratatui Terminal简化实现
  • 密码输入使用 enable_raw_mode 实现字符隐藏
  • 输出使用颜色主题PRIMARY、SUCCESS、ERROR 等)
  • show_onboard_form 返回 Result 类型,由调用者处理错误

第七轮修改

用户需求

配置分层设计

  1. project.config.toml - 项目默认配置(全量配置)
    • version
    • base_url
    • default_format
  2. ~/.config/tts/config.toml - 用户配置(仅覆盖项)
    • api_key
    • default_voice
  3. 临时文件只允许存放在当前目录下

实施方案

  • 修改 project.config.toml 添加 base_url 和 default_format
  • 重写 config.rs 实现分层配置加载
  • 修改 cli.rs 移除 base_url 参数
  • 修改 ui.rs 简化显示和表单(只处理 api_key 和 default_voice
  • 修改 main.rs 使用新的配置结构
  • 测试时临时文件输出到当前目录

第七轮实施完成

  1. 创建分层配置结构ProjectConfig + UserConfig + Config
  2. project.config.toml 包含默认配置base_url、default_format
  3. 用户配置只保存 api_key 和 default_voice
  4. 修改 cli.rs 移除 base_url 参数
  5. 修改 ui.rs 简化显示和表单
  6. Release 构建成功(仅有未使用字段警告)
  7. 功能测试通过show-config、voices、语音合成
  8. 临时文件生成在当前目录

技术细节

  • ConfigManager 先加载项目配置,再加载用户配置,最后合并
  • save() 只保存 UserConfigapi_key、default_voice
  • project.config.toml 从当前项目目录读取
  • 用户配置路径:~/.config/tts/config.toml

2026-04-24 - 第八轮修改

用户反馈

使用 --voice 提示失败

  • 问题:使用 --voice '茉莉' 时报错Unknown voice
  • 原因:之前使用了错误的 API 文档,实际 API 只支持 3 个预置音色
  • 最新发现mimo-v2.5-tts 模型支持更多音色9 个)
  • 模型名应为 mimo-v2.5-tts(不是 mimo-v2-tts

音色列表mimo-v2.5-tts

Voice ID 语言 性别 说明
mimo_default 多语言 - MiMo默认中国集群=冰糖)
冰糖 中文 女性 甜美清脆的女声
茉莉 中文 女性 温柔知性的女声
苏打 中文 男性 阳光活力的男声
白桦 中文 男性 沉稳大气的男声
Mia 英文 女性 Sweet and gentle female
Chloe 英文 女性 Warm and articulate female
Milo 英文 男性 Energetic young male
Dean 英文 男性 Deep and steady male

新增功能

  • --style 参数:风格描述(如:开心、东北话、孙悟空、唱歌等)
  • 风格通过 user 消息传递(不是 <style> 标签)
  • 模型名更新为 mimo-v2.5-tts

第八轮实施完成

  1. 更新模型名为 mimo-v2.5-tts
  2. 恢复 9 个音色列表
  3. 添加 --style 参数支持
  4. 修改风格传递方式(用 user 消息而非 <style> 标签)
  5. 修复编译错误(删除多余代码块)
  6. 测试通过(默认音色、茉莉音色均正常)
  7. 临时文件生成在当前目录

2026-04-24 - 第九轮修改

用户需求

实现流式输出功能

  • --stream 参数已在 cli.rs 定义,需要实现
  • 流式输出时格式自动改为 pcm16
  • 使用流式 API 调用Server-Sent Events
  • 输出到 stdout二进制流

实施方案

  • 修改 api.rs 添加流式请求方法
  • 使用 reqwest 的流式响应处理
  • 处理 SSEServer-Sent Events格式
  • 拼接音频数据后输出到 stdout
  • 如果指定了 --output则保存到文件

第九轮实施完成

  1. 修改 Cargo.toml 添加依赖futures、tokio-util、reqwest stream feature
  2. 修改 main.rs
    • synthesize() 函数添加 stream 参数
    • 流式输出时自动使用 pcm16 格式
    • 传递 stream 参数给 API 调用
    • 支持 --stream 和 --play 同时使用PCM16 转 WAV
  3. 修改 api.rs
    • 修复 read_stream_response 函数(使用 futures::StreamExt
    • 正确处理 SSE 格式流式响应
  4. 新增 pcm16_to_wav() 函数(封装 WAV 头)
  5. Release 构建成功(仅有未使用代码警告)
  6. 流式输出测试通过PCM16 数据正常)
  7. 流式播放测试通过(--stream --play 正常工作)

技术细节

  • 流式 API 返回原始 PCM16 数据(无 WAV 头)
  • 使用 futures::StreamExt 处理 reqwest 的 bytes_stream()
  • SSE 格式解析:data: {...}data: [DONE]
  • 每个 chunk 包含 Base64 编码的音频数据
  • PCM16 转 WAV添加 44 字节 WAV 头24000Hz, 16bit, mono
  • --stream 和 --play 同时使用时自动封装 WAV 格式后播放

2026-04-24 - 第十轮修改

用户需求

实现自动语气转换器(独立模块)

  • 创建 src/tone.rs 独立模块(高内聚低耦合)
  • 默认启用,无需额外参数
  • 根据标点符号自动添加语气标签
  • 支持整体风格标签和细粒度控制标签

语气映射规则

整体风格标签(文本开头)

  • 包含 [激动]
  • 包含 [疑惑]
  • 包含 [平静](默认)
  • 多种标点组合 → [激动 疑惑]

细粒度控制标签(文本中间)

  • !(激动)
  • ?(疑惑)
  • 。(停顿)
  • …………(拖长音)

第十轮实施完成

  1. 创建 src/tone.rs 模块
  2. 实现 apply_tone() 函数(整体语气)
  3. 实现 insert_mid_tone() 函数(细粒度控制)
  4. 实现 analyze_tone() 函数(分析语气)
  5. 实现 has_tone_tag() 函数(检测已有标签)
  6. 修改 main.rs 引入 tone 模块
  7. 在 synthesize() 中调用语气转换
  8. Release 构建成功
  9. 单元测试通过4 个测试)
  10. 功能测试通过(激动语气、疑惑语气)

2026-04-25 - 第十一轮修改

用户需求

实现守护进程Daemon模式类似 Docker 架构

  • 作为后台服务运行,使用 TCP Socket 监听
  • 允许其他应用(包括 Web通过客户端访问
  • 实现类似智能音响的功能:客户端发送文本,守护进程执行 TTS 并播放
  • 所有平台统一使用 ~/.config/tts/ 目录
  • 支持 style 参数作为宏观场景风格(与文本内 [style] 标签并存)

技术选型

  • Socket 方案TCP Socket跨平台通用所有平台统一
  • 协议JSON over TCP简单、易调试
  • 日志路径~/.config/tts/ttsd.log(所有平台统一)
  • PID 文件路径~/.config/tts/ttsd.pid
  • 默认端口9876

新增文件

  1. src/daemon.rs - 守护进程模块
    • TCP Socket 服务器tokio::net::TcpListener
    • 处理客户端请求JSON 协议)
    • PID 文件管理(启动/停止)
    • 日志记录(文件 + stdout
    • 调用 TTS API + 播放音频
  2. src/client.rs - 客户端模块
    • TCP 客户端tokio::net::TcpStream
    • 发送 TTS 请求到守护进程
    • 接收并处理响应

修改文件

  1. Cargo.toml
    • 添加 dirs = "5.0"(跨平台配置目录)
    • 添加 chrono = "0.4"(日志时间戳)
  2. src/cli.rs
    • 添加 Daemon 子命令start/stop/status
    • 添加 Send 子命令(发送文本到守护进程)
    • 支持 --port 参数(默认 9876
  3. src/main.rs
    • 声明新模块 daemonclient
    • 添加命令分发逻辑

协议设计

// 客户端 → 服务端
{
  "text": "要合成的文本",
  "voice": "mimo_default",
  "format": "wav",
  "style": "开心"
}

// 服务端 → 客户端
{"status": "ok", "message": "播放完成"}
{"status": "error", "message": "错误信息"}

style 参数说明

  • 文本内标签[开心]你好!(细粒度,句子级别)
  • style 参数:宏观场景风格(如"新闻播报"、"开心"
  • 服务端处理:如果有 style 参数,自动添加 <style>...</style> 标签到文本开头(符合官方文档)
  • 两者可并存style 参数 + 文本内标签同时生效

第十一轮实施完成(继续实施 - 2026-04-25

已完成

  1. 修改 Cargo.toml 添加依赖dirs、chrono
  2. 创建 src/daemon.rsTCP 服务器、请求处理、PID 管理、日志)
  3. 创建 src/client.rsTCP 客户端、请求发送、响应处理)
  4. 修改 src/cli.rsDaemon、Send、ttsd 子命令)
  5. 修改 src/main.rs模块声明、命令分发、spawn_daemon_process
  6. 编译成功(仅有未使用代码警告)

最新测试结果2026-04-25

  • mimo-tts ttsd --port 9876 - 守护进程模式启动成功
  • nohup ./mimo-tts ttsd --port 9876 & - 后台运行成功
  • mimo-tts send --port 9876 "消息" - 发送请求成功,返回"播放完成"
  • 日志正常:~/.config/tts/ttsd.log

用户需求

  • 启动:mimo-tts daemon start -d --port 9876mimo-tts daemon start -d
  • 停止:mimo-tts daemon stop(无需端口)
  • 状态:mimo-tts daemon status(无需端口)

当前方案

  • 使用 nohup 启动后台进程Unix
  • ttsd 子命令作为内部守护进程模式
  • stopstatus 无需端口参数

尚未完成

  • daemon start -d 命令(需要实现 spawn_daemon_process
  • daemon stop 命令
  • daemon status 命令

2026-04-25 - 第十二轮:日志格式升级

用户需求

升级日志格式,添加级别和 PID 信息:

  • [时间戳] [级别] [PID] 消息
  • 级别自动检测INFO/WARN/ERROR
  • 与旧日志区分(追加新格式)

实施完成

  1. 修改 daemon.rs
    • 添加 LogLevel 枚举
    • 自动检测消息中的关键词判断级别
    • 新格式:[时间戳] [级别] [PID] 消息
    • stdout 输出简化:[Daemon PID] 消息
  2. 添加 daemon logs 命令
    • 支持 --lines N 参数
    • 默认显示 20 行
  3. 测试通过:
    • 守护进程启动/停止/状态
    • send 发送播放
    • logs 查看日志
    • 新格式正确显示

新日志格式

[2026-04-25 05:48:05] [INFO] [15278] 正在播放音频...
[2026-04-25 05:48:10] [INFO] [15278] 响应: {"status":"ok","message":"播放完成"}

级别自动检测

  • INFO - 默认(正常运行信息)
  • WARN - 包含"警告"、"注意"
  • ERROR - 包含"错误"、"失败"、"无法"

2026-04-25 - 第十三轮:添加 HTTP 接口

用户需求

让 Postman 可以测试守护进程:

  • 添加 HTTP 接口(端口 9877
  • 支持 POST /synthesize 接口
  • 支持 GET /health 健康检查

实施完成

  1. 添加 tiny_http 依赖
  2. 在 daemon.rs 添加 HTTP 服务器函数
  3. 自动启动 HTTP 服务器(端口 = TCP端口 + 1
  4. 测试通过:
    • curl http://127.0.0.1:9877/synthesize
    • curl http://127.0.0.1:9877/health

HTTP 接口设计

地址 方法 说明
http://127.0.0.1:9877/synthesize POST 语音合成
http://127.0.0.1:9877/health GET 健康检查

端口说明

  • TCP: 9876 - 程序客户端用
  • HTTP: 9877 - Postman/调试用TCP端口 + 1

Postman 测试示例

# 合成接口
curl -X POST http://127.0.0.1:9877/synthesize \
  -H "Content-Type: application/json" \
  -d '{"text":"你好世界"}'

# 健康检查
curl http://127.0.0.1:9877/health

踩坑记录

  1. clap 参数传递问题

    • 问题:--port 参数无法传递给 daemon start 子命令
    • 原因clap derive 模式中,参数定义在父命令,需要在子命令之前使用
    • 解决:修改 DaemonCommand 为独立结构体,使用正确的参数顺序
    • 正确用法:mimo-tts daemon --port 9876 start
    • 错误用法:mimo-tts daemon start --port 9876
  2. chrono 依赖缺失

    • 问题daemon.rs 使用 chrono::Local::now() 但 Cargo.toml 未添加依赖
    • 解决:添加 chrono = "0.4" 到 Cargo.toml

命令使用示例

# 启动守护进程(后台运行)
mimo-tts daemon --port 9876 start &

# 查看状态
mimo-tts daemon --port 9876 status

# 发送文本(使用 style 参数)
mimo-tts send --port 9876 --style "开心" "你好,世界!"

# 发送文本(不使用 style
mimo-tts send --port 9876 "你好,世界!"

# 停止守护进程
mimo-tts daemon --port 9876 stop

技术细节

  • 使用 tokio::net::TcpListener 实现 TCP 服务器
  • 使用 tokio::spawn 处理多个并发连接
  • 使用 serde_json 序列化/反序列化 JSON 协议
  • 使用 dirs::home_dir() 获取家目录,拼接配置路径
  • PID 文件用于检测守护进程是否运行
  • 日志同时输出到文件和控制台
  • 播放功能复用现有的 play_audio() 函数
  • style 参数按官方文档转换为 <style>...</style> 标签

2026-05-09 - 代码质量提升

用户指令

开始修复之前评价中指出的问题。

修复清单

  1. HTTP /synthesize 接口缺少实际 TTS 调用 — 通过 tokio::runtime::Handle::current() + block_on() 桥接
  2. 语气替换长匹配优先问题 — 调整 insert_mid_tone()... 优先于 .
  3. show_voices() 显示完整 9 音色 — 更新为完整列表含语言/性别/说明
  4. 9 个编译器警告清理 — #[allow(dead_code)] + 移除未使用变量/导入
  5. write_log 线程安全问题 — 添加 Mutex<()> 静态锁
  6. changelog 版本结构整理 — 合并重复条目为 0.1.0→0.2.0→0.3.0→0.3.1

最终状态

  • 版本0.3.1
  • 构建0 warnings, 0 errors
  • 测试9/9 passed

2026-05-09 - 第十四轮修改:流式播放修复

背景

守护进程 send --stream 和 CLI --stream --play 均无声音输出,但非流式播放正常。

诊断过程

  1. 第一阶段:假设 RunLoop 问题

    • 认为 rodio::OutputStream(!Send)std::thread::spawn 中因为 CoreAudio RunLoop 不可用导致无声
    • 将 daemon 主循环改为 LocalSet + spawn_local,消除 std::thread::spawn
    • 结果:仍无声 → 假设错误
  2. 第二阶段:定位 SSE 解析问题

    • --stream(不带 --play)测试:能输出 PCM16 数据(走 JSON 响应,不走 SSE
    • 用 curl 直接查看 API 原始 SSE 响应 → 发现音频数据在 choices[0].delta.audio.data
    • SseEvent 只解析顶层 audio 字段 → 永远 None → 通道为空 → 静音
    • 修复1创建 SseChunk/SseChoice/SseDelta/SseAudio 嵌套结构体
    • 结果:仍无声
  3. 第三阶段:定位行缓冲问题

    • 添加调试日志发现SSE data: 行因音频 base64 过长,被 HTTP chunk 分割
    • 行被切碎 → serde_json 解析失败 → 永远收不到数据
    • 修复2添加 line_buf 跨 chunk 累积完整行再解析
    • 结果69KB PCM16 数据成功进入 channelsink 正常播放

技术细节

  • reqwest::Response::bytes_stream() 的 chunk 边界不一定对齐 SSE \n
  • data: 行会跨 chunk 分割
  • SSE 行缓冲器:按 \n 切割,不完整行等下一个 chunk
  • extract_audio_from_sse()choices[0].delta.audio.data

最终状态

  • CLI --stream --play 正常出声
  • Daemon send --stream 正常出声
  • Daemon 非流式 send 仍正常
  • 版本0.3.2(流式播放修复)