feat(voice): real TTS fallback chain and whisper-compatible STT

- TTS providers: cloud (OpenAI-compatible /audio/speech, e.g. Bailian
  compatible mode with cosyvoice), local self-hosted HTTP server,
  edge-tts CLI, espeak CLI, silent — ordered by TTS_PROVIDER list,
  skipping providers whose binary/config is missing
- STT: whisper-compatible /audio/transcriptions endpoint (cloud or
  self-hosted faster-whisper), replacing the empty dashscope stub
- config: TTS_CLOUD_URL/KEY/MODEL/VOICE, TTS_LOCAL_URL, STT_API_URL/
  KEY/MODEL; cloud keys fall back to DASHSCOPE_API_KEY
- gateway /voice/tts now returns real audio/mpeg bytes instead of a
  byte count
- tests: cloud/local/whisper round-trips via httptest, chain skip
  behavior, fallback order; kept and adapted prior service tests
This commit is contained in:
Aska committed 2026-08-21 18:28:39 +08:00
1 parent b8fb7feb86
commit a0357a7bbd
7 files changed
+410 -63

No files matched your search

+14 -3
View File
@@ -53,8 +53,16 @@ type Config struct {
// Voice
DashScopeAPIKey string
TTSProvider string // "edge-tts" | "espeak"
ASRProvider string // "dashscope" | "whisper-local"
TTSProvider string // comma-separated fallback chain: "cloud,local,edge-tts,espeak,silent"
TTSCloudURL string // OpenAI-compatible base URL for cloud TTS (e.g. Bailian compatible mode)
TTSCloudKey string // falls back to DashScopeAPIKey
TTSCloudModel string
TTSCloudVoice string
TTSLocalURL string // self-hosted TTS POST endpoint
ASRProvider string // legacy label
STTApiURL string // Whisper-compatible base URL (cloud or self-hosted)
STTApiKey string // falls back to DashScopeAPIKey
STTModel string
// Auth
JWTSecret string
@@ -139,7 +147,10 @@ func DefaultConfig() *Config {
EmbeddingModel: "text-embedding-3-small",
TTSProvider: "edge-tts",
TTSProvider: "cloud,local,edge-tts,silent",
TTSCloudURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
TTSCloudModel: "cosyvoice-v1",
TTSCloudVoice: "longxiaochun",
ASRProvider: "dashscope",
JWTExpiryHours: 720,
+8
View File
@@ -93,7 +93,15 @@ func (c *Config) mergeEnv() {
// Voice
c.DashScopeAPIKey = getEnv("DASHSCOPE_API_KEY", c.DashScopeAPIKey)
c.TTSProvider = getEnv("TTS_PROVIDER", c.TTSProvider)
c.TTSCloudURL = getEnv("TTS_CLOUD_URL", c.TTSCloudURL)
c.TTSCloudKey = getEnv("TTS_CLOUD_KEY", c.TTSCloudKey)
c.TTSCloudModel = getEnv("TTS_CLOUD_MODEL", c.TTSCloudModel)
c.TTSCloudVoice = getEnv("TTS_CLOUD_VOICE", c.TTSCloudVoice)
c.TTSLocalURL = getEnv("TTS_LOCAL_URL", c.TTSLocalURL)
c.ASRProvider = getEnv("ASR_PROVIDER", c.ASRProvider)
c.STTApiURL = getEnv("STT_API_URL", c.STTApiURL)
c.STTApiKey = getEnv("STT_API_KEY", c.STTApiKey)
c.STTModel = getEnv("STT_MODEL", c.STTModel)
// Auth
c.JWTSecret = getEnv("JWT_SECRET", c.JWTSecret)