Docs: oppdater CLAUDE.md arbeidsflyt, ai_gateway og chat-docs

- CLAUDE.md: lagt til standard arbeidsmodus, testmiljø og browser-testing
- ai_gateway.md: PG-eier-config arkitektur, datamodell, config-generering, admin-panel
- chat.md: trådvisning, meldingskollaps, AI-behandling, konvertering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-16 09:18:50 +01:00
parent 88a22e131b
commit 89a8f99766
3 changed files with 116 additions and 61 deletions

View file

@ -5,6 +5,9 @@ Sidelinja er et redaksjonelt operativsystem og kunnskapsgraf for podcast-produks
Self-hosted på Hetzner VPS med full datakontroll.
## Arbeidsflyt
- **Standard arbeidsmodus:** Start i planleggingsmodus. Lag en grundig plan, få godkjenning, deretter implementer. Jobbene er ment å kunne kjøre lenge autonomt uten input underveis.
- **Testmiljø:** `./dev.sh` er den kanoniske måten å starte utviklingsmiljøet. Når nye tjenester, steg eller oppsett-quirks oppdages, oppdater alltid `dev.sh` slik at kunnskapen bevares i scriptet — ikke bare i hodet. Før Vegard tester i browser: kjør `./dev.sh`, verifiser med `cargo check`/`svelte-check`/`curl`, og meld tilbake at det er klart.
- **Browser-testing:** Claude har ikke tilgang til browser. Visuell testing og interaksjon gjøres av Vegard. Claude kan verifisere backend (kompilering, API-kall, database-state) men ikke frontend-rendering.
- **Commit og push:** Bruk egen vurdering. Commit når arbeidet er logisk komplett, push til Forgejo når det gir mening. Ingen grunn til å spørre — det er trygt og reverserbart.
- **Deploy til produksjon:** Krever alltid eksplisitt godkjenning fra Vegard. Deploy = SSH til server + pull + docker compose up. Aldri gjør dette uten å spørre først.
- **Diskusjon:** Forklar og diskuter før arkitekturendringer eller uvanlige valg. For implementering innenfor eksisterende spec — bare kjør.

View file

@ -86,13 +86,16 @@ Kun aktive når `config.mentions = true`.
* **Mobil-optimalisert:** Autocomplete-listen er tappbar og tilpasset mindre skjermer.
## 5. Tråder
Kun aktive når `config.threads = true`. Meldinger kan ha en `reply_to`-referanse. Frontend viser tråder som innrykk eller ekspanderbare grupper.
Kun aktive når `config.threads = true`. Meldinger kan ha en `reply_to`-referanse. Frontend grupperer meldinger i tråder (rot + svar) med visuell skillelinje mellom hver tråd. Svar vises med innrykk og vertikal linje under rot-meldingen, uten ekstra skillelinje mellom rot og svar.
## 6. Vedlegg
Kun aktive når `config.attachments = true`. Meldinger kan ha vedlegg via `message_attachments``media_files`. Whiteboard-eksport kan knyttes som vedlegg.
## 7. Versjonshistorikk
Alle meldinger støtter redigering med full historikk via `message_revisions`. Original tekst bevares alltid.
Alle meldinger støtter redigering med full historikk via `message_revisions`. Original tekst bevares alltid. AI-behandlede meldinger har en revisjons-toggle i UI — brukeren kan veksle mellom AI-versjon og original tekst. AI-output rendres som Markdown via `marked`.
## 7.1 Meldingsvisning
Lange meldinger (mer enn 2 linjer) kollapses automatisk med en "Vis mer"-knapp. Ved ekspandering vises "Vis mindre" både over og under meldingen, slik at man slipper å scrolle for å kollapse igjen.
## 8. Tale-til-tekst (Voice-to-text)
Mobilvennlig diktering for situasjoner der tastatur er upraktisk. Brukeren trykker en mikrofon-knapp, snakker, og får teksten tilbake som en vanlig melding klar til redigering og sending.
@ -124,8 +127,11 @@ Channels med `config.ttl_days` satt til et tall får sine meldinger automatisk s
- **Worker warmup (`worker/src/warmup.rs`):** PG → SpacetimeDB ved oppstart. Per-kanal konfig (all/messages/days/none). Trådbasert henting.
- **Worker sync (`worker/src/sync.rs`):** SpacetimeDB → PG hvert sekund. Insert/delete/update meldinger + reaksjoner.
- **Admin-side (`/admin/channels`):** Per-kanal warmup-konfigurasjon.
- **Tråder:** Komplett trådvisning med datogruppering og autoscroll.
- **Tråder:** Komplett trådvisning med datogruppering, autoscroll og visuell skillelinje mellom tråder.
- **Reaksjoner:** Via SpacetimeDB-reducers, synket til PG.
- **Meldingskollaps:** Lange meldinger begrenses til 2 linjer med "Vis mer"/"Vis mindre".
- **AI-behandling:** Meldinger kan AI-behandles (✨-knapp). Revisjons-toggle viser original vs. AI-versjon. Markdown-rendering for AI-output.
- **Konvertering:** Meldinger kan opprettes som kanban-kort eller kalenderhendelse (dialog sier "Opprett", ikke "Konverter" — meldingen beholdes i chatten).
### Gjenstår
- **Vedlegg, TTL** — avventer implementering.

