feat(voice): self-hosted speech recognition via Whisper (STT_*)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m49s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m49s
- STT_URL switches voice input to an OpenAI-compatible Whisper server (e.g. speaches); without it the browser's recognition stays. Configured by environment only, with limits for upload size, parallel jobs, hourly budget per user and timeout. The demo always uses the browser. - POST /voice/transcribe forwards recordings and returns the text; nothing is stored. On startup MindSift waits for the server and asks it to download the model if missing. - Admin card shows the active mode and server state, with a test recording. - docker-compose: optional "whisper" service (speaches 0.8.3 CPU, profile "whisper", models in ./whisper). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a5e82431f
commit
7801cd13b9
18 files changed
+816
-6
No files matched your search
@@ -43,6 +43,37 @@ type Config struct {
|
||||
|
||||
// Legal notices (Impressum, Datenschutz): an external URL and/or Markdown text, shown even before login
|
||||
Legal LegalTexts
|
||||
|
||||
// Speech recognition, configured only by environment (STT_*)
|
||||
Speech SpeechConfig
|
||||
}
|
||||
|
||||
// SpeechConfig: with STT_URL set, voice input is transcribed by a self-hosted, OpenAI-compatible
|
||||
// Whisper server (e.g. speaches); otherwise the browser's own speech recognition is used.
|
||||
type SpeechConfig struct {
|
||||
URL string // base URL, e.g. http://whisper:8000
|
||||
Model string
|
||||
Language string
|
||||
APIKey string
|
||||
Disabled bool // STT_DISABLED=true hides voice input everywhere
|
||||
MaxSeconds int // recordings stop after this long (enforced in the browser)
|
||||
MaxUploadMB int
|
||||
MaxParallel int // transcriptions running at the same time; more wait in line
|
||||
PerUserPerHour int
|
||||
TimeoutSeconds int
|
||||
}
|
||||
|
||||
// Mode is "server", "browser" or "off". The demo never uses the server, so visitors cannot keep
|
||||
// its CPU busy.
|
||||
func (s SpeechConfig) Mode(demo bool) string {
|
||||
switch {
|
||||
case s.Disabled:
|
||||
return "off"
|
||||
case s.URL != "" && !demo:
|
||||
return "server"
|
||||
default:
|
||||
return "browser"
|
||||
}
|
||||
}
|
||||
|
||||
type LegalTexts struct {
|
||||
@@ -179,6 +210,18 @@ func Load() *Config {
|
||||
SMTPFrom: smtpFrom,
|
||||
SMTPTLS: smtpTLS,
|
||||
SMTPSkipVerify: smtpSkipVerify,
|
||||
Speech: SpeechConfig{
|
||||
URL: strings.TrimRight(getEnv("STT_URL", ""), "/"),
|
||||
Model: getEnv("STT_MODEL", "deepdml/faster-whisper-large-v3-turbo-ct2"),
|
||||
Language: getEnv("STT_LANGUAGE", "de"),
|
||||
APIKey: getEnv("STT_API_KEY", ""),
|
||||
Disabled: strings.ToLower(getEnv("STT_DISABLED", "false")) == "true",
|
||||
MaxSeconds: envInt("STT_MAX_SECONDS", 600, 5, 3600),
|
||||
MaxUploadMB: envInt("STT_MAX_UPLOAD_MB", 25, 1, 500),
|
||||
MaxParallel: envInt("STT_MAX_PARALLEL", 1, 1, 32),
|
||||
PerUserPerHour: envInt("STT_PER_USER_PER_HOUR", 60, 1, 100000),
|
||||
TimeoutSeconds: envInt("STT_TIMEOUT_SECONDS", 120, 5, 3600),
|
||||
},
|
||||
Legal: LegalTexts{
|
||||
ImprintURL: legalURL("LEGAL_IMPRINT_URL"),
|
||||
ImprintContent: legalContent("LEGAL_IMPRINT_CONTENT"),
|
||||
@@ -209,6 +252,20 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// envInt reads a whole number within [min, max]; anything else falls back to def (with a warning).
|
||||
func envInt(key string, def, min, max int) int {
|
||||
raw := getEnv(key, "")
|
||||
if raw == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(raw)
|
||||
if err != nil || n < min || n > max {
|
||||
slog.Warn("Ungültiger Wert, Standard wird verwendet", "variable", key, "value", raw, "default", def)
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func demoResetMinutes() int {
|
||||
n, err := strconv.Atoi(strings.TrimSpace(getEnv("DEMO_RESET_MINUTES", "20")))
|
||||
if err != nil || n < 0 {
|
||||
|
||||
Reference in new issue
Block a user