feat: implement admin knowledge extraction system with document upload and AI pipeline integration
This commit is contained in:
@@ -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 {
|
||||
|
||||
112
src/lib/extractionPipeline.js
Normal file
112
src/lib/extractionPipeline.js
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user