Ny jobbtype `suggest_edges` som automatisk trigges ved opprettelse av
content-noder med tilstrekkelig tekst (≥20 tegn). Sender innholdet til
LiteLLM (sidelinja/rutine) via AI Gateway, parser JSON-respons med
topics og mentions, og oppretter topic-noder + mentions-edges i grafen.
Flyten:
1. create_node oppdager content-node med nok tekst → enqueue suggest_edges
2. Worker henter node-innhold og eksisterende topics fra PG
3. LLM analyserer tekst og returnerer foreslåtte topics/mentions
4. Nye topic-noder opprettes (med ai_generated-flagg i metadata)
5. mentions-edges opprettes fra innholdsnode til topic/entitet-noder
6. Deduplisering: gjenbruker eksisterende topics ved case-insensitivt match
Filer:
- maskinrommet/src/ai_edges.rs: Ny modul med LLM-kall og edge-opprettelse
- maskinrommet/src/jobs.rs: suggest_edges registrert i dispatcher
- maskinrommet/src/intentions.rs: Trigger i create_node
- docs/: Oppdatert jobbkø og AI gateway-docs med ny jobbtype
NB: Krever gyldig API-nøkkel i LiteLLM (OpenRouter/Gemini/Anthropic).
Jobben feiler gracefully med retry+backoff ved manglende nøkkel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Setter opp AI Gateway med LiteLLM som sentralisert proxy for alle
AI-kall. PG eier all modellkonfigurasjon — LiteLLM er stateløs.
- Migrasjon 008: ai_model_aliases, ai_model_providers, ai_job_routing
med seed-data for sidelinja/rutine og sidelinja/resonering
- Config-generering fra PG: scripts/generate-litellm-config.sh
filtrerer bort providers med tomme API-nøkler
- Docker-container kjører på sidelinja-net (intern, ingen eksponert port)
- Maskinrommet har AI_GATEWAY_URL via maskinrommet-env.sh
- API-nøkkel-placeholders i .env (GEMINI, ANTHROPIC, XAI)
- Oppdatert docs/infra/ai_gateway.md med faktisk config
Verifisert: container healthy, modellaliaser eksponert, maskinrommet
har korrekt gateway-URL. Reelle API-kall krever at Vegard fyller
inn leverandør-nøkler i /srv/synops/.env.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- query_graph endpoint med rekursiv CTE-traversering (fokus + dybde)
- Filtrering på node_kind og edge_type, RLS-beskyttet
- Maks 200 noder, 1-3 hops dybde
Frontend:
- D3 force-directed graf på /graph med fargekodede noder
- Opprett topic-noder (node_kind='topic') direkte fra graf-visningen
- Opprett mentions-edges mellom vilkårlige noder
- Klikk for detaljer, dobbeltklikk for fokus, dra for å flytte
- Filter-legende for nodetyper og kanttyper
- Zoom, auto-fit, sidepanel med koblingsinfo
- Graf-knapp lagt til i mottaksflaten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Legger til LiveKit som Docker-tjeneste for WebRTC-støtte.
Konfigurasjonen bruker livekit/livekit-server med signaling
proxyet gjennom Caddy på /livekit/*, og UDP 50000-50100 eksponert
direkte for WebRTC media-strømmer.
Endringer:
- docker-compose.yml: livekit-service (på /srv/synops/)
- livekit.yaml: server-konfig (på /srv/synops/config/livekit/)
- Caddy: /livekit/* route aktivert
- UFW: åpnet UDP 50000-50100 + TCP 7881
- maskinrommet-env.sh: LIVEKIT_URL/KEY/SECRET for Rust-API
- produksjon.md: oppdatert med LiveKit-detaljer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ny /diary-route som viser brukerens private noder — de som kun har
owner-edge og ingen delte edges til andre. Gruppert etter dato,
nyeste først, med inline oppretting av nye innlegg.
Dagbok-knapp med tellebadge lagt til i mottak-siden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ny rute /calendar som viser alle noder med scheduled-edge i et
månedsbasert kalenderrutenett. Bruker edge-metadata { at: ISO8601 }
for tidspunkt, med T12:00:00-konvensjon for heldagshendelser.
Funksjoner:
- Månedsnavigering med «I dag»-snarvei
- Drag-and-drop for å flytte hendelser mellom datoer (updateEdge)
- Inline-oppretting med tittel og valgfritt klokkeslett
- Fargekoding etter node_kind
- Hendelsesliste under rutenett for gjeldende måned
- Kalender-lenke med hendelsesteller på mottak-siden
- Sanntid via SpacetimeDB (edgeStore.byType('scheduled'))
Arkitekturvalg: Bruker scheduled-edges direkte fra SpacetimeDB
i stedet for legacy calendar_events-tabellen. En node blir en
kalenderoppføring ved å ha en scheduled-edge — konsistent med
«hva edges gjør med noder»-prinsippet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implementerer kanban som noder+edges uten separate tabeller:
- Board = collection-node med metadata.board og metadata.columns
- Kort = content-noder med belongs_to-edge til board
- Status via status-edge (kort→board) med metadata.value
- Posisjon via belongs_to-edge metadata.position
Backend:
- POST /intentions/update_edge — oppdater edge-type/metadata
- GET /query/board?board_id= — hent kort med status og posisjon
Frontend:
- /board/[id] route med kolonner, drag-and-drop, kortoppretting
- Sanntid via SpacetimeDB edge-subscriptions
- Board-oppretting og navigasjon fra mottak-siden
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Når en bruker oppretter en node i en kommunikasjonskontekst der
brukerens alias er deltaker (owner/member_of/host_of), settes
created_by til alias-noden i stedet for brukerens hovednode.
Endringer:
- resolve_context_identity(): slår opp brukerens alias i konteksten
- create_node(): bruker alias som created_by når context_id er satt
- user_can_modify_node/edge(): gjenkjenner alias-eierskap ved endring/sletting
- 006_alias_aware_rls.sql: RLS-policies inkluderer alias-opprettede noder
- current_node_alias_ids(): PG-funksjon for alias-oppslag i RLS
Verifisert med integrasjonstest på server:
- Node i alias-kontekst → created_by = alias ✓
- Node uten kontekst → created_by = bruker ✓
- Update/delete av alias-node fungerer for hovedbruker ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implementert:
- POST /intentions/create_alias: oppretter person-node og alias-edge
(system=true) fra brukerens hovednode til aliasnoden
- GET /query/aliases: returnerer brukerens alias-noder via alias-edges
- Alias-edgen er usynlig for traversering via eksisterende RLS-policy
som filtrerer system-edges (edges.system = true)
Verifisert med integrasjonstest på server: opprettelse, PG-persistering,
spørring, og at system-edge er korrekt flagget.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nytt GET /query/segments/srt-endepunkt som genererer nedlastbar SRT-fil
fra transcription_segments-tabellen. Bruker RLS-verifisert tilgang.
Frontend har nedlastingsknapp i TranscriptionView med autentisert fetch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ny funksjonalitet for å kjøre re-transkripsjon på eksisterende media-noder
og sammenligne gammel vs ny versjon per segment. Manuelt redigerte segmenter
fra forrige versjon blir uthevet, og brukeren velger per segment hvilken
versjon som skal beholdes.
Backend (Rust):
- POST /intentions/retranscribe — trigger ny Whisper-jobb for media-node
- GET /query/transcription_versions — list alle versjoner for en node
- GET /query/segments_version — hent segmenter for spesifikk versjon
- POST /intentions/resolve_retranscription — anvend per-segment-valg
Frontend (Svelte):
- RetranscriptionCompare.svelte — side-om-side visning med per-segment-valg
- TranscriptionView: re-transkriber-knapp, auto-detect nye versjoner, polling
- API-klient: nye funksjoner for alle re-transkripsjonsendepunkter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Oppretter transcription_segments-tabellen i PostgreSQL som master-kopi
for alle transkripsjoner. transcribe.rs er oppdatert fra verbose_json
til SRT-format med full parse → segment-innsetting pipeline.
Endringer:
- Migration 005: transcription_segments med GIN fulltekstsøk (norsk)
- transcribe.rs: SRT-parser, segment-innsetting, node-oppdatering
- Miljøvariabler: WHISPER_MODEL (default "medium"), WHISPER_INITIAL_PROMPT
- Docker-compose: nye env vars for maskinrommet-containeren
- Docs: oppdatert podcastfabrikken, arkitektur, primitiver, CLAUDE.md
Tabellen kjørt på server, maskinrommet restartet med nye env vars.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Legger til AudioPlayer-komponent som spiller av lyd fra CAS-noder
med waveform-visualisering via wavesurfer.js. Komponenten viser
play/pause, tidslinje, og kan ekspandere transkripsjonen.
Chat-visningen inkluderer nå media-noder (has_media-edges) sammen
med tekstmeldinger, sortert kronologisk. Talenotater vises med
mikrofon-ikon, waveform og transkripsjon.
Mottak-siden viser også AudioPlayer for media-noder med lyd.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Legger til VoiceRecorder-komponent som bruker MediaRecorder API for
lydopptak i nettleseren. Opptaket lastes opp til CAS via eksisterende
uploadMedia-endepunkt, som automatisk trigger Whisper-transkripsjon.
Komponenten er integrert i:
- ChatInput: mikrofon-knapp mellom tekstfelt og send-knapp
- NodeEditor: mikrofon-knapp i verktøylinjen
Flyten: opptak → webm/opus blob → upload → CAS → whisper_transcribe-jobb.
Ingen backend-endringer nødvendig — hele transkripsjons-pipelinen fra
oppgave 7.2 gjenbrukes uendret.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implementerer komplett pipeline for automatisk transkripsjon av lydfiler:
- PostgreSQL jobbkø (job_queue-tabell med status, retry, backoff)
- Worker-loop i maskinrommet som poller hvert 2. sekund
- Whisper-integrasjon: leser CAS-fil, sender multipart til faster-whisper API
- Postprosessering: filtrerer hallusinerte segmenter (no_speech_prob > 0.6)
- Oppdaterer media-nodens content-felt med transkripsjon og metadata
- Automatisk trigger: upload_media enqueuer jobb for audio/*-filer
Testet ende-til-ende på server: jobb plukkes opp, Whisper prosesserer,
node oppdateres. Retry med eksponentiell backoff ved feil.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Satt opp faster-whisper-server (fedirz/faster-whisper-server:latest-cpu)
som Docker-tjeneste på produksjonsserveren. Ingen GPU tilgjengelig —
bruker CPU med int8-kvantisering og large-v3 modell for best norsk kvalitet.
Verifisert:
- Transkripsjon fungerer via OpenAI-kompatibelt API
- verbose_json med segmenter og tidskoder OK
- Docker DNS-oppslag fra sidelinja-net fungerer
- Maskinrommet har WHISPER_URL=http://faster-whisper:8000
- RAM-bruk ~2.5 GB med modell lastet
Konfigurasjon:
- Image: fedirz/faster-whisper-server:latest-cpu
- Modell: large-v3 (norsk), int8, CPU
- CAS montert read-only for direkte filtilgang
- Healthcheck via python3 (curl ikke tilgjengelig i image)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Brukeren kan nå dra eller lime inn bilder i TipTap-editoren.
Bildet lastes opp til CAS via upload_media-endepunktet, og settes
inn som <img> med CAS-URL i metadata.document (HTML).
Endringer:
- Ny uploadMedia() og casUrl() i api.ts for multipart upload
- @tiptap/extension-image med CasImage-utvidelse (data-node-id attr)
- handleDrop/handlePaste i editor intercepter bildefiler
- Upload-status vises i editoren mens bilder lastes opp
- accessToken sendes ned til NodeEditor fra +page.svelte
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nytt endepunkt streamer CAS-filer fra disk med riktig Content-Type
(oppslått fra media-nodens metadata i PG) og Cache-Control: immutable.
Hash-validering (64 hex-tegn) hindrer path traversal.
Tokio-streaming for effektiv håndtering av store filer.
Docker-compose oppdatert med CAS-volum for maskinrommet-containeren.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /intentions/upload_media mottar multipart form data, lagrer filen
i CAS med SHA-256 hashing, og oppretter en media-node. Valgfri
source_id oppretter en has_media-edge fra kildenoden til media-noden.
Endepunktet følger etablert skrivestimønster: STDB først (instant),
async PG-persistering i bakgrunnen. Maks filstørrelse 100 MB.
Deduplisering via CAS — identiske filer gir ingen ekstra diskbruk.
Verifisert med curl mot produksjonsserver: upload uten og med
source_id, deduplisering, og PG-persistering fungerer korrekt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implementerer full 1:1 chat-loop:
- NewChatDialog: velg person å starte samtale med
- Fikser API-felt (participant_ids → participants) for korrekt
kommunikasjon med maskinrommets create_communication-endepunkt
- Opprett kommunikasjonsnode med to deltakere (owner + member_of)
- Dedupliserer: finner eksisterende samtale før ny opprettes
- Chat-header viser den andre deltakerens navn i 1:1-samtaler
- Testbruker-node opprettet på server for verifisering
Full loop verifisert via STDB: node + edges + melding + belongs_to
fungerer, WebSocket-subscribers ser endringer i sanntid.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chat-visning i frontend som viser noder med belongs_to-edge til en
kommunikasjonsnode, sortert på tid, med sanntidsoppdatering via
SpacetimeDB.
Nye filer:
- frontend/src/routes/chat/[id]/+page.svelte — Chat-side som viser
meldinger (noder med belongs_to-edge), deltakere, auto-scroll,
og avsender-info. Bruker edgeStore.byTarget() for reaktive
oppdateringer når nye meldinger kommer via STDB.
- frontend/src/lib/components/ChatInput.svelte — Enkel meldings-input
med Enter-for-send, auto-resize textarea.
Endringer:
- frontend/src/lib/api.ts — Lagt til createCommunication()-funksjon
for å opprette kommunikasjonsnoder fra frontend.
- frontend/src/routes/+page.svelte — Kommunikasjonsnoder i mottaket
er nå klikkbare lenker til chat-visningen. "Ny samtale"-knapp.
- tasks.md — Oppgave 5.3 markert som ferdig.
Arkitektur: Chat-visningen bruker context_id-parameteren i
create_node-intensjonen (implementert i 5.2) for automatisk
belongs_to-edge. Meldinger hentes reaktivt fra STDB-stores —
ingen polling, ingen ekstra API-kall.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
create_node-intensjonen støtter nå context_id-parameter. Når satt:
- Validerer at kontekstnoden eksisterer og er en kommunikasjonsnode
- Oppretter automatisk belongs_to-edge fra ny node → kontekstnode
- Returnerer belongs_to_edge_id i responsen
Verifisert med curl-tester: feilvalidering for ugyldig/feil node_kind,
og korrekt edge-opprettelse i både STDB og PG for gyldig context_id.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>