Upload a recording (audio, or video with the camera off), get a private Swedish transcript, and chat about it. - ADR-033: RLS-scoped private transcript store; ffprobe/ffmpeg intake; gateway transcription (iguana kb-whisper, berget fallback); in-process single-replica worker; chat over the transcript (local models only). - ADR-034: optional two-engine mode (pianissimo + kb-whisper) reconciled by a LOCAL model; off unless TAPIR_RECONCILE_BASE_MODEL is set. - ADR-035: real progress (phases, running/quiet/lost), phase logging, every judged spot stored, merged segments. - Hardening from four adversarial reviews: errors logged and stored as kinds never text; per-engine deadlines; converted-length check; bounded queue; client hang-ups are not errors; strict local-alias check. - Dockerfile: static ffmpeg/ffprobe 7.0.2 pinned by sha256 (#34). Built and inspected by hand; CI does not build the image on a PR, so its first CI build is this merge. Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set in the deployment. Closes #33, #34, #42, #43, #44. Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #39.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
||||
"git.d-ma.be/mathias/tapir/internal/domain"
|
||||
"git.d-ma.be/mathias/tapir/internal/uploads"
|
||||
)
|
||||
|
||||
// Store is the read/write surface the web handlers depend on — a narrow port over
|
||||
@@ -53,6 +54,12 @@ type Store interface {
|
||||
// the most recent discovery pass, shown on the account page as a warning.
|
||||
ListChannelErrors(ctx context.Context, userID string) ([]store.ChannelError, error)
|
||||
|
||||
// Uploaded-audio transcripts (ADR-033): the caller's own PRIVATE uploads,
|
||||
// stored apart from the shared public captions. Both are user-scoped; a
|
||||
// non-owner's id is ErrUploadNotFound.
|
||||
GetUpload(ctx context.Context, userID, id string) (*domain.UploadedTranscript, error)
|
||||
ListUploads(ctx context.Context, userID string) ([]domain.UploadedTranscript, error)
|
||||
|
||||
// SetTranscriptStatus clears or updates a video's transcript backoff state.
|
||||
// Used by handleRetryNow to clear rate_limited_at before immediate processing.
|
||||
SetTranscriptStatus(ctx context.Context, userID, videoID, status string) error
|
||||
@@ -100,6 +107,26 @@ type App struct {
|
||||
// summary view shows no "dig deeper" affordance. It holds no caption-fetch
|
||||
// dependency, so an enabled chat cannot reach YouTube or the rate gate.
|
||||
Chat Chatter
|
||||
// Uploads, when non-nil, accepts audio recordings for private transcription
|
||||
// (ADR-033). Nil = the /uploads routes are not mounted. UploadLimits are the
|
||||
// same limits the Uploader enforces, shown on the form so they are stated up
|
||||
// front rather than discovered by failing.
|
||||
Uploads Uploader
|
||||
UploadLimits uploads.Limits
|
||||
// UploadChatMaxChars is the transcript budget for chat over an uploaded
|
||||
// recording. The caption default (18,000 chars, ~22 minutes of speech) would
|
||||
// keep only the first third of an hour-long meeting.
|
||||
UploadChatMaxChars int
|
||||
// UploadChatModel is the model chat over an uploaded recording opens with. Empty
|
||||
// means the Chatter's own default. It exists because that default is the
|
||||
// summarizer's (koala/phi4-mini, 8,192-token context) and cannot hold an hour of
|
||||
// speech.
|
||||
UploadChatModel string
|
||||
// UploadChatModels is the set of models chat over an uploaded recording offers and
|
||||
// accepts. The whole private transcript goes to the one chosen, so it must hold
|
||||
// LOCAL models only, and the caller decides that. Nil or empty fails closed: no
|
||||
// model is offered and none is called (ADR-035 review).
|
||||
UploadChatModels []string
|
||||
// Processing tracks in-flight immediate summarizations so the status endpoint
|
||||
// shows the animation until the summary lands. The zero value is ready to use.
|
||||
Processing ProcessingSet
|
||||
@@ -166,6 +193,20 @@ func (a *App) Router() http.Handler {
|
||||
app.HandleFunc("GET /v/{videoId}/chat", a.handleChat)
|
||||
app.HandleFunc("POST /v/{videoId}/chat", a.handleChatMessage)
|
||||
}
|
||||
// Audio upload → private transcript (ADR-033). Mounted only when an Uploader is
|
||||
// wired; every handler is user-scoped, so another user's upload is a 404.
|
||||
if a.Uploads != nil {
|
||||
app.HandleFunc("GET /uploads", a.handleUploadsPage)
|
||||
app.HandleFunc("POST /uploads", a.handleUploadPost)
|
||||
app.HandleFunc("GET /uploads/{id}", a.handleUploadDetail)
|
||||
app.HandleFunc("GET /uploads/{id}/status", a.handleUploadStatus)
|
||||
// Chat over the uploaded transcript reuses the ADR-027 Chatter; mounted only
|
||||
// when one is wired.
|
||||
if a.Chat != nil {
|
||||
app.HandleFunc("GET /uploads/{id}/chat", a.handleUploadChat)
|
||||
app.HandleFunc("POST /uploads/{id}/chat", a.handleUploadChatMessage)
|
||||
}
|
||||
}
|
||||
app.HandleFunc("GET /register", a.handleRegisterForm)
|
||||
app.HandleFunc("POST /register", a.handleRegister)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user