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:
135
backend/app/services/ai_service.py
Normal file
135
backend/app/services/ai_service.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Gemini AI Service
|
||||
Handles resume text extraction and HTML generation
|
||||
"""
|
||||
import google.generativeai as genai
|
||||
from typing import Optional
|
||||
import base64
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
class AIService:
|
||||
"""Service for interacting with Google Gemini AI"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize Gemini AI with API key"""
|
||||
if not settings.GEMINI_API_KEY:
|
||||
raise ValueError("GEMINI_API_KEY not configured")
|
||||
genai.configure(api_key=settings.GEMINI_API_KEY)
|
||||
self.model = genai.GenerativeModel('gemini-2.0-flash-exp')
|
||||
|
||||
async def extract_text_from_resume(
|
||||
self,
|
||||
file_content: bytes,
|
||||
mime_type: str
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Extract text from resume file using Gemini Vision
|
||||
Args:
|
||||
file_content: File content as bytes
|
||||
mime_type: MIME type of the file (application/pdf or application/vnd.openxmlformats-officedocument.wordprocessingml.document)
|
||||
Returns: Extracted text or None if failed
|
||||
"""
|
||||
try:
|
||||
# Convert bytes to base64
|
||||
base64_data = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
prompt = """Extract all text from this resume document.
|
||||
Preserve the original structure, including sections, headings, bullet points, and line breaks, as plain text.
|
||||
Focus on maintaining the hierarchical structure of the content."""
|
||||
|
||||
response = self.model.generate_content([
|
||||
{
|
||||
'mime_type': mime_type,
|
||||
'data': base64_data
|
||||
},
|
||||
prompt
|
||||
])
|
||||
|
||||
return response.text
|
||||
except Exception as e:
|
||||
print(f"Error extracting text from resume: {e}")
|
||||
return None
|
||||
|
||||
async def generate_html_from_template(
|
||||
self,
|
||||
resume_text: str,
|
||||
template_html: str
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Generate formatted HTML by merging resume content with template
|
||||
Args:
|
||||
resume_text: Extracted resume text
|
||||
template_html: HTML template content
|
||||
Returns: Generated HTML or None if failed
|
||||
"""
|
||||
try:
|
||||
prompt = self._build_generation_prompt(resume_text, template_html)
|
||||
|
||||
response = self.model.generate_content(prompt)
|
||||
|
||||
# Clean up the response (remove code blocks if present)
|
||||
html_content = response.text.strip()
|
||||
if html_content.startswith('```html'):
|
||||
html_content = html_content[7:] # Remove ```html
|
||||
if html_content.endswith('```'):
|
||||
html_content = html_content[:-3] # Remove ```
|
||||
|
||||
return html_content.strip()
|
||||
except Exception as e:
|
||||
print(f"Error generating HTML: {e}")
|
||||
return None
|
||||
|
||||
def _build_generation_prompt(self, resume_text: str, template_html: str) -> str:
|
||||
"""Build the prompt for HTML generation"""
|
||||
instructions = """### 🎯 EXACT TEMPLATE PRESERVATION INSTRUCTIONS:
|
||||
|
||||
**🚨 RULE #1: COPY TEMPLATE EXACTLY - NO STRUCTURAL CHANGES! 🚨**
|
||||
**🚨 RULE #2: ONLY REPLACE PLACEHOLDER TEXT - NOTHING ELSE! 🚨**
|
||||
|
||||
**YOU ARE A FIND-AND-REPLACE TOOL - NOT A DESIGNER!**
|
||||
|
||||
**SIMPLE 3-STEP PROCESS:**
|
||||
1. **COPY**: Take the entire HTML template (every character from <!DOCTYPE to </html>).
|
||||
2. **FIND**: Locate placeholder text in the template (like "{{name}}", "John Doe", "Software Engineer", "2020-2023", etc.).
|
||||
3. **REPLACE**: Replace ONLY that placeholder text with the user's corresponding information.
|
||||
|
||||
**WHAT TO REPLACE:**
|
||||
- Names, contact info
|
||||
- Job titles, companies, dates, descriptions
|
||||
- Education details
|
||||
- Skills lists
|
||||
|
||||
**WHAT TO NEVER CHANGE:**
|
||||
- HTML tags (div, p, h1, etc.), CSS classes, IDs, or any inline styles.
|
||||
- The overall HTML structure, layout, nesting, alignment, spacing, colors, and fonts.
|
||||
|
||||
**FOR EXTRA USER CONTENT:**
|
||||
If the user's resume has sections not present in the template (e.g., 'Projects', 'Certifications'):
|
||||
- Find a similar section in the template (e.g., 'Experience').
|
||||
- Copy that section's HTML structure.
|
||||
- Add it at a logical place (usually at the end) with the user's content.
|
||||
- Reuse the same CSS classes and styling patterns to maintain consistency.
|
||||
|
||||
**CRITICAL:** Ensure ALL information from the user's resume is included in the final HTML. Do not omit any details.
|
||||
"""
|
||||
|
||||
return f"""You are an expert HTML resume generator. Your task is to take the user's resume content and perfectly merge it into the provided company HTML template by acting as a precise find-and-replace tool.
|
||||
|
||||
**User's Resume Content:**
|
||||
---
|
||||
{resume_text}
|
||||
---
|
||||
|
||||
**Company HTML Template:**
|
||||
---
|
||||
{template_html}
|
||||
---
|
||||
|
||||
{instructions}
|
||||
|
||||
Now, generate the final, complete HTML file. Your entire output must be only the HTML code, starting with `<!DOCTYPE html>` and ending with `</html>`. Do not include any explanations or surrounding text."""
|
||||
|
||||
|
||||
# Singleton instance
|
||||
ai_service = AIService()
|
||||
Reference in New Issue
Block a user