package handler import ( "bytes" "encoding/json" "io" "github.com/yourname/cyrene-ai/pkg/logger" "net/http" "strings" "github.com/gin-gonic/gin" ) // VoiceHandler 语音处理器 — 代理到 voice-service type VoiceHandler struct { voiceServiceURL string client *http.Client } // NewVoiceHandler 创建语音处理器 func NewVoiceHandler(voiceServiceURL string) *VoiceHandler { return &VoiceHandler{ voiceServiceURL: voiceServiceURL, client: &http.Client{}, } } // Transcribe POST /api/v1/voice/transcribe // 代理 multipart/form-data 请求到 voice-service func (h *VoiceHandler) Transcribe(c *gin.Context) { // 限制上传大小 (10MB) c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10<<20) // 读取原始请求体 bodyBytes, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败或文件过大,最大支持 10MB"}) return } // 构建代理请求 url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/transcribe" proxyReq, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "构建代理请求失败"}) return } proxyReq.Header.Set("Content-Type", c.GetHeader("Content-Type")) resp, err := h.client.Do(proxyReq) if err != nil { logger.Printf("[voice] Voice-Service 不可达 (Transcribe): %v", err) c.JSON(http.StatusBadGateway, gin.H{ "error": "Voice-Service 不可达: " + err.Error(), "errorType": "voice_service_unreachable", "hint": "Voice-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Voice-Service", }) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) // 透传状态码和响应 c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody) } // TTSSynthesize POST /api/v1/voice/tts // 代理 JSON 请求到 voice-service TTS 合成 func (h *VoiceHandler) TTSSynthesize(c *gin.Context) { // 读取 JSON 请求体 bodyBytes, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"}) return } // 构建代理请求 url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/synthesize" proxyReq, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "构建代理请求失败"}) return } proxyReq.Header.Set("Content-Type", "application/json") resp, err := h.client.Do(proxyReq) if err != nil { logger.Printf("[voice] Voice-Service 不可达 (TTS): %v", err) c.JSON(http.StatusBadGateway, gin.H{ "error": "Voice-Service 不可达: " + err.Error(), "errorType": "voice_service_unreachable", "hint": "Voice-Service 服务未启动或不可达", }) return } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "读取 Voice-Service 响应失败"}) return } // 透传状态码、Content-Type 和响应体 c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody) } // TTSVoices GET /api/v1/voice/tts/voices // 代理请求到 voice-service 获取可用语音列表 func (h *VoiceHandler) TTSVoices(c *gin.Context) { url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/voices" resp, err := h.client.Get(url) if err != nil { logger.Printf("[voice] Voice-Service 不可达 (Voices): %v", err) c.JSON(http.StatusBadGateway, gin.H{ "error": "Voice-Service 不可达: " + err.Error(), "errorType": "voice_service_unreachable", }) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) // 解析并透传 var data interface{} json.Unmarshal(respBody, &data) c.JSON(resp.StatusCode, data) } // TTSStatus GET /api/v1/voice/tts/status // 代理请求到 voice-service 获取 TTS 状态 func (h *VoiceHandler) TTSStatus(c *gin.Context) { url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/tts/status" resp, err := h.client.Get(url) if err != nil { logger.Printf("[voice] Voice-Service 不可达 (TTS Status): %v", err) c.JSON(http.StatusBadGateway, gin.H{ "error": "Voice-Service 不可达: " + err.Error(), "errorType": "voice_service_unreachable", }) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var data interface{} json.Unmarshal(respBody, &data) c.JSON(resp.StatusCode, data) } // VoiceStatus GET /api/v1/voice/status // 代理请求到 voice-service 获取完整状态(STT + TTS) func (h *VoiceHandler) VoiceStatus(c *gin.Context) { url := strings.TrimRight(h.voiceServiceURL, "/") + "/api/v1/status" resp, err := h.client.Get(url) if err != nil { logger.Printf("[voice] Voice-Service 不可达 (Status): %v", err) c.JSON(http.StatusBadGateway, gin.H{ "error": "Voice-Service 不可达: " + err.Error(), "errorType": "voice_service_unreachable", }) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var data interface{} json.Unmarshal(respBody, &data) c.JSON(resp.StatusCode, data) }