import json import logging from datetime import datetime from typing import Dict, List, Optional, Any import gradio as gr from openai import AsyncOpenAI import PyPDF2 import io # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) CONVERSATION_PROMPT = """You are LOSS DOG, a professional profile builder. Your goal is to have natural conversations with users to gather information about their professional background across 9 categories: 1. Work History & Experience 2. Salary & Compensation 3. Skills & Certifications 4. Education & Learning 5. Personal Branding & Online Presence 6. Achievements & Awards 7. Social Proof & Networking 8. Project Contributions & Leadership 9. Work Performance & Impact Metrics Be friendly and conversational. Ask follow-up questions naturally. When appropriate, guide users to share more details but respect their boundaries. Once you believe you have gathered sufficient information (or if the user indicates they have nothing more to share), let them know they can click 'Generate Profile' to proceed. """ EXTRACTION_PROMPT = """You are a professional information extraction system. Your task is to extract information from the potentially unstructure conversation and return ONLY a valid JSON object. Proactively determine how to fill the json schema using limited information provided. Do not include any explanatory text before or after the JSON. Return the data in this exact structure: { "work_history_experience": { "positions": [ { "title": string, "company": string, "industry": string, "location": string, "employment_type": string, "adaptability": { "career_shifts": [], "upskilling": [] }, "promotions": [], "confidence": number } ] }, "salary_compensation": { "history": [ { "base_salary": number, "bonus_structure": string, "stock_options": { "type": string, "details": string }, "commission": null, "benefits": { "health": string, "pto": string, "retirement": string, "other": [] }, "confidence": number } ] }, "skills_certifications": { "hard_skills": [], "soft_skills": [], "certifications": [], "licenses": [] }, "education_learning": { "formal_education": [], "online_courses": [], "executive_education": [] }, "personal_branding": { "portfolio": { "github": null, "behance": null, "other": [] }, "blog_posts": [], "blockchain_projects": { "nfts": [], "defi": [], "dapps": [] }, "social_media": { "platforms": [], "influence_metrics": {} } }, "achievements_awards": { "industry_awards": [], "hackathons": [], "peer_endorsements": [], "creative_projects": { "ai_art": [], "other": [] } }, "social_proof_networking": { "mentors": [], "references": [], "memberships": [], "conference_engagement": [] }, "project_contributions": { "major_projects": [], "open_source": [], "team_leadership": [], "patents": [], "impact": { "description": string, "metrics": [], "confidence": number } }, "work_performance_metrics": { "kpis": [], "revenue_impact": [], "efficiency_gains": [], "career_growth": [], "leadership_influence": [] } } IMPORTANT: Return ONLY the JSON. Do not add any explanation text.""" import json import logging from datetime import datetime from typing import Dict, List, Optional, Any import gradio as gr from openai import AsyncOpenAI import PyPDF2 import io # ... (previous imports and prompts remain the same) class ProfileBuilder: def __init__(self): self.conversation_history = [] self.client = None def _initialize_client(self, api_key: str) -> None: if not api_key.startswith("sk-"): raise ValueError("Invalid API key format") self.client = AsyncOpenAI(api_key=api_key) async def extract_from_pdf(self, pdf_content: bytes) -> str: """Extract text from PDF file""" try: pdf_file = io.BytesIO(pdf_content) pdf_reader = PyPDF2.PdfReader(pdf_file) text = "" for page in pdf_reader.pages: text += page.extract_text() return text except Exception as e: logger.error(f"Error extracting PDF: {str(e)}") raise async def process_pdf(self, pdf_path: str, api_key: str) -> Dict[str, Any]: """Process PDF resume and extract information""" try: if not self.client: self._initialize_client(api_key) with open(pdf_path, 'rb') as file: pdf_content = file.read() resume_text = await self.extract_from_pdf(pdf_content) # Use the extraction prompt directly on PDF content completion = await self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": EXTRACTION_PROMPT}, {"role": "user", "content": f"Extract profile information from this resume:\n\n{resume_text}"} ], temperature=0.3 ) response_text = completion.choices[0].message.content.strip() profile_data = json.loads(response_text) return { "profile_data": profile_data, "metadata": { "generated_at": datetime.now().isoformat(), "source": "pdf_resume" } } except Exception as e: logger.error(f"Error processing PDF: {str(e)}") return {"error": str(e)} # ... (rest of the ProfileBuilder class remains the same) def create_gradio_interface(): builder = ProfileBuilder() with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# 🐕 LOSS DOG - Professional Profile Builder") api_key = gr.Textbox( label="OpenAI API Key", type="password", placeholder="Enter your OpenAI API key" ) with gr.Tabs() as tabs: with gr.Tab("Upload Resume"): upload_text = gr.Markdown(""" # Upload Your Resume Upload your existing resume in PDF format and let LOSS DOG extract your professional profile. """) pdf_file = gr.File( label="Upload PDF Resume", file_types=[".pdf"] ) process_pdf_btn = gr.Button("Process Resume") with gr.Tab("Chat with AI"): chat_text = gr.Markdown(""" # Chat with LOSS DOG Start a conversation with LOSS DOG to build your professional profile from scratch. """) chatbot = gr.Chatbot(label="Conversation") with gr.Row(): msg = gr.Textbox( label="Message", placeholder="Chat with LOSS DOG..." ) send = gr.Button("Send") with gr.Column(): generate_btn = gr.Button("Generate Profile") profile_output = gr.JSON(label="Generated Profile") download_btn = gr.File(label="Download Profile") async def on_pdf_upload(pdf, key): if not pdf: return {"error": "No PDF file uploaded"} try: result = await builder.process_pdf(pdf.name, key) if "error" in result: return {"error": result["error"]}, None # Save profile timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"profile_{timestamp}.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(result, f, indent=2) return result["profile_data"], filename except Exception as e: return {"error": str(e)}, None # Event handlers remain the same for chat functionality async def on_message(message: str, history: List[List[str]], key: str): if not message.strip(): return history, None result = await builder.process_message(message, key) if "error" in result: return history, {"error": result["error"]} history = history + [[message, result["response"]]] return history, None async def on_generate(): profile, filename = await builder.generate_profile() if "error" in profile: return profile, None return profile["profile_data"], filename # Bind events msg.submit( on_message, inputs=[msg, chatbot, api_key], outputs=[chatbot, profile_output] ).then(lambda: "", None, msg) send.click( on_message, inputs=[msg, chatbot, api_key], outputs=[chatbot, profile_output] ).then(lambda: "", None, msg) process_pdf_btn.click( on_pdf_upload, inputs=[pdf_file, api_key], outputs=[profile_output, download_btn] ) generate_btn.click( on_generate, outputs=[profile_output, download_btn] ) return demo if __name__ == "__main__": demo = create_gradio_interface() demo.queue() demo.launch(server_name="0.0.0.0", server_port=7860)