feat: Complete Smart Resume Formatter with R2 and Gemini AI integration
Some checks failed
Profile Linker Docker Build / Build and push Docker image (push) Failing after 3s
Some checks failed
Profile Linker Docker Build / Build and push Docker image (push) Failing after 3s
- Integrated Cloudflare R2 for template storage and converted file management - Added Google Gemini AI for resume parsing and HTML generation - Created backend API endpoints for templates, conversion, and history - Refactored frontend to use real API instead of mock data - Fixed Docker networking issues (IPv6/IPv4) for R2 connectivity - Added resumeService.ts for frontend API integration - Updated Vite configuration for proper asset serving in Docker - Successfully tested with 13 templates from R2 bucket
This commit is contained in:
150
frontend/services/resumeService.ts
Normal file
150
frontend/services/resumeService.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Resume API Service
|
||||
* Handles all API calls to the backend for resume processing
|
||||
*/
|
||||
|
||||
const APP_NAME = import.meta.env.VITE_APP_NAME || 'resumeformatter';
|
||||
const API_BASE_URL = `/${APP_NAME}/api/resumes`;
|
||||
|
||||
export interface ConvertedFile {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
size: number;
|
||||
lastModified: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface ConvertResponse {
|
||||
success: boolean;
|
||||
html_url: string;
|
||||
html_content: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch list of available templates from R2
|
||||
*/
|
||||
export const fetchTemplates = async (): Promise<string[]> => {
|
||||
console.log('Fetching templates from API...');
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/templates`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('Templates loaded:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching templates:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch HTML content of a specific template
|
||||
*/
|
||||
export const fetchTemplateContent = async (templateName: string): Promise<string> => {
|
||||
console.log(`Fetching template content for: ${templateName}`);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/templates/${encodeURIComponent(templateName)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('Template content loaded');
|
||||
return data.content;
|
||||
} catch (error) {
|
||||
console.error('Error fetching template content:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a resume file using the specified template
|
||||
*/
|
||||
export const convertResume = async (
|
||||
file: File,
|
||||
templateName: string,
|
||||
onProgress?: (message: string) => void
|
||||
): Promise<ConvertResponse> => {
|
||||
console.log(`Converting resume: ${file.name} with template: ${templateName}`);
|
||||
|
||||
try {
|
||||
// Create form data
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('template_name', templateName);
|
||||
|
||||
onProgress?.('Uploading file to server...');
|
||||
|
||||
// Send request
|
||||
const response = await fetch(`${API_BASE_URL}/convert`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
throw new Error(errorData.detail || `API error: ${response.status}`);
|
||||
}
|
||||
|
||||
onProgress?.('Processing complete!');
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Conversion complete:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error converting resume:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch list of converted resumes from R2
|
||||
*/
|
||||
export const fetchConversionHistory = async (limit: number = 50): Promise<ConvertedFile[]> => {
|
||||
console.log('Fetching conversion history from API...');
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/history?limit=${limit}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// Transform data to match ConvertedFile interface
|
||||
const files: ConvertedFile[] = data.map((file: any) => ({
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
timestamp: new Date(file.lastModified),
|
||||
}));
|
||||
|
||||
console.log('Conversion history loaded:', files.length, 'files');
|
||||
return files;
|
||||
} catch (error) {
|
||||
console.error('Error fetching conversion history:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get download URL for a specific file
|
||||
*/
|
||||
export const getDownloadUrl = async (fileKey: string): Promise<string> => {
|
||||
console.log(`Getting download URL for: ${fileKey}`);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/download/${encodeURIComponent(fileKey)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('Download URL retrieved');
|
||||
return data.url;
|
||||
} catch (error) {
|
||||
console.error('Error getting download URL:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user