Valider fase 9–10: fiks typefeil, konsistens og feilhåndtering

Fase 9 (visninger):
- graph: fiks TypeScript-feil (string|undefined → string|null)
- kanban board: les kolonner fra metadata.traits.kanban.columns
  (konsistent med KanbanTrait), behold fallback til metadata.columns
- dagbok: fiks createdAt-type (number, ikke BigInt med microsSinceUnixEpoch)

Fase 10 (AI):
- summarize.rs: refaktorer til cli_dispatch helper (DRY, konsistent
  med ai_edges.rs og tts.rs)
- ai_process.rs: feil tidlig hvis LITELLM_MASTER_KEY mangler
  (var unwrap_or_default → tom streng → forvirrende 401)

Alle 61 maskinrommet-tester bestått, alle CLI-verktøy kompilerer.
LiteLLM kjører med riktig modellruting (rutine/resonering).
This commit is contained in:
vegard 2026-03-18 15:09:03 +00:00
parent 3ae46a05eb
commit 382f93132f
6 changed files with 19 additions and 54 deletions

View file

@ -17,6 +17,9 @@
if (!boardNode) return ['todo', 'in_progress', 'done']; if (!boardNode) return ['todo', 'in_progress', 'done'];
try { try {
const meta = JSON.parse(boardNode.metadata ?? '{}'); const meta = JSON.parse(boardNode.metadata ?? '{}');
const traitConf = meta.traits?.kanban;
if (traitConf && Array.isArray(traitConf.columns) && traitConf.columns.length > 0) return traitConf.columns as string[];
// Fallback: legacy metadata.columns path
if (Array.isArray(meta.columns) && meta.columns.length > 0) return meta.columns as string[]; if (Array.isArray(meta.columns) && meta.columns.length > 0) return meta.columns as string[];
} catch { /* ignore */ } } catch { /* ignore */ }
return ['todo', 'in_progress', 'done']; return ['todo', 'in_progress', 'done'];

View file

