feat: implement admin knowledge extraction system with document upload and AI pipeline integration

This commit is contained in:
RaymondVerhoef
2026-05-10 11:18:48 +02:00
parent f8d0c68f84
commit 260644b41a
7 changed files with 625 additions and 5 deletions

View File

@@ -1,3 +1,5 @@
import { storage } from './storage';
/**
* Anthropic API Service
* Handles communication with the /v1/messages endpoint.
@@ -16,7 +18,8 @@ export const anthropicApi = {
// In a real application, the API key should not be exposed to the client.
// For this prototype, we'll assume it's passed or available in the environment,
// or proxied via a secure endpoint if deployed.
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY || 'no-key-provided';
// Prioritize key from local storage (set via Admin Settings)
const apiKey = storage.get('admin:anthropic_key') || import.meta.env.VITE_ANTHROPIC_API_KEY || 'no-key-provided';
while (retries <= maxRetries) {
try {

View File

@@ -0,0 +1,112 @@
import { anthropicApi } from './api';
import { storage } from './storage';
const SYSTEM_PROMPT = `Je bent een AI-kennisextractor voor Respellion, een IT-bedrijf gericht op "radicale transparantie".
Je krijgt een brontekst. Jouw taak is om kernconcepten, rollen, processen en feiten te extraheren en deze als een gestructureerde JSON Kennisgraaf terug te geven.
Geef ALTIJD een valide JSON object terug in het volgende formaat:
{
"topics": [
{
"id": "unieke-slug",
"label": "Titel van onderwerp",
"type": "concept | role | process | fact",
"description": "Een beknopte, heldere uitleg van max 3 zinnen."
}
],
"relations": [
{
"source": "id-van-topic-1",
"target": "id-van-topic-2",
"type": "related_to | depends_on | part_of | executed_by"
}
]
}
Zorg dat je alleen JSON teruggeeft, geen markdown blokken of andere tekst.`;
/**
* Voert tekst door de Anthropic API en slaat de resulterende topics op.
* @param {string} textContent De te analyseren tekst.
* @param {string} sourceName Naam van de bron (voor tracking).
*/
export async function processSourceText(textContent, sourceName) {
// 1. Sla de bron eerst op als "processing"
const sourceId = `src_${Date.now()}`;
const newSource = {
id: sourceId,
name: sourceName,
status: 'processing',
date: new Date().toISOString()
};
const sources = storage.get('admin:sources', []);
storage.set('admin:sources', [newSource, ...sources]);
try {
// 2. Roep Anthropic API aan
const responseText = await anthropicApi.generateContent(SYSTEM_PROMPT, `Analyseer de volgende tekst:\n\n${textContent}`);
// 3. Parse JSON (veilig)
let extractedData;
try {
// Zoek naar JSON in de response (indien het toch in markdown was verpakt)
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : responseText;
extractedData = JSON.parse(jsonStr);
} catch (e) {
throw new Error('AI response was geen geldige JSON.');
}
// 4. Deduplicatie en opslag van Topics en Relaties
mergeKnowledgeGraph(extractedData);
// 5. Markeer bron als voltooid
updateSourceStatus(sourceId, 'completed');
return { success: true, data: extractedData };
} catch (error) {
// Markeer bron als mislukt
updateSourceStatus(sourceId, 'failed', error.message);
throw error;
}
}
function updateSourceStatus(sourceId, status, errorMsg = '') {
const sources = storage.get('admin:sources', []);
const updated = sources.map(s =>
s.id === sourceId ? { ...s, status, error: errorMsg } : s
);
storage.set('admin:sources', updated);
}
function mergeKnowledgeGraph(newData) {
const existingTopics = storage.get('kb:topics', []);
const existingRelations = storage.get('kb:relations', []);
// Simpele deduplicatie op ID
const topicsMap = new Map(existingTopics.map(t => [t.id, t]));
if (newData.topics && Array.isArray(newData.topics)) {
for (const t of newData.topics) {
if (!topicsMap.has(t.id)) {
topicsMap.set(t.id, t);
}
}
}
// Voeg relaties toe (we gaan er nu vanuit dat ze uniek genoeg zijn of dubbele negeren)
const newRelations = [...existingRelations];
if (newData.relations && Array.isArray(newData.relations)) {
for (const r of newData.relations) {
// Simpele check om exacte duplicaten te voorkomen
const isDup = newRelations.some(ex => ex.source === r.source && ex.target === r.target && ex.type === r.type);
if (!isDup) {
newRelations.push(r);
}
}
}
storage.set('kb:topics', Array.from(topicsMap.values()));
storage.set('kb:relations', newRelations);
}

View File

@@ -11,13 +11,13 @@ export const storage = {
* @param {string} key - e.g. 'kb:topics'
* @returns {any}
*/
get(key) {
get(key, defaultValue = null) {
try {
const item = window.localStorage.getItem(STORAGE_PREFIX + key);
return item ? JSON.parse(item) : null;
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error(`Error reading ${key} from storage:`, e);
return null;
return defaultValue;
}
},