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:
parent
88a22e131b
commit
89a8f99766
3 changed files with 116 additions and 61 deletions
|
|
@ -5,6 +5,9 @@ Sidelinja er et redaksjonelt operativsystem og kunnskapsgraf for podcast-produks
|
||||||
Self-hosted på Hetzner VPS med full datakontroll.
|
Self-hosted på Hetzner VPS med full datakontroll.
|
||||||
|
|
||||||
## Arbeidsflyt
|
## 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.
|
- **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.
|
- **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.
|
- **Diskusjon:** Forklar og diskuter før arkitekturendringer eller uvanlige valg. For implementering innenfor eksisterende spec — bare kjør.
|
||||||
|
|
|
||||||
|
|
@ -86,13 +86,16 @@ Kun aktive når `config.mentions = true`.
|
||||||
* **Mobil-optimalisert:** Autocomplete-listen er tappbar og tilpasset mindre skjermer.
|
* **Mobil-optimalisert:** Autocomplete-listen er tappbar og tilpasset mindre skjermer.
|
||||||
|
|
||||||
## 5. Tråder
|
## 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
|
## 6. Vedlegg
|
||||||
Kun aktive når `config.attachments = true`. Meldinger kan ha vedlegg via `message_attachments` → `media_files`. Whiteboard-eksport kan knyttes som 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
|
## 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)
|
## 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.
|
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 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.
|
- **Worker sync (`worker/src/sync.rs`):** SpacetimeDB → PG hvert sekund. Insert/delete/update meldinger + reaksjoner.
|
||||||
- **Admin-side (`/admin/channels`):** Per-kanal warmup-konfigurasjon.
|
- **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.
|
- **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
|
### Gjenstår
|
||||||
- **Vedlegg, TTL** — avventer implementering.
|
- **Vedlegg, TTL** — avventer implementering.
|
||||||
|
|
|
||||||
|
|
@ -24,59 +24,86 @@ Fordeler:
|
||||||
|
|
||||||
## 3. Modellruting
|
## 3. Modellruting
|
||||||
|
|
||||||
Modellvalg styres av to mekanismer:
|
### 3.1 Arkitekturprinsipp: PG eier config, LiteLLM er stateløs
|
||||||
|
|
||||||
### 3.1 Standard ruting (config.yaml)
|
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:
|
||||||
LiteLLM konfigureres med modellaliaser som mapper til billigste egnede leverandør:
|
|
||||||
|
|
||||||
```yaml
|
* **Ingen avhengighet til LiteLLM sitt admin API** — de endrer API mellom versjoner
|
||||||
model_list:
|
* **All konfig i samme backup/migrasjon** som resten av systemet
|
||||||
# Ruting: billigste først, fallback til dyrere
|
* **Enkel bytte** — hvis LiteLLM erstattes, er all konfig intakt i PG
|
||||||
- model_name: "sidelinja/rutine"
|
* **Admin-UI i SvelteKit** — gjenbruker eksisterende `/admin/`-mønster
|
||||||
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"
|
|
||||||
|
|
||||||
- model_name: "sidelinja/resonering"
|
### 3.2 Datamodell
|
||||||
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"
|
|
||||||
|
|
||||||
router_settings:
|
```sql
|
||||||
routing_strategy: "simple-shuffle" # prøv første, fallback til neste
|
-- Globale modellaliaser (server-nivå, ikke per workspace)
|
||||||
num_retries: 2
|
CREATE TABLE ai_model_aliases (
|
||||||
timeout: 60
|
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:
|
-- Leverandør-modeller med prioritert fallback per alias
|
||||||
master_key: "os.environ/LITELLM_MASTER_KEY"
|
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
|
### 3.3 Config-generering
|
||||||
Jobbkøen (se `jobbkø.md`) spesifiserer modellalias per jobbtype:
|
|
||||||
|
|
||||||
| 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 |
|
| `ai_text_process` (✨-behandling) | `sidelinja/rutine` | Tekstvasking, høyt volum |
|
||||||
| `openrouter_analyze` (metadata-uttrekk) | `sidelinja/rutine` | Strukturert output, lav kompleksitet |
|
| `whisper_postprocess` | `sidelinja/rutine` | Transkripsjonsvasking, høyt volum |
|
||||||
| `research_clip` (research-oppsummering) | `sidelinja/rutine` | Høyt volum |
|
| `research_clip` | `sidelinja/rutine` | Research-oppsummering, høyt volum |
|
||||||
| `live_factoid_eval` (live-assistent) | `sidelinja/resonering` | Krever presis vurdering under tidspress |
|
| `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.
|
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
|
## 4. Docker-oppsett
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# docker-compose.dev.yml / docker-compose.yml
|
# docker-compose.dev.yml / docker-compose.yml
|
||||||
ai-gateway:
|
ai-gateway:
|
||||||
image: ghcr.io/berriai/litellm:main
|
image: ghcr.io/berriai/litellm:main-stable
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: --config /etc/litellm/config.yaml
|
command: --config /etc/litellm/config.yaml
|
||||||
environment:
|
environment:
|
||||||
|
|
@ -151,36 +178,55 @@ tests/prompts/
|
||||||
└── dataset.json
|
└── 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
|
Rust-workeren logger tokenforbruk etter hvert AI-kall. Dataen lagres i PG:
|
||||||
Hver workspace har et månedlig AI-budsjett lagret i `workspaces.settings` (JSONB):
|
|
||||||
|
|
||||||
```json
|
```sql
|
||||||
{
|
CREATE TABLE ai_usage_log (
|
||||||
"ai_budget": {
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
"monthly_limit_usd": 50,
|
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||||
"alert_threshold_pct": 80,
|
job_id UUID REFERENCES job_queue(id) ON DELETE SET NULL,
|
||||||
"auto_fallback": true
|
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).
|
**Flyten:**
|
||||||
- **Alert:** Når 80 % av budsjettet er brukt, postes varsel i workspace-chat (system-channel).
|
1. Rust-worker sender AI-kall via gateway, får tilbake `usage` i responsen
|
||||||
- **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.
|
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).
|
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)
|
## 7. Dataklassifisering (ref. docs/arkitektur.md 2.2)
|
||||||
|
|
||||||
| Data | Kategori | Detaljer |
|
| Data | Kategori | Detaljer |
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue