package services import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) var whisperHTTPClient = &http.Client{Timeout: 90 * time.Second} // TranscribirAudio envía un archivo de audio a un endpoint de transcripción // compatible con la API de OpenAI (POST /audio/transcriptions, multipart) y // devuelve el texto. Funciona tanto con OpenAI (modelo whisper-1) como con Groq // (modelo whisper-large-v3), reutilizando la config que ya se administra en // /app/ai-config con el módulo "whisper". func TranscribirAudio(ai *models.AiConfig, audioPath string) (string, error) { if ai == nil { return "", fmt.Errorf("no hay una configuración de Whisper activa: ve a /app/ai-config, crea o edita una y márcale el módulo 'Transcripción de audio (Whisper)'") } if ai.ClaveEnClaro() == "" { return "", fmt.Errorf("la configuración de Whisper '%s' no tiene API key", ai.Nombre) } baseURL := ai.BaseURL if baseURL == "" { baseURL = ProviderDefaultURL(ai.Provider) } baseURL = strings.TrimRight(baseURL, "/") model := ai.ModelName if model == "" { model = "whisper-1" } f, err := os.Open(audioPath) if err != nil { return "", fmt.Errorf("no se pudo abrir el audio descargado: %w", err) } defer f.Close() var body bytes.Buffer writer := multipart.NewWriter(&body) part, err := writer.CreateFormFile("file", filepath.Base(audioPath)) if err != nil { return "", err } if _, err := io.Copy(part, f); err != nil { return "", fmt.Errorf("error leyendo el audio: %w", err) } _ = writer.WriteField("model", model) _ = writer.WriteField("response_format", "json") if err := writer.Close(); err != nil { return "", err } req, err := http.NewRequest("POST", baseURL+"/audio/transcriptions", &body) if err != nil { return "", err } req.Header.Set("Authorization", "Bearer "+ai.ClaveEnClaro()) req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := whisperHTTPClient.Do(req) if err != nil { return "", fmt.Errorf("no se pudo conectar con el servicio de transcripción: %w", err) } defer resp.Body.Close() raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle := strings.TrimSpace(string(raw)) if len(detalle) > 300 { detalle = detalle[:300] } return "", fmt.Errorf("el servicio de transcripción respondió %d: %s", resp.StatusCode, detalle) } var out struct { Text string `json:"text"` } if err := json.Unmarshal(raw, &out); err != nil { return "", fmt.Errorf("respuesta inesperada del servicio de transcripción") } texto := strings.TrimSpace(out.Text) if texto == "" { return "", fmt.Errorf("no se detectó texto en el audio") } return texto, nil }