@ -42,9 +42,9 @@
// Sort by created_at descending (newest first) // Sort by created_at descending (newest first)
entries.sort((a, b) => { entries.sort((a, b) => {
const ta = a.createdAt?.microsSinceUnixEpoch ?? 0n; const ta = a.createdAt ?? 0;
const tb = b.createdAt?.microsSinceUnixEpoch ?? 0n; const tb = b.createdAt ?? 0;
return tb > ta ? 1 : tb < ta ? -1 : 0; return tb - ta;
}); });
return entries; return entries;
@ -102,8 +102,8 @@
const dayNames = ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag']; const dayNames = ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'];
function nodeDate(node: Node): Date { function nodeDate(node: Node): Date {
const micros = node.createdAt?.microsSinceUnixEpoch ?? 0n; const micros = node.createdAt ?? 0;
return new Date(Number(micros / 1000n)); return new Date(micros / 1000);
} }
function formatDateKey(d: Date): string { function formatDateKey(d: Date): string {

View file

@ -644,7 +644,7 @@
<div class="mt-4 flex flex-col gap-1"> <div class="mt-4 flex flex-col gap-1">
<button <button
onclick={() => { focusId = selectedNode?.id; loadGraph(); }} onclick={() => { focusId = selectedNode?.id ?? null; loadGraph(); }}
class="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700 hover:bg-gray-200" class="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700 hover:bg-gray-200"
> >
Fokuser på denne Fokuser på denne
@ -653,7 +653,7 @@
onclick={() => { onclick={() => {
showMentionsForm = true; showMentionsForm = true;
showTopicForm = false; showTopicForm = false;
mentionsSourceId = selectedNode?.id; mentionsSourceId = selectedNode?.id ?? '';
}} }}
class="rounded bg-purple-100 px-2 py-1 text-xs text-purple-700 hover:bg-purple-200" class="rounded bg-purple-100 px-2 py-1 text-xs text-purple-700 hover:bg-purple-200"
> >

View file

@ -428,7 +428,8 @@ async fn call_ai_gateway(
) -> Result<(String, Option<UsageInfo>, Option<String>), String> { ) -> Result<(String, Option<UsageInfo>, Option<String>), String> {
let gateway_url = std::env::var("AI_GATEWAY_URL") let gateway_url = std::env::var("AI_GATEWAY_URL")
.unwrap_or_else(|_| "http://localhost:4000".to_string()); .unwrap_or_else(|_| "http://localhost:4000".to_string());
let api_key = std::env::var("LITELLM_MASTER_KEY").unwrap_or_default(); let api_key = std::env::var("LITELLM_MASTER_KEY")
.map_err(|_| "LITELLM_MASTER_KEY ikke satt — kan ikke kalle AI Gateway".to_string())?;
let request = ChatRequest { let request = ChatRequest {
model: model_alias.to_string(), model: model_alias.to_string(),

View file

@ -6,9 +6,9 @@
// Jobbtype: "summarize_communication" // Jobbtype: "summarize_communication"
// Payload: { "communication_id": "<uuid>", "requested_by": "<uuid>" } // Payload: { "communication_id": "<uuid>", "requested_by": "<uuid>" }
use std::process::Stdio;
use uuid::Uuid; use uuid::Uuid;
use crate::cli_dispatch;
use crate::jobs::JobRow; use crate::jobs::JobRow;
/// Synops-summarize binary path. /// Synops-summarize binary path.
@ -55,24 +55,10 @@ pub async fn handle_summarize_communication(
.arg("--write"); .arg("--write");
// Sett miljøvariabler CLI-verktøyet trenger // Sett miljøvariabler CLI-verktøyet trenger
let db_url = std::env::var("DATABASE_URL") cli_dispatch::set_database_url(&mut cmd)?;
.map_err(|_| "DATABASE_URL ikke satt".to_string())?; cli_dispatch::forward_env(&mut cmd, "AI_GATEWAY_URL");
cli_dispatch::forward_env(&mut cmd, "LITELLM_MASTER_KEY");
cmd.env("DATABASE_URL", &db_url); cli_dispatch::forward_env(&mut cmd, "AI_SUMMARY_MODEL");
// Videresend AI-relaterte env-variabler hvis satt
if let Ok(v) = std::env::var("AI_GATEWAY_URL") {
cmd.env("AI_GATEWAY_URL", v);
}
if let Ok(v) = std::env::var("LITELLM_MASTER_KEY") {
cmd.env("LITELLM_MASTER_KEY", v);
}
if let Ok(v) = std::env::var("AI_SUMMARY_MODEL") {
cmd.env("AI_SUMMARY_MODEL", v);
}
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped());
tracing::info!( tracing::info!(
communication_id = %communication_id, communication_id = %communication_id,
@ -81,31 +67,7 @@ pub async fn handle_summarize_communication(
"Starter synops-summarize" "Starter synops-summarize"
); );
// Spawn og vent let result = cli_dispatch::run_cli_tool(&bin, &mut cmd).await?;
let child = cmd
.spawn()
.map_err(|e| format!("Kunne ikke starte {bin}: {e}"))?;
let output = child
.wait_with_output()
.await
.map_err(|e| format!("Feil ved kjøring av {bin}: {e}"))?;
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.is_empty() {
tracing::info!(stderr = %stderr, "synops-summarize stderr");
}
if !output.status.success() {
let code = output.status.code().unwrap_or(-1);
return Err(format!(
"synops-summarize feilet (exit {code}): {stderr}"
));
}
// Parse stdout som JSON — det er resultatet
let stdout = String::from_utf8_lossy(&output.stdout);
let result: serde_json::Value = serde_json::from_str(&stdout)
.map_err(|e| format!("Kunne ikke parse synops-summarize output: {e}"))?;
tracing::info!( tracing::info!(
communication_id = %communication_id, communication_id = %communication_id,

View file

@ -298,8 +298,7 @@ med spesifikasjon for det som trenger en dedikert sesjon.
- [x] 23.1 Valider fase 12 (infra + maskinrommet): PG-skjema, indekser, auth-middleware, intensjoner, STDB-klient (nå erstattet av WS). Verifiser at skjema matcher docs, at auth fungerer, at skrivestien er konsistent. - [x] 23.1 Valider fase 12 (infra + maskinrommet): PG-skjema, indekser, auth-middleware, intensjoner, STDB-klient (nå erstattet av WS). Verifiser at skjema matcher docs, at auth fungerer, at skrivestien er konsistent.
- [x] 23.2 Valider fase 34 (frontend + tilgang): SvelteKit-oppsett, OIDC-flow, sanntid, mottaksflate, TipTap-editor, node_access-matrise, team-transitivitet, visibility-filtrering. - [x] 23.2 Valider fase 34 (frontend + tilgang): SvelteKit-oppsett, OIDC-flow, sanntid, mottaksflate, TipTap-editor, node_access-matrise, team-transitivitet, visibility-filtrering.
- [x] 23.3 Valider fase 58 (kommunikasjon + CAS + lyd + aliaser): chat-loop, kontekst-arv, CAS-hashing/deduplisering, Whisper-pipeline, segmenttabell, SRT-eksport, alias-identitet. - [x] 23.3 Valider fase 58 (kommunikasjon + CAS + lyd + aliaser): chat-loop, kontekst-arv, CAS-hashing/deduplisering, Whisper-pipeline, segmenttabell, SRT-eksport, alias-identitet.
- [~] 23.4 Valider fase 910 (visninger + AI): kanban drag-and-drop, kalender, dagbok, kunnskapsgraf, LiteLLM-ruting, AI-foreslåtte edges, oppsummering, TTS. - [x] 23.4 Valider fase 910 (visninger + AI): kanban drag-and-drop, kalender, dagbok, kunnskapsgraf, LiteLLM-ruting, AI-foreslåtte edges, oppsummering, TTS.
> Påbegynt: 2026-03-18T15:00
- [ ] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS. - [ ] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS.
- [ ] 23.6 Valider fase 1314 (traits + publisering): trait-validering, pakkevelger, Tera-templates, HTML-rendering, forside, slot-håndtering, redaksjonell flyt, planlagt publisering, A/B-testing. - [ ] 23.6 Valider fase 1314 (traits + publisering): trait-validering, pakkevelger, Tera-templates, HTML-rendering, forside, slot-håndtering, redaksjonell flyt, planlagt publisering, A/B-testing.
- [ ] 23.7 Valider fase 1516 (admin + lydmixer): systemvarsler, graceful shutdown, jobbkø-oversikt, ressursstyring, serverhelse, Web Audio mixer, delt kontroll, sound pads, EQ, stemmeeffekter. - [ ] 23.7 Valider fase 1516 (admin + lydmixer): systemvarsler, graceful shutdown, jobbkø-oversikt, ressursstyring, serverhelse, Web Audio mixer, delt kontroll, sound pads, EQ, stemmeeffekter.