Spaces:
Runtime error
Runtime error
| 'use client'; | |
| import React from 'react'; | |
| import { Accordion } from '@/components/ui/Accordion'; | |
| interface VisualItem { | |
| id: string; | |
| alt: string; | |
| caption?: string; | |
| page: number; | |
| image_url?: string; // real URL from backend ColPali retrieval | |
| } | |
| interface SourceVisualEvidenceAccordionProps { | |
| visuals: VisualItem[]; | |
| onOpenPage?: (page: number) => void; | |
| } | |
| export function SourceVisualEvidenceAccordion({ visuals, onOpenPage }: SourceVisualEvidenceAccordionProps) { | |
| const [selected, setSelected] = React.useState<number | null>(null); | |
| const [imgErrors, setImgErrors] = React.useState<Record<string, boolean>>({}); | |
| const handleImgError = (id: string) => { | |
| setImgErrors(prev => ({ ...prev, [id]: true })); | |
| }; | |
| return ( | |
| <Accordion title="Graph / Chart Display" icon="📈"> | |
| {visuals.length === 0 ? ( | |
| <div style={{ | |
| textAlign: 'center', padding: '2rem', | |
| color: 'var(--text-muted)', fontSize: '0.8rem', | |
| border: '1px dashed var(--border-mid)', borderRadius: 'var(--radius-md)', | |
| }}> | |
| <div style={{ fontSize: '1.5rem', marginBottom: '0.5rem' }}>📉</div> | |
| No chart or graph evidence was found for this response. | |
| </div> | |
| ) : ( | |
| <> | |
| <p style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginBottom: '0.875rem', lineHeight: 1.5 }}> | |
| Showing only chart and graph evidence retrieved by ColPali visual search. | |
| Only exact PDF page screenshots are shown — no recreated or AI-generated visuals. | |
| </p> | |
| <div className="visual-grid"> | |
| {visuals.map((v, i) => { | |
| const hasRealImage = !!v.image_url && !imgErrors[v.id]; | |
| return ( | |
| <div | |
| key={v.id} | |
| className="visual-item" | |
| style={{ cursor: 'pointer' }} | |
| onClick={() => { | |
| setSelected(i); | |
| onOpenPage?.(v.page); | |
| }} | |
| id={`visual-item-${v.id}`} | |
| role="button" | |
| tabIndex={0} | |
| onKeyDown={e => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| setSelected(i); | |
| onOpenPage?.(v.page); | |
| } | |
| }} | |
| > | |
| {/* Image area */} | |
| {hasRealImage ? ( | |
| // Real ColPali-retrieved page screenshot | |
| <div style={{ position: 'relative', background: '#fff' }}> | |
| {/* eslint-disable-next-line @next/next/no-img-element */} | |
| <img | |
| src={v.image_url} | |
| alt={v.alt} | |
| onError={() => handleImgError(v.id)} | |
| style={{ | |
| width: '100%', | |
| display: 'block', | |
| borderRadius: 'var(--radius-md) var(--radius-md) 0 0', | |
| maxHeight: '600px', | |
| objectFit: 'contain', | |
| background: '#fff', | |
| }} | |
| /> | |
| {/* ColPali badge */} | |
| <div style={{ | |
| position: 'absolute', top: 6, right: 6, | |
| background: 'rgba(0,51,102,0.85)', | |
| border: '1px solid var(--gold-border)', | |
| borderRadius: 'var(--radius-sm)', | |
| padding: '0.15rem 0.45rem', | |
| fontSize: '0.58rem', fontWeight: 700, | |
| color: 'var(--enbd-blue)', backdropFilter: 'blur(4px)', | |
| }}> | |
| ColPali | |
| </div> | |
| </div> | |
| ) : ( | |
| // Placeholder (demo mode or image load error) | |
| <div className="visual-placeholder"> | |
| <div style={{ fontSize: '2rem' }}>📊</div> | |
| <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-muted)' }}> | |
| {v.alt} | |
| </div> | |
| <div style={{ fontSize: '0.65rem', color: 'var(--text-disabled)' }}> | |
| Page {v.page} · ColPali retrieved | |
| </div> | |
| <div style={{ | |
| marginTop: '0.35rem', | |
| background: 'var(--enbd-blue-muted)', | |
| border: '1px solid var(--enbd-blue-border)', | |
| borderRadius: 'var(--radius-sm)', | |
| padding: '0.2rem 0.5rem', | |
| fontSize: '0.62rem', color: 'var(--enbd-blue)', fontWeight: 600, | |
| }}> | |
| Start backend to view screenshot | |
| </div> | |
| </div> | |
| )} | |
| {/* Expand detail */} | |
| {selected === i && ( | |
| <div style={{ | |
| padding: '0.75rem', | |
| borderTop: '1px solid var(--border-subtle)', | |
| background: 'var(--enbd-blue-muted)', | |
| fontSize: '0.75rem', color: 'var(--text-muted)', lineHeight: 1.55, | |
| animation: 'fadeInUp 0.2s ease both', | |
| }}> | |
| <div style={{ color: 'var(--enbd-blue)', fontWeight: 600, marginBottom: '0.25rem' }}> | |
| {v.caption || `Page ${v.page} visual evidence`} | |
| </div> | |
| <p style={{ marginBottom: '0.5rem' }}> | |
| {hasRealImage | |
| ? 'Exact PDF page screenshot retrieved by ColPali visual search. No values approximated from visual shape.' | |
| : 'Connect the backend to display the exact PDF page screenshot from ColPali retrieval.'} | |
| </p> | |
| <button | |
| style={{ | |
| background: 'none', | |
| border: '1px solid var(--enbd-blue-border)', | |
| borderRadius: 'var(--radius-sm)', | |
| color: 'var(--enbd-blue)', | |
| fontSize: '0.68rem', fontWeight: 600, | |
| padding: '0.2rem 0.6rem', | |
| cursor: 'pointer', fontFamily: 'var(--font-body)', | |
| }} | |
| onClick={e => { e.stopPropagation(); onOpenPage?.(v.page); }} | |
| > | |
| Page {v.page} | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| {/* Guardrail notice */} | |
| <div style={{ | |
| marginTop: '1rem', padding: '0.6rem 0.875rem', | |
| background: 'rgba(255,255,255,0.02)', | |
| border: '1px solid var(--border-subtle)', | |
| borderRadius: 'var(--radius-sm)', | |
| fontSize: '0.68rem', color: 'var(--text-disabled)', lineHeight: 1.5, | |
| }}> | |
| IRIS never recreates, redraws, or approximates charts. Only exact PDF page screenshots | |
| retrieved by ColPali are shown. Numeric values are sourced exclusively from validated | |
| text or table data — never from visual shape estimation. | |
| </div> | |
| </> | |
| )} | |
| </Accordion> | |
| ); | |
| } | |