Some checks failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Add complete Academy backend (Go) and frontend (Next.js) for DSGVO/IT-Security/AI-Literacy compliance training: - Go backend: Course CRUD, enrollments, quiz evaluation, PDF certificates (gofpdf), video generation pipeline (ElevenLabs + HeyGen) - In-memory data store with PostgreSQL migration for future DB support - Frontend: Course creation (AI + manual), lesson viewer, interactive quiz, certificate viewer with PDF download - Fix existing compile errors in generate.go (SearchResult type mismatch), llm/service.go (unused var), rag/service.go (Unicode chars) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
2.5 KiB
Go
106 lines
2.5 KiB
Go
package academy
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// ElevenLabsClient handles text-to-speech via the ElevenLabs API
|
|
type ElevenLabsClient struct {
|
|
apiKey string
|
|
voiceID string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewElevenLabsClient creates a new ElevenLabs client
|
|
func NewElevenLabsClient() *ElevenLabsClient {
|
|
apiKey := os.Getenv("ELEVENLABS_API_KEY")
|
|
voiceID := os.Getenv("ELEVENLABS_VOICE_ID")
|
|
if voiceID == "" {
|
|
voiceID = "EXAVITQu4vr4xnSDxMaL" // Default: "Sarah" voice
|
|
}
|
|
|
|
return &ElevenLabsClient{
|
|
apiKey: apiKey,
|
|
voiceID: voiceID,
|
|
client: &http.Client{
|
|
Timeout: 120 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsConfigured returns true if API key is set
|
|
func (c *ElevenLabsClient) IsConfigured() bool {
|
|
return c.apiKey != ""
|
|
}
|
|
|
|
// TextToSpeechRequest represents the API request
|
|
type TextToSpeechRequest struct {
|
|
Text string `json:"text"`
|
|
ModelID string `json:"model_id"`
|
|
VoiceSettings VoiceSettings `json:"voice_settings"`
|
|
}
|
|
|
|
// VoiceSettings controls voice parameters
|
|
type VoiceSettings struct {
|
|
Stability float64 `json:"stability"`
|
|
SimilarityBoost float64 `json:"similarity_boost"`
|
|
Style float64 `json:"style"`
|
|
}
|
|
|
|
// TextToSpeech converts text to speech audio (MP3)
|
|
func (c *ElevenLabsClient) TextToSpeech(text string) ([]byte, error) {
|
|
if !c.IsConfigured() {
|
|
return nil, fmt.Errorf("ElevenLabs API key not configured")
|
|
}
|
|
|
|
url := fmt.Sprintf("https://api.elevenlabs.io/v1/text-to-speech/%s", c.voiceID)
|
|
|
|
reqBody := TextToSpeechRequest{
|
|
Text: text,
|
|
ModelID: "eleven_multilingual_v2",
|
|
VoiceSettings: VoiceSettings{
|
|
Stability: 0.5,
|
|
SimilarityBoost: 0.75,
|
|
Style: 0.5,
|
|
},
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("xi-api-key", c.apiKey)
|
|
req.Header.Set("Accept", "audio/mpeg")
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ElevenLabs API request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("ElevenLabs API error %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
audioData, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read audio response: %w", err)
|
|
}
|
|
|
|
return audioData, nil
|
|
}
|