/** * Client for maskinrommet intentions API. * Uses the Vite dev proxy (/api → api.sidelinja.org) in development. * In production, set VITE_API_URL to the maskinrommet URL. */ const BASE_URL = import.meta.env.VITE_API_URL ?? '/api'; export interface CreateNodeRequest { node_kind?: string; title?: string; content?: string; visibility?: string; metadata?: Record; /** Kontekst-node (kommunikasjonsnode). Gir automatisk belongs_to-edge. */ context_id?: string; } export interface CreateNodeResponse { node_id: string; /** Edge-ID for automatisk belongs_to-edge (kun ved context_id). */ belongs_to_edge_id?: string; } export interface CreateEdgeRequest { source_id: string; target_id: string; edge_type: string; metadata?: Record; system?: boolean; } export interface CreateEdgeResponse { edge_id: string; } async function post(accessToken: string, path: string, data: unknown): Promise { const res = await fetch(`${BASE_URL}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}` }, body: JSON.stringify(data) }); if (!res.ok) { const body = await res.text(); throw new Error(`${path} failed (${res.status}): ${body}`); } return res.json(); } export function createNode( accessToken: string, data: CreateNodeRequest ): Promise { return post(accessToken, '/intentions/create_node', data); } export function createEdge( accessToken: string, data: CreateEdgeRequest ): Promise { return post(accessToken, '/intentions/create_edge', data); } // ============================================================================= // Node-oppdatering // ============================================================================= export interface UpdateNodeRequest { node_id: string; node_kind?: string; title?: string; content?: string; visibility?: string; metadata?: Record; } export interface UpdateNodeResponse { node_id: string; } export function updateNode( accessToken: string, data: UpdateNodeRequest ): Promise { return post(accessToken, '/intentions/update_node', data); } // ============================================================================= // Edge-oppdatering // ============================================================================= export interface UpdateEdgeRequest { edge_id: string; edge_type?: string; metadata?: Record; } export interface UpdateEdgeResponse { edge_id: string; } export function updateEdge( accessToken: string, data: UpdateEdgeRequest ): Promise { return post(accessToken, '/intentions/update_edge', data); } // ============================================================================= // Edge-sletting (avpublisering m.m.) // ============================================================================= export interface DeleteEdgeRequest { edge_id: string; } export interface DeleteEdgeResponse { deleted: boolean; } export function deleteEdge( accessToken: string, data: DeleteEdgeRequest ): Promise { return post(accessToken, '/intentions/delete_edge', data); } // ============================================================================= // Board / Kanban // ============================================================================= export interface BoardCard { node_id: string; title: string | null; content: string | null; node_kind: string; metadata: Record; created_at: string; created_by: string | null; status: string | null; position: number; belongs_to_edge_id: string; status_edge_id: string | null; } export interface BoardResponse { board_id: string; board_title: string | null; columns: string[]; cards: BoardCard[]; } export async function fetchBoard(accessToken: string, boardId: string): Promise { const res = await fetch(`${BASE_URL}/query/board?board_id=${encodeURIComponent(boardId)}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`board failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Redaksjonell arbeidsflate (Editorial Board) // ============================================================================= export interface EditorialCard { node_id: string; title: string | null; content: string | null; node_kind: string; metadata: Record; created_at: string; created_by: string | null; author_name: string | null; status: string; submitted_to_edge_id: string; edge_metadata: Record; /** Kommunikasjonsnoder knyttet til artikkelen (redaksjonelle samtaler) */ discussion_ids: string[]; } export interface EditorialBoardResponse { collection_id: string; collection_title: string | null; columns: string[]; column_labels: Record; cards: EditorialCard[]; } export async function fetchEditorialBoard( accessToken: string, collectionId: string ): Promise { const res = await fetch( `${BASE_URL}/query/editorial_board?collection_id=${encodeURIComponent(collectionId)}`, { headers: { Authorization: `Bearer ${accessToken}` } } ); if (!res.ok) { const body = await res.text(); throw new Error(`editorial_board failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Kommunikasjon // ============================================================================= export interface CreateCommunicationRequest { title?: string; participants?: string[]; visibility?: string; metadata?: Record; /** Kontekst-node (f.eks. artikkel). Gir automatisk belongs_to-edge. */ context_id?: string; } export interface CreateCommunicationResponse { node_id: string; edge_ids: string[]; } export function createCommunication( accessToken: string, data: CreateCommunicationRequest ): Promise { return post(accessToken, '/intentions/create_communication', data); } export interface UploadMediaRequest { file: File; source_id?: string; visibility?: string; title?: string; } export interface UploadMediaResponse { media_node_id: string; cas_hash: string; size_bytes: number; already_existed: boolean; has_media_edge_id?: string; } export async function uploadMedia( accessToken: string, data: UploadMediaRequest ): Promise { const form = new FormData(); form.append('file', data.file); if (data.source_id) form.append('source_id', data.source_id); if (data.visibility) form.append('visibility', data.visibility); if (data.title) form.append('title', data.title); const res = await fetch(`${BASE_URL}/intentions/upload_media`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}` }, body: form }); if (!res.ok) { const body = await res.text(); throw new Error(`upload_media failed (${res.status}): ${body}`); } return res.json(); } /** Build the CAS URL for a given hash. */ export function casUrl(hash: string): string { return `${BASE_URL}/cas/${hash}`; } // ============================================================================= // Transkripsjons-segmenter // ============================================================================= export interface Segment { id: number; seq: number; start_ms: number; end_ms: number; content: string; edited: boolean; } export interface SegmentsResponse { segments: Segment[]; transcribed_at: string | null; } /** Hent transkripsjons-segmenter for en media-node. */ export async function fetchSegments( accessToken: string, nodeId: string ): Promise { const res = await fetch(`${BASE_URL}/query/segments?node_id=${encodeURIComponent(nodeId)}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`segments failed (${res.status}): ${body}`); } return res.json(); } /** Last ned SRT-fil for en media-node. Trigger filnedlasting i nettleseren. */ export async function downloadSrt(accessToken: string, nodeId: string): Promise { const res = await fetch( `${BASE_URL}/query/segments/srt?node_id=${encodeURIComponent(nodeId)}`, { headers: { Authorization: `Bearer ${accessToken}` } } ); if (!res.ok) { const body = await res.text(); throw new Error(`SRT-eksport feilet (${res.status}): ${body}`); } const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'transcription.srt'; a.click(); URL.revokeObjectURL(url); } /** Oppdater teksten i et transkripsjons-segment. */ export function updateSegment( accessToken: string, segmentId: number, content: string ): Promise<{ segment_id: number; edited: boolean }> { return post(accessToken, '/intentions/update_segment', { segment_id: segmentId, content }); } // ============================================================================= // Re-transkripsjon // ============================================================================= export interface TranscriptionVersion { transcribed_at: string; segment_count: number; edited_count: number; } export interface TranscriptionVersionsResponse { versions: TranscriptionVersion[]; } /** Hent alle transkripsjonsversjoner for en node. */ export async function fetchTranscriptionVersions( accessToken: string, nodeId: string ): Promise { const res = await fetch( `${BASE_URL}/query/transcription_versions?node_id=${encodeURIComponent(nodeId)}`, { headers: { Authorization: `Bearer ${accessToken}` } } ); if (!res.ok) { const body = await res.text(); throw new Error(`transcription_versions failed (${res.status}): ${body}`); } return res.json(); } /** Hent segmenter for en spesifikk transkripsjonsversjon. */ export async function fetchSegmentsVersion( accessToken: string, nodeId: string, transcribedAt: string ): Promise { const params = new URLSearchParams({ node_id: nodeId, transcribed_at: transcribedAt }); const res = await fetch(`${BASE_URL}/query/segments_version?${params}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`segments_version failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Graf / Kunnskapsgraf // ============================================================================= export interface GraphNode { id: string; node_kind: string; title: string | null; visibility: string; metadata: Record; created_at: string; } export interface GraphEdge { id: string; source_id: string; target_id: string; edge_type: string; metadata: Record; } export interface GraphResponse { nodes: GraphNode[]; edges: GraphEdge[]; } export interface FetchGraphParams { focusId?: string; depth?: number; edgeTypes?: string[]; nodeKinds?: string[]; } /** Hent graf-data for visualisering. */ export async function fetchGraph( accessToken: string, params: FetchGraphParams = {} ): Promise { const searchParams = new URLSearchParams(); if (params.focusId) searchParams.set('focus_id', params.focusId); if (params.depth) searchParams.set('depth', String(params.depth)); if (params.edgeTypes?.length) searchParams.set('edge_types', params.edgeTypes.join(',')); if (params.nodeKinds?.length) searchParams.set('node_kinds', params.nodeKinds.join(',')); const res = await fetch(`${BASE_URL}/query/graph?${searchParams}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`graph failed (${res.status}): ${body}`); } return res.json(); } /** Trigger re-transkripsjon for en media-node. */ export function retranscribe( accessToken: string, nodeId: string ): Promise<{ job_id: string }> { return post(accessToken, '/intentions/retranscribe', { node_id: nodeId }); } export interface SegmentChoice { seq: number; choice: 'new' | 'old'; } // ============================================================================= // Lydstudio // ============================================================================= export interface AudioInfo { duration_ms: number; sample_rate: number; channels: number; codec: string; format: string; bit_rate: number | null; } export interface LoudnessInfo { input_i: number; input_tp: number; input_lra: number; input_thresh: number; } export interface SilenceRegion { start_ms: number; end_ms: number; duration_ms: number; } export interface AnalyzeResult { loudness: LoudnessInfo; silence_regions: SilenceRegion[]; info: AudioInfo; } export interface EdlOperation { type: string; [key: string]: unknown; } export interface EdlDocument { source_hash: string; operations: EdlOperation[]; } /** Analyser lydfil: loudness, silence-regioner, metadata. */ export function audioAnalyze( accessToken: string, casHash: string, silenceThresholdDb?: number, silenceMinDurationMs?: number ): Promise { return post(accessToken, '/intentions/audio_analyze', { cas_hash: casHash, silence_threshold_db: silenceThresholdDb, silence_min_duration_ms: silenceMinDurationMs }); } /** Køer audio-prosessering med EDL. Returnerer job_id. */ export function audioProcess( accessToken: string, mediaNodeId: string, edl: EdlDocument, outputFormat?: string ): Promise<{ job_id: string }> { return post(accessToken, '/intentions/audio_process', { media_node_id: mediaNodeId, edl, output_format: outputFormat }); } // ============================================================================= // Publisering / Forside-slots // ============================================================================= export interface SetSlotRequest { edge_id: string; slot: string | null; slot_order?: number; pinned?: boolean; } export interface SetSlotResponse { edge_id: string; displaced: string[]; } /** Sett slot-metadata (hero/featured/strøm) på en belongs_to-edge. */ export function setSlot( accessToken: string, data: SetSlotRequest ): Promise { return post(accessToken, '/intentions/set_slot', data); } /** Hent metadata om lydfil (ffprobe). */ export async function audioInfo(accessToken: string, hash: string): Promise { const res = await fetch(`${BASE_URL}/query/audio_info?hash=${encodeURIComponent(hash)}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`audio_info failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Presentasjonselementer // ============================================================================= export interface PresentationElement { node_id: string; edge_id: string; element_type: string; title: string | null; content: string | null; node_kind: string; metadata: Record; edge_metadata: Record; created_at: string; } export interface PresentationElementsResponse { article_id: string; elements: PresentationElement[]; } /** Hent presentasjonselementer (tittel, undertittel, ingress, OG-bilde) for en artikkel. */ export async function fetchPresentationElements( accessToken: string, articleId: string ): Promise { const res = await fetch( `${BASE_URL}/query/presentation_elements?article_id=${encodeURIComponent(articleId)}`, { headers: { Authorization: `Bearer ${accessToken}` } } ); if (!res.ok) { const body = await res.text(); throw new Error(`presentation_elements failed (${res.status}): ${body}`); } return res.json(); } /** Slett en node (brukes for å fjerne presentasjonselementer). */ export function deleteNode( accessToken: string, nodeId: string ): Promise<{ deleted: boolean }> { return post(accessToken, '/intentions/delete_node', { node_id: nodeId }); } // ============================================================================= // Systemvarsler (oppgave 15.1) // ============================================================================= export interface CreateAnnouncementRequest { title: string; content: string; announcement_type: 'info' | 'warning' | 'critical'; scheduled_at?: string; expires_at?: string; blocks_new_sessions?: boolean; } export interface CreateAnnouncementResponse { node_id: string; } /** Opprett et systemvarsel (vises for alle klienter umiddelbart via WebSocket). */ export function createAnnouncement( accessToken: string, data: CreateAnnouncementRequest ): Promise { return post(accessToken, '/intentions/create_announcement', data); } export interface ExpireAnnouncementRequest { node_id: string; } export interface ExpireAnnouncementResponse { expired: boolean; } /** Fjern/utløp et systemvarsel. */ export function expireAnnouncement( accessToken: string, data: ExpireAnnouncementRequest ): Promise { return post(accessToken, '/intentions/expire_announcement', data); } /** Anvend brukerens per-segment-valg etter re-transkripsjon. */ export function resolveRetranscription( accessToken: string, nodeId: string, newVersion: string, oldVersion: string, choices: SegmentChoice[] ): Promise<{ resolved: boolean; kept_old: number; kept_new: number }> { return post(accessToken, '/intentions/resolve_retranscription', { node_id: nodeId, new_version: newVersion, old_version: oldVersion, choices }); } // ============================================================================= // Vedlikeholdsmodus (oppgave 15.2) // ============================================================================= export interface RunningJob { id: string; job_type: string; started_at: string | null; collection_node_id: string | null; } export interface MaintenanceStatus { initiated: boolean; active: boolean; scheduled_at: string | null; announcement_node_id: string | null; initiated_by: string | null; running_jobs: RunningJob[]; } /** Hent vedlikeholdsstatus (aktive sesjoner, kjørende jobber). */ export async function fetchMaintenanceStatus(accessToken: string): Promise { const res = await fetch(`${BASE_URL}/admin/maintenance_status`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`maintenance_status failed (${res.status}): ${body}`); } return res.json(); } export interface InitiateMaintenanceRequest { scheduled_at: string; } export interface InitiateMaintenanceResponse { announcement_node_id: string; scheduled_at: string; } /** Initier planlagt vedlikehold med nedtelling. */ export function initiateMaintenance( accessToken: string, data: InitiateMaintenanceRequest ): Promise { return post(accessToken, '/intentions/initiate_maintenance', data); } /** Avbryt planlagt vedlikehold. */ export function cancelMaintenance( accessToken: string ): Promise<{ cancelled: boolean }> { return post(accessToken, '/intentions/cancel_maintenance', {}); } // ============================================================================= // Jobbkø-oversikt (oppgave 15.3) // ============================================================================= export interface JobDetail { id: string; collection_node_id: string | null; job_type: string; payload: Record; status: string; priority: number; result: Record | null; error_msg: string | null; attempts: number; max_attempts: number; created_at: string; started_at: string | null; completed_at: string | null; scheduled_for: string; } export interface JobCountByStatus { status: string; count: number; } export interface ListJobsResponse { jobs: JobDetail[]; counts: JobCountByStatus[]; job_types: string[]; } export interface ListJobsParams { status?: string; type?: string; collection_id?: string; limit?: number; offset?: number; } /** Hent jobbliste med valgfrie filtre. */ export async function fetchJobs( accessToken: string, params: ListJobsParams = {} ): Promise { const searchParams = new URLSearchParams(); if (params.status) searchParams.set('status', params.status); if (params.type) searchParams.set('type', params.type); if (params.collection_id) searchParams.set('collection_id', params.collection_id); if (params.limit) searchParams.set('limit', String(params.limit)); if (params.offset) searchParams.set('offset', String(params.offset)); const qs = searchParams.toString(); const url = `${BASE_URL}/admin/jobs${qs ? `?${qs}` : ''}`; const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`jobs failed (${res.status}): ${body}`); } return res.json(); } /** Sett en feilet jobb tilbake til pending for nytt forsøk. */ export function retryJob( accessToken: string, jobId: string ): Promise<{ success: boolean }> { return post(accessToken, '/intentions/retry_job', { job_id: jobId }); } /** Avbryt en ventende jobb. */ export function cancelJob( accessToken: string, jobId: string ): Promise<{ success: boolean }> { return post(accessToken, '/intentions/cancel_job', { job_id: jobId }); } // ============================================================================= // AI Gateway-konfigurasjon (oppgave 15.4) // ============================================================================= export interface AiModelAlias { id: string; alias: string; description: string | null; is_active: boolean; created_at: string; } export interface AiModelProvider { id: string; alias_id: string; provider: string; model: string; api_key_env: string; priority: number; is_active: boolean; } export interface AiJobRouting { job_type: string; alias: string; description: string | null; } export interface AiUsageSummary { collection_node_id: string | null; collection_title: string | null; model_alias: string; job_type: string | null; total_prompt_tokens: number; total_completion_tokens: number; total_tokens: number; estimated_cost: number; call_count: number; } export interface ApiKeyStatus { env_var: string; is_set: boolean; } export interface AiOverviewResponse { aliases: AiModelAlias[]; providers: AiModelProvider[]; routing: AiJobRouting[]; usage: AiUsageSummary[]; api_key_status: ApiKeyStatus[]; } /** Hent AI Gateway-oversikt (aliaser, providers, ruting, forbruk). */ export async function fetchAiOverview(accessToken: string): Promise { const res = await fetch(`${BASE_URL}/admin/ai`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`ai overview failed (${res.status}): ${body}`); } return res.json(); } /** Hent AI-forbruksoversikt med filtre. */ export async function fetchAiUsage( accessToken: string, params: { days?: number; collection_id?: string } = {} ): Promise { const sp = new URLSearchParams(); if (params.days) sp.set('days', String(params.days)); if (params.collection_id) sp.set('collection_id', params.collection_id); const qs = sp.toString(); const res = await fetch(`${BASE_URL}/admin/ai/usage${qs ? `?${qs}` : ''}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`ai usage failed (${res.status}): ${body}`); } return res.json(); } /** Oppdater modellalias (beskrivelse, aktiv-status). */ export function updateAiAlias( accessToken: string, data: { id: string; description: string | null; is_active: boolean } ): Promise<{ success: boolean }> { return post(accessToken, '/admin/ai/update_alias', data); } /** Opprett nytt modellalias. */ export function createAiAlias( accessToken: string, data: { alias: string; description: string | null } ): Promise<{ id: string; success: boolean }> { return post(accessToken, '/admin/ai/create_alias', data); } /** Oppdater provider (prioritet, aktiv-status). */ export function updateAiProvider( accessToken: string, data: { id: string; priority?: number; is_active?: boolean } ): Promise<{ success: boolean }> { return post(accessToken, '/admin/ai/update_provider', data); } /** Legg til ny provider på et alias. */ export function createAiProvider( accessToken: string, data: { alias_id: string; provider: string; model: string; api_key_env: string; priority: number } ): Promise<{ id: string; success: boolean }> { return post(accessToken, '/admin/ai/create_provider', data); } /** Slett en provider. */ export function deleteAiProvider( accessToken: string, id: string ): Promise<{ success: boolean }> { return post(accessToken, '/admin/ai/delete_provider', { id }); } /** Oppdater eller opprett rutingregel (jobbtype → alias). */ export function updateAiRouting( accessToken: string, data: { job_type: string; alias: string; description: string | null } ): Promise<{ success: boolean }> { return post(accessToken, '/admin/ai/update_routing', data); } /** Slett en rutingregel. */ export function deleteAiRouting( accessToken: string, jobType: string ): Promise<{ success: boolean }> { return post(accessToken, '/admin/ai/delete_routing', { job_type: jobType }); } // ============================================================================= // Serverhelse-dashboard (oppgave 15.6) // ============================================================================= export interface ServiceStatus { name: string; status: 'up' | 'down' | 'degraded'; latency_ms: number | null; details: string | null; } export interface SystemMetrics { cpu_usage_percent: number; cpu_cores: number; load_avg: [number, number, number]; memory_total_bytes: number; memory_used_bytes: number; memory_available_bytes: number; memory_usage_percent: number; disk: { mount_point: string; total_bytes: number; used_bytes: number; available_bytes: number; usage_percent: number; alert_level: string | null; }; uptime_seconds: number; } export interface BackupInfo { backup_type: string; last_success: string | null; path: string | null; status: 'ok' | 'missing' | 'stale'; } export interface PgStats { active_connections: number; max_connections: number; database_size_bytes: number; active_queries: number; } export interface HealthDashboard { services: ServiceStatus[]; metrics: SystemMetrics; backups: BackupInfo[]; pg_stats: PgStats; } export interface LogEntry { timestamp: string; service: string; level: string; message: string; } export interface LogsResponse { entries: LogEntry[]; } /** Hent komplett serverhelse-dashboard. */ export async function fetchHealthDashboard(accessToken: string): Promise { const res = await fetch(`${BASE_URL}/admin/health`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`health dashboard failed (${res.status}): ${body}`); } return res.json(); } /** Hent logger for en tjeneste (eller alle). */ export async function fetchHealthLogs( accessToken: string, params: { service?: string; lines?: number } = {} ): Promise { const qs = new URLSearchParams(); if (params.service) qs.set('service', params.service); if (params.lines) qs.set('lines', String(params.lines)); const query = qs.toString(); const res = await fetch(`${BASE_URL}/admin/health/logs${query ? `?${query}` : ''}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`health logs failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Forbruksoversikt (oppgave 15.8) // ============================================================================= export interface CollectionUsageSummary { collection_id: string | null; collection_title: string | null; resource_type: string; event_count: number; total_value: number; secondary_value: number; } export interface AiDrillDown { collection_id: string | null; collection_title: string | null; job_type: string | null; model_level: string | null; tokens_in: number; tokens_out: number; event_count: number; } export interface DailyUsage { day: string; resource_type: string; event_count: number; total_value: number; } export interface UsageOverviewResponse { by_collection: CollectionUsageSummary[]; ai_drilldown: AiDrillDown[]; daily: DailyUsage[]; } /** Hent aggregert forbruksoversikt for admin. */ export async function fetchUsageOverview( accessToken: string, params: { days?: number; collection_id?: string } = {} ): Promise { const sp = new URLSearchParams(); if (params.days) sp.set('days', String(params.days)); if (params.collection_id) sp.set('collection_id', params.collection_id); const qs = sp.toString(); const res = await fetch(`${BASE_URL}/admin/usage${qs ? `?${qs}` : ''}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`usage overview failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================ // Brukersynlig forbruk (oppgave 15.9) // ============================================================================ export interface ResourceTypeSummary { resource_type: string; event_count: number; total_value: number; secondary_value: number; } export interface UserDailyUsage { day: string; resource_type: string; event_count: number; total_value: number; } export interface GraphStats { nodes_created: number; edges_created: number; } export interface UserUsageResponse { by_type: ResourceTypeSummary[]; daily: UserDailyUsage[]; graph: GraphStats; } export interface NodeUsageResponse { node_id: string; node_title: string | null; by_type: ResourceTypeSummary[]; daily: UserDailyUsage[]; } /** Hent innlogget brukers eget ressursforbruk. */ export async function fetchMyUsage( accessToken: string, params: { days?: number } = {} ): Promise { const sp = new URLSearchParams(); if (params.days) sp.set('days', String(params.days)); const qs = sp.toString(); const res = await fetch(`${BASE_URL}/my/usage${qs ? `?${qs}` : ''}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`my usage failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // LiveKit / Kommunikasjonsrom (oppgave 16.1) // ============================================================================= export interface JoinCommunicationRequest { communication_id: string; role?: 'publisher' | 'subscriber'; } export interface RoomParticipantInfo { user_id: string; display_name: string; role: string; } export interface JoinCommunicationResponse { livekit_room_name: string; livekit_token: string; livekit_url: string; identity: string; participants: RoomParticipantInfo[]; } export interface LeaveCommunicationResponse { status: string; } /** Bli med i et LiveKit-rom for en kommunikasjonsnode. */ export function joinCommunication( accessToken: string, data: JoinCommunicationRequest ): Promise { return post(accessToken, '/intentions/join_communication', data); } /** Forlat et LiveKit-rom. */ export function leaveCommunication( accessToken: string, communicationId: string ): Promise { return post(accessToken, '/intentions/leave_communication', { communication_id: communicationId }); } // ============================================================================= // AI-prosessering // ============================================================================= export interface AiProcessRequest { source_node_id: string; ai_preset_id: string; direction: 'node_to_tool' | 'tool_to_node'; } export interface AiProcessResponse { job_id: string; } export function aiProcess( accessToken: string, data: AiProcessRequest ): Promise { return post(accessToken, '/intentions/ai_process', data); } // ============================================================================= // Egendefinerte AI-presets (oppgave 18.6) // ============================================================================= export interface CreateAiPresetRequest { title: string; prompt: string; default_direction: 'node_to_tool' | 'tool_to_node' | 'both'; icon: string; color: string; share_with_collection_id?: string; } export interface CreateAiPresetResponse { node_id: string; shared_edge_id?: string; } /** Opprett en egendefinert AI-preset (custom, model_profile=flash). */ export function createAiPreset( accessToken: string, data: CreateAiPresetRequest ): Promise { return post(accessToken, '/intentions/create_ai_preset', data); } // ============================================================================= // Personlig arbeidsflate (oppgave 19.6) // ============================================================================= export interface WorkspaceResponse { node_id: string; title: string; metadata: Record; created: boolean; } /** Hent (eller opprett) brukerens personlige workspace. */ export async function fetchMyWorkspace(accessToken: string): Promise { const res = await fetch(`${BASE_URL}/my/workspace`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`workspace failed (${res.status}): ${body}`); } return res.json(); } /** Hent ressursforbruk for en spesifikk node (kun eier). */ export async function fetchNodeUsage( accessToken: string, nodeId: string, params: { days?: number } = {} ): Promise { const sp = new URLSearchParams(); sp.set('node_id', nodeId); if (params.days) sp.set('days', String(params.days)); const res = await fetch(`${BASE_URL}/query/node_usage?${sp.toString()}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`node usage failed (${res.status}): ${body}`); } return res.json(); } // ============================================================================= // Mixer-kanaler // ============================================================================= export async function createMixerChannel( accessToken: string, roomId: string, targetUserId: string, ): Promise { await post(accessToken, '/intentions/create_mixer_channel', { room_id: roomId, target_user_id: targetUserId, }); } export async function setMixerGain( accessToken: string, roomId: string, targetUserId: string, gain: number, ): Promise { await post(accessToken, '/intentions/set_gain', { room_id: roomId, target_user_id: targetUserId, gain, }); } export async function setMixerMute( accessToken: string, roomId: string, targetUserId: string, isMuted: boolean, ): Promise { await post(accessToken, '/intentions/set_mute', { room_id: roomId, target_user_id: targetUserId, is_muted: isMuted, }); } export async function toggleMixerEffect( accessToken: string, roomId: string, targetUserId: string, effectName: string, ): Promise { await post(accessToken, '/intentions/toggle_effect', { room_id: roomId, target_user_id: targetUserId, effect_name: effectName, }); } // ============================================================================= // Orkestrering (oppgave 24.6) // ============================================================================= export interface CompileScriptResponse { diagnostics: Array<{ line: number; severity: 'Ok' | 'Error'; message: string; suggestion: string | null; raw_input: string; compiled_output: string | null; }>; compiled: { steps: Array<{ step_number: number; binary: string; args: string[]; }>; global_fallback: { binary: string; args: string[]; } | null; technical: string; } | null; } /** Kompiler et orkestreringsscript og få diagnostikk + kompilert resultat. */ export function compileScript( accessToken: string, script: string ): Promise { return post(accessToken, '/intentions/compile_script', { script }); } export interface TestOrchestrationResponse { job_id: string; } /** Trigger en manuell testkjøring av en orkestrering. */ export function testOrchestration( accessToken: string, orchestrationId: string ): Promise { return post(accessToken, '/intentions/test_orchestration', { orchestration_id: orchestrationId }); } export interface OrchestrationLogEntry { id: string; job_id: string | null; step_number: number; tool_binary: string; args: unknown[]; is_fallback: boolean; status: string; exit_code: number | null; error_msg: string | null; duration_ms: number | null; created_at: string; } export interface OrchestrationLogResponse { entries: OrchestrationLogEntry[]; } /** Hent kjørehistorikk for en orkestrering. */ export async function fetchOrchestrationLog( accessToken: string, orchestrationId: string, limit?: number ): Promise { const sp = new URLSearchParams({ orchestration_id: orchestrationId }); if (limit) sp.set('limit', String(limit)); const res = await fetch(`${BASE_URL}/query/orchestration_log?${sp}`, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!res.ok) { const body = await res.text(); throw new Error(`orchestration_log failed (${res.status}): ${body}`); } return res.json(); } export async function setMixerRole( accessToken: string, roomId: string, targetUserId: string, role: string, ): Promise { await post(accessToken, '/intentions/set_mixer_role', { room_id: roomId, target_user_id: targetUserId, role, }); } // ========================================================================= // AI-assistert script-generering (oppgave 24.7) // ========================================================================= export interface AiSuggestScriptRequest { description: string; trigger_event?: string; trigger_conditions?: Record; eventually?: boolean; collection_id?: string; } export interface AiSuggestScriptResponse { status: string; script?: string; compile_result?: CompileScriptResponse; work_item_id?: string; message?: string; } /** AI-assistert generering av orkestreringsscript fra fritekst-beskrivelse. */ export function aiSuggestScript( accessToken: string, req: AiSuggestScriptRequest ): Promise { return post(accessToken, '/intentions/ai_suggest_script', req); }