Merge pull request #13 from mateusnobre/feature/elevenlabs-scribe-support
feat: Add Eleven Labs Scribe API support
This commit is contained in:
@@ -7,7 +7,7 @@ Press a toggle key, speak, and get instant text input. Built natively for Waylan
|
|||||||
- **Toggle workflow**: Press once to start recording, press again to stop and inject text
|
- **Toggle workflow**: Press once to start recording, press again to stop and inject text
|
||||||
- **Wayland native**: Purpose-built for Wayland compositors - no legacy X11 dependencies or hacky workarounds
|
- **Wayland native**: Purpose-built for Wayland compositors - no legacy X11 dependencies or hacky workarounds
|
||||||
- **Real-time feedback**: Desktop notifications for recording states and transcription status
|
- **Real-time feedback**: Desktop notifications for recording states and transcription status
|
||||||
- **Multiple transcription backends**: OpenAI Whisper and Groq (planned: whisper.cpp for local processing, gemini, and more)
|
- **Multiple transcription backends**: OpenAI Whisper, Groq, Mistral Voxtral, and Eleven Labs Scribe (99 languages, excellent accuracy)
|
||||||
- **Smart text injection**: Clipboard save/restore with direct typing fallback
|
- **Smart text injection**: Clipboard save/restore with direct typing fallback
|
||||||
- **Daemon architecture**: Lightweight control plane with efficient pipeline management
|
- **Daemon architecture**: Lightweight control plane with efficient pipeline management
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export PATH="$HOME/.local/bin:$PATH"
|
|||||||
|
|
||||||
- **Wayland desktop** (Hyprland, Niri, GNOME, KDE, etc.)
|
- **Wayland desktop** (Hyprland, Niri, GNOME, KDE, etc.)
|
||||||
- **PipeWire audio system** with tools
|
- **PipeWire audio system** with tools
|
||||||
- **API key for transcription**: OpenAI API key or Groq API key (Groq offers faster processing and free tier)
|
- **API key for transcription**: OpenAI, Groq, Mistral, or Eleven Labs API key (check each provider's pricing)
|
||||||
|
|
||||||
**System packages** (automatically installed with AUR package):
|
**System packages** (automatically installed with AUR package):
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ After installing via AUR:
|
|||||||
```bash
|
```bash
|
||||||
hyprvoice configure
|
hyprvoice configure
|
||||||
```
|
```
|
||||||
This wizard will guide you through setting up your OpenAI API key, audio preferences, and other settings.
|
This wizard will guide you through setting up your transcription provider, API key, audio preferences, and other settings.
|
||||||
|
|
||||||
2. **Enable and start the service:**
|
2. **Enable and start the service:**
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+52
-6
@@ -166,7 +166,8 @@ func runInteractiveConfig() error {
|
|||||||
fmt.Println(" 2. groq-transcription - Groq Whisper API (fast transcription)")
|
fmt.Println(" 2. groq-transcription - Groq Whisper API (fast transcription)")
|
||||||
fmt.Println(" 3. groq-translation - Groq Whisper API (translate to English)")
|
fmt.Println(" 3. groq-translation - Groq Whisper API (translate to English)")
|
||||||
fmt.Println(" 4. mistral-transcription - Mistral Voxtral API (excellent for European languages)")
|
fmt.Println(" 4. mistral-transcription - Mistral Voxtral API (excellent for European languages)")
|
||||||
fmt.Printf("Provider [1-4] (current: %s): ", cfg.Transcription.Provider)
|
fmt.Println(" 5. elevenlabs - ElevenLabs Scribe API (99 languages, excellent accuracy)")
|
||||||
|
fmt.Printf("Provider [1-5] (current: %s): ", cfg.Transcription.Provider)
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -183,10 +184,12 @@ func runInteractiveConfig() error {
|
|||||||
cfg.Transcription.Provider = "groq-translation"
|
cfg.Transcription.Provider = "groq-translation"
|
||||||
case "4":
|
case "4":
|
||||||
cfg.Transcription.Provider = "mistral-transcription"
|
cfg.Transcription.Provider = "mistral-transcription"
|
||||||
case "openai", "groq-transcription", "groq-translation", "mistral-transcription":
|
case "5":
|
||||||
|
cfg.Transcription.Provider = "elevenlabs"
|
||||||
|
case "openai", "groq-transcription", "groq-translation", "mistral-transcription", "elevenlabs":
|
||||||
cfg.Transcription.Provider = input
|
cfg.Transcription.Provider = input
|
||||||
default:
|
default:
|
||||||
fmt.Println("❌ Error: invalid provider. Please enter 1-4 or provider name.")
|
fmt.Println("❌ Error: invalid provider. Please enter 1-5 or provider name.")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -275,6 +278,38 @@ func runInteractiveConfig() error {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "elevenlabs":
|
||||||
|
for {
|
||||||
|
fmt.Println("\nElevenLabs Scribe Model:")
|
||||||
|
fmt.Println(" Language Support:")
|
||||||
|
fmt.Println(" scribe_v1: 99 languages (96.7% accuracy for English, ≤5% WER for Portuguese)")
|
||||||
|
fmt.Println(" scribe_v2: 90 languages (real-time optimized, lower latency)")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" Available Models:")
|
||||||
|
fmt.Println(" 1. scribe_v1 - Best accuracy, full timestamps (recommended)")
|
||||||
|
fmt.Println(" 2. scribe_v2 - Real-time streaming, lower latency")
|
||||||
|
fmt.Printf("Model [1-2] (current: %s): ", cfg.Transcription.Model)
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
input := strings.TrimSpace(scanner.Text())
|
||||||
|
switch input {
|
||||||
|
case "1":
|
||||||
|
cfg.Transcription.Model = "scribe_v1"
|
||||||
|
case "2":
|
||||||
|
cfg.Transcription.Model = "scribe_v2"
|
||||||
|
case "scribe_v1", "scribe_v2":
|
||||||
|
cfg.Transcription.Model = input
|
||||||
|
case "":
|
||||||
|
if cfg.Transcription.Model == "" {
|
||||||
|
cfg.Transcription.Model = "scribe_v1"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("❌ Error: invalid model. Please enter 1, 2 or model name.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Key (provider-aware)
|
// API Key (provider-aware)
|
||||||
@@ -284,6 +319,8 @@ func runInteractiveConfig() error {
|
|||||||
envVarName = "OPENAI_API_KEY"
|
envVarName = "OPENAI_API_KEY"
|
||||||
case "mistral-transcription":
|
case "mistral-transcription":
|
||||||
envVarName = "MISTRAL_API_KEY"
|
envVarName = "MISTRAL_API_KEY"
|
||||||
|
case "elevenlabs":
|
||||||
|
envVarName = "ELEVENLABS_API_KEY"
|
||||||
default:
|
default:
|
||||||
envVarName = "GROQ_API_KEY"
|
envVarName = "GROQ_API_KEY"
|
||||||
}
|
}
|
||||||
@@ -299,6 +336,13 @@ func runInteractiveConfig() error {
|
|||||||
if cfg.Transcription.Provider == "groq-translation" {
|
if cfg.Transcription.Provider == "groq-translation" {
|
||||||
fmt.Printf("\nSource language hint (empty for auto-detect, current: %s): ", cfg.Transcription.Language)
|
fmt.Printf("\nSource language hint (empty for auto-detect, current: %s): ", cfg.Transcription.Language)
|
||||||
fmt.Println("\n Note: Translation always outputs English. Language hints at source audio language.")
|
fmt.Println("\n Note: Translation always outputs English. Language hints at source audio language.")
|
||||||
|
} else if cfg.Transcription.Provider == "elevenlabs" {
|
||||||
|
fmt.Println("\nLanguage Performance:")
|
||||||
|
fmt.Println(" Excellent (≤5% WER): English, Portuguese, +25 languages")
|
||||||
|
fmt.Println(" High (5-10% WER): French, German, Spanish, Italian, etc.")
|
||||||
|
fmt.Println(" Good (10-20% WER): Most supported languages")
|
||||||
|
fmt.Println(" Leave empty for auto-detection (recommended)")
|
||||||
|
fmt.Printf("Language (current: %s): ", cfg.Transcription.Language)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nLanguage (empty for auto-detect, current: %s): ", cfg.Transcription.Language)
|
fmt.Printf("\nLanguage (empty for auto-detect, current: %s): ", cfg.Transcription.Language)
|
||||||
}
|
}
|
||||||
@@ -608,10 +652,10 @@ func saveConfig(cfg *config.Config) error {
|
|||||||
|
|
||||||
# Speech Transcription Configuration
|
# Speech Transcription Configuration
|
||||||
[transcription]
|
[transcription]
|
||||||
provider = "%s" # Transcription service: "openai", "groq-transcription", "groq-translation", or "mistral-transcription"
|
provider = "%s" # Transcription service: "openai", "groq-transcription", "groq-translation", "mistral-transcription", or "elevenlabs"
|
||||||
api_key = "%s" # API key (or set OPENAI_API_KEY/GROQ_API_KEY/MISTRAL_API_KEY environment variable)
|
api_key = "%s" # API key (or set OPENAI_API_KEY/GROQ_API_KEY/MISTRAL_API_KEY/ELEVENLABS_API_KEY environment variable)
|
||||||
language = "%s" # Language code (empty for auto-detect, "en", "it", "es", "fr", etc.)
|
language = "%s" # Language code (empty for auto-detect, "en", "it", "es", "fr", etc.)
|
||||||
model = "%s" # Model: OpenAI="whisper-1", Groq="whisper-large-v3", Mistral="voxtral-mini-latest"
|
model = "%s" # Model: OpenAI="whisper-1", Groq="whisper-large-v3", Mistral="voxtral-mini-latest", ElevenLabs="scribe_v1" or "scribe_v2"
|
||||||
|
|
||||||
# Text Injection Configuration
|
# Text Injection Configuration
|
||||||
[injection]
|
[injection]
|
||||||
@@ -635,6 +679,8 @@ func saveConfig(cfg *config.Config) error {
|
|||||||
# Models: whisper-large-v3 only (turbo not supported for translation)
|
# Models: whisper-large-v3 only (turbo not supported for translation)
|
||||||
# - "mistral-transcription": Mistral Voxtral API (excellent for European languages, requires MISTRAL_API_KEY)
|
# - "mistral-transcription": Mistral Voxtral API (excellent for European languages, requires MISTRAL_API_KEY)
|
||||||
# Models: voxtral-mini-latest or voxtral-mini-2507
|
# Models: voxtral-mini-latest or voxtral-mini-2507
|
||||||
|
# - "elevenlabs": ElevenLabs Scribe API (excellent accuracy, 99 languages, requires ELEVENLABS_API_KEY)
|
||||||
|
# Models: scribe_v1 (99 languages, best accuracy) or scribe_v2 (90 languages, real-time)
|
||||||
#
|
#
|
||||||
# Language codes: Use empty string ("") for automatic detection, or specific codes like:
|
# Language codes: Use empty string ("") for automatic detection, or specific codes like:
|
||||||
# "en" (English), "it" (Italian), "es" (Spanish), "fr" (French), "de" (German), etc.
|
# "en" (English), "it" (Italian), "es" (Spanish), "fr" (French), "de" (German), etc.
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ func (c *Config) ToTranscriberConfig() transcriber.Config {
|
|||||||
config.APIKey = os.Getenv("GROQ_API_KEY")
|
config.APIKey = os.Getenv("GROQ_API_KEY")
|
||||||
case "mistral-transcription":
|
case "mistral-transcription":
|
||||||
config.APIKey = os.Getenv("MISTRAL_API_KEY")
|
config.APIKey = os.Getenv("MISTRAL_API_KEY")
|
||||||
|
case "elevenlabs":
|
||||||
|
config.APIKey = os.Getenv("ELEVENLABS_API_KEY")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,8 +245,28 @@ func (c *Config) Validate() error {
|
|||||||
return fmt.Errorf("invalid model for mistral-transcription: %s (must be voxtral-mini-latest or voxtral-mini-2507)", c.Transcription.Model)
|
return fmt.Errorf("invalid model for mistral-transcription: %s (must be voxtral-mini-latest or voxtral-mini-2507)", c.Transcription.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "elevenlabs":
|
||||||
|
apiKey := c.Transcription.APIKey
|
||||||
|
if apiKey == "" {
|
||||||
|
apiKey = os.Getenv("ELEVENLABS_API_KEY")
|
||||||
|
}
|
||||||
|
if apiKey == "" {
|
||||||
|
return fmt.Errorf("ElevenLabs API key required: not found in config (transcription.api_key) or environment variable (ELEVENLABS_API_KEY)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate language code if provided (empty string means auto-detect)
|
||||||
|
if c.Transcription.Language != "" && !isValidLanguageCode(c.Transcription.Language) {
|
||||||
|
return fmt.Errorf("invalid transcription.language: %s (use empty string for auto-detect or ISO-639-1 codes like 'en', 'pt', 'es')", c.Transcription.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Eleven Labs model
|
||||||
|
validModels := map[string]bool{"scribe_v1": true, "scribe_v2": true}
|
||||||
|
if c.Transcription.Model != "" && !validModels[c.Transcription.Model] {
|
||||||
|
return fmt.Errorf("invalid model for elevenlabs: %s (must be scribe_v1 or scribe_v2)", c.Transcription.Model)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported transcription.provider: %s (must be openai, groq-transcription, groq-translation, or mistral-transcription)", c.Transcription.Provider)
|
return fmt.Errorf("unsupported transcription.provider: %s (must be openai, groq-transcription, groq-translation, mistral-transcription, or elevenlabs)", c.Transcription.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Transcription.Model == "" {
|
if c.Transcription.Model == "" {
|
||||||
@@ -412,10 +434,10 @@ func SaveDefaultConfig() error {
|
|||||||
|
|
||||||
# Speech Transcription Configuration
|
# Speech Transcription Configuration
|
||||||
[transcription]
|
[transcription]
|
||||||
provider = "openai" # Transcription service: "openai", "groq-transcription", "groq-translation", or "mistral-transcription"
|
provider = "openai" # Transcription service: "openai", "groq-transcription", "groq-translation", "mistral-transcription", or "elevenlabs"
|
||||||
api_key = "" # API key (or set OPENAI_API_KEY/GROQ_API_KEY/MISTRAL_API_KEY environment variable)
|
api_key = "" # API key (or set OPENAI_API_KEY/GROQ_API_KEY/MISTRAL_API_KEY/ELEVENLABS_API_KEY environment variable)
|
||||||
language = "" # Language code (empty for auto-detect, "en", "it", "es", "fr", etc.)
|
language = "" # Language code (empty for auto-detect, "en", "it", "es", "fr", etc.)
|
||||||
model = "whisper-1" # Model: OpenAI="whisper-1", Groq="whisper-large-v3", Mistral="voxtral-mini-latest"
|
model = "whisper-1" # Model: OpenAI="whisper-1", Groq="whisper-large-v3", Mistral="voxtral-mini-latest", ElevenLabs="scribe_v1"
|
||||||
|
|
||||||
# Text Injection Configuration
|
# Text Injection Configuration
|
||||||
[injection]
|
[injection]
|
||||||
@@ -479,6 +501,8 @@ func SaveDefaultConfig() error {
|
|||||||
# Models: whisper-large-v3 only (turbo not supported for translation)
|
# Models: whisper-large-v3 only (turbo not supported for translation)
|
||||||
# - "mistral-transcription": Mistral Voxtral API (excellent for European languages, requires MISTRAL_API_KEY)
|
# - "mistral-transcription": Mistral Voxtral API (excellent for European languages, requires MISTRAL_API_KEY)
|
||||||
# Models: voxtral-mini-latest or voxtral-mini-2507
|
# Models: voxtral-mini-latest or voxtral-mini-2507
|
||||||
|
# - "elevenlabs": ElevenLabs Scribe API (excellent accuracy, 99 languages, requires ELEVENLABS_API_KEY)
|
||||||
|
# Models: scribe_v1 (99 languages, best accuracy) or scribe_v2 (90 languages, real-time)
|
||||||
#
|
#
|
||||||
# Language codes: Use empty string ("") for automatic detection, or specific codes like:
|
# Language codes: Use empty string ("") for automatic detection, or specific codes like:
|
||||||
# "en" (English), "it" (Italian), "es" (Spanish), "fr" (French), "de" (German), etc.
|
# "en" (English), "it" (Italian), "es" (Spanish), "fr" (French), "de" (German), etc.
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package transcriber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ElevenLabsAdapter implements TranscriptionAdapter for ElevenLabs Scribe API
|
||||||
|
type ElevenLabsAdapter struct {
|
||||||
|
client *http.Client
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElevenLabsResponse represents the API response
|
||||||
|
type ElevenLabsResponse struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewElevenLabsAdapter creates a new ElevenLabs adapter
|
||||||
|
func NewElevenLabsAdapter(config Config) *ElevenLabsAdapter {
|
||||||
|
return &ElevenLabsAdapter{
|
||||||
|
client: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcribe sends audio to ElevenLabs API for transcription
|
||||||
|
func (a *ElevenLabsAdapter) Transcribe(ctx context.Context, audioData []byte) (string, error) {
|
||||||
|
if len(audioData) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert raw PCM to WAV format
|
||||||
|
wavData, err := convertToWAV(audioData)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("convert to WAV: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create multipart form body
|
||||||
|
var body bytes.Buffer
|
||||||
|
writer := multipart.NewWriter(&body)
|
||||||
|
|
||||||
|
// Add audio file
|
||||||
|
part, err := writer.CreateFormFile("file", "audio.wav")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create form file: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(part, bytes.NewReader(wavData)); err != nil {
|
||||||
|
return "", fmt.Errorf("copy audio data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add model_id
|
||||||
|
if err := writer.WriteField("model_id", a.config.Model); err != nil {
|
||||||
|
return "", fmt.Errorf("write model_id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add language_code if specified
|
||||||
|
if a.config.Language != "" {
|
||||||
|
if err := writer.WriteField("language_code", a.config.Language); err != nil {
|
||||||
|
return "", fmt.Errorf("write language_code: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
return "", fmt.Errorf("close writer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
url := "https://api.elevenlabs.io/v1/speech-to-text"
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
req.Header.Set("xi-api-key", a.config.APIKey)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
resp, err := a.client.Do(req)
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("elevenlabs-adapter: API call failed after %v: %v", duration, err)
|
||||||
|
return "", fmt.Errorf("elevenlabs request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
log.Printf("elevenlabs-adapter: API returned status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
return "", fmt.Errorf("elevenlabs API status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result ElevenLabsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return "", fmt.Errorf("decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("elevenlabs-adapter: transcribed %d bytes in %v: %q", len(audioData), duration, result.Text)
|
||||||
|
return result.Text, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package transcriber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewElevenLabsAdapter(t *testing.T) {
|
||||||
|
config := Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "test-api-key",
|
||||||
|
Language: "en",
|
||||||
|
Model: "scribe_v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := NewElevenLabsAdapter(config)
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
t.Fatalf("NewElevenLabsAdapter() returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if adapter.config.APIKey != "test-api-key" {
|
||||||
|
t.Errorf("APIKey not set correctly, got: %s", adapter.config.APIKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if adapter.config.Model != "scribe_v1" {
|
||||||
|
t.Errorf("Model not set correctly, got: %s", adapter.config.Model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestElevenLabsAdapter_Transcribe_EmptyAudio(t *testing.T) {
|
||||||
|
config := Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "test-key",
|
||||||
|
Model: "scribe_v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := NewElevenLabsAdapter(config)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
result, err := adapter.Transcribe(ctx, []byte{})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Transcribe() with empty audio should not error, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != "" {
|
||||||
|
t.Errorf("Transcribe() with empty audio should return empty string, got: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestElevenLabsAdapter_Transcribe_ValidAudio(t *testing.T) {
|
||||||
|
// This test will require mocking the HTTP client
|
||||||
|
// For now, we test the structure exists
|
||||||
|
config := Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "test-key",
|
||||||
|
Language: "en",
|
||||||
|
Model: "scribe_v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := NewElevenLabsAdapter(config)
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
t.Fatal("NewElevenLabsAdapter() returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that adapter has a client
|
||||||
|
if adapter.client == nil {
|
||||||
|
t.Error("adapter.client is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,12 @@ func NewTranscriber(config Config) (Transcriber, error) {
|
|||||||
}
|
}
|
||||||
adapter = NewMistralAdapter(config)
|
adapter = NewMistralAdapter(config)
|
||||||
|
|
||||||
|
case "elevenlabs":
|
||||||
|
if config.APIKey == "" {
|
||||||
|
return nil, fmt.Errorf("ElevenLabs API key required")
|
||||||
|
}
|
||||||
|
adapter = NewElevenLabsAdapter(config)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported provider: %s", config.Provider)
|
return nil, fmt.Errorf("unsupported provider: %s", config.Provider)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,36 @@ func TestNewTranscriber(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "valid elevenlabs config with scribe_v1",
|
||||||
|
config: Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "test-key",
|
||||||
|
Language: "en",
|
||||||
|
Model: "scribe_v1",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid elevenlabs config with scribe_v2",
|
||||||
|
config: Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "test-key",
|
||||||
|
Language: "pt",
|
||||||
|
Model: "scribe_v2",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "elevenlabs config without api key",
|
||||||
|
config: Config{
|
||||||
|
Provider: "elevenlabs",
|
||||||
|
APIKey: "",
|
||||||
|
Language: "en",
|
||||||
|
Model: "scribe_v1",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unsupported provider",
|
name: "unsupported provider",
|
||||||
config: Config{
|
config: Config{
|
||||||
|
|||||||
Reference in New Issue
Block a user