/** * AI Intel API client — frontend functions for interacting with * the /api/ai/* endpoints. */ import { API_BASE } from '@/lib/api'; import type { AIIntelPin, AIIntelLayer, AIIntelStatus, AIIntelGeoJSON, SatelliteScene, NewsNearResult, PinCategory, EntityAttachment, AIIntelPinComment, } from '@/types/aiIntel'; const AI_API = `${API_BASE}/api/ai`; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- async function aiGet(path: string, params?: Record): Promise { const url = new URL(`${AI_API}${path}`, window.location.origin); if (params) { Object.entries(params).forEach(([k, v]) => { if (v !== '' && v !== undefined) url.searchParams.set(k, String(v)); }); } const resp = await fetch(url.toString()); if (!resp.ok) throw new Error(`AI Intel API error: ${resp.status}`); return resp.json(); } async function aiPost(path: string, body: unknown): Promise { const resp = await fetch(`${AI_API}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!resp.ok) throw new Error(`AI Intel API error: ${resp.status}`); return resp.json(); } async function aiPatch(path: string, body: unknown): Promise { const resp = await fetch(`${AI_API}${path}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!resp.ok) throw new Error(`AI Intel API error: ${resp.status}`); return resp.json(); } async function aiDelete(path: string, params?: Record): Promise { const url = new URL(`${AI_API}${path}`, window.location.origin); if (params) { Object.entries(params).forEach(([k, v]) => { if (v) url.searchParams.set(k, v); }); } const resp = await fetch(url.toString(), { method: 'DELETE' }); if (!resp.ok) throw new Error(`AI Intel API error: ${resp.status}`); return resp.json(); } // --------------------------------------------------------------------------- // Status // --------------------------------------------------------------------------- export async function fetchAIIntelStatus(): Promise { return aiGet('/status'); } // --------------------------------------------------------------------------- // Layers // --------------------------------------------------------------------------- export async function fetchLayers(): Promise<{ ok: boolean; count: number; layers: AIIntelLayer[] }> { return aiGet('/layers'); } export async function createLayer(layer: { name: string; description?: string; source?: string; color?: string; feed_url?: string; feed_interval?: number; }): Promise<{ ok: boolean; layer: AIIntelLayer }> { return aiPost('/layers', layer); } export async function updateLayer( layerId: string, updates: Partial>, ): Promise<{ ok: boolean; layer: AIIntelLayer }> { return aiPatch(`/layers/${layerId}`, updates); } export async function deleteLayer( layerId: string, ): Promise<{ ok: boolean; layer_id: string; pins_removed: number }> { return aiDelete(`/layers/${layerId}`); } export async function refreshLayerFeed( layerId: string, ): Promise<{ ok: boolean; layer: AIIntelLayer }> { return aiPost(`/layers/${layerId}/refresh`, {}); } // --------------------------------------------------------------------------- // Pins // --------------------------------------------------------------------------- export async function fetchAIIntelPins( category?: string, source?: string, layer_id?: string, limit?: number, ): Promise<{ ok: boolean; count: number; pins: AIIntelPin[] }> { return aiGet('/pins', { ...(category ? { category } : {}), ...(source ? { source } : {}), ...(layer_id ? { layer_id } : {}), ...(limit ? { limit } : {}), }); } export async function fetchAIIntelGeoJSON(layer_id?: string): Promise { return aiGet('/pins/geojson', layer_id ? { layer_id } : {}); } export async function createAIIntelPin(pin: { lat: number; lng: number; label: string; category?: PinCategory; layer_id?: string; color?: string; description?: string; source?: string; entity_attachment?: EntityAttachment; }): Promise<{ ok: boolean; pin: AIIntelPin }> { return aiPost('/pins', pin); } export async function createAIIntelPinsBatch( pins: Array<{ lat: number; lng: number; label: string; category?: PinCategory; description?: string; layer_id?: string; entity_attachment?: EntityAttachment; }>, layer_id?: string, ): Promise<{ ok: boolean; created: number; pins: AIIntelPin[] }> { return aiPost('/pins/batch', { pins, layer_id: layer_id || '' }); } export async function deleteAIIntelPin( pinId: string, ): Promise<{ ok: boolean; deleted: string }> { return aiDelete(`/pins/${pinId}`); } export async function fetchAIIntelPin( pinId: string, ): Promise<{ ok: boolean; pin: AIIntelPin }> { return aiGet(`/pins/${pinId}`); } export async function updateAIIntelPin( pinId: string, updates: Partial>, ): Promise<{ ok: boolean; pin: AIIntelPin }> { return aiPatch(`/pins/${pinId}`, updates); } export async function addAIIntelPinComment( pinId: string, comment: { text: string; author?: 'user' | 'agent' | 'openclaw'; author_label?: string; reply_to?: string; }, ): Promise<{ ok: boolean; pin: AIIntelPin }> { return aiPost(`/pins/${pinId}/comments`, comment); } export async function deleteAIIntelPinComment( pinId: string, commentId: string, ): Promise<{ ok: boolean; deleted: string }> { return aiDelete(`/pins/${pinId}/comments/${commentId}`); } // Re-export for convenience (some consumers may want the type). export type { AIIntelPinComment }; export async function clearAIIntelPins( category?: string, source?: string, ): Promise<{ ok: boolean; removed: number }> { return aiDelete('/pins', { ...(category ? { category } : {}), ...(source ? { source } : {}), }); } // --------------------------------------------------------------------------- // Satellite Imagery // --------------------------------------------------------------------------- export async function fetchSatelliteImages( lat: number, lng: number, count: number = 3, ): Promise<{ ok: boolean; lat: number; lng: number; scenes: SatelliteScene[]; count: number; source: string; }> { return aiGet('/satellite-images', { lat, lng, count }); } // --------------------------------------------------------------------------- // News Near // --------------------------------------------------------------------------- export async function fetchNewsNear( lat: number, lng: number, radius: number = 500, ): Promise { return aiGet('/news-near', { lat, lng, radius }); } // --------------------------------------------------------------------------- // Data Injection // --------------------------------------------------------------------------- export async function injectData( layer: string, items: Record[], mode: 'append' | 'replace' = 'append', ): Promise<{ ok: boolean; layer: string; injected: number; total: number }> { return aiPost('/inject', { layer, items, mode }); } export async function clearInjectedData( layer?: string, ): Promise<{ ok: boolean; removed: number; layer: string }> { return aiDelete('/inject', layer ? { layer } : {}); }