feat(voice): self-hosted speech recognition via Whisper (STT_*)
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:
sandrodachandClaude Opus 5.5 committed 2026-10-29 22:12:57 +01:00
1 parent 6a5e82431f
commit 7801cd13b9
18 files changed
+816 -6

No files matched your search

+5 -3
View File
@@ -12,8 +12,10 @@ import (
// with huge uploads. Note sync and imports need more; their handlers apply the same limits again.
const defaultMaxBody = 2 << 20
func bodyLimitFor(path string) int64 {
func (s *Server) bodyLimitFor(path string) int64 {
switch {
case path == "/api/v1/voice/transcribe":
return int64(s.cfg.Speech.MaxUploadMB)<<20 + 1<<20
case strings.HasPrefix(path, "/api/v1/import/"):
return storage.MaxImportUploadBytes + 1<<20
case strings.HasPrefix(path, "/api/v1/docs/") && strings.HasSuffix(path, "/sync"):
@@ -22,10 +24,10 @@ func bodyLimitFor(path string) int64 {
return defaultMaxBody
}
func bodyLimit(next http.Handler) http.Handler {
func (s *Server) bodyLimit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil && r.Body != http.NoBody {
r.Body = http.MaxBytesReader(w, r.Body, bodyLimitFor(r.URL.Path))
r.Body = http.MaxBytesReader(w, r.Body, s.bodyLimitFor(r.URL.Path))
}
next.ServeHTTP(w, r)
})