Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4aeb5efcd | ||
|
|
8c6c7ca947 | ||
|
|
25215cbcbd | ||
|
|
404f74c55c |
+24
-21
@@ -22,15 +22,11 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"time"
|
"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/secrets"
|
||||||
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
"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/auth"
|
||||||
"gitea.d-ma.be/mathias/tapir/internal/config"
|
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||||
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
"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"
|
||||||
"gitea.d-ma.be/mathias/tapir/internal/web/oidc"
|
"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()
|
defer st.Close()
|
||||||
|
|
||||||
secretStore := secrets.NewFileStore(cfg.SecretsFile)
|
// Same wiring the web serve path uses (buildProcessor). ValidateForRun above
|
||||||
src := youtube.New(youtube.Config{
|
// already required the engine's inputs, so a nil here is a genuine config gap.
|
||||||
ClientID: cfg.YTClientID,
|
engine, err := buildProcessor(cfg, st)
|
||||||
ClientSecret: cfg.YTClientSecret,
|
if err != nil {
|
||||||
TokenSecretRef: cfg.YTTokenRef,
|
return err
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
sum := summarizer.New(primary, nil)
|
if engine == nil {
|
||||||
|
return fmt.Errorf("run: incomplete summarization config (gateway, youtube credentials, secrets file)")
|
||||||
engine := usecase.NewEngine(src, sum, st)
|
}
|
||||||
r := runner.New(src, st, engine, cfg.UserID, log)
|
r := runner.New(engine.Source, st, engine, cfg.UserID, log)
|
||||||
|
|
||||||
log.Info("starting run", "user", cfg.UserID, "model", cfg.SummarizerModel,
|
log.Info("starting run", "user", cfg.UserID, "model", cfg.SummarizerModel,
|
||||||
"gateway", cfg.GatewayURL, "poll_interval", cfg.PollInterval)
|
"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)
|
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{
|
srv := &http.Server{
|
||||||
Addr: cfg.HTTPAddr,
|
Addr: cfg.HTTPAddr,
|
||||||
Handler: app.Router(),
|
Handler: app.Router(),
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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/config"
|
||||||
|
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||||
|
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildProcessor wires the summarization engine — YouTube source (captions-first),
|
||||||
|
// AI-router summarizer, store sink — shared by `tapir run` and the web
|
||||||
|
// "Summarize now" path so the wiring lives in one place. It returns (nil, nil) —
|
||||||
|
// not an error — when the config cannot support live summarization (no gateway
|
||||||
|
// URL, no YouTube client credentials, or no secrets file). That nil is the
|
||||||
|
// queue-only fallback: the web UI keeps working (the button just queues) and
|
||||||
|
// `tapir run` reports the gap via its own ValidateForRun. Missing engine config
|
||||||
|
// is never an error here.
|
||||||
|
func buildProcessor(cfg config.Config, st *store.Store) (*usecase.Engine, error) {
|
||||||
|
if cfg.GatewayURL == "" || cfg.YTClientID == "" || cfg.YTClientSecret == "" || cfg.SecretsFile == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
sum := summarizer.New(primary, nil)
|
||||||
|
|
||||||
|
return usecase.NewEngine(src, sum, st), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// engineProcessor adapts the engine (which works in terms of a domain.Video) to
|
||||||
|
// the web.Processor port (which works in terms of a stored video id): it loads the
|
||||||
|
// video row, runs the engine, and — on a produced summary — clears the manual
|
||||||
|
// queue flag, mirroring the runner so the video is not re-summarized on the next
|
||||||
|
// `tapir run` and the UI drops the "Queued" chip. A skip (no transcript) leaves
|
||||||
|
// the flag set so a later run can retry.
|
||||||
|
type engineProcessor struct {
|
||||||
|
engine *usecase.Engine
|
||||||
|
store *store.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *engineProcessor) ProcessVideo(ctx context.Context, userID, videoID string) error {
|
||||||
|
row, err := p.store.GetVideoRow(ctx, userID, videoID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load video %q: %w", videoID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := domain.Video{
|
||||||
|
ID: row.VideoID,
|
||||||
|
UserID: userID,
|
||||||
|
Provider: domain.Provider(row.Channel),
|
||||||
|
ProviderVideoID: row.ProviderVideoID,
|
||||||
|
Title: row.Title,
|
||||||
|
URL: row.URL,
|
||||||
|
PublishedAt: row.PublishedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.engine.ProcessNewVideo(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("process video %q: %w", videoID, err)
|
||||||
|
}
|
||||||
|
if res.Summary != nil {
|
||||||
|
if err := p.store.ClearSummarizeRequested(ctx, userID, videoID); err != nil {
|
||||||
|
return fmt.Errorf("clear summarize flag %q: %w", videoID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/tapir/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBuildProcessorNilOnIncompleteConfig asserts the queue-only fallback: when a
|
||||||
|
// required input is missing, buildProcessor returns (nil, nil) — never an error —
|
||||||
|
// so the web UI degrades to queue-only instead of failing to start.
|
||||||
|
func TestBuildProcessorNilOnIncompleteConfig(t *testing.T) {
|
||||||
|
// A complete config (the fields buildProcessor gates on). The store is nil:
|
||||||
|
// buildProcessor must not touch it on the incomplete paths, and the complete
|
||||||
|
// path only stores the pointer (no connection), so nil is fine for this test.
|
||||||
|
complete := config.Config{
|
||||||
|
GatewayURL: "http://gw/v1",
|
||||||
|
YTClientID: "id",
|
||||||
|
YTClientSecret: "secret",
|
||||||
|
SecretsFile: "/tmp/secrets.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mutate func(config.Config) config.Config
|
||||||
|
wantNil bool
|
||||||
|
}{
|
||||||
|
{"complete", func(c config.Config) config.Config { return c }, false},
|
||||||
|
{"no gateway url", func(c config.Config) config.Config { c.GatewayURL = ""; return c }, true},
|
||||||
|
{"no yt client id", func(c config.Config) config.Config { c.YTClientID = ""; return c }, true},
|
||||||
|
{"no yt client secret", func(c config.Config) config.Config { c.YTClientSecret = ""; return c }, true},
|
||||||
|
{"no secrets file", func(c config.Config) config.Config { c.SecretsFile = ""; return c }, true},
|
||||||
|
{"empty config", func(config.Config) config.Config { return config.Config{} }, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
engine, err := buildProcessor(tt.mutate(complete), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildProcessor returned an error, want nil: %v", err)
|
||||||
|
}
|
||||||
|
if (engine == nil) != tt.wantNil {
|
||||||
|
t.Fatalf("engine == nil is %v, want %v", engine == nil, tt.wantNil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,13 @@ type App struct {
|
|||||||
// Secrets removes a user's OAuth tokens on disconnect / delete-account. The
|
// Secrets removes a user's OAuth tokens on disconnect / delete-account. The
|
||||||
// account routes require it; cmd/tapir wires the file-backed store.
|
// account routes require it; cmd/tapir wires the file-backed store.
|
||||||
Secrets SecretRemover
|
Secrets SecretRemover
|
||||||
|
// Processor, when non-nil, summarizes a queued video immediately in a
|
||||||
|
// background goroutine (the "Summarize" button kicks it off). Nil = queue-only:
|
||||||
|
// the button flips the DB flag and the next `tapir run` does the work.
|
||||||
|
Processor Processor
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) logger() *slog.Logger {
|
func (a *App) logger() *slog.Logger {
|
||||||
@@ -84,6 +91,7 @@ func (a *App) Router() http.Handler {
|
|||||||
app.HandleFunc("GET /v/{videoId}", a.handleDetail)
|
app.HandleFunc("GET /v/{videoId}", a.handleDetail)
|
||||||
app.HandleFunc("POST /v/{videoId}/action", a.handleAction)
|
app.HandleFunc("POST /v/{videoId}/action", a.handleAction)
|
||||||
app.HandleFunc("POST /v/{videoId}/summarize", a.handleRequestSummarize)
|
app.HandleFunc("POST /v/{videoId}/summarize", a.handleRequestSummarize)
|
||||||
|
app.HandleFunc("GET /v/{videoId}/status", a.handleStatus)
|
||||||
app.HandleFunc("GET /register", a.handleRegisterForm)
|
app.HandleFunc("GET /register", a.handleRegisterForm)
|
||||||
app.HandleFunc("POST /register", a.handleRegister)
|
app.HandleFunc("POST /register", a.handleRegister)
|
||||||
|
|
||||||
@@ -208,10 +216,12 @@ func (a *App) handleAction(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, "/v/"+videoID, http.StatusSeeOther)
|
http.Redirect(w, r, "/v/"+videoID, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRequestSummarize queues a video for manual summarization. It does NOT run
|
// handleRequestSummarize handles the "Summarize" button. It always flips the DB
|
||||||
// the engine inline — it only flips summarize_requested; the next `tapir run`
|
// flag (summarize_requested) so the work is durable. With a Processor wired it
|
||||||
// picks it up (the single summarization driver). For HTMX it returns the refreshed
|
// then summarizes immediately in the background and answers with the animated
|
||||||
// card (now showing "Queued"); without JS it redirects back to the list.
|
// processing card that polls /status until done; without one (queue-only) it
|
||||||
|
// answers with the "Queued" card — the next `tapir run` does the work. Without
|
||||||
|
// JS it redirects back to the list (POST→redirect→GET).
|
||||||
func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) {
|
func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, ok := a.currentUserID(w, r)
|
userID, ok := a.currentUserID(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -238,9 +248,60 @@ func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.serverError(w, r, "get video", err)
|
a.serverError(w, r, "get video", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.Processor != nil {
|
||||||
|
a.startProcessing(userID, videoID)
|
||||||
|
a.render(w, r, processingCard(*row))
|
||||||
|
return
|
||||||
|
}
|
||||||
a.render(w, r, VideoCard(*row))
|
a.render(w, r, VideoCard(*row))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// startProcessing marks a video in-flight and summarizes it in the background.
|
||||||
|
// The goroutine uses a detached context — not the request's, which is cancelled
|
||||||
|
// when the handler returns — and clears the in-flight mark on completion. On
|
||||||
|
// error the DB flag stays set, so the video remains queued for the next
|
||||||
|
// `tapir run`; a successful Processor.ProcessVideo clears it itself.
|
||||||
|
func (a *App) startProcessing(userID, videoID string) {
|
||||||
|
key := processingKey(userID, videoID)
|
||||||
|
a.Processing.Add(key)
|
||||||
|
go func() {
|
||||||
|
defer a.Processing.Remove(key)
|
||||||
|
if err := a.Processor.ProcessVideo(context.Background(), userID, videoID); err != nil {
|
||||||
|
a.logger().Error("background summarize", "user", userID, "video", videoID, "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleStatus is the HTMX poll target for an in-flight summarization. It returns
|
||||||
|
// the card in its current state: the full summary card once the summary exists,
|
||||||
|
// otherwise the animated processing card while still in-flight (which keeps
|
||||||
|
// polling), or the queued/button card when neither holds. VideoCard carries no
|
||||||
|
// polling attributes, so HTMX stops polling once it swaps in.
|
||||||
|
func (a *App) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, ok := a.currentUserID(w, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoID := r.PathValue("videoId")
|
||||||
|
|
||||||
|
row, err := a.Store.GetVideoRow(r.Context(), userID, videoID)
|
||||||
|
if errors.Is(err, store.ErrNotFound) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
a.serverError(w, r, "get video", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.Summarized || !a.Processing.Has(processingKey(userID, videoID)) {
|
||||||
|
a.render(w, r, VideoCard(*row))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.render(w, r, processingCard(*row))
|
||||||
|
}
|
||||||
|
|
||||||
// handleSummarizeMode toggles the user's auto/manual summarization mode. The form
|
// handleSummarizeMode toggles the user's auto/manual summarization mode. The form
|
||||||
// submits the desired new value (enabled=true|false). For HTMX it returns the
|
// submits the desired new value (enabled=true|false). For HTMX it returns the
|
||||||
// refreshed mode control; without JS it redirects back to the account page.
|
// refreshed mode control; without JS it redirects back to the account page.
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processor runs the core summarization use case for a single already-discovered
|
||||||
|
// video — resolve its transcript, summarize, deliver to the store. *usecase.Engine
|
||||||
|
// wrapped with the store satisfies it (wired in cmd/tapir). Optional on App: a nil
|
||||||
|
// Processor means queue-only — the "Summarize" button only flips the DB flag and
|
||||||
|
// the next `tapir run` does the work.
|
||||||
|
type Processor interface {
|
||||||
|
ProcessVideo(ctx context.Context, userID, videoID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessingSet tracks the (user, video) ids currently being summarized in-process
|
||||||
|
// so the status endpoint can show the animation until the summary lands. It is
|
||||||
|
// ephemeral (single-instance Stage-1): a restart drops it, and the DB holds the
|
||||||
|
// durable state — the summary is present, or summarize_requested is still set so
|
||||||
|
// `tapir run` retries. The zero value is ready to use; methods are concurrency-safe.
|
||||||
|
type ProcessingSet struct {
|
||||||
|
m sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add marks a key in-flight.
|
||||||
|
func (p *ProcessingSet) Add(key string) { p.m.Store(key, struct{}{}) }
|
||||||
|
|
||||||
|
// Remove clears a key once its summarization finishes (success or failure).
|
||||||
|
func (p *ProcessingSet) Remove(key string) { p.m.Delete(key) }
|
||||||
|
|
||||||
|
// Has reports whether a key is currently in-flight.
|
||||||
|
func (p *ProcessingSet) Has(key string) bool {
|
||||||
|
_, ok := p.m.Load(key)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// processingKey scopes the in-flight key by user so one user's summarization is
|
||||||
|
// never confused with another's for the same video id.
|
||||||
|
func processingKey(userID, videoID string) string {
|
||||||
|
return userID + "|" + videoID
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package web_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"gitea.d-ma.be/mathias/tapir/internal/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeProcessor records ProcessVideo calls. With block set it parks until the
|
||||||
|
// channel is closed, so a test can observe the handler return before the
|
||||||
|
// background work finishes (proving it ran in a goroutine).
|
||||||
|
type fakeProcessor struct {
|
||||||
|
block chan struct{}
|
||||||
|
done chan struct{}
|
||||||
|
calls []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeProcessor) ProcessVideo(_ context.Context, _, videoID string) error {
|
||||||
|
if f.block != nil {
|
||||||
|
<-f.block
|
||||||
|
}
|
||||||
|
f.calls = append(f.calls, videoID)
|
||||||
|
if f.done != nil {
|
||||||
|
close(f.done)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestSummarizeImmediateProcessing(t *testing.T) {
|
||||||
|
app := newApp(t)
|
||||||
|
p := rawPool(t)
|
||||||
|
resetDB(t, p)
|
||||||
|
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
|
||||||
|
|
||||||
|
fp := &fakeProcessor{block: make(chan struct{}), done: make(chan struct{})}
|
||||||
|
app.Processor = fp
|
||||||
|
|
||||||
|
rec := postSummarize(t, app, videoX, true)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
html := body(t, rec)
|
||||||
|
|
||||||
|
// The processing card came back while ProcessVideo is still parked on block:
|
||||||
|
// the work runs in a goroutine, the handler did not wait for it.
|
||||||
|
require.Contains(t, html, "Summarizing", "processing card returned")
|
||||||
|
require.Contains(t, html, "╭", "charm box rendered")
|
||||||
|
require.Contains(t, html, "▓", "tapir body block chars rendered")
|
||||||
|
require.Contains(t, html, "∩", "wiggling snout frame rendered")
|
||||||
|
require.Contains(t, html, web.CharmPurple, "charm palette applied to the border")
|
||||||
|
require.Contains(t, html, "/v/"+videoX+"/status", "card polls the status endpoint")
|
||||||
|
require.Contains(t, html, `hx-trigger="every 2s"`, "card auto-polls every 2s")
|
||||||
|
require.NotContains(t, html, "Queued", "not the queue-only card")
|
||||||
|
|
||||||
|
close(fp.block)
|
||||||
|
select {
|
||||||
|
case <-fp.done:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("ProcessVideo was not called in the background")
|
||||||
|
}
|
||||||
|
require.Equal(t, []string{videoX}, fp.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusProcessingThenDone(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
app := newApp(t)
|
||||||
|
p := rawPool(t)
|
||||||
|
resetDB(t, p)
|
||||||
|
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
|
||||||
|
|
||||||
|
// Park ProcessVideo so the video stays in-flight while we poll status.
|
||||||
|
fp := &fakeProcessor{block: make(chan struct{})}
|
||||||
|
app.Processor = fp
|
||||||
|
require.Equal(t, http.StatusOK, postSummarize(t, app, videoX, true).Code)
|
||||||
|
|
||||||
|
// Processing: status returns the animation card, still polling.
|
||||||
|
rec := getStatus(t, app, videoX)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
html := body(t, rec)
|
||||||
|
require.Contains(t, html, "Summarizing", "in-flight → animation card")
|
||||||
|
require.Contains(t, html, `hx-trigger="every 2s"`, "still polling")
|
||||||
|
|
||||||
|
close(fp.block)
|
||||||
|
|
||||||
|
// Done: once a summary exists, status returns the summary card with no poll.
|
||||||
|
require.NoError(t, deliver(ctx, app, videoX, "the summary body"))
|
||||||
|
rec = getStatus(t, app, videoX)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
html = body(t, rec)
|
||||||
|
require.NotContains(t, html, "Summarizing", "done → no animation")
|
||||||
|
require.NotContains(t, html, "every 2s", "done card does not poll (polling stops)")
|
||||||
|
require.Contains(t, html, "/v/"+videoX+"\"", "links to the detail page")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusQueuedWhenNotInFlight(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
app := newApp(t)
|
||||||
|
p := rawPool(t)
|
||||||
|
resetDB(t, p)
|
||||||
|
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
|
||||||
|
|
||||||
|
// Flag set but nothing in-flight (e.g. queue-only, or after a restart).
|
||||||
|
require.NoError(t, app.Store.RequestSummarize(ctx, userID, videoX))
|
||||||
|
|
||||||
|
rec := getStatus(t, app, videoX)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
html := body(t, rec)
|
||||||
|
require.Contains(t, html, "Queued", "queued chip card")
|
||||||
|
require.NotContains(t, html, "Summarizing", "not processing")
|
||||||
|
require.NotContains(t, html, "every 2s", "queued card does not poll")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusNotFound(t *testing.T) {
|
||||||
|
app := newApp(t)
|
||||||
|
resetDB(t, rawPool(t))
|
||||||
|
rec := getStatus(t, app, videoX)
|
||||||
|
require.Equal(t, http.StatusNotFound, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatus(t *testing.T, app *web.App, videoID string) *httptest.ResponseRecorder {
|
||||||
|
t.Helper()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v/"+videoID+"/status", nil)
|
||||||
|
req.Header.Set("HX-Request", "true")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
app.Router().ServeHTTP(rec, req)
|
||||||
|
return rec
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestProcessingSetAddHasRemove(t *testing.T) {
|
||||||
|
var s ProcessingSet // zero value is usable
|
||||||
|
|
||||||
|
key := processingKey("user-1", "video-1")
|
||||||
|
if s.Has(key) {
|
||||||
|
t.Fatal("fresh set must not report a key as in-flight")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Add(key)
|
||||||
|
if !s.Has(key) {
|
||||||
|
t.Fatal("Add must mark the key in-flight")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A different user with the same video id is a distinct key.
|
||||||
|
if s.Has(processingKey("user-2", "video-1")) {
|
||||||
|
t.Fatal("keys must be scoped by user")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Remove(key)
|
||||||
|
if s.Has(key) {
|
||||||
|
t.Fatal("Remove must clear the key")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
|
|
||||||
@@ -176,6 +177,95 @@ func summarizeURL(videoID string) templ.SafeURL {
|
|||||||
return templ.SafeURL("/v/" + videoID + "/summarize")
|
return templ.SafeURL("/v/" + videoID + "/summarize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// statusURL builds the processing-status poll path (GET) for a video id — the
|
||||||
|
// HTMX poll target while an immediate summarization is in flight.
|
||||||
|
func statusURL(videoID string) templ.SafeURL {
|
||||||
|
return templ.SafeURL("/v/" + videoID + "/status")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charmbracelet-inspired palette for the summarizing animation (TapirSpinner) —
|
||||||
|
// a charm purple box, pink tapir, mint snout/eyes/progress. Kept as named consts
|
||||||
|
// so the inline span colours and the CSS track/fill share one source of truth.
|
||||||
|
const (
|
||||||
|
CharmPurple = "#7653FC" // box border
|
||||||
|
CharmPink = "#FF6E9C" // tapir body
|
||||||
|
CharmMint = "#0EF9B6" // snout, eyes, progress fill
|
||||||
|
CharmCream = "#FFFDF5" // bright text
|
||||||
|
CharmDim = "#6C6C6C" // dim text
|
||||||
|
charmTrack = "#2D2D2D" // empty progress track (internal: dark char colour)
|
||||||
|
)
|
||||||
|
|
||||||
|
// tapirInteriorW is the fixed inner width of the Charm box, in monospace cells.
|
||||||
|
const tapirInteriorW = 34
|
||||||
|
|
||||||
|
// tapirBarFill is the mint progress fill (27 cells), revealed left→right by the
|
||||||
|
// CSS width/clip animation over the dim track drawn in each frame.
|
||||||
|
const tapirBarFill = "███████████████████████████"
|
||||||
|
|
||||||
|
// tapirRun is one coloured (or uncoloured) text segment of a box row.
|
||||||
|
type tapirRun struct {
|
||||||
|
s string
|
||||||
|
color string // "" = no span (plain text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tapirSpan(color, s string) string {
|
||||||
|
if color == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return `<span style="color:` + color + `">` + s + `</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// tapirLine renders one interior box row: concatenate the coloured runs, pad with
|
||||||
|
// spaces to the fixed interior width, then flank with the purple side borders.
|
||||||
|
// Padding is computed from the runs' rune counts, so every row's right border
|
||||||
|
// lines up no matter how many runs it has (assuming 1-cell monospace glyphs).
|
||||||
|
func tapirLine(runs ...tapirRun) string {
|
||||||
|
var b strings.Builder
|
||||||
|
width := 0
|
||||||
|
for _, r := range runs {
|
||||||
|
b.WriteString(tapirSpan(r.color, r.s))
|
||||||
|
width += utf8.RuneCountInString(r.s)
|
||||||
|
}
|
||||||
|
if width < tapirInteriorW {
|
||||||
|
b.WriteString(strings.Repeat(" ", tapirInteriorW-width))
|
||||||
|
}
|
||||||
|
bar := tapirSpan(CharmPurple, "│")
|
||||||
|
return bar + b.String() + bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// tapirFrameHTML builds one animation frame: a rounded Charm box around a colored
|
||||||
|
// ASCII tapir, a dim progress track, and labels. snout is the wiggling nose glyph
|
||||||
|
// that differs between the three frames. Returned as raw HTML (coloured spans),
|
||||||
|
// emitted verbatim by the template via templ.Raw.
|
||||||
|
func tapirFrameHTML(snout string) string {
|
||||||
|
top := tapirSpan(CharmPurple, "╭"+strings.Repeat("─", tapirInteriorW)+"╮")
|
||||||
|
bottom := tapirSpan(CharmPurple, "╰"+strings.Repeat("─", tapirInteriorW)+"╯")
|
||||||
|
lines := []string{
|
||||||
|
top,
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "◆", color: CharmMint}, tapirRun{s: " "}, tapirRun{s: "tapir", color: CharmCream}),
|
||||||
|
tapirLine(),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "▄▄▄▄▄", color: CharmPink}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "▄█▓▓▓▓█▄", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: snout, color: CharmMint}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "█▓(", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: "◕ ◕", color: CharmMint}, tapirRun{s: ")▓█", color: CharmPink}, tapirRun{s: "──┘", color: CharmMint}, tapirRun{s: " "}, tapirRun{s: "< thinking...", color: CharmDim}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "▀█▓▓▓▓█▀", color: CharmPink}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "██▄▄██", color: CharmPink}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "▀▀", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: "▀▀", color: CharmPink}),
|
||||||
|
tapirLine(),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "[", color: CharmDim}, tapirRun{s: strings.Repeat("░", 27), color: charmTrack}, tapirRun{s: "]", color: CharmDim}),
|
||||||
|
tapirLine(tapirRun{s: " "}, tapirRun{s: "summarizing", color: CharmDim}),
|
||||||
|
bottom,
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The three frames differ only in the snout glyph (∩ → ∪ → ~), cross-faded by CSS
|
||||||
|
// to read as a tapir wiggling its nose while it thinks.
|
||||||
|
var (
|
||||||
|
tapirFrameHTML1 = tapirFrameHTML("∩")
|
||||||
|
tapirFrameHTML2 = tapirFrameHTML("∪")
|
||||||
|
tapirFrameHTML3 = tapirFrameHTML("~")
|
||||||
|
)
|
||||||
|
|
||||||
// summarizeModeLabel names the current mode for display.
|
// summarizeModeLabel names the current mode for display.
|
||||||
func summarizeModeLabel(auto bool) string {
|
func summarizeModeLabel(auto bool) string {
|
||||||
if auto {
|
if auto {
|
||||||
@@ -381,6 +471,31 @@ main { max-width: 60rem; margin: 0 auto; padding: var(--s4) var(--s3); }
|
|||||||
.card-pending { border-style: dashed; }
|
.card-pending { border-style: dashed; }
|
||||||
.card-pending .card-title { color: var(--muted); font-weight: 600; }
|
.card-pending .card-title { color: var(--muted); font-weight: 600; }
|
||||||
|
|
||||||
|
/* summarizing animation — a Charmbracelet-style TUI panel rendered in the
|
||||||
|
browser: a dark terminal card, a rounded purple box around a pink ASCII tapir,
|
||||||
|
and a lipgloss-style progress bar. Three frames are stacked and cross-faded by
|
||||||
|
a stepped keyframe (staggered delays) so the snout appears to wiggle; the
|
||||||
|
progress fill grows independently via a clip animation over the dim track. */
|
||||||
|
.card-processing { border-style: dashed; }
|
||||||
|
.tapir-charm { position: relative; display: inline-block; background: #0d0d12; border-radius: 10px; padding: .8em 1em; margin: var(--s2) 0; font: .82rem/1.15 ui-monospace, SFMono-Regular, Menlo, "Cascadia Code", monospace; box-shadow: 0 2px 14px rgba(118, 83, 252, .25); }
|
||||||
|
.tapir-charm pre { margin: 0; white-space: pre; opacity: 0; animation: tapir-cycle 1.2s steps(1, end) infinite; }
|
||||||
|
.tapir-charm .tapir-f1 { position: relative; animation-delay: 0s; }
|
||||||
|
.tapir-charm .tapir-f2 { position: absolute; top: .8em; left: 1em; animation-delay: .4s; }
|
||||||
|
.tapir-charm .tapir-f3 { position: absolute; top: .8em; left: 1em; animation-delay: .8s; }
|
||||||
|
@keyframes tapir-cycle { 0%, 33.32% { opacity: 1; } 33.33%, 100% { opacity: 0; } }
|
||||||
|
/* progress fill: 27 mint cells overlaying the dim track at box row 10, col 3,
|
||||||
|
revealed left→right over 8s, looping. */
|
||||||
|
.tapir-bar { position: absolute; top: calc(.8em + 11.5em); left: calc(1em + 3ch); height: 1.15em; line-height: 1.15; overflow: hidden; }
|
||||||
|
.tapir-bar-fill { animation: tapir-fill 8s linear infinite; text-shadow: 0 0 6px rgba(14, 249, 182, .7); }
|
||||||
|
@keyframes tapir-fill { 0% { clip-path: inset(0 100% 0 0); } 100% { clip-path: inset(0 0 0 0); } }
|
||||||
|
.tapir-label { color: var(--muted); font-size: .9rem; margin: 0; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.tapir-charm pre { animation: none; }
|
||||||
|
.tapir-charm .tapir-f2, .tapir-charm .tapir-f3 { display: none; }
|
||||||
|
.tapir-charm .tapir-f1 { opacity: 1; }
|
||||||
|
.tapir-bar-fill { animation: none; clip-path: inset(0 35% 0 0); }
|
||||||
|
}
|
||||||
|
|
||||||
/* summarization mode toggle on the account page */
|
/* summarization mode toggle on the account page */
|
||||||
.summarize-mode { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
|
.summarize-mode { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
|
||||||
.summarize-mode p { margin: 0; }
|
.summarize-mode p { margin: 0; }
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
func TestEmbedURL(t *testing.T) {
|
func TestEmbedURL(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -27,3 +32,19 @@ func TestEmbedURL(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTapirFrameRowsAligned asserts every box row has the same cell width once
|
||||||
|
// the inline-colour spans are stripped, so the rounded border lines up on every
|
||||||
|
// line (the panel only looks right if the right │ is flush across all rows).
|
||||||
|
func TestTapirFrameRowsAligned(t *testing.T) {
|
||||||
|
stripSpan := regexp.MustCompile(`</?span[^>]*>`)
|
||||||
|
for name, frame := range map[string]string{"f1": tapirFrameHTML1, "f2": tapirFrameHTML2, "f3": tapirFrameHTML3} {
|
||||||
|
plain := stripSpan.ReplaceAllString(frame, "")
|
||||||
|
want := tapirInteriorW + 2 // both purple side borders
|
||||||
|
for i, line := range strings.Split(plain, "\n") {
|
||||||
|
if got := utf8.RuneCountInString(line); got != want {
|
||||||
|
t.Errorf("%s line %d width = %d, want %d: %q", name, i, got, want, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,6 +140,42 @@ templ VideoCard(r store.SummaryRow) {
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapirSpinner is the summarizing animation: a Charmbracelet-style TUI panel —
|
||||||
|
// three richly coloured ASCII tapir frames (inline span colours, snout wiggling
|
||||||
|
// ∩→∪→~) cross-faded by CSS, plus a lipgloss-style progress bar whose mint fill
|
||||||
|
// grows over the dim track. The panel is aria-hidden (decorative); the
|
||||||
|
// "Summarizing…" label below carries the meaning for assistive tech.
|
||||||
|
templ TapirSpinner() {
|
||||||
|
<div class="tapir-charm" aria-hidden="true">
|
||||||
|
<pre class="tapir-f1">@templ.Raw(tapirFrameHTML1)</pre>
|
||||||
|
<pre class="tapir-f2">@templ.Raw(tapirFrameHTML2)</pre>
|
||||||
|
<pre class="tapir-f3">@templ.Raw(tapirFrameHTML3)</pre>
|
||||||
|
<div class="tapir-bar"><span class="tapir-bar-fill" style={ "color:" + CharmMint }>{ tapirBarFill }</span></div>
|
||||||
|
</div>
|
||||||
|
<p class="tapir-label" role="status" aria-live="polite"><em>Summarizing…</em></p>
|
||||||
|
}
|
||||||
|
|
||||||
|
// processingCard is the in-flight summarization card. It replaces the Summarize
|
||||||
|
// button card and polls /v/{id}/status every 2s, swapping itself (outerHTML, same
|
||||||
|
// id as VideoCard) for whatever state comes back: it keeps polling while still
|
||||||
|
// processing, and the summary/queued card it is eventually replaced by carries no
|
||||||
|
// poll, so polling stops on its own when the fragment changes.
|
||||||
|
templ processingCard(r store.SummaryRow) {
|
||||||
|
<li
|
||||||
|
class="card card-processing"
|
||||||
|
id={ "video-" + r.VideoID }
|
||||||
|
hx-get={ string(statusURL(r.VideoID)) }
|
||||||
|
hx-trigger="every 2s"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
<div class="card-title">{ displayTitle(r) }</div>
|
||||||
|
if cardMeta(r) != "" {
|
||||||
|
<div class="card-meta">{ cardMeta(r) }</div>
|
||||||
|
}
|
||||||
|
@TapirSpinner()
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
|
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
|
||||||
// and the action button group.
|
// and the action button group.
|
||||||
templ DetailPage(r store.SummaryRow) {
|
templ DetailPage(r store.SummaryRow) {
|
||||||
|
|||||||
+375
-191
@@ -595,9 +595,12 @@ func VideoCard(r store.SummaryRow) templ.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
|
// TapirSpinner is the summarizing animation: a Charmbracelet-style TUI panel —
|
||||||
// and the action button group.
|
// three richly coloured ASCII tapir frames (inline span colours, snout wiggling
|
||||||
func DetailPage(r store.SummaryRow) templ.Component {
|
// ∩→∪→~) cross-faded by CSS, plus a lipgloss-style progress bar whose mint fill
|
||||||
|
// grows over the dim track. The panel is aria-hidden (decorative); the
|
||||||
|
// "Summarizing…" label below carries the meaning for assistive tech.
|
||||||
|
func TapirSpinner() templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -618,7 +621,188 @@ func DetailPage(r store.SummaryRow) templ.Component {
|
|||||||
templ_7745c5c3_Var28 = templ.NopComponent
|
templ_7745c5c3_Var28 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Var29 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"tapir-charm\" aria-hidden=\"true\"><pre class=\"tapir-f1\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.Raw(tapirFrameHTML1).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</pre><pre class=\"tapir-f2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.Raw(tapirFrameHTML2).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</pre><pre class=\"tapir-f3\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.Raw(tapirFrameHTML3).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</pre><div class=\"tapir-bar\"><span class=\"tapir-bar-fill\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var29 string
|
||||||
|
templ_7745c5c3_Var29, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color:" + CharmMint)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 153, Col: 82}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var30 string
|
||||||
|
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(tapirBarFill)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 153, Col: 99}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</span></div></div><p class=\"tapir-label\" role=\"status\" aria-live=\"polite\"><em>Summarizing…</em></p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// processingCard is the in-flight summarization card. It replaces the Summarize
|
||||||
|
// button card and polls /v/{id}/status every 2s, swapping itself (outerHTML, same
|
||||||
|
// id as VideoCard) for whatever state comes back: it keeps polling while still
|
||||||
|
// processing, and the summary/queued card it is eventually replaced by carries no
|
||||||
|
// poll, so polling stops on its own when the fragment changes.
|
||||||
|
func processingCard(r store.SummaryRow) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var31 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var31 == nil {
|
||||||
|
templ_7745c5c3_Var31 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<li class=\"card card-processing\" id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var32 string
|
||||||
|
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue("video-" + r.VideoID)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 166, Col: 27}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\" hx-get=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var33 string
|
||||||
|
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(statusURL(r.VideoID)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 167, Col: 39}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" hx-trigger=\"every 2s\" hx-swap=\"outerHTML\"><div class=\"card-title\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var34 string
|
||||||
|
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 171, Col: 43}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if cardMeta(r) != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<div class=\"card-meta\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var35 string
|
||||||
|
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(cardMeta(r))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 173, Col: 39}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = TapirSpinner().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
|
||||||
|
// and the action button group.
|
||||||
|
func DetailPage(r store.SummaryRow) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var36 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var36 == nil {
|
||||||
|
templ_7745c5c3_Var36 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var37 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
if !templ_7745c5c3_IsBuffer {
|
if !templ_7745c5c3_IsBuffer {
|
||||||
@@ -630,99 +814,99 @@ func DetailPage(r store.SummaryRow) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<article class=\"detail\"><h1>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<article class=\"detail\"><h1>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var30 string
|
var templ_7745c5c3_Var38 string
|
||||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
|
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 148, Col: 24}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 184, Col: 24}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</h1><p class=\"meta\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</h1><p class=\"meta\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if detailMeta(r) != "" {
|
if detailMeta(r) != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<span>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var31 string
|
var templ_7745c5c3_Var39 string
|
||||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r))
|
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 151, Col: 26}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 187, Col: 26}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.FallbackUsed {
|
if r.FallbackUsed {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"badge\" title=\"summarized with the fallback model\" aria-label=\"summarized with the fallback model\">fallback</span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<span class=\"badge\" title=\"summarized with the fallback model\" aria-label=\"summarized with the fallback model\">fallback</span>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if url, ok := embedURL(r.ProviderVideoID); ok {
|
if url, ok := embedURL(r.ProviderVideoID); ok {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<div class=\"embed\"><iframe src=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<div class=\"embed\"><iframe src=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var32 string
|
var templ_7745c5c3_Var40 string
|
||||||
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(url)
|
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(url)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 160, Col: 15}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 196, Col: 15}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\" title=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" title=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var33 string
|
var templ_7745c5c3_Var41 string
|
||||||
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(displayTitle(r))
|
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(displayTitle(r))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 161, Col: 29}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 197, Col: 29}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.URL != "" {
|
if r.URL != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<p class=\"source\"><a href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<p class=\"source\"><a href=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var34 templ.SafeURL
|
var templ_7745c5c3_Var42 templ.SafeURL
|
||||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(externalURL(r.URL))
|
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinURLErrs(externalURL(r.URL))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 170, Col: 50}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 206, Col: 50}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\" rel=\"noopener noreferrer\">watch on source ↗</a></p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\" rel=\"noopener noreferrer\">watch on source ↗</a></p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -731,88 +915,88 @@ func DetailPage(r store.SummaryRow) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<section><h2>Summary</h2><p class=\"body\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<section><h2>Summary</h2><p class=\"body\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var35 string
|
var templ_7745c5c3_Var43 string
|
||||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary)
|
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 175, Col: 31}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 211, Col: 31}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</p></section>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</p></section>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if len(r.Highlights) > 0 {
|
if len(r.Highlights) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<section><h2>Highlights</h2><ul>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<section><h2>Highlights</h2><ul>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, h := range r.Highlights {
|
for _, h := range r.Highlights {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var36 string
|
var templ_7745c5c3_Var44 string
|
||||||
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(h)
|
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(h)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 182, Col: 14}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 218, Col: 14}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</ul></section>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</ul></section>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r.Takeaways) > 0 {
|
if len(r.Takeaways) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<section><h2>Takeaways</h2><ul>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<section><h2>Takeaways</h2><ul>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, t := range r.Takeaways {
|
for _, t := range r.Takeaways {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var37 string
|
var templ_7745c5c3_Var45 string
|
||||||
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(t)
|
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(t)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 192, Col: 14}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 228, Col: 14}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</ul></section>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</ul></section>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</article>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</article>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var29), templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var37), templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -839,12 +1023,12 @@ func RegisterPage(email, errMsg string) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var38 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var46 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var38 == nil {
|
if templ_7745c5c3_Var46 == nil {
|
||||||
templ_7745c5c3_Var38 = templ.NopComponent
|
templ_7745c5c3_Var46 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Var39 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
templ_7745c5c3_Var47 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
if !templ_7745c5c3_IsBuffer {
|
if !templ_7745c5c3_IsBuffer {
|
||||||
@@ -856,59 +1040,59 @@ func RegisterPage(email, errMsg string) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<article class=\"register\"><h1>Complete your registration</h1>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<article class=\"register\"><h1>Complete your registration</h1>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "<p class=\"meta\">Signed in as ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<p class=\"meta\">Signed in as ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var40 string
|
var templ_7745c5c3_Var48 string
|
||||||
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 209, Col: 40}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 245, Col: 40}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, ".</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, ".</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<p>Choose a display name to finish setting up your Tapir account.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<p>Choose a display name to finish setting up your Tapir account.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if errMsg != "" {
|
if errMsg != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"error\" role=\"alert\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<p class=\"error\" role=\"alert\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var41 string
|
var templ_7745c5c3_Var49 string
|
||||||
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
|
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 213, Col: 42}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 249, Col: 42}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<form method=\"post\" action=\"/register\" class=\"register-form\"><label>Display name <input type=\"text\" name=\"display_name\" required autofocus></label> <label class=\"checkbox\"><input type=\"checkbox\" name=\"accept_terms\" value=\"yes\" required> I accept the terms of use</label> <button type=\"submit\" class=\"btn\">Register</button></form></article>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<form method=\"post\" action=\"/register\" class=\"register-form\"><label>Display name <input type=\"text\" name=\"display_name\" required autofocus></label> <label class=\"checkbox\"><input type=\"checkbox\" name=\"accept_terms\" value=\"yes\" required> I accept the terms of use</label> <button type=\"submit\" class=\"btn\">Register</button></form></article>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var39), templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var47), templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -936,12 +1120,12 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var42 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var50 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var42 == nil {
|
if templ_7745c5c3_Var50 == nil {
|
||||||
templ_7745c5c3_Var42 = templ.NopComponent
|
templ_7745c5c3_Var50 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Var43 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
templ_7745c5c3_Var51 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
if !templ_7745c5c3_IsBuffer {
|
if !templ_7745c5c3_IsBuffer {
|
||||||
@@ -957,43 +1141,43 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, " <article class=\"account\"><h1>Account</h1><dl class=\"account-meta\"><dt>Display name</dt><dd>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, " <article class=\"account\"><h1>Account</h1><dl class=\"account-meta\"><dt>Display name</dt><dd>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var44 string
|
var templ_7745c5c3_Var52 string
|
||||||
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName))
|
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 241, Col: 36}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 277, Col: 36}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</dd>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</dd>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<dt>Signed in as</dt><dd>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<dt>Signed in as</dt><dd>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var45 string
|
var templ_7745c5c3_Var53 string
|
||||||
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 244, Col: 16}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 280, Col: 16}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</dd>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</dd>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</dl><section><h2>Summarization</h2><p class=\"muted\">Automatic summarizes every new video as it is discovered. Manual lets you pick which videos to summarize — new videos appear in your list with a Summarize button.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</dl><section><h2>Summarization</h2><p class=\"muted\">Automatic summarizes every new video as it is discovered. Manual lets you pick which videos to summarize — new videos appear in your list with a Summarize button.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1001,119 +1185,119 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</section><section><h2>Connected accounts</h2>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</section><section><h2>Connected accounts</h2>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if len(conns) == 0 {
|
if len(conns) == 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<p class=\"muted\">No connected video accounts yet.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<p class=\"muted\">No connected video accounts yet.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<ul class=\"conn-list\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<ul class=\"conn-list\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<li class=\"conn\"><div class=\"conn-main\"><span class=\"conn-provider\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<li class=\"conn\"><div class=\"conn-main\"><span class=\"conn-provider\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var46 string
|
var templ_7745c5c3_Var54 string
|
||||||
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider))
|
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 265, Col: 64}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 301, Col: 64}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if c.ProviderAccount != "" {
|
if c.ProviderAccount != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<span class=\"muted\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<span class=\"muted\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var47 string
|
var templ_7745c5c3_Var55 string
|
||||||
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount)
|
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 267, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 303, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<span class=\"chip\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "<span class=\"chip\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var48 string
|
var templ_7745c5c3_Var56 string
|
||||||
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status)
|
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 269, Col: 38}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 305, Col: 38}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</span></div><div class=\"conn-meta muted\">connected ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</span></div><div class=\"conn-meta muted\">connected ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var49 string
|
var templ_7745c5c3_Var57 string
|
||||||
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02"))
|
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 271, Col: 83}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 307, Col: 83}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</div><form method=\"post\" action=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</div><form method=\"post\" action=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var50 templ.SafeURL
|
var templ_7745c5c3_Var58 templ.SafeURL
|
||||||
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinURLErrs(disconnectURL(c.Provider))
|
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinURLErrs(disconnectURL(c.Provider))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 272, Col: 62}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 308, Col: 62}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\"><button type=\"submit\" class=\"btn-secondary\">Disconnect</button></form></li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "\"><button type=\"submit\" class=\"btn-secondary\">Disconnect</button></form></li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</ul>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</ul>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasYouTube(conns) {
|
if !hasYouTube(conns) {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<p><a class=\"btn\" href=\"/oauth/youtube/connect\">Connect YouTube</a></p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "<p><a class=\"btn\" href=\"/oauth/youtube/connect\">Connect YouTube</a></p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</section><section class=\"danger-zone\"><h2>Delete account</h2><p class=\"muted\">Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.</p><details class=\"confirm-delete\"><summary class=\"btn-danger\">Delete account…</summary><div class=\"confirm-body\"><p>This permanently deletes your account and all data. Are you sure?</p><form method=\"post\" action=\"/account/delete\"><button type=\"submit\" class=\"btn-danger\">Yes, permanently delete my account</button></form></div></details></section></article>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</section><section class=\"danger-zone\"><h2>Delete account</h2><p class=\"muted\">Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.</p><details class=\"confirm-delete\"><summary class=\"btn-danger\">Delete account…</summary><div class=\"confirm-body\"><p>This permanently deletes your account and all data. Are you sure?</p><form method=\"post\" action=\"/account/delete\"><button type=\"submit\" class=\"btn-danger\">Yes, permanently delete my account</button></form></div></details></section></article>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var43), templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var51), templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1141,51 +1325,51 @@ func summarizeModeControl(auto bool) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var51 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var59 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var51 == nil {
|
if templ_7745c5c3_Var59 == nil {
|
||||||
templ_7745c5c3_Var51 = templ.NopComponent
|
templ_7745c5c3_Var59 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<div id=\"summarize-mode\" class=\"summarize-mode\"><p>Current mode: <strong>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "<div id=\"summarize-mode\" class=\"summarize-mode\"><p>Current mode: <strong>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var52 string
|
var templ_7745c5c3_Var60 string
|
||||||
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto))
|
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 309, Col: 53}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 345, Col: 53}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</strong></p><form method=\"post\" action=\"/account/summarize-mode\" hx-post=\"/account/summarize-mode\" hx-target=\"#summarize-mode\" hx-swap=\"outerHTML\"><input type=\"hidden\" name=\"enabled\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</strong></p><form method=\"post\" action=\"/account/summarize-mode\" hx-post=\"/account/summarize-mode\" hx-target=\"#summarize-mode\" hx-swap=\"outerHTML\"><input type=\"hidden\" name=\"enabled\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var53 string
|
var templ_7745c5c3_Var61 string
|
||||||
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolStr(!auto))
|
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolStr(!auto))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 317, Col: 61}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 353, Col: 61}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\"> <button type=\"submit\" class=\"btn-secondary\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\"> <button type=\"submit\" class=\"btn-secondary\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var54 string
|
var templ_7745c5c3_Var62 string
|
||||||
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto))
|
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 318, Col: 79}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 354, Col: 79}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</button></form></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</button></form></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1213,117 +1397,117 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var55 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var63 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var55 == nil {
|
if templ_7745c5c3_Var63 == nil {
|
||||||
templ_7745c5c3_Var55 = templ.NopComponent
|
templ_7745c5c3_Var63 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<form id=\"action-buttons\" class=\"actions\" method=\"post\" action=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<form id=\"action-buttons\" class=\"actions\" method=\"post\" action=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var56 templ.SafeURL
|
var templ_7745c5c3_Var64 templ.SafeURL
|
||||||
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinURLErrs(actionURL(videoID))
|
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinURLErrs(actionURL(videoID))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 332, Col: 29}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 368, Col: 29}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "\" hx-post=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\" hx-post=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var57 string
|
var templ_7745c5c3_Var65 string
|
||||||
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(actionURL(videoID)))
|
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(actionURL(videoID)))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 333, Col: 38}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 369, Col: 38}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var65)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, v := range actionVerbs {
|
for _, v := range actionVerbs {
|
||||||
var templ_7745c5c3_Var58 = []any{"action", templ.KV("active", active[v])}
|
var templ_7745c5c3_Var66 = []any{"action", templ.KV("active", active[v])}
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var58...)
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var66...)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<button type=\"submit\" name=\"action\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "<button type=\"submit\" name=\"action\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var59 string
|
var templ_7745c5c3_Var67 string
|
||||||
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(v)
|
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue(v)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 341, Col: 13}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 377, Col: 13}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\" class=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\" class=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var60 string
|
var templ_7745c5c3_Var68 string
|
||||||
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var58).String())
|
templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var66).String())
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 1, Col: 0}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 1, Col: 0}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var68)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "\" aria-pressed=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" aria-pressed=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var61 string
|
var templ_7745c5c3_Var69 string
|
||||||
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaPressed(active[v]))
|
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaPressed(active[v]))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 343, Col: 41}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 379, Col: 41}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if active[v] {
|
if active[v] {
|
||||||
var templ_7745c5c3_Var62 string
|
var templ_7745c5c3_Var70 string
|
||||||
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(v))
|
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(v))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 346, Col: 30}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 382, Col: 30}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var templ_7745c5c3_Var63 string
|
var templ_7745c5c3_Var71 string
|
||||||
templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(v))
|
templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(v))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 348, Col: 21}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 384, Col: 21}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</button>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</button>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</form>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "</form>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user