feat: knowledge graph table view met sort, group en bulk edit
Voegt een Graph/Table toggle toe aan de admin Knowledge Graph. De tabel ondersteunt sorteren per kolom, groeperen op type/relevance/theme/difficulty, filteren op label, multi-select en bulk-wijzigen van type, learning_relevance en relevance_locked. Closes #10 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,11 @@ import * as d3 from 'd3';
|
||||
import * as db from '../../lib/db';
|
||||
import { callLLM } from '../../lib/llm';
|
||||
import { EMIT_GRAPH_ACTIONS_TOOL } from '../../lib/llmTools';
|
||||
import { Network, Table2 } from 'lucide-react';
|
||||
import { useGraphData } from '../../hooks/useGraphData';
|
||||
import GraphControls from './graph/GraphControls';
|
||||
import NodeDetailPanel from './graph/NodeDetailPanel';
|
||||
import GraphTable from './graph/GraphTable';
|
||||
|
||||
// ── Edge visual style per relation type ────────────────────────────────────────
|
||||
// stroke — line colour
|
||||
@@ -72,10 +74,11 @@ const KnowledgeGraph = () => {
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
const [analyzeError, setAnalyzeError] = useState(null);
|
||||
const [isRestoring, setIsRestoring] = useState(false);
|
||||
const [viewMode, setViewMode] = useState('graph'); // 'graph' | 'table'
|
||||
|
||||
const {
|
||||
topics, relations, snapshotMeta,
|
||||
reload, updateTopic, deleteTopic, addRelation, removeRelation, bulkSave, restoreSnapshot,
|
||||
reload, updateTopic, bulkUpdateTopics, deleteTopic, addRelation, removeRelation, bulkSave, restoreSnapshot,
|
||||
} = useGraphData();
|
||||
|
||||
// ── Canvas sizing ────────────────────────────────────────────────────────────
|
||||
@@ -94,6 +97,7 @@ const KnowledgeGraph = () => {
|
||||
// ── D3 force graph ───────────────────────────────────────────────────────────
|
||||
|
||||
useEffect(() => {
|
||||
if (viewMode !== 'graph') return;
|
||||
if (!svgRef.current || topics.length === 0) return;
|
||||
|
||||
const { width, height } = dimensions;
|
||||
@@ -236,7 +240,7 @@ const KnowledgeGraph = () => {
|
||||
});
|
||||
|
||||
return () => simulation.stop();
|
||||
}, [dimensions, topics, relations, showExcludeNodes]);
|
||||
}, [dimensions, topics, relations, showExcludeNodes, viewMode]);
|
||||
|
||||
// ── Pan/zoom canvas to a node ────────────────────────────────────────────────
|
||||
|
||||
@@ -396,17 +400,58 @@ const KnowledgeGraph = () => {
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col md:flex-row">
|
||||
{/* D3 canvas */}
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="flex-1 h-[400px] md:h-full cursor-grab active:cursor-grabbing border-r border-bg-warm"
|
||||
>
|
||||
{topics.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center text-fg-muted p-8 text-center">
|
||||
No knowledge graph data yet. Upload source material in the Sources tab first.
|
||||
{/* Main view: graph canvas or table */}
|
||||
<div className="flex-1 flex flex-col border-r border-bg-warm min-w-0">
|
||||
{/* View-mode toggle */}
|
||||
<div className="flex items-center gap-1 px-3 py-2 border-b border-bg-warm bg-paper">
|
||||
<button
|
||||
onClick={() => setViewMode('graph')}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 text-xs rounded-[var(--r-sm)] transition-colors ${
|
||||
viewMode === 'graph' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50'
|
||||
}`}
|
||||
title="Force-directed graph view"
|
||||
>
|
||||
<Network size={13} /> Graph
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('table')}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 text-xs rounded-[var(--r-sm)] transition-colors ${
|
||||
viewMode === 'table' ? 'bg-bg-warm text-teal font-medium' : 'text-fg-muted hover:bg-bg-warm/50'
|
||||
}`}
|
||||
title="Sortable table with bulk edit"
|
||||
>
|
||||
<Table2 size={13} /> Table
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{viewMode === 'graph' ? (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="flex-1 h-[400px] md:h-full cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
{topics.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center text-fg-muted p-8 text-center">
|
||||
No knowledge graph data yet. Upload source material in the Sources tab first.
|
||||
</div>
|
||||
) : (
|
||||
<svg ref={svgRef} className="w-full h-full" />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<svg ref={svgRef} className="w-full h-full" />
|
||||
<div className="flex-1 min-h-[400px] overflow-hidden">
|
||||
{topics.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center text-fg-muted p-8 text-center">
|
||||
No knowledge graph data yet. Upload source material in the Sources tab first.
|
||||
</div>
|
||||
) : (
|
||||
<GraphTable
|
||||
topics={topics}
|
||||
relations={relations}
|
||||
onBulkUpdate={bulkUpdateTopics}
|
||||
onSelectNode={setSelectedNode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user