corpus integrity UI: Ingest Health section in SettingsPanel, corpus API types

This commit is contained in:
2026-04-30 21:54:39 +00:00
parent 3d0a2168f1
commit 3101df3846
2 changed files with 117 additions and 0 deletions
+74
View File
@@ -3,6 +3,7 @@
import { useEffect, useState } from 'react';
import { useStore } from '@/lib/store';
import { api, auth } from '@/lib/api';
import type { CorpusStatus } from '@/lib/api';
import type { Status, DreamerStatus } from '@/lib/api';
export default function SettingsPanel() {
@@ -17,6 +18,9 @@ export default function SettingsPanel() {
const [dreaming, setDreaming] = useState(false);
const [dreamStarted, setDreamStarted] = useState(false);
const [captures, setCaptures] = useState<{name: string}[]>([]);
const [corpusStatus, setCorpusStatus] = useState<CorpusStatus | null>(null);
const [reconciling, setReconciling] = useState(false);
const [retrying, setRetrying] = useState<string | null>(null);
function formatDreamerTime(raw: string): string {
if (!raw || raw === 'never' || raw === '—') return raw;
@@ -39,6 +43,7 @@ export default function SettingsPanel() {
api.getDreamerStatus().then(setDreamerStatus).catch(console.error);
fetch('/api/captures').then(r => r.json()).then(d => setCaptures(d.captures || [])).catch(() => {});
api.getMemory().then(d => setMemory(d.content)).catch(console.error);
api.getCorpusStatus().then(setCorpusStatus).catch(console.error);
}, [settingsOpen]);
async function updateSetting(key: string, value: unknown) {
@@ -110,6 +115,24 @@ export default function SettingsPanel() {
useStore.getState().setMessages([]);
}
async function runReconcile() {
setReconciling(true);
await api.runReconciliation();
setTimeout(() => {
api.getCorpusStatus().then(setCorpusStatus).catch(console.error);
setReconciling(false);
}, 6000);
}
async function retryFailure(source: string) {
setRetrying(source);
await api.retryIngestFailure(source);
setTimeout(() => {
api.getCorpusStatus().then(setCorpusStatus).catch(console.error);
setRetrying(null);
}, 2000);
}
return (
<>
<div
@@ -183,6 +206,57 @@ export default function SettingsPanel() {
</Row>
</Section>
{/* Ingest Health */}
<Section title="Ingest Health">
<div className="grid grid-cols-3 gap-2 mb-3">
<StatCard number={(corpusStatus?.filesystem || 0).toLocaleString()} label="on disk" />
<StatCard number={(corpusStatus?.pgvector || 0).toLocaleString()} label="pgvector" />
<StatCard number={(corpusStatus?.graphiti || 0).toLocaleString()} label="graphiti" />
</div>
{corpusStatus?.last_reconciliation && (
<div className="text-xs mb-2" style={{ color: 'var(--text3)' }}>
Last check: {new Date(corpusStatus.last_reconciliation.timestamp).toLocaleString()} {corpusStatus.last_reconciliation.gaps} gap(s), {corpusStatus.last_reconciliation.auto_queued} queued
</div>
)}
<Row label="Check corpus" desc="Find and re-queue missing files">
<SBtn primary onClick={runReconcile} disabled={reconciling}>
{reconciling ? 'Running...' : 'Run check'}
</SBtn>
</Row>
{corpusStatus && corpusStatus.failure_count > 0 && (
<div className="mt-3">
<div className="text-xs mb-2" style={{ color: 'var(--text3)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
{corpusStatus.failure_count} ingest failure{corpusStatus.failure_count !== 1 ? 's' : ''}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
{corpusStatus.failures.slice(0, 10).map((f, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'flex-start', gap: '8px',
padding: '6px 8px', background: 'var(--bg2)',
borderRadius: '6px', border: '1px solid var(--border)',
}}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: '11px', color: 'var(--text2)', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{f.source}
</div>
<div style={{ fontSize: '10px', color: '#a32d2d', marginTop: '2px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{f.error} {f.retry_count > 0 ? `(${f.retry_count} retr${f.retry_count === 1 ? 'y' : 'ies'})` : ''}
</div>
</div>
<SBtn onClick={() => retryFailure(f.source)} disabled={retrying === f.source || f.retry_count >= 2}>
{retrying === f.source ? '...' : f.retry_count >= 2 ? 'Manual' : 'Retry'}
</SBtn>
</div>
))}
</div>
</div>
)}
{corpusStatus && corpusStatus.failure_count === 0 && (
<div className="text-xs mt-1" style={{ color: 'var(--text3)' }}>No ingest failures</div>
)}
</Section>
{/* Memory */}
<Section title="Memory">
{editingMemory ? (