fh / src /utils /backendService.ts
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
11.6 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
*/
async uploadPreviousRFP(file: File, category: string): Promise<StoredDocument> {
const formData = new FormData();
formData.append('file', file);
formData.append('category', category);
const response = await fetch(`${API_BASE_URL}/upload-previous-rfp`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Upload failed');
}
return result.document;
}
/**
* 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, optionally filtered by category
*/
async getDocumentsForAI(categoryFilter?: string): Promise<Array<{
content: string;
source: string;
category: string;
pageNumber: number;
relevanceScore: number;
}>> {
try {
const documents = categoryFilter
? await this.getPreviousRFPsByCategory(categoryFilter)
: await this.getPreviousRFPs();
const documentsWithContent = await Promise.all(
documents.map(async (doc) => {
try {
const docWithContent = await this.getDocumentContent(doc.id);
return {
content: docWithContent.content,
source: doc.name,
category: doc.category || 'general',
pageNumber: 1, // Default page number, can be enhanced
relevanceScore: 1.0 // Default relevance score
};
} catch (error) {
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 [];
}
}
/**
* Search documents with semantic similarity
*/
async searchDocuments(query: string, options?: {
topK?: number;
categoryFilter?: string;
useSemanticSearch?: boolean;
}): Promise<Array<{
content: string;
source: string;
category: string;
relevanceScore: number;
matchedKeywords: string[];
}>> {
try {
// Import vector storage
const { vectorStorage } = await import('./vectorStorage');
// Check if we have documents in vector storage
const stats = vectorStorage.getStats();
if (stats.totalChunks === 0) {
console.log('Vector storage empty, populating with documents...');
await this.populateVectorStorage();
}
// Perform search
const searchMethod = options?.useSemanticSearch !== false
? vectorStorage.semanticSearch.bind(vectorStorage)
: vectorStorage.search.bind(vectorStorage);
const results = searchMethod(query, {
topK: options?.topK || 10,
categoryFilter: options?.categoryFilter
});
return results.map(result => ({
content: result.chunk.content,
source: result.chunk.metadata.source,
category: result.chunk.metadata.category || 'general',
relevanceScore: result.score,
matchedKeywords: result.matchedKeywords
}));
} catch (error) {
console.error('Search documents failed:', error);
// Fallback to basic retrieval
const basicResults = await this.getDocumentsForAI(options?.categoryFilter);
return basicResults.map(doc => ({
content: doc.content,
source: doc.source,
category: doc.category,
relevanceScore: doc.relevanceScore,
matchedKeywords: []
}));
}
}
/**
* Populate vector storage with all documents
*/
private async populateVectorStorage(): Promise<void> {
try {
const { vectorStorage } = await import('./vectorStorage');
const documents = await this.getPreviousRFPs();
console.log(`Populating vector storage with ${documents.length} documents...`);
for (const doc of documents) {
try {
const docWithContent = await this.getDocumentContent(doc.id);
await vectorStorage.addDocument(docWithContent.content, {
source: doc.name,
category: doc.category,
pageNumber: 1
});
} catch (error) {
console.warn(`Failed to add document ${doc.name} to vector storage:`, error);
}
}
const stats = vectorStorage.getStats();
console.log(`Vector storage populated: ${stats.totalChunks} chunks from ${stats.totalDocuments} documents`);
} catch (error) {
console.error('Failed to populate vector storage:', error);
}
}
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();