feat(cmd): shared buildProcessor + wire immediate web summarization
CI / Lint / Test / Vet (push) Successful in 20s
CI / Build & Import (push) Successful in 11s
CI / Mirror to GitHub (push) Failing after 3s

Extract the engine wiring (YouTube source, AI-router summarizer, store sink)
into buildProcessor, shared by cmdRun and cmdServe. It returns (nil, nil) — not
an error — on incomplete config, which is the queue-only fallback for serve.
engineProcessor adapts the engine to web.Processor: load the video row, run the
engine, clear the manual queue flag on a produced summary (mirrors the runner).
cmdServe wires it onto web.App.Processor; cmdRun reuses buildProcessor so the
wiring is no longer duplicated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 19:50:06 +02:00
co-authored by Claude Opus 4.8
parent 25215cbcbd
commit 8c6c7ca947
3 changed files with 157 additions and 21 deletions
+24 -21
View File
@@ -22,15 +22,11 @@ import (
"os/signal"
"time"
"gitea.d-ma.be/mathias/tapir/internal/adapters/llm"
"gitea.d-ma.be/mathias/tapir/internal/adapters/secrets"
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
"gitea.d-ma.be/mathias/tapir/internal/adapters/summarizer"
"gitea.d-ma.be/mathias/tapir/internal/adapters/youtube"
"gitea.d-ma.be/mathias/tapir/internal/auth"
"gitea.d-ma.be/mathias/tapir/internal/config"
"gitea.d-ma.be/mathias/tapir/internal/runner"
"gitea.d-ma.be/mathias/tapir/internal/usecase"
"gitea.d-ma.be/mathias/tapir/internal/web"
"gitea.d-ma.be/mathias/tapir/internal/web/oidc"
)
@@ -120,24 +116,16 @@ func cmdRun(ctx context.Context, log *slog.Logger) error {
}
defer st.Close()
secretStore := secrets.NewFileStore(cfg.SecretsFile)
src := youtube.New(youtube.Config{
ClientID: cfg.YTClientID,
ClientSecret: cfg.YTClientSecret,
TokenSecretRef: cfg.YTTokenRef,
PreferredLanguages: []string{"en"},
}, secretStore)
// Local Primary only; no BYO fallback for the demo (fallback nil).
primary := summarizer.Endpoint{
Client: llm.New(cfg.GatewayURL, cfg.GatewayKey, cfg.SummarizerModel, cfg.SummarizerTimeout),
Provider: "local",
Model: cfg.SummarizerModel,
// Same wiring the web serve path uses (buildProcessor). ValidateForRun above
// already required the engine's inputs, so a nil here is a genuine config gap.
engine, err := buildProcessor(cfg, st)
if err != nil {
return err
}
sum := summarizer.New(primary, nil)
engine := usecase.NewEngine(src, sum, st)
r := runner.New(src, st, engine, cfg.UserID, log)
if engine == nil {
return fmt.Errorf("run: incomplete summarization config (gateway, youtube credentials, secrets file)")
}
r := runner.New(engine.Source, st, engine, cfg.UserID, log)
log.Info("starting run", "user", cfg.UserID, "model", cfg.SummarizerModel,
"gateway", cfg.GatewayURL, "poll_interval", cfg.PollInterval)
@@ -204,6 +192,21 @@ func cmdServe(ctx context.Context, log *slog.Logger) error {
log.Info("web youtube connect enabled", "redirect", cfg.YTConnectRedirectURL)
}
// Immediate summarization for the web "Summarize" button. When the engine can
// be built (gateway + YouTube credentials + secrets present), a click runs the
// summary now in the background; otherwise the button stays queue-only and the
// next `tapir run` does the work (buildProcessor returns nil — never an error).
engine, err := buildProcessor(cfg, st)
if err != nil {
return err
}
if engine != nil {
app.Processor = &engineProcessor{engine: engine, store: st}
log.Info("web immediate summarization enabled", "model", cfg.SummarizerModel)
} else {
log.Info("web summarization is queue-only (incomplete engine config)")
}
srv := &http.Server{
Addr: cfg.HTTPAddr,
Handler: app.Router(),