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

176
src/pages/Admin/index.jsx Normal file
View File

@@ -0,0 +1,176 @@
import React, { useState, useEffect } from 'react';
import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save } from 'lucide-react';
import Card from '../../components/ui/Card';
import Tag from '../../components/ui/Tag';
import Button from '../../components/ui/Button';
import Input from '../../components/ui/Input';
import { storage } from '../../lib/storage';
import UploadZone from '../../components/admin/UploadZone';
import KnowledgeGraph from '../../components/admin/KnowledgeGraph';
const Admin = () => {
const [activeTab, setActiveTab] = useState('bronnen');
const [sources, setSources] = useState([]);
const [apiKey, setApiKey] = useState('');
const [saveStatus, setSaveStatus] = useState(null);
const loadSources = () => {
setSources(storage.get('admin:sources', []));
};
useEffect(() => {
if (activeTab === 'bronnen') {
loadSources();
}
if (activeTab === 'instellingen') {
setApiKey(storage.get('admin:anthropic_key', ''));
}
}, [activeTab]);
const saveSettings = (e) => {
e.preventDefault();
storage.set('admin:anthropic_key', apiKey);
setSaveStatus('Opgeslagen!');
setTimeout(() => setSaveStatus(null), 3000);
};
return (
<div className="flex flex-col md:flex-row h-[calc(100vh-64px)] overflow-hidden">
{/* Admin Sidebar */}
<div className="w-full md:w-64 bg-paper border-r border-bg-warm flex-shrink-0 flex flex-row md:flex-col p-4 gap-2 overflow-x-auto md:overflow-y-auto">
<h2 className="hidden md:block text-teal font-bold text-lg mb-4 px-2">Kennisbeheer</h2>
<button
onClick={() => setActiveTab('bronnen')}
className={`flex flex-col md:flex-row items-center gap-3 p-3 rounded-[var(--r-sm)] transition-colors min-w-[80px] md:min-w-0 ${activeTab === 'bronnen' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50 hover:text-fg'}`}
>
<Database size={20} />
<span className="text-sm mt-1 md:mt-0">Bronnen</span>
</button>
<button
onClick={() => setActiveTab('graaf')}
className={`flex flex-col md:flex-row items-center gap-3 p-3 rounded-[var(--r-sm)] transition-colors min-w-[80px] md:min-w-0 ${activeTab === 'graaf' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50 hover:text-fg'}`}
>
<Network size={20} />
<span className="text-sm mt-1 md:mt-0">Graaf</span>
</button>
<button
onClick={() => setActiveTab('gebruikers')}
className={`flex flex-col md:flex-row items-center gap-3 p-3 rounded-[var(--r-sm)] transition-colors min-w-[80px] md:min-w-0 ${activeTab === 'gebruikers' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50 hover:text-fg'}`}
>
<Users size={20} />
<span className="text-sm mt-1 md:mt-0">Team</span>
</button>
<button
onClick={() => setActiveTab('instellingen')}
className={`flex flex-col md:flex-row items-center gap-3 p-3 rounded-[var(--r-sm)] transition-colors min-w-[80px] md:min-w-0 mt-0 md:mt-auto ${activeTab === 'instellingen' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50 hover:text-fg'}`}
>
<Settings size={20} />
<span className="text-sm mt-1 md:mt-0">Settings</span>
</button>
</div>
{/* Admin Main Content */}
<div className="flex-1 overflow-y-auto p-4 md:p-8 bg-bg pb-24 md:pb-8">
{activeTab === 'bronnen' && (
<div className="animate-in fade-in duration-300 max-w-4xl mx-auto">
<h1 className="text-3xl text-teal mb-2">Bronmateriaal</h1>
<p className="text-fg-muted mb-8">Upload bestanden of geef URL's op voor de AI-kennisextractie.</p>
<UploadZone onUploadComplete={loadSources} />
<h3 className="text-xl mb-4 mt-12">Recente Bronnen</h3>
<Card className="p-0 border border-bg-warm overflow-hidden">
<div className="divide-y divide-bg-warm">
{sources.length === 0 ? (
<div className="p-8 text-center text-fg-muted">Geen bronnen geüpload.</div>
) : (
sources.map((source) => (
<div key={source.id} className="p-4 flex items-center justify-between hover:bg-bg-warm/30 transition-colors">
<div className="flex items-center gap-4">
<div className={`p-2 rounded-full ${
source.status === 'completed' ? 'bg-teal-50 text-teal-600' :
source.status === 'processing' ? 'bg-purple-50 text-purple-600 animate-pulse' :
'bg-red-50 text-red-600'
}`}>
<FileText size={20} />
</div>
<div>
<p className="font-medium">{source.name}</p>
<p className="text-xs text-fg-muted flex items-center gap-1 mt-1">
<Clock size={12} /> {new Date(source.date).toLocaleString()}
</p>
</div>
</div>
<div>
{source.status === 'completed' && <Tag variant="success" className="flex items-center gap-1"><CheckCircle2 size={12}/> Voltooid</Tag>}
{source.status === 'processing' && <Tag variant="accent" className="flex items-center gap-1"><Clock size={12}/> Bezig</Tag>}
{source.status === 'failed' && <Tag variant="dark" className="bg-red-100 text-red-800 flex items-center gap-1"><AlertCircle size={12}/> Mislukt</Tag>}
</div>
</div>
))
)}
</div>
</Card>
</div>
)}
{activeTab === 'graaf' && (
<div className="animate-in fade-in duration-300 h-full flex flex-col">
<h1 className="text-3xl text-teal mb-2">Kennisgraaf</h1>
<p className="text-fg-muted mb-4">Visualisatie van de opgebouwde Respellion kennis.</p>
<Card className="flex-1 p-0 border border-bg-warm overflow-hidden bg-paper min-h-[400px]">
<KnowledgeGraph />
</Card>
</div>
)}
{activeTab === 'gebruikers' && (
<div className="animate-in fade-in duration-300">
<h1 className="text-3xl text-teal mb-2">Gebruikersbeheer</h1>
<Card className="border border-bg-warm">
<p className="text-fg-muted">Hier kunnen medewerkers worden toegevoegd en beheerd.</p>
</Card>
</div>
)}
{activeTab === 'instellingen' && (
<div className="animate-in fade-in duration-300 max-w-2xl">
<h1 className="text-3xl text-teal mb-2">Instellingen</h1>
<p className="text-fg-muted mb-8">Beheer applicatie-instellingen en API-sleutels.</p>
<Card className="border border-bg-warm">
<form onSubmit={saveSettings} className="space-y-6">
<div>
<h3 className="text-lg font-medium mb-4">AI Configuratie</h3>
<Input
label="Anthropic API Key"
type="password"
placeholder="sk-ant-..."
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
<p className="text-xs text-fg-muted mt-2">
Deze sleutel wordt lokaal in je browser opgeslagen (`localStorage`) en wordt gebruikt voor alle AI-interacties.
</p>
</div>
<div className="flex items-center gap-4">
<Button type="submit">
<Save size={18} className="mr-2" /> Instellingen Opslaan
</Button>
{saveStatus && <span className="text-teal font-medium animate-in fade-out duration-1000 fill-mode-forwards">{saveStatus}</span>}
</div>
</form>
</Card>
</div>
)}
</div>
</div>
);
};
export default Admin;