import { storage } from './storage'; /** * Anthropic API Service * Handles communication with the /v1/messages endpoint via Nginx proxy. */ const DEFAULT_MODEL = 'claude-sonnet-4-20250514'; export const anthropicApi = { async generateContent(systemPrompt, userMessage, maxRetries = 1) { // Check if simulation mode is on const useSimulation = storage.get('admin:use_simulation') === true; if (useSimulation) { console.log('[API] Simulation mode active. Mock data will be returned.'); return await simulateResponse(); } // The API key is now securely injected by the Caddy reverse proxy via environment variables. // Model is configurable from Admin > Settings, defaults to the original spec model const model = storage.get('admin:model') || DEFAULT_MODEL; console.log(`[API] Calling with model: ${model}`); let retries = 0; while (retries <= maxRetries) { try { const response = await fetch('/api/anthropic/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model: model, max_tokens: 8192, temperature: 0, system: systemPrompt, messages: [{ role: 'user', content: userMessage }] }) }); if (!response.ok) { const errData = await response.json().catch(() => ({})); console.error('[API] Error response:', errData); throw new Error(`API Error: ${response.status} ${response.statusText} - ${JSON.stringify(errData)}`); } // Detect auth portal session expiry: the portal returns HTML instead of JSON const contentType = response.headers.get('content-type') || ''; if (!contentType.includes('application/json')) { throw new Error('Your session has expired. Please refresh the page and log in again.'); } const data = await response.json(); return data.content[0].text; } catch (error) { console.error('API call failed:', error); retries++; if (retries > maxRetries) throw error; await new Promise(r => setTimeout(r, 1000)); } } } }; /** * Multi-turn chat with optional tool use. * Returns the raw Anthropic response so callers can read both `text` and * `tool_use` content blocks. * * @param {string} systemPrompt * @param {Array<{role: 'user'|'assistant', content: string}>} messages * @param {{tools?: Array}} opts * @returns {Promise<{content: Array, stop_reason: string}>} */ anthropicApi.chat = async function chat(systemPrompt, messages, opts = {}) { const useSimulation = storage.get('admin:use_simulation') === true; if (useSimulation) { await new Promise(r => setTimeout(r, 600)); return { content: [{ type: 'text', text: 'Simulatiemodus staat aan — vraag een beheerder om Simulation Mode uit te zetten in Admin → Settings om met R42 te chatten.', }], stop_reason: 'end_turn', }; } const model = storage.get('admin:model') || DEFAULT_MODEL; const body = { model, max_tokens: 1024, system: systemPrompt, messages, }; if (opts.tools && opts.tools.length) body.tools = opts.tools; const response = await fetch('/api/anthropic/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', }, body: JSON.stringify(body), }); if (!response.ok) { const errData = await response.json().catch(() => ({})); throw new Error(`API Error: ${response.status} ${response.statusText} - ${JSON.stringify(errData)}`); } const contentType = response.headers.get('content-type') || ''; if (!contentType.includes('application/json')) { throw new Error('Your session has expired. Please refresh the page and log in again.'); } return await response.json(); }; async function simulateResponse() { await new Promise(r => setTimeout(r, 2000)); return JSON.stringify({ topics: [ { id: "radicale-transparantie", label: "Radicale Transparantie", type: "concept", description: "De kernwaarde van Respellion waarbij alle informatie publiek toegankelijk is." }, { id: "kennisbeheer", label: "Kennisbeheer", type: "process", description: "Het proces van het vastleggen en ontsluiten van organisatiekennis." }, { id: "wekelijkse-sessie", label: "Wekelijkse Leersessie", type: "process", description: "Elke week leren medewerkers via AI-gegenereerde vragen en quizzen." } ], relations: [ { source: "kennisbeheer", target: "radicale-transparantie", type: "depends_on" }, { source: "wekelijkse-sessie", target: "kennisbeheer", type: "part_of" } ] }); }