Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 724981b50a | |||
| 94328d4f28 | |||
| a7c240c833 | |||
| 1c73fb469c | |||
| 73b480323d | |||
| 6c74d4032c | |||
| b12378df10 | |||
| e4e5cd82c3 |
76
agents.md
76
agents.md
@@ -41,36 +41,43 @@
|
|||||||
|
|
||||||
## 当前任务
|
## 当前任务
|
||||||
|
|
||||||
### v0.1.0 目标
|
### v0.2.0 目标
|
||||||
|
|
||||||
实现流式输出功能:
|
实现 TTS 语音朗读功能:
|
||||||
1. 创建 go.mod 配置依赖
|
1. 集成 mimo-tts client(TCP 连接本地 daemon)
|
||||||
2. 实现 main.go 入口
|
2. 添加配置文件 TTS 开关
|
||||||
3. 实现流式 Provider 调用
|
3. 实现命令行切换(/tts on/off/status)
|
||||||
4. 实时打印 token
|
4. 实现临时 TTS 前缀(`T 消息`)
|
||||||
5. 处理非流式 Provider 回退
|
5. 动态提示符显示状态(👀 🔊)
|
||||||
6. Markdown 终端渲染(glamour)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 实现进度
|
## 实现进度
|
||||||
|
|
||||||
### v0.1.0 已完成功能
|
### v0.2.0 已完成功能
|
||||||
|
|
||||||
1. **流式输出(新流程)**
|
1. **TTS 语音朗读**
|
||||||
|
- 集成 mimo-tts client(TCP 连接)
|
||||||
|
- 配置文件开关(tts.enabled)
|
||||||
|
- 命令行切换(/tts on/off/status)
|
||||||
|
- 临时 TTS 前缀(`T 消息`)
|
||||||
|
- 动态提示符显示状态(👀 🔊)
|
||||||
|
- 静默失败处理(网络异常时仅记录日志)
|
||||||
|
|
||||||
|
2. **流式输出(新流程)**
|
||||||
- 等待 AI 返回完整响应
|
- 等待 AI 返回完整响应
|
||||||
- Markdown 转译
|
- Markdown 转译
|
||||||
- 模拟流式输出(从配置读取速度)
|
- 模拟流式输出(从配置读取速度)
|
||||||
- 效果更好,无残留问题
|
- 效果更好,无残留问题
|
||||||
|
|
||||||
2. **Markdown 渲染**
|
3. **Markdown 渲染**
|
||||||
- 使用 glamour 库渲染 Markdown
|
- 使用 glamour 库渲染 Markdown
|
||||||
- 支持多种主题(dark, light, dracula, tokyo-night 等)
|
- 支持多种主题(dark, light, dracula, tokyo-night 等)
|
||||||
- 通过 project.config.yml 配置主题
|
- 通过 project.config.yml 配置主题
|
||||||
|
|
||||||
3. **项目配置**
|
4. **项目配置**
|
||||||
- 通过 project.config.yml 统一管理配置项
|
- 通过 project.config.yml 统一管理配置项
|
||||||
- 支持流式速度、渲染主题、Logo 等配置
|
- 支持流式速度、渲染主题、Logo、TTS 等配置
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -85,7 +92,8 @@
|
|||||||
|
|
||||||
# 模拟流式输出配置
|
# 模拟流式输出配置
|
||||||
streaming:
|
streaming:
|
||||||
simulated_speed_ms: 30 # 模拟流式输出速度(毫秒/字符)
|
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
|
||||||
|
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
|
||||||
|
|
||||||
# Markdown 渲染配置
|
# Markdown 渲染配置
|
||||||
markdown:
|
markdown:
|
||||||
@@ -94,6 +102,13 @@ markdown:
|
|||||||
# UI 配置
|
# UI 配置
|
||||||
ui:
|
ui:
|
||||||
logo: "🦐"
|
logo: "🦐"
|
||||||
|
user_prefix: "👀 "
|
||||||
|
|
||||||
|
# TTS 语音配置
|
||||||
|
tts:
|
||||||
|
enabled: false # 全局开关(默认关闭)
|
||||||
|
port: 9876 # mimo-tts daemon 端口
|
||||||
|
auto: true # AI 回复后自动朗读
|
||||||
```
|
```
|
||||||
|
|
||||||
配置加载优先级:
|
配置加载优先级:
|
||||||
@@ -102,6 +117,31 @@ ui:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## TTS 使用指南
|
||||||
|
|
||||||
|
### 命令
|
||||||
|
|
||||||
|
| 输入 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| `/tts` | 切换 TTS 开关 |
|
||||||
|
| `/tts on` | 开启 TTS |
|
||||||
|
| `/tts off` | 关闭 TTS |
|
||||||
|
| `/tts status` | 显示 TTS 状态 |
|
||||||
|
| `T 消息` | 临时开启 TTS 并发送消息 |
|
||||||
|
|
||||||
|
### 动态提示符
|
||||||
|
|
||||||
|
- 关闭:`👀 `
|
||||||
|
- 开启:`👀 🔊 `
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
- 需要先安装并启动 mimo-tts daemon:`mimo-tts daemon start`
|
||||||
|
- TTS 服务端地址:本地 9876 端口(默认)
|
||||||
|
- 网络异常时会静默失败,仅记录日志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 依赖管理
|
## 依赖管理
|
||||||
|
|
||||||
### Go 依赖
|
### Go 依赖
|
||||||
@@ -111,26 +151,28 @@ ui:
|
|||||||
- `charm.land/x/term` - 终端控制
|
- `charm.land/x/term` - 终端控制
|
||||||
- `github.com/muesli/termenv` - 终端环境工具
|
- `github.com/muesli/termenv` - 终端环境工具
|
||||||
- `gopkg.in/yaml.v3` - 配置文件解析
|
- `gopkg.in/yaml.v3` - 配置文件解析
|
||||||
|
- `github.com/ergochat/readline` - 终端输入
|
||||||
|
|
||||||
### 配置文件
|
### 配置文件
|
||||||
|
|
||||||
- `cmd/hxclaw/main.go` - 主入口逻辑
|
- `cmd/hxclaw/main.go` - 主入口逻辑
|
||||||
- `cmd/hxclaw/internal/markdown.go` - Markdown 渲染器
|
- `cmd/hxclaw/internal/markdown.go` - Markdown 渲染器
|
||||||
- `cmd/hxclaw/internal/helpers.go` - 辅助函数
|
- `cmd/hxclaw/internal/helpers.go` - 辅助函数(Readline)
|
||||||
- `cmd/hxclaw/internal/config.go` - 项目配置加载
|
- `cmd/hxclaw/internal/config.go` - 项目配置加载
|
||||||
|
- `cmd/hxclaw/internal/tts.go` - TTS 客户端
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 已知问题
|
## 已知问题
|
||||||
|
|
||||||
1. **重绘残留**:某些情况下有轻微文本重复(可接受)
|
1. **重绘残留**:某些情况下有轻微文本重复(已通过新流程解决)
|
||||||
2. **终端兼容性**:termenv 在某些终端可能不完全工作
|
2. **终端兼容性**:termenv 在某些终端可能不完全工作
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 待优化
|
## 待优化
|
||||||
|
|
||||||
1. 优化重绘逻辑,解决残留问题(已通过新流程解决)
|
1. 打印和 TTS 朗读同时进行(而非先打印完再读)
|
||||||
2. 添加更多主题支持
|
2. 添加更多主题支持
|
||||||
3. 添加命令行参数支持主题选择
|
3. 添加命令行参数支持主题选择
|
||||||
|
|
||||||
|
|||||||
62
changelog.md
62
changelog.md
@@ -2,44 +2,51 @@
|
|||||||
|
|
||||||
## 版本记录
|
## 版本记录
|
||||||
|
|
||||||
### v0.1.0 (规划中)
|
### v0.2.1
|
||||||
|
|
||||||
|
- 修复 TTS JSON 请求格式,兼容 Windows daemon
|
||||||
|
- 发送格式改为 `{"text": "内容"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### v0.2.0
|
||||||
|
|
||||||
|
- 新增 TTS 语音朗读功能
|
||||||
|
- 集成 mimo-tts client 功能,通过 TCP 连接本地 daemon
|
||||||
|
- 支持配置文件开关(tts.enabled)
|
||||||
|
- 支持命令行切换(/tts on/off/status)
|
||||||
|
- 支持临时 TTS 前缀(`T 消息` 临时开启)
|
||||||
|
- 动态提示符显示 TTS 状态(👀 🔊)
|
||||||
|
- 静默失败处理(网络异常时警告日志)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### v0.1.0
|
||||||
|
|
||||||
- 创建 hxclaw 项目
|
- 创建 hxclaw 项目
|
||||||
- 实现流式输出功能
|
- 实现流式输出功能
|
||||||
- Markdown 渲染功能(待实现)
|
- Markdown 渲染(glamour,自动代码高亮)
|
||||||
- 代码高亮功能(待实现)
|
- 项目配置化(project.config.yml)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 待实现功能
|
## 待实现功能
|
||||||
|
|
||||||
### v0.1.0 (当前)
|
### v0.2.0 (当前)
|
||||||
|
|
||||||
- [x] 流式输出功能
|
- [x] TTS 语音朗读功能
|
||||||
- [x] 导入 picoclaw 核心库
|
- [x] 集成 mimo-tts client (TCP 连接)
|
||||||
- [x] 实现流式 Provider 调用
|
- [x] 配置文件开关 (tts.enabled)
|
||||||
- [x] 实时打印 token
|
- [x] 命令行切换 (/tts on/off/status)
|
||||||
- [x] 处理非流式 Provider 回退
|
- [x] 临时 TTS 前缀 (T 消息)
|
||||||
- [x] 添加加载动画(spinner 组件)
|
- [x] 动态提示符显示状态
|
||||||
- [x] 使用 bubbletea v2 spinner.MiniDot 样式
|
- [x] 静默失败处理
|
||||||
- [x] 用户输入后显示思考中动画
|
|
||||||
- [x] 第一个 token 返回后显示思考完成
|
|
||||||
- [x] 流式输出完成后添加空行分隔
|
|
||||||
|
|
||||||
### v0.2.0 (计划)
|
|
||||||
|
|
||||||
- [ ] Markdown 渲染
|
|
||||||
- [ ] Markdown 解析
|
|
||||||
- [ ] 基础样式(粗体、斜体、链接)
|
|
||||||
- [ ] 代码块渲染
|
|
||||||
- [ ] 表格渲染
|
|
||||||
- [ ] 列表渲染
|
|
||||||
|
|
||||||
### v0.3.0 (计划)
|
### v0.3.0 (计划)
|
||||||
|
|
||||||
- [ ] 代码高亮
|
- [ ] 命令行参数支持(--theme, --tts 等)
|
||||||
- [ ] 集成 glow 或类似库
|
- [ ] 多语言支持
|
||||||
- [ ] 支持常见语言语法高亮
|
- [ ] 会话历史持久化
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -54,6 +61,9 @@
|
|||||||
- [x] 实现流式输出核心逻辑
|
- [x] 实现流式输出核心逻辑
|
||||||
- [x] 编译成功,生成 hxclaw 二进制
|
- [x] 编译成功,生成 hxclaw 二进制
|
||||||
- [x] 添加 spinner 加载动画组件
|
- [x] 添加 spinner 加载动画组件
|
||||||
|
- [x] 实现 Markdown 渲染(glamour)
|
||||||
|
- [x] 实现项目配置化(project.config.yml)
|
||||||
|
- [x] 实现 TTS 语音朗读功能
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type ProjectConfig struct {
|
|||||||
Streaming StreamingConfig `yaml:"streaming"`
|
Streaming StreamingConfig `yaml:"streaming"`
|
||||||
Markdown MarkdownConfig `yaml:"markdown"`
|
Markdown MarkdownConfig `yaml:"markdown"`
|
||||||
UI UIConfig `yaml:"ui"`
|
UI UIConfig `yaml:"ui"`
|
||||||
|
TTS TTSConfig `yaml:"tts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamingConfig struct {
|
type StreamingConfig struct {
|
||||||
@@ -29,6 +30,12 @@ type UIConfig struct {
|
|||||||
UserPrefix string `yaml:"user_prefix"`
|
UserPrefix string `yaml:"user_prefix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TTSConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Auto bool `yaml:"auto"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultCfg = ProjectConfig{
|
defaultCfg = ProjectConfig{
|
||||||
Streaming: StreamingConfig{
|
Streaming: StreamingConfig{
|
||||||
@@ -43,6 +50,11 @@ var (
|
|||||||
Logo: "🦐",
|
Logo: "🦐",
|
||||||
UserPrefix: "👀 ",
|
UserPrefix: "👀 ",
|
||||||
},
|
},
|
||||||
|
TTS: TTSConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Port: 9876,
|
||||||
|
Auto: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
projCfg *ProjectConfig
|
projCfg *ProjectConfig
|
||||||
projCfgLock sync.RWMutex
|
projCfgLock sync.RWMutex
|
||||||
@@ -90,6 +102,9 @@ func LoadProjectConfig() error {
|
|||||||
if cfg.UI.UserPrefix == "" {
|
if cfg.UI.UserPrefix == "" {
|
||||||
cfg.UI.UserPrefix = defaultCfg.UI.UserPrefix
|
cfg.UI.UserPrefix = defaultCfg.UI.UserPrefix
|
||||||
}
|
}
|
||||||
|
if cfg.TTS.Port <= 0 {
|
||||||
|
cfg.TTS.Port = defaultCfg.TTS.Port
|
||||||
|
}
|
||||||
|
|
||||||
projCfg = &cfg
|
projCfg = &cfg
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ func GetConfigPath() string {
|
|||||||
|
|
||||||
// Readline 实例包装
|
// Readline 实例包装
|
||||||
type Readline struct {
|
type Readline struct {
|
||||||
rl *readline.Instance
|
rl *readline.Instance
|
||||||
|
basePrompt string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReadline 创建一个新的 Readline 实例
|
// NewReadline 创建一个新的 Readline 实例
|
||||||
@@ -68,7 +69,18 @@ func NewReadline(prompt string) (*Readline, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Readline{rl: rl}, nil
|
return &Readline{rl: rl, basePrompt: prompt}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrompt 更新提示符
|
||||||
|
func (r *Readline) SetPrompt(prompt string) {
|
||||||
|
r.basePrompt = prompt
|
||||||
|
r.rl.SetPrompt(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBasePrompt 返回基础提示符
|
||||||
|
func (r *Readline) GetBasePrompt() string {
|
||||||
|
return r.basePrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readline 读取一行输入
|
// Readline 读取一行输入
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||||
prompt := internal.GetProjectConfig().UI.UserPrefix
|
basePrompt := internal.GetProjectConfig().UI.UserPrefix
|
||||||
|
prompt := internal.GetTTSPrompt(basePrompt)
|
||||||
|
|
||||||
rl, err := internal.NewReadline(prompt)
|
rl, err := internal.NewReadline(prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,6 +78,11 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|||||||
}
|
}
|
||||||
defer rl.Close()
|
defer rl.Close()
|
||||||
|
|
||||||
|
ttsCfg := internal.GetProjectConfig().TTS
|
||||||
|
if ttsCfg.Enabled {
|
||||||
|
internal.SetTTSEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := rl.Readline()
|
line, err := rl.Readline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,14 +104,37 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
runWithStreaming(agentLoop, input, sessionKey)
|
isTempTTS := false
|
||||||
|
if len(input) > 0 && input[0] == 'T' && (len(input) == 1 || input[1] == ' ') {
|
||||||
|
input = strings.TrimPrefix(input, "T")
|
||||||
|
input = strings.TrimPrefix(input, " ")
|
||||||
|
isTempTTS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(input, "/tts") {
|
||||||
|
handleTTSCommand(input, rl, basePrompt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTempTTS {
|
||||||
|
enabled := internal.ToggleTTS()
|
||||||
|
if enabled {
|
||||||
|
rl.SetPrompt(internal.GetTTSPrompt(basePrompt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runWithStreaming(agentLoop, input, sessionKey, isTempTTS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||||
reader := internal.NewSimpleReader()
|
reader := internal.NewSimpleReader()
|
||||||
|
ttsCfg := internal.GetProjectConfig().TTS
|
||||||
|
if ttsCfg.Enabled {
|
||||||
|
internal.SetTTSEnabled(true)
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
fmt.Print(internal.GetProjectConfig().UI.UserPrefix)
|
fmt.Print(internal.GetTTSPrompt(internal.GetProjectConfig().UI.UserPrefix))
|
||||||
line, err := reader.ReadString()
|
line, err := reader.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == internal.ErrEOF {
|
if err == internal.ErrEOF {
|
||||||
@@ -126,12 +155,28 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
runWithStreaming(agentLoop, input, sessionKey)
|
isTempTTS := false
|
||||||
|
if len(input) > 0 && input[0] == 'T' && (len(input) == 1 || input[1] == ' ') {
|
||||||
|
input = strings.TrimPrefix(input, "T")
|
||||||
|
input = strings.TrimPrefix(input, " ")
|
||||||
|
isTempTTS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(input, "/tts") {
|
||||||
|
handleTTSCommandSimple(input)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTempTTS {
|
||||||
|
internal.ToggleTTS()
|
||||||
|
}
|
||||||
|
|
||||||
|
runWithStreaming(agentLoop, input, sessionKey, isTempTTS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runWithStreaming 使用 ProcessDirect 处理请求,支持工具调用和结果显示
|
// runWithStreaming 使用 ProcessDirect 处理请求,支持工具调用和结果显示
|
||||||
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string, tempTTS bool) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
spinner := internal.NewSpinner("思考中...")
|
spinner := internal.NewSpinner("思考中...")
|
||||||
@@ -150,6 +195,11 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
|||||||
clearSpinnerLine()
|
clearSpinnerLine()
|
||||||
outputLineByLine(rendered)
|
outputLineByLine(rendered)
|
||||||
|
|
||||||
|
ttsCfg := internal.GetProjectConfig().TTS
|
||||||
|
if ttsCfg.Enabled || tempTTS || internal.IsTTSEnabled() {
|
||||||
|
go internal.SpeakText(resp)
|
||||||
|
}
|
||||||
|
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
printElapsed(elapsed)
|
printElapsed(elapsed)
|
||||||
}
|
}
|
||||||
@@ -218,3 +268,66 @@ func formatDuration(s float64) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%.1fs", s)
|
return fmt.Sprintf("%.1fs", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleTTSCommand(input string, rl *internal.Readline, basePrompt string) {
|
||||||
|
args := strings.Fields(input)
|
||||||
|
if len(args) == 1 {
|
||||||
|
enabled := internal.ToggleTTS()
|
||||||
|
rl.SetPrompt(internal.GetTTSPrompt(basePrompt))
|
||||||
|
status := "关闭"
|
||||||
|
if enabled {
|
||||||
|
status = "开启"
|
||||||
|
}
|
||||||
|
fmt.Printf("TTS 已%s\n", status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[1] {
|
||||||
|
case "on":
|
||||||
|
internal.SetTTSEnabled(true)
|
||||||
|
rl.SetPrompt(internal.GetTTSPrompt(basePrompt))
|
||||||
|
fmt.Println("TTS 已开启")
|
||||||
|
case "off":
|
||||||
|
internal.SetTTSEnabled(false)
|
||||||
|
rl.SetPrompt(internal.GetTTSPrompt(basePrompt))
|
||||||
|
fmt.Println("TTS 已关闭")
|
||||||
|
case "status":
|
||||||
|
status := "关闭"
|
||||||
|
if internal.IsTTSEnabled() {
|
||||||
|
status = "开启"
|
||||||
|
}
|
||||||
|
fmt.Printf("TTS 状态: %s\n", status)
|
||||||
|
default:
|
||||||
|
fmt.Println("用法: /tts [on|off|status]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTTSCommandSimple(input string) {
|
||||||
|
args := strings.Fields(input)
|
||||||
|
if len(args) == 1 {
|
||||||
|
internal.ToggleTTS()
|
||||||
|
status := "关闭"
|
||||||
|
if internal.IsTTSEnabled() {
|
||||||
|
status = "开启"
|
||||||
|
}
|
||||||
|
fmt.Printf("TTS 已%s\n", status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[1] {
|
||||||
|
case "on":
|
||||||
|
internal.SetTTSEnabled(true)
|
||||||
|
fmt.Println("TTS 已开启")
|
||||||
|
case "off":
|
||||||
|
internal.SetTTSEnabled(false)
|
||||||
|
fmt.Println("TTS 已关闭")
|
||||||
|
case "status":
|
||||||
|
status := "关闭"
|
||||||
|
if internal.IsTTSEnabled() {
|
||||||
|
status = "开启"
|
||||||
|
}
|
||||||
|
fmt.Printf("TTS 状态: %s\n", status)
|
||||||
|
default:
|
||||||
|
fmt.Println("用法: /tts [on|off|status]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,4 +13,10 @@ markdown:
|
|||||||
# UI 配置
|
# UI 配置
|
||||||
ui:
|
ui:
|
||||||
logo: "🦐"
|
logo: "🦐"
|
||||||
user_prefix: "👀 "
|
user_prefix: "👀 "
|
||||||
|
|
||||||
|
# TTS 语音配置
|
||||||
|
tts:
|
||||||
|
enabled: false # 全局开关(默认关闭)
|
||||||
|
port: 9876 # mimo-tts daemon 端口
|
||||||
|
auto: true # AI 回复后自动朗读
|
||||||
80
taolun.md
80
taolun.md
@@ -46,8 +46,8 @@ hxclaw/
|
|||||||
### 6. Markdown 终端渲染
|
### 6. Markdown 终端渲染
|
||||||
|
|
||||||
- 使用 charmbracelet 家族库
|
- 使用 charmbracelet 家族库
|
||||||
|
- glamour:Markdown 渲染(自带代码高亮)
|
||||||
- lipgloss:终端样式
|
- lipgloss:终端样式
|
||||||
- glow:代码高亮
|
|
||||||
- 流程:Markdown → ANSI 转义序列 → 终端显示
|
- 流程:Markdown → ANSI 转义序列 → 终端显示
|
||||||
|
|
||||||
### 7. 部署方式
|
### 7. 部署方式
|
||||||
@@ -330,7 +330,7 @@ func outputLineByLine(text string) {
|
|||||||
# 模拟流式输出配置
|
# 模拟流式输出配置
|
||||||
streaming:
|
streaming:
|
||||||
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
|
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
|
||||||
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
|
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
|
||||||
|
|
||||||
# Markdown 渲染配置
|
# Markdown 渲染配置
|
||||||
markdown:
|
markdown:
|
||||||
@@ -341,6 +341,12 @@ markdown:
|
|||||||
ui:
|
ui:
|
||||||
logo: "🦐" # Logo
|
logo: "🦐" # Logo
|
||||||
user_prefix: "👀 " # 用户输入前缀
|
user_prefix: "👀 " # 用户输入前缀
|
||||||
|
|
||||||
|
# TTS 语音配置
|
||||||
|
tts:
|
||||||
|
enabled: false # 全局开关(默认关闭)
|
||||||
|
port: 9876 # daemon 端口
|
||||||
|
auto: true # AI 回复后自动朗读
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 配置加载优先级
|
#### 配置加载优先级
|
||||||
@@ -381,4 +387,72 @@ func getConfigPath() string {
|
|||||||
|
|
||||||
- 人眼需要约 30-50ms 才能感知单次视觉变化
|
- 人眼需要约 30-50ms 才能感知单次视觉变化
|
||||||
- 空白字符不应逐个输出,应批量处理
|
- 空白字符不应逐个输出,应批量处理
|
||||||
- 终端宽度 100% 时 Markdown 渲染会显著增加行数和字符数
|
- 终端宽度 100% 时 Markdown 渲染会显著增加行数和字符数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 19. TTS 语音朗读集成
|
||||||
|
|
||||||
|
#### 架构设计
|
||||||
|
|
||||||
|
hxclaw 作为 mimo-tts 的客户端,通过 TCP Socket 连接本地 daemon:
|
||||||
|
|
||||||
|
```
|
||||||
|
hxclaw (客户端) --TCP:9876--> mimo-tts daemon (服务端)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
API 调用 (mimo-v2.5-tts)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
返回音频文件路径
|
||||||
|
|
|
||||||
|
v
|
||||||
|
afplay 播放
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 配置文件
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tts:
|
||||||
|
enabled: false # 全局开关(默认关闭)
|
||||||
|
port: 9876 # daemon 端口
|
||||||
|
auto: true # AI 回复后自动朗读
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 命令支持
|
||||||
|
|
||||||
|
| 输入 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| `/tts` | 切换 TTS 开关 |
|
||||||
|
| `/tts on` | 开启 TTS |
|
||||||
|
| `/tts off` | 关闭 TTS |
|
||||||
|
| `/tts status` | 显示状态 |
|
||||||
|
| `T 消息` | 临时开启并发送 |
|
||||||
|
|
||||||
|
#### 动态提示符
|
||||||
|
|
||||||
|
- 关闭:`👀 `
|
||||||
|
- 开启:`👀 🔊 `
|
||||||
|
|
||||||
|
#### 实现要点
|
||||||
|
|
||||||
|
1. TCP 连接:使用 Go 标准库 `net` 包
|
||||||
|
2. JSON 请求:发送格式 `{"text": "内容"}`
|
||||||
|
3. 异步朗读:使用 `go func()` 异步调用
|
||||||
|
4. 静默失败:网络异常只记录警告日志,不阻塞用户
|
||||||
|
|
||||||
|
#### 踩坑记录
|
||||||
|
|
||||||
|
**ergochat/readline SetPrompt 无返回值**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 错误
|
||||||
|
func (r *Readline) SetPrompt(prompt string) error {
|
||||||
|
return r.rl.SetPrompt(prompt) // SetPrompt 返回 void
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确
|
||||||
|
func (r *Readline) SetPrompt(prompt string) {
|
||||||
|
r.rl.SetPrompt(prompt) // void 类型
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user