add tts tool
This commit is contained in:
parent
9fe634895e
commit
4b6fd66e04
5 changed files with 141 additions and 0 deletions
|
|
@ -522,6 +522,9 @@
|
||||||
"read_file": {
|
"read_file": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
"send_tts": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
"spawn": {
|
"spawn": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/skills"
|
"github.com/sipeed/picoclaw/pkg/skills"
|
||||||
"github.com/sipeed/picoclaw/pkg/state"
|
"github.com/sipeed/picoclaw/pkg/state"
|
||||||
"github.com/sipeed/picoclaw/pkg/tools"
|
"github.com/sipeed/picoclaw/pkg/tools"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/tts"
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -155,6 +156,13 @@ func registerSharedTools(
|
||||||
provider providers.LLMProvider,
|
provider providers.LLMProvider,
|
||||||
) {
|
) {
|
||||||
allowReadPaths := buildAllowReadPatterns(cfg)
|
allowReadPaths := buildAllowReadPatterns(cfg)
|
||||||
|
var ttsProvider tts.TTSProvider
|
||||||
|
if cfg.Tools.IsToolEnabled("send_tts") {
|
||||||
|
ttsProvider = tts.DetectTTS(cfg)
|
||||||
|
if ttsProvider == nil {
|
||||||
|
logger.WarnCF("voice-tts", "send_tts enabled but no TTS provider configured", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, agentID := range registry.ListAgentIDs() {
|
for _, agentID := range registry.ListAgentIDs() {
|
||||||
agent, ok := registry.GetAgent(agentID)
|
agent, ok := registry.GetAgent(agentID)
|
||||||
|
|
@ -251,6 +259,10 @@ func registerSharedTools(
|
||||||
agent.Tools.Register(sendFileTool)
|
agent.Tools.Register(sendFileTool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ttsProvider != nil {
|
||||||
|
agent.Tools.Register(tools.NewSendTTSTool(ttsProvider, nil))
|
||||||
|
}
|
||||||
|
|
||||||
// Skill discovery and installation tools
|
// Skill discovery and installation tools
|
||||||
skills_enabled := cfg.Tools.IsToolEnabled("skills")
|
skills_enabled := cfg.Tools.IsToolEnabled("skills")
|
||||||
find_skills_enable := cfg.Tools.IsToolEnabled("find_skills")
|
find_skills_enable := cfg.Tools.IsToolEnabled("find_skills")
|
||||||
|
|
@ -1038,6 +1050,11 @@ func (al *AgentLoop) SetMediaStore(s media.MediaStore) {
|
||||||
sf.SetMediaStore(s)
|
sf.SetMediaStore(s)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
registry.ForEachTool("send_tts", func(t tools.Tool) {
|
||||||
|
if st, ok := t.(*tools.SendTTSTool); ok {
|
||||||
|
st.SetMediaStore(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTranscriber injects a voice transcriber for agent-level audio transcription.
|
// SetTranscriber injects a voice transcriber for agent-level audio transcription.
|
||||||
|
|
|
||||||
|
|
@ -1218,6 +1218,7 @@ type ToolsConfig struct {
|
||||||
Message ToolConfig `json:"message" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
Message ToolConfig `json:"message" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
||||||
ReadFile ReadFileToolConfig `json:"read_file" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
ReadFile ReadFileToolConfig `json:"read_file" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
||||||
SendFile ToolConfig `json:"send_file" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
SendFile ToolConfig `json:"send_file" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
||||||
|
SendTTS ToolConfig `json:"send_tts" envPrefix:"PICOCLAW_TOOLS_SEND_TTS_"`
|
||||||
Spawn ToolConfig `json:"spawn" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
Spawn ToolConfig `json:"spawn" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
||||||
SpawnStatus ToolConfig `json:"spawn_status" envPrefix:"PICOCLAW_TOOLS_SPAWN_STATUS_"`
|
SpawnStatus ToolConfig `json:"spawn_status" envPrefix:"PICOCLAW_TOOLS_SPAWN_STATUS_"`
|
||||||
SPI ToolConfig `json:"spi" envPrefix:"PICOCLAW_TOOLS_SPI_"`
|
SPI ToolConfig `json:"spi" envPrefix:"PICOCLAW_TOOLS_SPI_"`
|
||||||
|
|
@ -2156,6 +2157,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
||||||
return t.WebFetch.Enabled
|
return t.WebFetch.Enabled
|
||||||
case "send_file":
|
case "send_file":
|
||||||
return t.SendFile.Enabled
|
return t.SendFile.Enabled
|
||||||
|
case "send_tts":
|
||||||
|
return t.SendTTS.Enabled
|
||||||
case "write_file":
|
case "write_file":
|
||||||
return t.WriteFile.Enabled
|
return t.WriteFile.Enabled
|
||||||
case "mcp":
|
case "mcp":
|
||||||
|
|
|
||||||
|
|
@ -460,6 +460,9 @@ func DefaultConfig() *Config {
|
||||||
SendFile: ToolConfig{
|
SendFile: ToolConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
|
SendTTS: ToolConfig{
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
MCP: MCPConfig{
|
MCP: MCPConfig{
|
||||||
ToolConfig: ToolConfig{
|
ToolConfig: ToolConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
|
|
||||||
115
pkg/tools/tts_send.go
Normal file
115
pkg/tools/tts_send.go
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/media"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/tts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendTTSTool struct {
|
||||||
|
provider tts.TTSProvider
|
||||||
|
mediaStore media.MediaStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendTTSTool(provider tts.TTSProvider, store media.MediaStore) *SendTTSTool {
|
||||||
|
return &SendTTSTool{
|
||||||
|
provider: provider,
|
||||||
|
mediaStore: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SendTTSTool) Name() string { return "send_tts" }
|
||||||
|
|
||||||
|
func (t *SendTTSTool) Description() string {
|
||||||
|
return "Synthesize speech from text and send it as an audio file to the user."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SendTTSTool) Parameters() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"text": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The text to synthesize into speech.",
|
||||||
|
},
|
||||||
|
"filename": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional filename for the audio file (e.g., response.ogg).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": []string{"text"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SendTTSTool) SetMediaStore(store media.MediaStore) {
|
||||||
|
t.mediaStore = store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SendTTSTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
|
||||||
|
text, _ := args["text"].(string)
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
return ErrorResult("text is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.provider == nil {
|
||||||
|
return ErrorResult("tts provider is not configured")
|
||||||
|
}
|
||||||
|
if t.mediaStore == nil {
|
||||||
|
return ErrorResult("media store not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := ToolChannel(ctx)
|
||||||
|
chatID := ToolChatID(ctx)
|
||||||
|
if channel == "" || chatID == "" {
|
||||||
|
return ErrorResult("no target channel/chat available")
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := t.provider.Synthesize(ctx, text)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("tts synthesize failed: %v", err)).WithError(err)
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(media.TempDir(), 0o755); err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("failed to create media temp dir: %v", err)).WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.CreateTemp(media.TempDir(), "tts-*.ogg")
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("failed to create temp file: %v", err)).WithError(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(file, stream); err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("failed to write tts audio: %v", err)).WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, _ := args["filename"].(string)
|
||||||
|
filename = strings.TrimSpace(filename)
|
||||||
|
if filename == "" {
|
||||||
|
filename = fmt.Sprintf("tts-%d.ogg", time.Now().Unix())
|
||||||
|
}
|
||||||
|
if filepath.Ext(filename) == "" {
|
||||||
|
filename += ".ogg"
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := fmt.Sprintf("tool:send_tts:%s:%s:%d", channel, chatID, time.Now().UnixNano())
|
||||||
|
ref, err := t.mediaStore.Store(file.Name(), media.MediaMeta{
|
||||||
|
Filename: filename,
|
||||||
|
ContentType: "audio/ogg",
|
||||||
|
Source: "tool:send_tts",
|
||||||
|
}, scope)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("failed to register audio: %v", err)).WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaResult("TTS audio sent", []string{ref})
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue