Add Cortex Portal — web visualization for knowledge graph

Express API server wrapping existing store/graph core with REST endpoints
for nodes, edges, graph, and search. React + Vite portal with React Flow
for interactive graph visualization, Tailwind CSS styling, and full CRUD UI
(sidebar, node panel, add/link modals, search bar, toast notifications).
This commit is contained in:
2026-02-02 17:01:29 +01:00
parent 21107443a7
commit 08c26754a8
25 changed files with 4566 additions and 1 deletions

62
portal/src/App.tsx Normal file
View File

@@ -0,0 +1,62 @@
import { useState, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import GraphView from './components/GraphView';
import Sidebar from './components/Sidebar';
import NodePanel from './components/NodePanel';
import AddNodeModal from './components/AddNodeModal';
import LinkModal from './components/LinkModal';
import Toast from './components/Toast';
export default function App() {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [showAddNode, setShowAddNode] = useState(false);
const [linkFromId, setLinkFromId] = useState<string | null>(null);
const [toast, setToast] = useState<string | null>(null);
const qc = useQueryClient();
const refresh = useCallback(() => {
qc.invalidateQueries({ queryKey: ['graph'] });
qc.invalidateQueries({ queryKey: ['nodes'] });
}, [qc]);
const notify = useCallback((msg: string) => {
setToast(msg);
setTimeout(() => setToast(null), 3000);
}, []);
return (
<div className="flex h-screen w-screen overflow-hidden">
<Sidebar
selectedId={selectedId}
onSelect={setSelectedId}
onAddNode={() => setShowAddNode(true)}
/>
<div className="flex-1 relative">
<GraphView selectedId={selectedId} onSelect={setSelectedId} />
</div>
{selectedId && (
<NodePanel
nodeId={selectedId}
onClose={() => setSelectedId(null)}
onLink={(id) => setLinkFromId(id)}
onRefresh={refresh}
onNotify={notify}
/>
)}
{showAddNode && (
<AddNodeModal
onClose={() => setShowAddNode(false)}
onCreated={() => { refresh(); notify('Node created'); }}
/>
)}
{linkFromId && (
<LinkModal
fromId={linkFromId}
onClose={() => setLinkFromId(null)}
onCreated={() => { refresh(); notify('Edge created'); }}
/>
)}
{toast && <Toast message={toast} />}
</div>
);
}