import { useState, useEffect } from 'react'; import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save, Info, Layers, CheckSquare, CalendarDays, RefreshCw, AlertTriangle } 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 * as db from '../../lib/db'; import UploadZone from '../../components/admin/UploadZone'; import KnowledgeGraph from '../../components/admin/KnowledgeGraph'; import ContentManager from '../../components/admin/ContentManager'; import TestManager from '../../components/admin/TestManager'; import TeamManager from '../../components/admin/TeamManager'; import CurriculumManager from '../../components/admin/CurriculumManager'; import { Trash2 } from 'lucide-react'; const TIER_PLACEHOLDERS = { fast: 'claude-haiku-4-5-20251001', standard: 'claude-sonnet-4-6', reasoning: 'claude-opus-4-7', }; const Admin = () => { const [activeTab, setActiveTab] = useState('sources'); const [sources, setSources] = useState([]); const [modelFast, setModelFast] = useState(''); const [modelStandard, setModelStandard] = useState(''); const [modelReasoning, setModelReasoning] = useState(''); const [useSimulation, setUseSimulation] = useState(false); const [saveStatus, setSaveStatus] = useState(null); const [resetConfirmText, setResetConfirmText] = useState(''); const [resetIncludeProgress, setResetIncludeProgress] = useState(false); const [isResetting, setIsResetting] = useState(false); const [resetReport, setResetReport] = useState(null); const loadSources = async () => { const data = await db.getSources(); setSources(data); }; useEffect(() => { if (activeTab === 'sources') { loadSources(); } if (activeTab === 'settings') { const legacyStandard = storage.get('admin:model', ''); setModelFast(storage.get('admin:model:fast', '') || ''); setModelStandard(storage.get('admin:model:standard', '') || legacyStandard || ''); setModelReasoning(storage.get('admin:model:reasoning', '') || ''); setUseSimulation(storage.get('admin:use_simulation', false)); } }, [activeTab]); const saveSettings = async (e) => { e.preventDefault(); storage.set('admin:model:fast', modelFast.trim()); storage.set('admin:model:standard', modelStandard.trim()); storage.set('admin:model:reasoning', modelReasoning.trim()); storage.set('admin:use_simulation', useSimulation); setSaveStatus('Saved!'); setTimeout(() => setSaveStatus(null), 3000); }; const runSmokeReset = async () => { setIsResetting(true); setResetReport(null); try { const report = await db.resetForSmokeTest({ includeProgress: resetIncludeProgress, }); setResetReport(report); setResetConfirmText(''); } catch (err) { setResetReport([{ collection: '(unexpected)', deleted: 0, error: err?.message || String(err) }]); } finally { setIsResetting(false); } }; const handleDeleteSource = async (id) => { if (confirm('Are you sure you want to delete this source? This will not delete topics already extracted.')) { await db.deleteSource(id); await loadSources(); } }; const navItems = [ { key: 'sources', icon: Database, label: 'Sources' }, { key: 'content', icon: Layers, label: 'Theme Content' }, { key: 'tests', icon: CheckSquare, label: 'Topic Quizzes' }, { key: 'curriculum', icon: CalendarDays, label: 'Curriculum' }, { key: 'graph', icon: Network, label: 'Graph' }, { key: 'team', icon: Users, label: 'Team' }, { key: 'settings', icon: Settings, label: 'Settings', bottom: true }, ]; return (

Knowledge Mgmt

{navItems.map(({ key, icon: Icon, label, bottom }) => ( ))}
{activeTab === 'sources' && (

Source Material

Upload files to feed the AI knowledge extraction pipeline.

Recent Sources

{sources.length === 0 ? (
No sources uploaded yet.
) : ( sources.map((source) => (

{source.name}

{source.created ? new Date(source.created).toLocaleString() : '—'}

{source.status === 'completed' && Completed} {source.status === 'processing' && Processing} {source.status === 'failed' && Failed}
)) )}
)} {activeTab === 'content' && (

Theme Content

Weekly theme sessions generated by the AI for the active curriculum. Each session covers all topics in that week's theme.

)} {activeTab === 'tests' && (

Topic Question Banks

Pre-generate and review per-topic quiz questions, grouped by theme. These power both the weekly theme test and the on-demand Topic Test.

)} {activeTab === 'curriculum' && (

Curriculum

AI-generated 26-week learning cycle. Review and activate curriculum versions.

)} {activeTab === 'graph' && (

Knowledge Graph

Visual map of all extracted Respellion knowledge.

)} {activeTab === 'team' && (

Team Management

Review team members and manage their roles. Accounts are created automatically via Microsoft (Azure) sign-in.

)} {activeTab === 'settings' && (

Settings

Manage API keys and application configuration.

AI Configuration

Models are split into three tiers. Leave a field blank to use its default. Check your Anthropic Console for available model IDs.

setModelFast(e.target.value)} />

Default: {TIER_PLACEHOLDERS.fast}

setModelStandard(e.target.value)} />

Default: {TIER_PLACEHOLDERS.standard}

setModelReasoning(e.target.value)} />

Default: {TIER_PLACEHOLDERS.reasoning}

Note: Your Anthropic API key is securely managed on the server side via environment variables.

Simulation Mode

Use simulated AI responses when the API is unavailable.

{useSimulation && (

When enabled, the platform will simulate AI processing without making real API calls. Ideal for UI testing.

)}
{saveStatus && {saveStatus}}

Danger Zone — Reset for Smoke Test

Deletes all AI-generated content so the platform can be exercised from a clean slate. Team members, settings, and LLM telemetry are preserved.

Will be cleared:

  • Sources
  • Curriculum
  • Content (per-topic learning modules)
  • Quiz banks, quiz results, quiz cache
  • Topics & relations
setResetConfirmText(e.target.value)} placeholder="RESET" className="w-full border border-bg-warm rounded-[var(--r-sm)] px-3 py-2 text-sm bg-paper font-mono" disabled={isResetting} />
{resetReport && (

Reset complete

{resetReport.map((row) => (
{row.collection} {row.error ? ( error: {row.error} ) : ( {row.deleted} deleted )}
))}
)}
)}
); }; export default Admin;