Files
viettranx 9f88f12c2a feat(audio): proxy STT wrapper
Implement STT proxy provider that wraps arbitrary HTTP-based STT services. Ported 12 test cases from legacy Telegram STT integration to validate multipart/form-data submission, language/model overrides, and API error handling.
2026-04-15 11:24:57 +07:00

87 lines
2.4 KiB
Go

// Package proxy_stt wraps internal/channels/media.TranscribeAudio as an
// audio.STTProvider, preserving all existing proxy behavior for backward compat.
package proxy_stt
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/nextlevelbuilder/goclaw/internal/audio"
"github.com/nextlevelbuilder/goclaw/internal/channels/media"
)
// Provider wraps media.TranscribeAudio as an audio.STTProvider.
// Preserves all proxy behaviors (Bearer auth, tenant_id field, semaphore, etc.)
// so channels can fall back to the proxy without any behavior change.
type Provider struct {
cfg media.STTConfig
}
// NewProvider returns a proxy STT provider backed by media.TranscribeAudio.
func NewProvider(cfg media.STTConfig) *Provider {
return &Provider{cfg: cfg}
}
// Name returns the stable provider identifier.
func (p *Provider) Name() string { return "proxy" }
// Transcribe delegates to media.TranscribeAudio. When in.FilePath is empty but
// in.Bytes is present, writes a 0600 temp file and defers cleanup.
// Returns ("", nil) when proxy URL is empty or filePath is empty (proxy no-op).
func (p *Provider) Transcribe(ctx context.Context, in audio.STTInput, _ audio.STTOptions) (*audio.TranscriptResult, error) {
filePath := in.FilePath
if filePath == "" && len(in.Bytes) > 0 {
ext := extFromMime(in.MimeType)
f, err := os.CreateTemp("", "stt-proxy-*"+ext)
if err != nil {
return nil, fmt.Errorf("proxy_stt: create temp file: %w", err)
}
if err := os.Chmod(f.Name(), 0600); err != nil {
f.Close()
os.Remove(f.Name())
return nil, fmt.Errorf("proxy_stt: chmod temp file: %w", err)
}
if _, err := f.Write(in.Bytes); err != nil {
f.Close()
os.Remove(f.Name())
return nil, fmt.Errorf("proxy_stt: write temp file: %w", err)
}
f.Close()
defer os.Remove(f.Name())
filePath = f.Name()
}
text, err := media.TranscribeAudio(ctx, p.cfg, filePath)
if err != nil {
return nil, err
}
return &audio.TranscriptResult{
Text: text,
Provider: "proxy",
}, nil
}
// extFromMime returns a file extension for a MIME type.
func extFromMime(mime string) string {
switch strings.Split(mime, ";")[0] {
case "audio/ogg":
return ".ogg"
case "audio/mpeg", "audio/mp3":
return ".mp3"
case "audio/wav", "audio/wave":
return ".wav"
case "audio/mp4", "audio/m4a":
return ".m4a"
case "audio/webm":
return ".webm"
case "audio/flac":
return ".flac"
default:
return filepath.Ext(mime) // fallback; usually empty
}
}