Spaces:
Running
Running
| /** | |
| * FinBot API Client | |
| * ================= | |
| * Connects the Next.js frontend to the IRIS API proxy. | |
| * The proxy forwards to FastAPI when available and serves validated cached | |
| * Investor Relations responses when the backend process is warming up. | |
| */ | |
| export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || '/api/proxy'; | |
| export interface Source { | |
| id: number; | |
| doc_name: string; | |
| page: number; | |
| support: string; | |
| image_url?: string; | |
| } | |
| export interface KPIRow { | |
| metric: string; | |
| current: string; | |
| previous: string; | |
| change: string; | |
| interpretation: string; | |
| direction: 'positive' | 'negative' | 'neutral'; | |
| period?: string; | |
| value?: string; | |
| } | |
| export interface Driver { | |
| title: string; | |
| detail: string; | |
| } | |
| export interface VisualItem { | |
| id: string; | |
| page: number; | |
| image_url: string; | |
| alt: string; | |
| } | |
| export interface ChatResponse { | |
| response_type: 'ir_response' | 'unsupported' | 'insufficient'; | |
| question: string; | |
| executive_summary?: string; | |
| sources: Source[]; | |
| financial_kpis: KPIRow[]; | |
| key_drivers_summary?: string; | |
| key_drivers: Driver[]; | |
| visual_evidence: VisualItem[]; | |
| latency_ms: number; | |
| model_used: string; | |
| } | |
| export interface Document { | |
| doc_id: string; | |
| name: string; | |
| doc_type: string; | |
| period: string; | |
| institution?: string; | |
| total_pages: number; | |
| status: string; | |
| filename: string; | |
| chunks_indexed?: number; | |
| tables_indexed?: number; | |
| colpali_pages?: number; | |
| pagemap_file?: string; | |
| page_section_map?: Record<string, string>; | |
| page_metadata_map?: Record<string, { | |
| slide: number; | |
| section: string; | |
| mapping_items: string[]; | |
| kpis: string[]; | |
| kpi_tags: string[]; | |
| description: string; | |
| period: string; | |
| synonyms: string[]; | |
| visual_layout: string; | |
| }>; | |
| retrieval_config?: string; | |
| } | |
| /** Check if the backend is reachable */ | |
| export async function checkBackendHealth(): Promise<boolean> { | |
| try { | |
| const res = await fetch(`${BACKEND_URL}/api/health`, { | |
| signal: AbortSignal.timeout(2000), | |
| }); | |
| return res.ok; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| /** Submit a question to the RAG pipeline */ | |
| export async function submitQuestion( | |
| question: string, | |
| docIds: string[], | |
| ): Promise<ChatResponse> { | |
| const res = await fetch(`${BACKEND_URL}/api/chat/query`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ question, doc_ids: docIds }), | |
| signal: AbortSignal.timeout(120_000), // ColPali can take time | |
| }); | |
| if (!res.ok) { | |
| const err = await res.text(); | |
| throw new Error(`Backend error ${res.status}: ${err}`); | |
| } | |
| return res.json(); | |
| } | |
| /** Get list of indexed documents */ | |
| export async function fetchDocuments(): Promise<Document[]> { | |
| const res = await fetch(`${BACKEND_URL}/api/documents/`, { | |
| signal: AbortSignal.timeout(5000), | |
| }); | |
| if (!res.ok) throw new Error('Failed to fetch documents'); | |
| return res.json(); | |
| } | |
| /** Get the URL for a page image (rendered PDF page) */ | |
| export function getPageImageUrl(docId: string, pageNumber: number): string { | |
| return `${BACKEND_URL}/api/visuals/${docId}/${pageNumber}`; | |
| } | |
| /** Normalize legacy static page URLs to the backend visual route. */ | |
| export function normalizePageImageUrl( | |
| imageUrl: string | undefined, | |
| fallbackDocId: string, | |
| fallbackPage: number, | |
| ): string { | |
| if (!imageUrl) { | |
| return getPageImageUrl(fallbackDocId, fallbackPage); | |
| } | |
| const legacyPageMatch = imageUrl.match( | |
| /\/pages\/([^/]+)\/pages\/page_(\d{4})(?:_[^/.]+)?\.png$/, | |
| ); | |
| if (legacyPageMatch) { | |
| return getPageImageUrl(legacyPageMatch[1], Number.parseInt(legacyPageMatch[2], 10)); | |
| } | |
| if (imageUrl.startsWith('/api/visuals/')) { | |
| return `${BACKEND_URL}${imageUrl}`; | |
| } | |
| if (imageUrl.startsWith('/')) { | |
| return `${BACKEND_URL}${imageUrl}`; | |
| } | |
| return imageUrl; | |
| } | |