Spaces:
Runtime error
Runtime error
File size: 5,131 Bytes
a45ece3 3da97ef a45ece3 98feb0f a45ece3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | import { useMemo, useState } from 'react'
import { useAppStore } from '@/store/appStore'
import type { VerdictLabel } from '@/lib/types'
import { confidencePercent, verdictColor } from '@/lib/utils'
export function ClaimsTable() {
const run = useAppStore((s) => s.runs.find((r) => r.id === s.currentRunId))
const filter = useAppStore((s) => s.ui.filter)
const setFilter = useAppStore((s) => s.setFilter)
const openEvidence = useAppStore((s) => s.openEvidence)
const [sortAsc, setSortAsc] = useState<boolean>(false)
const rows = useMemo(() => {
if (!run?.report) return []
const { claims, verdicts, evidence } = run.report
const byClaim: Record<string, any> = {}
for (const c of claims) byClaim[c.id] = { claim: c }
for (const v of verdicts) byClaim[v.claim_id] = { ...(byClaim[v.claim_id] || {}), verdict: v }
let data = Object.values(byClaim)
if (filter !== 'all') {
data = data.filter((r: any) => r.verdict?.label === filter)
}
data.sort((a: any, b: any) => (sortAsc ? (a.verdict?.confidence ?? 0) - (b.verdict?.confidence ?? 0) : (b.verdict?.confidence ?? 0) - (a.verdict?.confidence ?? 0)))
return data.map((r: any) => {
const cited = r.verdict?.citation_ids as string[] | undefined
const firstId = (cited && cited[0]) || r.verdict?.best_evidence_id
const evid = firstId ? evidence.find((e) => e.doc_id === firstId) : undefined
return { ...r, evidence: evid }
})
}, [run, filter, sortAsc])
if (!run?.report) return null
const filters: ('all' | VerdictLabel)[] = ['all', 'supported', 'refuted', 'insufficient']
return (
<div className="bg-card text-card-foreground rounded-lg border">
<div className="p-4 flex items-center justify-between">
<h3 className="font-medium">Claims</h3>
<div className="flex items-center gap-2">
{filters.map((f) => (
<button
key={f}
onClick={() => setFilter(f)}
className={`text-xs px-2 py-1 rounded-md border ${filter === f ? 'bg-accent' : 'hover:bg-muted'}`}
>
{f}
</button>
))}
<button className="text-xs px-2 py-1 rounded-md border hover:bg-muted" onClick={() => setSortAsc((v) => !v)}>
Sort by confidence {sortAsc ? '↑' : '↓'}
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm" role="table" aria-label="Claims table">
<thead className="text-left text-muted-foreground">
<tr>
<th className="px-4 py-2 w-[50%]">Claim</th>
<th className="px-4 py-2">Verdict</th>
<th className="px-4 py-2">Confidence</th>
<th className="px-4 py-2">Evidence</th>
</tr>
</thead>
<tbody>
{rows.map((r: any, idx: number) => (
<tr key={idx} className="border-t">
<td className="px-4 py-2 align-top">
<div>
<div>{r.claim?.text}</div>
{r.claim?.speaker && (
<div className="mt-1">
<span className="inline-flex items-center px-2 py-0.5 rounded-full bg-muted text-xs text-muted-foreground">
{r.claim.speaker}
</span>
</div>
)}
</div>
</td>
<td className="px-4 py-2 align-top">
{r.verdict && (
<span className={`inline-flex items-center px-2 py-1 rounded-md border text-xs ${verdictColor(r.verdict.label)}`}>
{labelText(r.verdict.label)}
</span>
)}
</td>
<td className="px-4 py-2 align-top">
<div className="min-w-[100px]">
<div className="h-2 rounded bg-muted">
<div className="h-2 rounded bg-primary" style={{ width: `${confidencePercent(r.verdict?.confidence)}%` }} />
</div>
<div className="text-xs text-muted-foreground mt-1">{confidencePercent(r.verdict?.confidence)}%</div>
</div>
</td>
<td className="px-4 py-2 align-top">
{r.evidence ? (
<button
className="text-xs px-2 py-1 rounded-md border hover:bg-muted"
onClick={() => openEvidence(r.claim.id)}
>
{r.evidence.source || r.evidence.doc_id}
</button>
) : (
<span className="text-xs text-muted-foreground">—</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
function labelText(v: VerdictLabel): string {
switch (v) {
case 'supported':
return 'Supported'
case 'refuted':
return 'Refuted'
default:
return 'Insufficient'
}
} |