cc-rag / frontend /src /components /TripleTable.tsx
matsuap's picture
feat: implement CC-RAG prototype with Neo4j, FastAPI, React
e675bdb
Raw
History Blame Contribute Delete
4.1 kB
import React, { useState, useEffect } from 'react'
import { api } from '../api'
import type { CausalTriple } from '../types'
interface Props { documentId: string }
export function TripleTable({ documentId }: Props) {
const [triples, setTriples] = useState<CausalTriple[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [minConf, setMinConf] = useState(0)
const [themeFilter, setThemeFilter] = useState('')
useEffect(() => {
api.getTriples(documentId)
.then(r => setTriples(r.triples))
.catch(e => setError(String(e.message)))
.finally(() => setLoading(false))
}, [documentId])
const themes = [...new Set(triples.map(t => t.theme))].filter(Boolean)
const filtered = triples
.filter(t => t.confidence >= minConf)
.filter(t => !themeFilter || t.theme === themeFilter)
if (loading) return <div className="spinner" />
if (error) return <div className="error-msg">{error}</div>
return (
<div>
<div style={{ display: 'flex', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
<div>
<div className="label">Min Confidence</div>
<input
type="range" min={0} max={1} step={0.05}
value={minConf}
onChange={e => setMinConf(Number(e.target.value))}
style={{ width: 120 }}
/>
<span style={{ fontSize: 12, color: 'var(--muted)', marginLeft: 8 }}>{minConf.toFixed(2)}</span>
</div>
{themes.length > 0 && (
<div>
<div className="label">Theme</div>
<select value={themeFilter} onChange={e => setThemeFilter(e.target.value)} style={{ width: 160 }}>
<option value="">All themes</option>
{themes.map(t => <option key={t} value={t}>{t}</option>)}
</select>
</div>
)}
<div style={{ marginLeft: 'auto', alignSelf: 'flex-end', fontSize: 13, color: 'var(--muted)' }}>
{filtered.length} / {triples.length} triples
</div>
</div>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--border)', color: 'var(--muted)' }}>
{['Cause', 'Relation', 'Effect', 'Theme', 'Conf', 'Evidence'].map(h => (
<th key={h} style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600, fontSize: 11, textTransform: 'uppercase' }}>{h}</th>
))}
</tr>
</thead>
<tbody>
{filtered.map(t => (
<tr key={t.id} style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 12px', color: '#f87171', fontWeight: 500 }}>{t.cause}</td>
<td style={{ padding: '8px 12px' }}>
<span className={`badge ${t.relation}`}>{t.relation}</span>
</td>
<td style={{ padding: '8px 12px', color: '#60a5fa', fontWeight: 500 }}>{t.effect}</td>
<td style={{ padding: '8px 12px', color: 'var(--muted)', fontSize: 12 }}>{t.theme}</td>
<td style={{ padding: '8px 12px' }}>
<span style={{
color: t.confidence >= 0.7 ? 'var(--success)' : t.confidence >= 0.5 ? 'var(--warning)' : 'var(--danger)',
fontWeight: 600,
}}>{t.confidence.toFixed(2)}</span>
</td>
<td style={{ padding: '8px 12px', color: 'var(--muted)', fontSize: 12, maxWidth: 300 }}>
<span title={t.evidence} style={{ display: '-webkit-box', WebkitLineClamp: 2, overflow: 'hidden' } as React.CSSProperties}>
{t.evidence}
</span>
</td>
</tr>
))}
</tbody>
</table>
{filtered.length === 0 && (
<p style={{ textAlign: 'center', color: 'var(--muted)', padding: 40 }}>No triples match filters.</p>
)}
</div>
</div>
)
}