/** * Example client for integrating with the Hono proxy from Next.js * Place this in your Next.js app at: lib/api-client.ts */ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api'; export interface SearchResult { root: { children: Array<{ id: string; relevance: number; fields: { id: string; title: string; page_number: number; text: string; image: string; // base64 image_url: string; // Added by proxy full_image_url: string; // Added by proxy }; }>; }; } export interface ChatMessage { role: 'user' | 'assistant' | 'system'; content: string; } class ColPaliClient { private async fetchWithTimeout(url: string, options: RequestInit, timeout = 30000) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal, }); return response; } finally { clearTimeout(id); } } async search(query: string, limit = 10): Promise { const response = await this.fetchWithTimeout(`${API_URL}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, limit }), }); if (!response.ok) { throw new Error(`Search failed: ${response.statusText}`); } return response.json(); } async* chat(messages: ChatMessage[], context: string[] = []) { const response = await fetch(`${API_URL}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages, context }), }); if (!response.ok) { throw new Error(`Chat failed: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) throw new Error('No response body'); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') return; try { const parsed = JSON.parse(data); yield parsed; } catch (e) { console.error('Failed to parse SSE data:', e); } } } } } async getSimilarityMap(docId: string, query: string) { const response = await this.fetchWithTimeout(`${API_URL}/search/similarity-map`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ docId, query }), }); if (!response.ok) { throw new Error(`Similarity map failed: ${response.statusText}`); } return response.json(); } getImageUrl(docId: string, type: 'thumbnail' | 'full' = 'thumbnail'): string { return `${API_URL}/search/image/${docId}/${type}`; } async checkHealth() { const response = await this.fetchWithTimeout(`${API_URL.replace('/api', '')}/health`, { method: 'GET', }, 5000); return response.json(); } } // Export singleton instance export const colpaliClient = new ColPaliClient(); // Usage examples: /* // In your Next.js component or API route: // Search const results = await colpaliClient.search('annual report 2023', 20); // Display images directly from proxy URLs results.root.children.forEach(hit => { const imageUrl = hit.fields.image_url; // Proxy URL for thumbnail const fullImageUrl = hit.fields.full_image_url; // Proxy URL for full image }); // Chat with streaming const messages = [{ role: 'user', content: 'What is the revenue?' }]; for await (const chunk of colpaliClient.chat(messages)) { console.log(chunk); } // Get image URL for direct use in tags const imageUrl = colpaliClient.getImageUrl('doc123', 'thumbnail'); // Check system health const health = await colpaliClient.checkHealth(); */