feat: implement R42 chat infrastructure with Anthropic API integration and custom design system
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
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 * as db from '../../lib/db';
|
||||
import { anthropicApi } from '../../lib/api';
|
||||
import Button from '../ui/Button';
|
||||
import SuggestionsQueue from './SuggestionsQueue';
|
||||
|
||||
const KnowledgeGraph = () => {
|
||||
const svgRef = useRef(null);
|
||||
@@ -19,11 +20,18 @@ const KnowledgeGraph = () => {
|
||||
const [topics, setTopics] = useState([]);
|
||||
const [relations, setRelations] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const reloadKb = useCallback(() => {
|
||||
db.getTopics().then(setTopics);
|
||||
db.getRelations().then(setRelations);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
reloadKb();
|
||||
const handler = () => reloadKb();
|
||||
window.addEventListener('respellion:kb-updated', handler);
|
||||
return () => window.removeEventListener('respellion:kb-updated', handler);
|
||||
}, [reloadKb]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
const { width, height } = wrapperRef.current.getBoundingClientRect();
|
||||
@@ -290,6 +298,11 @@ Analyze this graph and return ONLY the optimized JSON object with this EXACT str
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* R42 chatbot suggestions queue */}
|
||||
<div className="mb-6 pb-4 border-b border-bg-warm">
|
||||
<SuggestionsQueue onApplied={reloadKb} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-bold text-lg text-teal">Node Details</h3>
|
||||
{selectedNode && !isEditing && (
|
||||
|
||||
89
src/components/admin/SuggestionsQueue.jsx
Normal file
89
src/components/admin/SuggestionsQueue.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Check, X, Clock, Sparkles } from 'lucide-react';
|
||||
import { kbStore } from '../../lib/kbStore';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
/**
|
||||
* Admin sub-panel inside the Knowledge Graph view. Shows pending R42 chatbot
|
||||
* suggestions with approve / reject controls.
|
||||
*/
|
||||
export default function SuggestionsQueue({ onApplied }) {
|
||||
const [pending, setPending] = useState([]);
|
||||
|
||||
const refresh = () => setPending(kbStore.listSuggestions('pending'));
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
const onChange = () => refresh();
|
||||
window.addEventListener('respellion:kb-updated', onChange);
|
||||
return () => window.removeEventListener('respellion:kb-updated', onChange);
|
||||
}, []);
|
||||
|
||||
if (pending.length === 0) {
|
||||
return (
|
||||
<div className="text-xs text-fg-muted flex items-center gap-2">
|
||||
<Sparkles size={14} /> Geen openstaande voorstellen van R42.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs text-fg-muted uppercase tracking-wider font-mono flex items-center gap-2">
|
||||
<Sparkles size={14} /> R42-voorstellen ({pending.length})
|
||||
</div>
|
||||
{pending.map(s => (
|
||||
<div key={s.id} className="bg-bg rounded-[var(--r-sm)] border border-bg-warm p-3 text-sm">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-xs text-fg-muted flex items-center gap-2">
|
||||
<Clock size={12} />
|
||||
{new Date(s.ts).toLocaleString()}
|
||||
{s.proposedByName && <> · door {s.proposedByName}</>}
|
||||
</div>
|
||||
</div>
|
||||
{s.reason && <p className="mb-2 text-fg">{s.reason}</p>}
|
||||
{(s.topics?.length > 0 || s.relations?.length > 0) && (
|
||||
<ul className="text-xs space-y-1 mb-3">
|
||||
{s.topics?.map(t => (
|
||||
<li key={`t-${t.id}`}>
|
||||
<strong>{t.label}</strong>{' '}
|
||||
<span className="text-fg-muted">({t.type})</span>
|
||||
{t.description && <> — {t.description}</>}
|
||||
</li>
|
||||
))}
|
||||
{s.relations?.map((r, i) => (
|
||||
<li key={`r-${i}`}>
|
||||
<code className="text-teal">{r.source}</code>{' → '}
|
||||
<span className="text-fg-muted">{r.type}</span>{' → '}
|
||||
<code className="text-teal">{r.target}</code>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await kbStore.approveSuggestion(s.id);
|
||||
refresh();
|
||||
onApplied?.();
|
||||
}}
|
||||
className="text-xs py-1 px-3 flex items-center gap-1"
|
||||
>
|
||||
<Check size={14} /> Goedkeuren
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
kbStore.rejectSuggestion(s.id);
|
||||
refresh();
|
||||
}}
|
||||
className="text-xs py-1 px-3 flex items-center gap-1"
|
||||
>
|
||||
<X size={14} /> Afwijzen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user