Files
learning-platform/src/lib/api.js
RaymondVerhoef 06eb974825
All checks were successful
On Push to Main / test (push) Successful in 30s
On Push to Main / publish (push) Successful in 58s
On Push to Main / deploy-dev (push) Successful in 1m35s
feat: add Anthropic API client and knowledge graph extraction pipeline
2026-05-17 18:20:29 +02:00

136 lines
4.7 KiB
JavaScript

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" }
]
});
}