add tts tool
This commit is contained in:
parent
03d6ec10d2
commit
c7be7f9f0d
5 changed files with 141 additions and 0 deletions
|
|
@ -522,6 +522,9 @@
|
|||
"read_file": {
|
||||
"enabled": true
|
||||
},
|
||||
"send_tts": {
|
||||
"enabled": false
|
||||
},
|
||||
"spawn": {
|
||||
"enabled": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/skills"
|
||||
"github.com/sipeed/picoclaw/pkg/state"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
"github.com/sipeed/picoclaw/pkg/tts"
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
)
|
||||
|
||||
|
|
@ -155,6 +156,13 @@ func registerSharedTools(
|
|||
provider providers.LLMProvider,
|
||||
) {
|
||||
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() {
|
||||
agent, ok := registry.GetAgent(agentID)
|
||||
|
|
@ -251,6 +259,10 @@ func registerSharedTools(
|
|||
agent.Tools.Register(sendFileTool)
|
||||
}
|
||||
|
||||
if ttsProvider != nil {
|
||||
agent.Tools.Register(tools.NewSendTTSTool(ttsProvider, nil))
|
||||
}
|
||||
|
||||
// Skill discovery and installation tools
|
||||
skills_enabled := cfg.Tools.IsToolEnabled("skills")
|
||||
find_skills_enable := cfg.Tools.IsToolEnabled("find_skills")
|
||||
|
|
@ -1038,6 +1050,11 @@ func (al *AgentLoop) SetMediaStore(s media.MediaStore) {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -1246,6 +1246,7 @@ type ToolsConfig struct {
|
|||
Message ToolConfig `json:"message" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
||||
ReadFile ReadFileToolConfig `json:"read_file" envPrefix:"PICOCLAW_TOOLS_READ_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_"`
|
||||
SpawnStatus ToolConfig `json:"spawn_status" envPrefix:"PICOCLAW_TOOLS_SPAWN_STATUS_"`
|
||||
SPI ToolConfig `json:"spi" envPrefix:"PICOCLAW_TOOLS_SPI_"`
|
||||
|
|
@ -2197,6 +2198,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
|||
return t.WebFetch.Enabled
|
||||
case "send_file":
|
||||
return t.SendFile.Enabled
|
||||
case "send_tts":
|
||||
return t.SendTTS.Enabled
|
||||
case "write_file":
|
||||
return t.WriteFile.Enabled
|
||||
case "mcp":
|
||||
|
|
|
|||
|
|
@ -462,6 +462,9 @@ func DefaultConfig() *Config {
|
|||
SendFile: ToolConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
SendTTS: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
MCP: MCPConfig{
|
||||
ToolConfig: ToolConfig{
|
||||
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