136 lines
4.7 KiB
JavaScript
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" }
|
|
]
|
|
});
|
|
}
|