Files
hyprvoice/internal/transcriber/transcriber.go
T
leonardotrapani 3563def987 refactor transcriber factory to use model metadata
- factory now looks up Model via provider.GetModel()
- switches on model.AdapterType instead of provider name
- uses model.Endpoint for adapter configuration
- streaming models return clear error (not yet implemented)
- empty model falls back to provider default
- added tests for streaming and unknown model errors
2026-02-01 01:00:21 +01:00

114 lines
3.4 KiB
Go

package transcriber
import (
"context"
"fmt"
"strings"
"github.com/leonardotrapani/hyprvoice/internal/provider"
"github.com/leonardotrapani/hyprvoice/internal/recording"
)
// Main transcriber interface
type Transcriber interface {
Start(ctx context.Context, frameCh <-chan recording.AudioFrame) (<-chan error, error)
Stop(ctx context.Context) error
GetFinalTranscription() (string, error)
}
// BatchAdapter interface for batch transcription backends (collect all audio, transcribe at end)
type BatchAdapter interface {
Transcribe(ctx context.Context, audioData []byte) (string, error)
}
// Configuration for the transcriber
type Config struct {
Provider string
APIKey string
Language string
Model string
Keywords []string
}
// mapConfigProviderToRegistryName maps config provider names to provider registry names
// Config uses names like "groq-transcription", "groq-translation", "mistral-transcription"
// Registry uses base names like "groq", "mistral"
func mapConfigProviderToRegistryName(configProvider string) string {
switch configProvider {
case "groq-transcription", "groq-translation":
return "groq"
case "mistral-transcription":
return "mistral"
default:
return configProvider
}
}
// NewTranscriber creates a new transcriber based on model metadata
func NewTranscriber(config Config) (Transcriber, error) {
if config.Provider == "" {
return nil, fmt.Errorf("provider is required")
}
// special case: groq-translation uses CreateTranslation API (different from transcription)
if config.Provider == "groq-translation" {
if config.APIKey == "" {
return nil, fmt.Errorf("Groq API key required")
}
adapter := NewGroqTranslationAdapter(config)
return NewSimpleTranscriber(config, adapter), nil
}
// map config provider name to registry provider name
registryProvider := mapConfigProviderToRegistryName(config.Provider)
// lookup provider
p := provider.GetProvider(registryProvider)
if p == nil {
return nil, fmt.Errorf("unknown provider: %s", config.Provider)
}
// check API key requirement
if p.RequiresAPIKey() && config.APIKey == "" {
return nil, fmt.Errorf("%s API key required", strings.Title(registryProvider))
}
// lookup model from provider
model, err := provider.GetModel(registryProvider, config.Model)
if err != nil {
// if model not found, try to use default model
if config.Model == "" {
defaultModel := p.DefaultModel(provider.Transcription)
if defaultModel != "" {
model, err = provider.GetModel(registryProvider, defaultModel)
}
}
if err != nil || model == nil {
return nil, fmt.Errorf("model not found: %s (provider: %s)", config.Model, config.Provider)
}
}
// check model type
if model.Type != provider.Transcription {
return nil, fmt.Errorf("model %s is not a transcription model", config.Model)
}
// streaming models not supported yet
if model.Streaming {
return nil, fmt.Errorf("streaming model %s not supported yet (coming soon)", config.Model)
}
// create adapter based on model.AdapterType
var adapter BatchAdapter
switch model.AdapterType {
case "openai":
adapter = NewOpenAIAdapter(model.Endpoint, config.APIKey, model.ID, config.Language, config.Keywords, registryProvider)
case "elevenlabs":
adapter = NewElevenLabsAdapter(model.Endpoint, config.APIKey, model.ID, config.Language)
default:
return nil, fmt.Errorf("unsupported adapter type: %s", model.AdapterType)
}
return NewSimpleTranscriber(config, adapter), nil
}