mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-11 06:05:45 +08:00
Go: implement TTS for MiniMax provider and CLI testing for TTS (#14911)
### What problem does this PR solve?
This PR implement TTS for MiniMax provider and CLI testing for TTS
**The following functionalities are now supported:**
**MiniMax:**
- [x] Chat / Stream Chat
- [x] Embedding
- [x] Rerank
- [x] Model listing
- [x] Provider connection checking
- [x] Text To Speech
- [ ] OCRFile
- [ ] ~~Audio To Text~~
- [ ] ~~Balance~~
**Verified examples from the CLI:**
```plaintext
RAGFlow(user)> tts with 'speech-2.8-hd@test@minimax' text 'He who desires but acts not, breeds pestilence.' play format 'wav' save './internal' param '{"voice_setting": {"voice_id": "English_radiant_girl", "speed": 1, "vol": 1, "pitch": 0}, "audio_setting": {"sample_rate": 32000, "bitrate": 128000, "format": "wav", "channel": 1}, "output_format": "hex"}'
Saved to directory: /home/infiniflow/Documents/development/ragflow/internal/speech-2.8-hd_output.wav
SUCCESS
RAGFlow(user)> stream tts with 'speech-2.8-hd@test@minimax' text 'He who desires but acts not, breeds pestilence.' play format 'wav' save './internal' param '{"voice_setting": {"voice_id": "English_radiant_girl", "speed": 1, "vol": 1, "pitch": 0}, "audio_setting": {"sample_rate": 32000, "bitrate": 128000, "format": "wav", "channel": 1}, "output_format": "hex"}'
Saved to directory: /home/infiniflow/Documents/development/ragflow/internal/speech-2.8-hd_output.wav
SUCCESS
```
Set `Play` to play audio in CLI
Set `Save` `PATH_TO_SAVE` to save file
Set `format` to save file in wav or mp3
Set `Param` align with official request body
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@@ -27,6 +27,7 @@ import (
|
||||
"net"
|
||||
netUrl "net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
ce "ragflow/internal/cli/filesystem"
|
||||
"strings"
|
||||
@@ -1973,6 +1974,52 @@ func (c *RAGFlowClient) TTSUserCommand(cmd *Command) (ResponseIf, error) {
|
||||
"text": text,
|
||||
}
|
||||
|
||||
ttsConfigPayload := make(map[string]interface{})
|
||||
|
||||
explicitFormat, hasExplicitFormat := cmd.Params["format"].(string)
|
||||
|
||||
if paramStr, ok := cmd.Params["param_str"].(string); ok && paramStr != "" {
|
||||
var dynamicParams map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(paramStr), &dynamicParams); err != nil {
|
||||
return nil, fmt.Errorf("param string must be valid JSON. Error: %w", err)
|
||||
}
|
||||
|
||||
ttsConfigPayload["params"] = dynamicParams
|
||||
|
||||
if !hasExplicitFormat {
|
||||
var findFormat func(map[string]interface{}) string
|
||||
findFormat = func(m map[string]interface{}) string {
|
||||
if val, ok := m["format"]; ok {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
if val, ok := m["response_format"]; ok {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
for _, v := range m {
|
||||
if subMap, ok := v.(map[string]interface{}); ok {
|
||||
if res := findFormat(subMap); res != "" {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if ext := findFormat(dynamicParams); ext != "" {
|
||||
explicitFormat = ext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if explicitFormat != "" {
|
||||
ttsConfigPayload["format"] = explicitFormat
|
||||
} else {
|
||||
explicitFormat = "mp3"
|
||||
}
|
||||
|
||||
if len(ttsConfigPayload) > 0 {
|
||||
payload["tts_config"] = ttsConfigPayload
|
||||
}
|
||||
|
||||
url := "/audio/speech"
|
||||
|
||||
resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload)
|
||||
@@ -1982,21 +2029,91 @@ func (c *RAGFlowClient) TTSUserCommand(cmd *Command) (ResponseIf, error) {
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("failed to TTS document: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
|
||||
}
|
||||
var result CommonResponse
|
||||
if err = json.Unmarshal(resp.Body, &result); err != nil {
|
||||
|
||||
var ttsResult struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Audio string `json:"audio"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(resp.Body, &ttsResult); err != nil {
|
||||
return nil, fmt.Errorf("TTS document failed: invalid JSON (%w)", err)
|
||||
}
|
||||
if result.Code != 0 {
|
||||
return nil, fmt.Errorf("%s", result.Message)
|
||||
}
|
||||
result.Duration = resp.Duration
|
||||
|
||||
// save file
|
||||
//err = os.WriteFile(fileToSave, resp.Body, 0644)
|
||||
//if err != nil {
|
||||
// result.Message += fmt.Sprintf("failed to save file: %s", err.Error())
|
||||
// result.Code = 1
|
||||
//}
|
||||
if ttsResult.Code != 0 {
|
||||
return nil, fmt.Errorf("%s", ttsResult.Message)
|
||||
}
|
||||
|
||||
// Convert Base64 back to the original audio byte stream
|
||||
audioBytes, err := base64.StdEncoding.DecodeString(ttsResult.Data.Audio)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode audio base64: %w", err)
|
||||
}
|
||||
|
||||
shouldPlay, _ := cmd.Params["play"].(bool)
|
||||
shouldSave, _ := cmd.Params["save"].(bool)
|
||||
saveDir, _ := cmd.Params["save_path"].(string)
|
||||
|
||||
|
||||
fileName := fmt.Sprintf("%s_output.%s", modelName, explicitFormat)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
cwd = "."
|
||||
}
|
||||
localPath := filepath.Join(cwd, fileName)
|
||||
|
||||
if err := os.WriteFile(localPath, audioBytes, 0644); err != nil {
|
||||
return nil, fmt.Errorf("failed to write local audio file: %w", err)
|
||||
}
|
||||
|
||||
if shouldPlay {
|
||||
cmdExec := exec.Command("aplay", localPath)
|
||||
if err := cmdExec.Run(); err != nil {
|
||||
fmt.Printf("Play error: %v (Hint: did you use 'format: wav' in your params?)\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
var finalMessage string
|
||||
if shouldSave {
|
||||
if saveDir == "" {
|
||||
saveDir = cwd
|
||||
} else {
|
||||
absSaveDir, err := filepath.Abs(saveDir)
|
||||
if err == nil {
|
||||
saveDir = absSaveDir
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(saveDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create save directory: %w", err)
|
||||
}
|
||||
|
||||
finalPath := filepath.Join(saveDir, fileName)
|
||||
if err := os.WriteFile(finalPath, audioBytes, 0644); err != nil {
|
||||
return nil, fmt.Errorf("failed to save file to target directory: %w", err)
|
||||
}
|
||||
|
||||
if saveDir != cwd {
|
||||
os.Remove(localPath)
|
||||
}
|
||||
|
||||
finalMessage = fmt.Sprintf("Saved to directory: %s", finalPath)
|
||||
}
|
||||
} else {
|
||||
defer os.Remove(localPath)
|
||||
finalMessage = "TTS Task Completed (Audio not saved)"
|
||||
}
|
||||
|
||||
if finalMessage != "" && shouldSave {
|
||||
fmt.Println(finalMessage)
|
||||
}
|
||||
|
||||
var result SimpleResponse
|
||||
result.Code = 0
|
||||
result.Message = "SUCCESS"
|
||||
result.Duration = resp.Duration
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user