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 ef815edded - Show all commits
+34 -3
View File
@@ -220,7 +220,8 @@ func (a *App) handleUploadChat(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/uploads/"+u.ID, http.StatusSeeOther) http.Redirect(w, r, "/uploads/"+u.ID, http.StatusSeeOther)
return return
} }
a.render(w, r, UploadChatPage(*u, a.uploadChatView(*u, a.Chat.DefaultModel(a.UploadChatModel), nil, false, ""))) model, _ := a.resolveUploadModel("")
a.render(w, r, UploadChatPage(*u, a.uploadChatView(*u, model, nil, false, "")))
} }
// handleUploadChatMessage answers one question against the owner's uploaded // handleUploadChatMessage answers one question against the owner's uploaded
@@ -240,7 +241,13 @@ func (a *App) handleUploadChatMessage(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad form", http.StatusBadRequest) http.Error(w, "bad form", http.StatusBadRequest)
return return
} }
model := a.resolveModel(r.FormValue("model"), a.UploadChatModel) model, ok := a.resolveUploadModel(r.FormValue("model"))
if !ok {
a.logger().Error("upload chat: no local model is configured; nothing was sent", "upload", u.ID)
view := a.uploadChatView(*u, "", nil, false, "No local model is available for chat about a recording.")
a.render(w, r, UploadChatPage(*u, view))
return
}
history := parseHistory(r.Form["hq"], r.Form["ha"]) history := parseHistory(r.Form["hq"], r.Form["ha"])
question := strings.TrimSpace(r.FormValue("question")) question := strings.TrimSpace(r.FormValue("question"))
@@ -273,7 +280,7 @@ func (a *App) uploadChatView(u domain.UploadedTranscript, model string, history
VideoID: u.ID, VideoID: u.ID,
Title: u.Title, Title: u.Title,
Available: true, Available: true,
Models: a.Chat.Models(), Models: a.UploadChatModels,
Selected: model, Selected: model,
History: history, History: history,
Truncated: truncated, Truncated: truncated,
@@ -401,3 +408,27 @@ func minutesAgo(d time.Duration) string {
} }
return "under a minute" return "under a minute"
} }
// resolveUploadModel picks the model for chat over an uploaded recording from the
// offered LOCAL set only: the posted one when it is in the set, else the configured
// default when it is, else the first. ok is false when no model is offered, in which
// case nothing may be sent anywhere (the whole private transcript is the payload).
func (a *App) resolveUploadModel(posted string) (model string, ok bool) {
offered := func(m string) bool {
for _, o := range a.UploadChatModels {
if o == m {
return true
}
}
return false
}
switch {
case posted != "" && offered(posted):
return posted, true
case a.UploadChatModel != "" && offered(a.UploadChatModel):
return a.UploadChatModel, true
case len(a.UploadChatModels) > 0:
return a.UploadChatModels[0], true
}
return "", false
}