Files
ra-h-os/src/components/settings/ContextViewer.tsx
T

186 lines
5.7 KiB
TypeScript

"use client";
import { useEffect, useState, type CSSProperties } from 'react';
import type { Node } from '@/types/database';
interface AutoContextSettings {
autoContextEnabled: boolean;
lastPinnedMigration?: string;
}
interface NodeWithMetrics extends Node {
edge_count?: number;
}
export default function ContextViewer() {
const [nodes, setNodes] = useState<NodeWithMetrics[]>([]);
const [enabled, setEnabled] = useState(false);
const [loadingNodes, setLoadingNodes] = useState(true);
const [loadingSettings, setLoadingSettings] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
const loadNodes = async () => {
try {
const res = await fetch('/api/nodes?sortBy=edges&limit=10');
const payload = await res.json();
setNodes(payload.data || []);
} catch (e) {
console.error(e);
} finally {
setLoadingNodes(false);
}
};
const loadSettings = async () => {
try {
const res = await fetch('/api/system/auto-context');
const payload = await res.json() as { success: boolean; data?: AutoContextSettings };
if (payload.success && payload.data) {
setEnabled(payload.data.autoContextEnabled);
}
} catch (e) {
console.error(e);
} finally {
setLoadingSettings(false);
}
};
loadNodes();
loadSettings();
}, []);
const handleToggle = async () => {
if (saving) return;
setSaving(true);
try {
const res = await fetch('/api/system/auto-context', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ autoContextEnabled: !enabled }),
});
const payload = await res.json() as { success: boolean; data?: AutoContextSettings };
if (payload.success && payload.data) {
setEnabled(payload.data.autoContextEnabled);
}
} catch (e) {
console.error(e);
} finally {
setSaving(false);
}
};
return (
<div style={containerStyle}>
<p style={descStyle}>
Context summaries and anchor nodes are used first. Global hub nodes remain secondary diagnostics in background context.
</p>
{/* Toggle */}
<div style={cardStyle}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={labelStyle}>Auto-Context</div>
<div style={subLabelStyle}>
{loadingSettings ? 'Loading...' : enabled ? 'Enabled' : 'Disabled'}
</div>
</div>
<button
onClick={handleToggle}
disabled={loadingSettings || saving}
style={{
...toggleStyle,
background: enabled ? 'var(--settings-active-bg)' : 'var(--settings-code-bg)',
}}
>
<span style={{
...toggleKnobStyle,
left: enabled ? 26 : 4,
}} />
</button>
</div>
</div>
{/* Nodes List */}
<div style={labelStyle}>Top Nodes</div>
{loadingNodes ? (
<div style={mutedStyle}>Loading...</div>
) : nodes.length === 0 ? (
<div style={mutedStyle}>No connected nodes yet.</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{nodes.map((node) => (
<div key={node.id} style={nodeCardStyle}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={nodeTitleStyle}>{node.title || 'Untitled'}</span>
<span style={edgeCountStyle}>{node.edge_count ?? 0}</span>
</div>
{node.dimensions && node.dimensions.length > 0 && (
<div style={{ display: 'flex', gap: 4, marginTop: 6, flexWrap: 'wrap' }}>
{node.dimensions.slice(0, 3).map((dim) => (
<span key={dim} style={dimTagStyle}>{dim}</span>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
);
}
const containerStyle: CSSProperties = { padding: 24, height: '100%', overflow: 'auto' };
const descStyle: CSSProperties = { fontSize: 13, color: 'var(--settings-muted)', marginBottom: 20, lineHeight: 1.5 };
const cardStyle: CSSProperties = {
background: 'var(--settings-card-bg)',
border: '1px solid var(--settings-border)',
borderRadius: 8,
padding: 16,
marginBottom: 24,
};
const labelStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: 'var(--settings-text)', marginBottom: 8 };
const subLabelStyle: CSSProperties = { fontSize: 12, color: 'var(--settings-muted)' };
const mutedStyle: CSSProperties = { fontSize: 13, color: 'var(--settings-muted)' };
const toggleStyle: CSSProperties = {
width: 48,
height: 26,
borderRadius: 13,
border: '1px solid var(--settings-border)',
cursor: 'pointer',
position: 'relative',
transition: 'background 0.15s',
};
const toggleKnobStyle: CSSProperties = {
position: 'absolute',
top: 4,
width: 18,
height: 18,
borderRadius: '50%',
background: 'var(--settings-text)',
transition: 'left 0.15s',
};
const nodeCardStyle: CSSProperties = {
padding: '12px 14px',
background: 'var(--settings-card-bg)',
border: '1px solid var(--settings-border)',
borderRadius: 6,
};
const nodeTitleStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: 'var(--settings-text)' };
const edgeCountStyle: CSSProperties = { fontSize: 12, color: 'var(--settings-muted)' };
const dimTagStyle: CSSProperties = {
padding: '2px 8px',
borderRadius: 4,
fontSize: 11,
background: 'var(--settings-chip-bg)',
color: 'var(--settings-subtext)',
border: '1px solid var(--settings-border)',
};