# Feature: Chat (Channels & Meldinger) **Filsti:** `docs/features/chat.md` ## 1. Konsept En universell, sanntids meldingskomponent bygget på SpacetimeDB. Chat er ikke bundet til én kontekst — den kan knyttes til enhver node i Kunnskapsgrafen via **channels**. Ulike konsepter bruker chat med ulik konfigurasjon, men all infrastruktur er delt. ## 2. Channels-modellen En **channel** er en meldingsstrøm knyttet til en vilkårlig node (parent) i grafen. Channelen er selv en node (`node_type = 'channel'`), noe som betyr at den deltar i grafen og arver workspace-isolasjon. ``` ┌─────────────┐ parent_id ┌─────────────┐ │ Channel │ ──────────────────► │ Vilkårlig │ │ (node) │ │ node │ └──────┬──────┘ └─────────────┘ │ │ channel_id ▼ ┌─────────────┐ │ Meldinger │ │ (nodes) │ └─────────────┘ ``` En node kan ha **flere channels**. Eksempler: - Et Tema har "Diskusjon" (default) + "Research-dump" - En Episode har "Redaksjonelt" (intern kommentartråd) - Et Møte har "Scratchpad" (flyktig, TTL) - En Aktør har "Notater" (valgfri) ### 2.1 Channel-konfigurasjon Hver channel har en `config` (JSONB) som styrer hvilke capabilities den støtter: ```json { "threads": true, // reply-to-tråder "mentions": true, // #/@ parsing + automatiske graph_edges "attachments": true, // filopplasting "research_clips": true, // research_clip meldingstype (AI-prosessert) "ttl_days": null // null = permanent, tall = auto-slett etter N dager } ``` ### 2.2 Standard channel-presets per konsept | Konsept | Channel-navn | Config | |---|---|---| | **Redaksjonen** (Tema) | "Diskusjon" | `threads: true, mentions: true, attachments: true, research_clips: true, ttl_days: null` | | **Redaksjonen** (Tema) | "Research" (valgfri) | `threads: false, mentions: true, attachments: true, research_clips: true, ttl_days: null` | | **Møterommet** | "Scratchpad" | `threads: false, mentions: false, attachments: false, research_clips: false, ttl_days: 90` | | **Studioet** | "Studio-chat" | `threads: false, mentions: false, attachments: false, research_clips: false, ttl_days: 30` | | **Episode** | "Redaksjonelt" | `threads: true, mentions: true, attachments: true, research_clips: false, ttl_days: null` | Presets er kun defaults — en workspace-admin kan justere config per channel. ### 2.3 Automatisk opprettelse Når en node opprettes som forventes å ha chat (Tema, Episode, Møte), oppretter systemet automatisk en default-channel. Dette skjer i SvelteKit server-side som en del av node-opprettelsestransaksjonen. ## 3. Meldinger ### 3.1 Datamodell (PostgreSQL) Meldinger er noder i Kunnskapsgrafen (`node_type = 'melding'`): ```sql messages ( id UUID PK → nodes(id), channel_id UUID NOT NULL → channels(id), reply_to UUID → messages(id), -- tråder (hvis config.threads = true) author_id TEXT NOT NULL → users, message_type message_type, -- 'text', 'research_clip', 'factoid', 'system' body TEXT NOT NULL, metadata JSONB, -- ekstra data (research-klipp AI-resultat, etc.) edited_at TIMESTAMPTZ, created_at TIMESTAMPTZ ) ``` ### 3.2 SpacetimeDB (sanntid) SpacetimeDB holder aktive channels' meldinger i minnet som varm cache foran PG. Ved oppstart gjør worker warmup fra PG → SpacetimeDB (per-kanal konfigurasjon). Nye meldinger sendes via SpacetimeDB-reducers og kringkastes til alle tilkoblede klienter. Synkes til PostgreSQL med ~1 sek forsinkelse (se `docs/infra/synkronisering.md`). ## 4. Mentions & Autocomplete Kun aktive når `config.mentions = true`. * **Trigger-tegn:** `#` (Temaer/Aktører fra Kunnskapsgrafen), `@` (brukere/redaksjonsmedlemmer), `/` (kommandoer). * **Filtrering:** Svelte-klienten filtrerer den lokale SpacetimeDB-cachen umiddelbart. Skriver man `#Ha...` vises en klikkbar liste ("Hans Petter Sjøli", "Høyre"). * **Grafkobling:** Ved `#`-mention opprettes automatisk `MENTIONS`-edges i `graph_edges` mellom meldingen og den nevnte noden. * **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. ## 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. ## 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. ### 8.1 Flyt 1. Bruker trykker og holder (eller toggler) mikrofon-knappen i chat-feltet. 2. Nettleseren fanger lyd via `MediaRecorder` API (WebM/Opus). 3. Lydklippet sendes til Whisper (`POST /v1/audio/transcriptions`, `response_format=text`, `language=no`) via SvelteKit server-side. 4. Transkripsjonen settes inn i meldingsfeltet — brukeren kan redigere før sending. 5. Ingen lagring av lydfilen — den kastes etter transkripsjon. ### 8.2 Avgrensning * Dette er **ikke** en lydmelding-feature (à la WhatsApp). Lyden er et transportmiddel for tekst. Kun teksten lagres. * Whisper-kallet er kort (<30 sek tale) og kan rutes direkte til Whisper-serveren uten jobbkø. * Bruk `small`-modellen for lav latens. Navnenøyaktighet er mindre viktig for korte chatmeldinger. ## 9. TTL (automatisk opprydding) Channels med `config.ttl_days` satt til et tall får sine meldinger automatisk slettet av en nattlig jobbkø-jobb. Brukes for flyktige kontekster (scratchpads, studio-chat). ## 10. Implementeringsstatus ### Ferdig (mars 2026) - **ChatBlock.svelte:** Adapter-mønster via `createChat()` factory. Bruker `chat.edit()`, `chat.delete()`, `chat.react()` — ingen direkte PG API-kall. - **SpacetimeDB-adapter (`spacetime.svelte.ts`):** Ren SpacetimeDB-adapter. All data fra SpacetimeDB (historikk via warmup + sanntid). Reaksjoner fra `message_reaction`-tabellen. - **PG-adapter (`pg.svelte.ts`):** Polling hvert 3. sek. Readonly fallback når SpacetimeDB ikke er konfigurert. - **Factory (`create.svelte.ts`):** Velger adapter basert på `VITE_SPACETIMEDB_URL`. SSR-safe med `browser`-guard. - **Shared types (`types.ts`):** `ChatConnection` interface med `send`, `edit`, `delete`, `react`, `readonly`. - **SpacetimeDB Rust-modul (`spacetimedb/src/lib.rs`):** `ChatMessage`, `MessageReaction`, `SyncOutbox`-tabeller. Reducers: `send_message`, `delete_message`, `edit_message`, `add_reaction`, `remove_reaction`, `load_messages`, `load_reactions`, `clear_channel`, `mark_synced`. - **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. - **Reaksjoner:** Via SpacetimeDB-reducers, synket til PG. ### Gjenstår - **Vedlegg, TTL** — avventer implementering. - **Workspace-partisjonering:** SpacetimeDB har `workspace_id` men bruker ikke token ennå. - **Pin/konvertering:** Går fortsatt direkte til PG API (ikke via SpacetimeDB). ## 11. Instruks for Claude Code * **Opprettelsesrekkefølge:** Opprett `nodes`-rad → `channels`-rad → (for meldinger) `nodes`-rad → `messages`-rad. Alt i én transaksjon med riktig `workspace_id`. * **Channel-opprettelse:** Når en Tema, Episode eller Møte opprettes, opprett alltid en default-channel i samme transaksjon. * **Mentions-parsing:** Skjer i sync-workeren ved persistering til PG. Parser mention-UUIDs fra HTML body og oppretter `graph_edges`. * **Config-respekt:** Frontend-komponenten må lese `channel.config` og slå av/på UI-elementer. `channels.config` inneholder også `warmup_mode`/`warmup_value` for SpacetimeDB-oppvarming. * **PG er autoritativ** — SpacetimeDB er varm cache. Frontend snakker kun med SpacetimeDB. * **Alt er workspace-scopet.** Channels arver workspace via `nodes.workspace_id`.