rajvivan's picture
fix: make backend routes resilient on Hugging Face
c86876a verified
Raw
History Blame Contribute Delete
3.81 kB
/**
* 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;
}