fh / src /utils /backendService.ts.broken
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
12.1 kB
// 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<StoredDocument> {
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<StoredDocument[]> {
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<DocumentWithContent> {
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<void> {
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<StoredDocument[]> {
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<Array<{
content: string;
source: string;
category: string;
pageNumber: number;
relevanceScore: number;
}>> {
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<boolean> {
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();