feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39

Merged
mathias merged 299 commits from feat/33-audio-upload-transcript into main 2026-10-03 14:13:37 +00:00
Showing only changes of commit a9a2a9270f - Show all commits
+16 -12
View File
@@ -3,6 +3,7 @@ package config
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
@@ -116,6 +117,9 @@ func loadUploadConfig() (UploadConfig, error) {
if err := u.validateReconcile(); err != nil {
return UploadConfig{}, err
}
if !IsLocalModel(u.UploadChatModel) {
return UploadConfig{}, fmt.Errorf("config: TAPIR_UPLOAD_CHAT_MODEL=%q is not a local model (koala/ or iguana/): the whole private transcript is sent to it", u.UploadChatModel)
}
chars, err := intOr("TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS", defaultMaxUploadedChars)
if err != nil {
@@ -141,18 +145,18 @@ func positiveInt(key string, fallback int) (int, error) {
return n, nil
}
// localPrefixes are the host prefixes of the models that run on the homelab. Private
// meeting content may be sent to these and to nothing else for reconciliation
// (ADR-034): a cloud alias is refused at start-up rather than discovered later.
var localPrefixes = []string{"koala/", "iguana/"}
// localAlias is the shape of a model alias that runs on the homelab: a local host
// prefix, then one plain name. A bare prefix check let through aliases that are shaped
// like an escape (koala/../berget/x, koala//x, koala/x?host=berget): whether the
// gateway would route them elsewhere is unknown, so they are refused (ADR-035 review).
var localAlias = regexp.MustCompile(`^(koala|iguana)/[A-Za-z0-9][A-Za-z0-9._:-]*$`)
func isLocalModel(alias string) bool {
for _, p := range localPrefixes {
if strings.HasPrefix(alias, p) {
return true
}
}
return false
// IsLocalModel reports whether alias is a local model. Private meeting content may be
// sent to these and to nothing else: reconciliation (ADR-034) and chat over an uploaded
// recording (ADR-035) are both held to it, and a cloud alias is refused at start-up
// rather than discovered later.
func IsLocalModel(alias string) bool {
return localAlias.MatchString(alias) && !strings.Contains(alias, "..")
}
// validateReconcile checks the two-engine settings. They are inert while no base
@@ -168,7 +172,7 @@ func (u UploadConfig) validateReconcile() error {
{"TAPIR_RECONCILE_BASE_MODEL", u.ReconcileBaseModel},
{"TAPIR_RECONCILE_MODEL", u.ReconcileModel},
} {
if !isLocalModel(m.alias) {
if !IsLocalModel(m.alias) {
return fmt.Errorf("config: %s=%q is not a local model (koala/ or iguana/): private meeting content must not leave the homelab for reconciliation", m.name, m.alias)
}
}