feat: add knowledge graph component and persistent handbook sync state collection

This commit is contained in:
RaymondVerhoef
2026-05-18 21:13:17 +02:00
parent 9f3e1113a6
commit f35550f270
3 changed files with 191 additions and 16 deletions

View File

@@ -1,8 +1,9 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';
import { Trash2, Edit2, Save, X, RefreshCw, AlertCircle, Plus, Link as LinkIcon } from 'lucide-react';
import { Trash2, Edit2, Save, X, RefreshCw, AlertCircle, Plus, Link as LinkIcon, Github } from 'lucide-react';
import * as db from '../../lib/db';
import { anthropicApi } from '../../lib/api';
import { getRepoFolder } from '../../lib/giteaService';
import Button from '../ui/Button';
import SuggestionsQueue from './SuggestionsQueue';
@@ -17,6 +18,10 @@ const KnowledgeGraph = () => {
const [analyzeError, setAnalyzeError] = useState(null);
const [newRelData, setNewRelData] = useState({ target: '', type: 'related_to' });
const [isSyncing, setIsSyncing] = useState(false);
const [syncResult, setSyncResult] = useState(null);
const [syncError, setSyncError] = useState(null);
const [topics, setTopics] = useState([]);
const [relations, setRelations] = useState([]);
@@ -191,6 +196,38 @@ const KnowledgeGraph = () => {
)));
};
const syncHandbook = async () => {
setIsSyncing(true);
setSyncError(null);
setSyncResult(null);
try {
const files = await getRepoFolder('respellion', 'employee-handbook', 'docs');
const syncStates = await db.getHandbookSyncStates();
const added = [];
const modified = [];
const unchanged = [];
files.forEach(file => {
const state = syncStates.find(s => s.path === file.path);
if (!state) {
added.push(file);
} else if (state.sha !== file.sha) {
modified.push(file);
} else {
unchanged.push(file);
}
});
setSyncResult({ added, modified, unchanged });
} catch (e) {
setSyncError(e.message || 'Failed to check GitHub for updates.');
} finally {
setIsSyncing(false);
}
};
const analyzeGraph = async () => {
if (!confirm('This will send the entire graph to the AI to evaluate content, merge duplicates, and create new logical relations. This may take a moment. Proceed?')) return;
@@ -281,21 +318,52 @@ Analyze this graph and return ONLY the optimized JSON object with this EXACT str
</div>
<div className="w-full md:w-80 bg-paper p-6 overflow-y-auto border-t md:border-t-0 border-bg-warm flex flex-col">
<div className="mb-6 pb-4 border-b border-bg-warm">
<Button
onClick={analyzeGraph}
disabled={isAnalyzing || topics.length === 0}
className="w-full flex justify-center items-center gap-2"
>
<RefreshCw size={16} className={isAnalyzing ? "animate-spin" : ""} />
{isAnalyzing ? 'Analyzing Graph...' : 'Analyze & Optimize Graph'}
</Button>
{analyzeError && (
<p className="mt-2 text-xs text-red-600 flex items-start gap-1">
<AlertCircle size={14} className="shrink-0 mt-0.5" />
{analyzeError}
</p>
)}
<div className="mb-6 pb-4 border-b border-bg-warm space-y-3">
<div>
<Button
onClick={analyzeGraph}
disabled={isAnalyzing || topics.length === 0}
className="w-full flex justify-center items-center gap-2"
>
<RefreshCw size={16} className={isAnalyzing ? "animate-spin" : ""} />
{isAnalyzing ? 'Analyzing Graph...' : 'Analyze & Optimize Graph'}
</Button>
{analyzeError && (
<p className="mt-2 text-xs text-red-600 flex items-start gap-1">
<AlertCircle size={14} className="shrink-0 mt-0.5" />
{analyzeError}
</p>
)}
</div>
<div>
<Button
onClick={syncHandbook}
disabled={isSyncing}
variant="outline"
className="w-full flex justify-center items-center gap-2"
>
<Github size={16} className={isSyncing ? "animate-pulse" : ""} />
{isSyncing ? 'Checking for updates...' : 'Sync Employee Handbook'}
</Button>
{syncError && (
<p className="mt-2 text-xs text-red-600 flex items-start gap-1">
<AlertCircle size={14} className="shrink-0 mt-0.5" />
{syncError}
</p>
)}
{syncResult && (
<div className="mt-2 text-xs text-fg-muted space-y-1 p-2 bg-bg rounded">
<p className="font-medium text-fg">GitHub Sync Status:</p>
<p>Added files: {syncResult.added.length}</p>
<p>Modified files: {syncResult.modified.length}</p>
<p>Unchanged: {syncResult.unchanged.length}</p>
{(syncResult.added.length > 0 || syncResult.modified.length > 0) && (
<p className="mt-2 italic text-teal">Ready for Phase 2 implementation.</p>
)}
</div>
)}
</div>
</div>
{/* R42 chatbot suggestions queue */}