View file

@ -24,59 +24,86 @@ Fordeler:
## 3. Modellruting
Modellvalg styres av to mekanismer:
### 3.1 Arkitekturprinsipp: PG eier config, LiteLLM er stateløs
### 3.1 Standard ruting (config.yaml)
LiteLLM konfigureres med modellaliaser som mapper til billigste egnede leverandør:
PostgreSQL er single source of truth for all modellkonfigurasjon. LiteLLM er en stateløs proxy som får generert `config.yaml` fra PG-data. Dette gir:
```yaml
model_list:
# Ruting: billigste først, fallback til dyrere
- model_name: "sidelinja/rutine"
litellm_params:
model: "gemini/gemini-2.0-flash"
api_key: "os.environ/GEMINI_API_KEY"
- model_name: "sidelinja/rutine"
litellm_params:
model: "openrouter/google/gemini-2.0-flash-001"
api_key: "os.environ/OPENROUTER_API_KEY"
* **Ingen avhengighet til LiteLLM sitt admin API** — de endrer API mellom versjoner
* **All konfig i samme backup/migrasjon** som resten av systemet
* **Enkel bytte** — hvis LiteLLM erstattes, er all konfig intakt i PG
* **Admin-UI i SvelteKit** — gjenbruker eksisterende `/admin/`-mønster
- model_name: "sidelinja/resonering"
litellm_params:
model: "anthropic/claude-sonnet-4-20250514"
api_key: "os.environ/ANTHROPIC_API_KEY"
- model_name: "sidelinja/resonering"
litellm_params:
model: "openrouter/anthropic/claude-sonnet-4-20250514"
api_key: "os.environ/OPENROUTER_API_KEY"
### 3.2 Datamodell
router_settings:
routing_strategy: "simple-shuffle" # prøv første, fallback til neste
num_retries: 2
timeout: 60
```sql
-- Globale modellaliaser (server-nivå, ikke per workspace)
CREATE TABLE ai_model_aliases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
alias TEXT NOT NULL, -- 'sidelinja/rutine', 'sidelinja/resonering'
description TEXT, -- 'Billig, høyt volum'
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(alias)
);
general_settings:
master_key: "os.environ/LITELLM_MASTER_KEY"
-- Leverandør-modeller med prioritert fallback per alias
CREATE TABLE ai_model_providers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
alias_id UUID NOT NULL REFERENCES ai_model_aliases(id) ON DELETE CASCADE,
provider TEXT NOT NULL, -- 'gemini', 'openrouter', 'anthropic'
model TEXT NOT NULL, -- 'gemini/gemini-2.5-flash', 'openrouter/anthropic/claude-sonnet-4'
api_key_env TEXT NOT NULL, -- 'GEMINI_API_KEY', 'OPENROUTER_API_KEY'
priority SMALLINT NOT NULL, -- lavere = prøves først
is_active BOOLEAN NOT NULL DEFAULT true,
UNIQUE(alias_id, model)
);
-- Jobbtype → modellalias mapping
CREATE TABLE ai_job_routing (
job_type TEXT PRIMARY KEY, -- 'ai_text_process', 'whisper_postprocess', etc.
alias TEXT NOT NULL, -- 'sidelinja/rutine'
description TEXT
);
```
### 3.2 Jobbkø-styrt modellvalg
Jobbkøen (se `jobbkø.md`) spesifiserer modellalias per jobbtype:
### 3.3 Config-generering
| Jobbtype | Modellalias | Begrunnelse |
SvelteKit-serveren genererer `config.yaml` fra PG ved oppstart og ved endringer i admin-panelet:
1. Les aktive aliaser og deres providers (sortert etter priority)
2. Skriv `config.yaml` til volum delt med LiteLLM-containeren
3. Restart LiteLLM (`docker restart ai-gateway`) eller send `SIGHUP`
Generert config inkluderer alltid `router_settings` og `general_settings` fra faste verdier — kun `model_list` er dynamisk.
### 3.4 Jobbkø-styrt modellvalg
Jobbkøen bruker `ai_job_routing` for å bestemme modellalias per jobbtype:
| Jobbtype | Standard alias | Begrunnelse |
|---|---|---|
| `whisper_postprocess` (transkripsjonsvasking) | `sidelinja/rutine` | Høyt volum, lav kompleksitet |
| `openrouter_analyze` (metadata-uttrekk) | `sidelinja/rutine` | Strukturert output, lav kompleksitet |
| `research_clip` (research-oppsummering) | `sidelinja/rutine` | Høyt volum |
| `live_factoid_eval` (live-assistent) | `sidelinja/resonering` | Krever presis vurdering under tidspress |
| `ai_text_process` (✨-behandling) | `sidelinja/rutine` | Tekstvasking, høyt volum |
| `whisper_postprocess` | `sidelinja/rutine` | Transkripsjonsvasking, høyt volum |
| `research_clip` | `sidelinja/rutine` | Research-oppsummering, høyt volum |
| `live_factoid_eval` | `sidelinja/resonering` | Krever presis vurdering under tidspress |
Modellalias lagres som felt på jobben i PG — kan overstyres manuelt per jobb ved behov.
### 3.5 Admin-panel (`/admin/ai`)
Admin-panelet lar administrator:
* Se og redigere modellaliaser og deres fallback-liste (drag-and-drop prioritering)
* Aktivere/deaktivere individuelle leverandør-modeller
* Endre jobbtype → alias mapping
* Se live-status: hvilke leverandører som svarer, responstider
* Trigge config-regenerering og LiteLLM-restart
## 4. Docker-oppsett
```yaml
# docker-compose.dev.yml / docker-compose.yml
ai-gateway:
image: ghcr.io/berriai/litellm:main
image: ghcr.io/berriai/litellm:main-stable
restart: unless-stopped
command: --config /etc/litellm/config.yaml
environment:
@ -151,36 +178,55 @@ tests/prompts/
└── dataset.json
```
## 6. Kostnadskontroll
## 6. Tokenregnskap og kostnadskontroll
LiteLLM har innebygd logging, men mangler workspace-nivå budsjettering. For å forhindre kostnadssprekk:
### 6.1 Token-logging per workspace
### 6.1 Workspace-budsjett
Hver workspace har et månedlig AI-budsjett lagret i `workspaces.settings` (JSONB):
Rust-workeren logger tokenforbruk etter hvert AI-kall. Dataen lagres i PG:
```json
{
"ai_budget": {
"monthly_limit_usd": 50,
"alert_threshold_pct": 80,
"auto_fallback": true
}
}
```sql
CREATE TABLE ai_usage_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
job_id UUID REFERENCES job_queue(id) ON DELETE SET NULL,
model_alias TEXT NOT NULL, -- 'sidelinja/rutine'
model_actual TEXT, -- 'gemini/gemini-2.5-flash' (fra LiteLLM-respons)
prompt_tokens INT NOT NULL,
completion_tokens INT NOT NULL,
total_tokens INT NOT NULL,
estimated_cost NUMERIC(10, 6), -- USD, beregnet fra kjente priser
job_type TEXT, -- 'ai_text_process', etc.
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_ai_usage_workspace_month ON ai_usage_log (workspace_id, created_at);
```
- **Sporing:** SvelteKit logger token-bruk per AI-kall med workspace_id og jobbtype i `ai_usage_log`-tabellen (flyktig, TTL 90 dager).
- **Alert:** Når 80 % av budsjettet er brukt, postes varsel i workspace-chat (system-channel).
- **Auto-fallback:** Når budsjettet er nådd og `auto_fallback: true`, rutes alle kall til `sidelinja/rutine` (billigste modell). Ellers blokkeres AI-kall med feilmelding.
**Flyten:**
1. Rust-worker sender AI-kall via gateway, får tilbake `usage` i responsen
2. Worker skriver rad til `ai_usage_log` med workspace_id, tokens og modellinfo
3. Estimert kostnad beregnes fra en enkel prisliste i config (oppdateres manuelt)
### 6.2 Per-episode maks-kostnad
### 6.2 Visning — to nivåer
**Admin (`/admin/ai`):**
Aggregert oversikt over alle workspaces. Tabell med totaler per workspace/modell/periode. Identifiserer kostnadsdrivere.
**Workspace (sidebar-widget):**
Enkel tekst-indikator i workspace-sidebar: `✨ 12.4k tokens denne uken`. Klikk åpner detaljert visning med fordeling per jobbtype og modell. Ingen speedometer — det krever et definert budsjett for å gi mening, og det er overkill for MVP.
### 6.3 Workspace-budsjett (fase 2)
Når token-logging er på plass, kan budsjett-tak legges til:
- Budsjett lagres i `workspaces.settings` (JSONB): `{ "ai_budget": { "monthly_limit_usd": 50 } }`
- Rust-worker sjekker aggregert forbruk før AI-kall
- Ved budsjett nær: fall tilbake til `sidelinja/rutine` (billigste)
- Ved budsjett nådd: sett jobb i `paused` med varsel i workspace-chat
### 6.4 Per-episode maks-kostnad
Podcastfabrikken-jobber (whisper + metadata + oppsummering) kan estimere totalkostnad basert på lydlengde. Jobben avbrytes med varsel hvis estimert kostnad overstiger `max_cost_per_episode` (default: $5).
### 6.3 Modell-nedgradering
Jobbkøen støtter automatisk modell-nedgradering ved kostnadsmål:
1. Prøv `sidelinja/resonering` (Claude)
2. Ved budsjett-nær: fall tilbake til `sidelinja/rutine` (Gemini gratis)
3. Ved budsjett-nådd: sett jobb i `paused`-status med varsel
## 7. Dataklassifisering (ref. docs/arkitektur.md 2.2)
| Data | Kategori | Detaljer |