// Backend service for RFP Management System const API_BASE_URL = '/api'; export interface StoredDocument { id: string; name: string; category?: string; fileType: string; uploadDate: string; size?: number; contentLength?: number; processed?: boolean; } export interface CurrentRFPDocument { id: string; name: string; fileType: string; uploadDate: string; contentLength?: number; processed?: boolean; } export interface DocumentWithContent { id: string; name: string; category?: string; fileType: string; uploadDate: string; content: string; } class BackendService { /** * Upload a previous RFP document to backend storage with enhanced processing */ async uploadPreviousRFP(file: File, category: string = 'general'): Promise { const formData = new FormData(); formData.append('file', file); formData.append('category', category); formData.append('enhanced_processing', 'true'); // Request enhanced AI processing try { const response = await fetch(`${API_BASE_URL}/upload-previous-rfp`, { method: 'POST', body: formData }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Upload failed: ${response.statusText} - ${errorText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Upload failed'); } console.log('✅ Previous RFP uploaded successfully:', result.document.name); return result.document; } catch (error) { console.error('❌ Previous RFP upload failed:', error); throw error; } } /** * Get all previous RFP documents */ async getPreviousRFPs(): Promise { const response = await fetch(`${API_BASE_URL}/previous-rfps`); if (!response.ok) { throw new Error(`Failed to fetch documents: ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Failed to fetch documents'); } return result.documents; } /** * Get document content by ID */ async getDocumentContent(id: string): Promise { const response = await fetch(`${API_BASE_URL}/document/${id}/content`); if (!response.ok) { throw new Error(`Failed to fetch document content: ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Failed to fetch document content'); } return { ...result.document, content: result.content }; } /** * Delete a document */ async deleteDocument(id: string): Promise { const response = await fetch(`${API_BASE_URL}/document/${id}`, { method: 'DELETE' }); if (!response.ok) { throw new Error(`Failed to delete document: ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Failed to delete document'); } } /** * Get documents filtered by category */ async getPreviousRFPsByCategory(category: string): Promise { const allDocuments = await this.getPreviousRFPs(); return allDocuments.filter(doc => doc.category === category); } /** * Get all document contents for AI processing with enhanced relevance scoring */ async getDocumentsForAI(categoryFilter?: string): Promise> { try { console.log(`🔍 Fetching documents for AI processing${categoryFilter ? ` (category: ${categoryFilter})` : ''}`); const documents = categoryFilter ? await this.getPreviousRFPsByCategory(categoryFilter) : await this.getPreviousRFPs(); console.log(`📄 Found ${documents.length} documents to process`); const documentsWithContent = await Promise.all( documents.map(async (doc) => { try { const docWithContent = await this.getDocumentContent(doc.id); // Enhanced content processing for better AI accuracy const processedContent = this.preprocessContentForAI(docWithContent.content); const qualityScore = this.calculateDocumentQuality(processedContent); return { content: processedContent, source: doc.name, category: doc.category || 'general', pageNumber: 1, // Could be enhanced with actual page detection relevanceScore: qualityScore }; } catch (error) { console.warn(`⚠️ Failed to load content for document ${doc.name}:`, error); return null; } }) ); const validDocuments = documentsWithContent .filter(doc => doc !== null) .filter(doc => doc!.content.length > 100); // Filter out very short documents console.log(`✅ Successfully processed ${validDocuments.length} documents for AI`); return validDocuments as Array<{ content: string; source: string; category: string; pageNumber: number; relevanceScore: number; }>; } catch (error) { console.error('❌ Error fetching documents for AI:', error); return []; } } /** * Preprocess document content for better AI analysis */ private preprocessContentForAI(content: string): string { // Remove excessive whitespace and normalize line breaks let processed = content.replace(/\s+/g, ' ').trim(); // Remove common PDF artifacts processed = processed.replace(/\f/g, ' '); // Form feed characters processed = processed.replace(/[\u0000-\u001F\u007F-\u009F]/g, ' '); // Control characters // Normalize quotes and dashes processed = processed.replace(/[""]/g, '"'); processed = processed.replace(/['']/g, "'"); processed = processed.replace(/[–—]/g, '-'); return processed; } /** * Calculate document quality score for relevance ranking */ private calculateDocumentQuality(content: string): number { let score = 0.5; // Base score // Length bonus (longer documents often have more context) const wordCount = content.split(/\s+/).length; if (wordCount > 5000) score += 0.3; else if (wordCount > 2000) score += 0.2; else if (wordCount > 500) score += 0.1; // Structure indicators if (/\d+\.|•|▪|◦/.test(content)) score += 0.1; // Has bullet points or numbering if (/section|chapter|appendix/gi.test(content)) score += 0.1; // Has sections // Business content indicators const businessTerms = [ 'experience', 'services', 'approach', 'methodology', 'team', 'qualifications', 'implementation', 'project', 'deliverable', 'timeline', 'budget', 'compliance' ]; const termMatches = businessTerms.filter(term => content.toLowerCase().includes(term) ).length; score += Math.min(0.2, termMatches * 0.02); return Math.min(1.0, score); } console.warn(`Failed to get content for document ${doc.name}:`, error); return null; } }) ); return documentsWithContent.filter(doc => doc !== null) as Array<{ content: string; source: string; category: string; pageNumber: number; relevanceScore: number; }>; } catch (error) { console.error('Failed to get documents for AI:', error); return []; } } async healthCheck(): Promise { try { const response = await fetch(`${API_BASE_URL}/health`); return response.ok; } catch { return false; } } /** * Format answer using Ollama AI */ async formatAnswer(question: string, rawAnswer: string, context?: string): Promise<{ success: boolean; formattedAnswer: string; originalAnswer: string; }> { try { console.log('🔧 BackendService.formatAnswer called with:', { questionLength: question?.length || 0, answerLength: rawAnswer?.length || 0, context: context || 'none' }); const requestBody = { question, rawAnswer, context }; console.log('📤 Making request to:', `${API_BASE_URL}/format-answer`); console.log('📦 Request body:', requestBody); const response = await fetch(`${API_BASE_URL}/format-answer`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); console.log('📥 Response status:', response.status, response.statusText); if (!response.ok) { const errorText = await response.text(); console.error('❌ Response not ok:', errorText); throw new Error(`Format answer failed: ${response.statusText} - ${errorText}`); } const result = await response.json(); console.log('✅ Format result:', result); return { success: result.success, formattedAnswer: result.formattedAnswer || rawAnswer, originalAnswer: result.originalAnswer || rawAnswer }; } catch (error) { console.error('❌ Failed to format answer:', error); if (error instanceof Error) { console.error('❌ Error details:', { name: error.name, message: error.message, stack: error.stack }); } // Return original answer as fallback return { success: false, formattedAnswer: rawAnswer, originalAnswer: rawAnswer }; } } /** * Generate a comprehensive 1500+ word answer across all reference documents */ async comprehensiveAnswer(question: string): Promise<{ success: boolean; question: string; generatedAnswer: string; wordCount: number; citations: Array<{ citation: string; raw: string }>; sourceDocuments?: Array<{ id: number; file: string }>; error?: string; }> { try { const response = await fetch(`${API_BASE_URL}/generate-comprehensive-rfp`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question }) }); if (!response.ok) { const text = await response.text(); throw new Error(`Comprehensive generation failed: ${response.status} ${text}`); } const result = await response.json(); if (!result.success) { return { success: false, question, generatedAnswer: result.generatedAnswer || 'NO ANSWER GENERATED', wordCount: result.wordCount || 0, citations: result.citations || [], sourceDocuments: result.sourceDocuments, error: result.error || 'Comprehensive generation failed' }; } return { success: true, question: result.question, generatedAnswer: result.generatedAnswer, wordCount: result.wordCount, citations: result.citations || [], sourceDocuments: result.sourceDocuments }; } catch (error) { console.error('❌ Comprehensive answer error:', error); return { success: false, question, generatedAnswer: 'Failed to generate comprehensive answer.', wordCount: 0, citations: [], error: error instanceof Error ? error.message : 'Unknown error' }; } } } export const backendService = new BackendService();