feat: implement core knowledge graph UI components, extraction pipeline, and initial platform navigation pages
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save, Info } from 'lucide-react';
|
||||
import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save, Info, Layers, CheckSquare } from 'lucide-react';
|
||||
import Card from '../../components/ui/Card';
|
||||
import Tag from '../../components/ui/Tag';
|
||||
import Button from '../../components/ui/Button';
|
||||
@@ -7,9 +7,11 @@ import Input from '../../components/ui/Input';
|
||||
import { storage } from '../../lib/storage';
|
||||
import UploadZone from '../../components/admin/UploadZone';
|
||||
import KnowledgeGraph from '../../components/admin/KnowledgeGraph';
|
||||
import ContentManager from '../../components/admin/ContentManager';
|
||||
import TestManager from '../../components/admin/TestManager';
|
||||
|
||||
const Admin = () => {
|
||||
const [activeTab, setActiveTab] = useState('bronnen');
|
||||
const [activeTab, setActiveTab] = useState('sources');
|
||||
const [sources, setSources] = useState([]);
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [model, setModel] = useState('');
|
||||
@@ -21,10 +23,10 @@ const Admin = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'bronnen') {
|
||||
if (activeTab === 'sources') {
|
||||
loadSources();
|
||||
}
|
||||
if (activeTab === 'instellingen') {
|
||||
if (activeTab === 'settings') {
|
||||
setApiKey(storage.get('admin:anthropic_key', ''));
|
||||
setModel(storage.get('admin:model', 'claude-sonnet-4-20250514'));
|
||||
setUseSimulation(storage.get('admin:use_simulation', false));
|
||||
@@ -36,63 +38,53 @@ const Admin = () => {
|
||||
storage.set('admin:anthropic_key', apiKey.trim());
|
||||
storage.set('admin:model', model.trim());
|
||||
storage.set('admin:use_simulation', useSimulation);
|
||||
setSaveStatus('Opgeslagen!');
|
||||
setSaveStatus('Saved!');
|
||||
setTimeout(() => setSaveStatus(null), 3000);
|
||||
};
|
||||
|
||||
const navItems = [
|
||||
{ key: 'sources', icon: Database, label: 'Sources' },
|
||||
{ key: 'content', icon: Layers, label: 'Content' },
|
||||
{ key: 'tests', icon: CheckSquare, label: 'Quizzes' },
|
||||
{ key: 'graph', icon: Network, label: 'Graph' },
|
||||
{ key: 'team', icon: Users, label: 'Team' },
|
||||
{ key: 'settings', icon: Settings, label: 'Settings', bottom: true },
|
||||
];
|
||||
|
||||
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>
|
||||
<h2 className="hidden md:block text-teal font-bold text-lg mb-4 px-2">Knowledge Mgmt</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>
|
||||
{navItems.map(({ key, icon: Icon, label, bottom }) => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setActiveTab(key)}
|
||||
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 ${bottom ? 'mt-0 md:mt-auto' : ''} ${activeTab === key ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50 hover:text-fg'}`}
|
||||
>
|
||||
<Icon size={20} />
|
||||
<span className="text-sm mt-1 md:mt-0">{label}</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' && (
|
||||
|
||||
{/* ── Sources ─────────────────────────────── */}
|
||||
{activeTab === 'sources' && (
|
||||
<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>
|
||||
<h1 className="text-3xl text-teal mb-2">Source Material</h1>
|
||||
<p className="text-fg-muted mb-8">Upload files to feed the AI knowledge extraction pipeline.</p>
|
||||
|
||||
<UploadZone onUploadComplete={loadSources} />
|
||||
|
||||
<h3 className="text-xl mb-4 mt-12">Recente Bronnen</h3>
|
||||
<h3 className="text-xl mb-4 mt-12">Recent Sources</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>
|
||||
<div className="p-8 text-center text-fg-muted">No sources uploaded yet.</div>
|
||||
) : (
|
||||
sources.map((source) => (
|
||||
<div key={source.id} className="p-4 flex items-center justify-between hover:bg-bg-warm/30 transition-colors">
|
||||
@@ -112,9 +104,9 @@ const Admin = () => {
|
||||
</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>}
|
||||
{source.status === 'completed' && <Tag variant="success" className="flex items-center gap-1"><CheckCircle2 size={12}/> Completed</Tag>}
|
||||
{source.status === 'processing' && <Tag variant="accent" className="flex items-center gap-1"><Clock size={12}/> Processing</Tag>}
|
||||
{source.status === 'failed' && <Tag variant="dark" className="bg-red-100 text-red-800 flex items-center gap-1"><AlertCircle size={12}/> Failed</Tag>}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
@@ -124,34 +116,55 @@ const Admin = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'graaf' && (
|
||||
{/* ── Content Manager ──────────────────────── */}
|
||||
{activeTab === 'content' && (
|
||||
<div className="animate-in fade-in duration-300 max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl text-teal mb-2">Learning Content</h1>
|
||||
<p className="text-fg-muted mb-8">Manage, refine, or regenerate AI-generated learning modules for each knowledge topic.</p>
|
||||
<ContentManager />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Test Manager ─────────────────────────── */}
|
||||
{activeTab === 'tests' && (
|
||||
<div className="animate-in fade-in duration-300 max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl text-teal mb-2">Question Banks</h1>
|
||||
<p className="text-fg-muted mb-8">Pre-generate and review quiz questions for each topic to eliminate loading times for users.</p>
|
||||
<TestManager />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Knowledge Graph ──────────────────────── */}
|
||||
{activeTab === 'graph' && (
|
||||
<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>
|
||||
<h1 className="text-3xl text-teal mb-2">Knowledge Graph</h1>
|
||||
<p className="text-fg-muted mb-4">Visual map of all extracted Respellion knowledge.</p>
|
||||
<Card className="flex-1 p-0 border border-bg-warm overflow-hidden bg-paper min-h-[400px]">
|
||||
<KnowledgeGraph />
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'gebruikers' && (
|
||||
{/* ── Team ────────────────────────────────── */}
|
||||
{activeTab === 'team' && (
|
||||
<div className="animate-in fade-in duration-300">
|
||||
<h1 className="text-3xl text-teal mb-2">Gebruikersbeheer</h1>
|
||||
<h1 className="text-3xl text-teal mb-2">Team Management</h1>
|
||||
<Card className="border border-bg-warm">
|
||||
<p className="text-fg-muted">Hier kunnen medewerkers worden toegevoegd en beheerd.</p>
|
||||
<p className="text-fg-muted">Team members can be added and managed here.</p>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'instellingen' && (
|
||||
{/* ── Settings ────────────────────────────── */}
|
||||
{activeTab === 'settings' && (
|
||||
<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>
|
||||
<h1 className="text-3xl text-teal mb-2">Settings</h1>
|
||||
<p className="text-fg-muted mb-8">Manage API keys and application configuration.</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>
|
||||
<h3 className="text-lg font-medium mb-4">AI Configuration</h3>
|
||||
<Input
|
||||
label="Anthropic API Key"
|
||||
type="password"
|
||||
@@ -166,18 +179,18 @@ const Admin = () => {
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-fg-muted mt-1">Laat leeg voor de standaard. Controleer je Anthropic Console voor beschikbare modellen.</p>
|
||||
<p className="text-xs text-fg-muted mt-1">Leave blank for the default. Check your Anthropic Console for available models.</p>
|
||||
</div>
|
||||
<p className="text-xs text-fg-muted mt-2">
|
||||
Deze sleutel wordt lokaal in je browser opgeslagen (`localStorage`).
|
||||
Your API key is stored locally in your browser's <code>localStorage</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-bg-warm">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Simulatie Mode</h3>
|
||||
<p className="text-sm text-fg-muted">Gebruik gesimuleerde AI-antwoorden als de API niet werkt.</p>
|
||||
<h3 className="text-lg font-medium">Simulation Mode</h3>
|
||||
<p className="text-sm text-fg-muted">Use simulated AI responses when the API is unavailable.</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
@@ -192,16 +205,16 @@ const Admin = () => {
|
||||
{useSimulation && (
|
||||
<div className="p-3 bg-teal/5 border border-teal/20 rounded-[var(--r-sm)] flex gap-3 text-sm text-teal-800">
|
||||
<Info size={18} className="flex-shrink-0" />
|
||||
<p>Wanneer ingeschakeld, zal het platform doen alsof de AI teksten verwerkt zonder een echte API-aanroep te doen. Ideaal voor UI testen.</p>
|
||||
<p>When enabled, the platform will simulate AI processing without making real API calls. Ideal for UI testing.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 pt-4">
|
||||
<Button type="submit">
|
||||
<Save size={18} className="mr-2" /> Instellingen Opslaan
|
||||
<Save size={18} className="mr-2" /> Save Settings
|
||||
</Button>
|
||||
{saveStatus && <span className="text-teal font-medium animate-in fade-out duration-1000 fill-mode-forwards">{saveStatus}</span>}
|
||||
{saveStatus && <span className="text-teal font-medium">{saveStatus}</span>}
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user