| | import os |
| | import sys |
| | import json |
| | import time |
| | import gradio as gr |
| | import google.generativeai as genai |
| |
|
| | from intent_detector import detect_intent |
| | from rag_utils import retrieve_profiles, retrieve_jobs |
| |
|
| | |
| | API_KEY = os.getenv("GOOGLE_API_KEY") |
| | if not API_KEY: |
| | raise ValueError("Error: Set GOOGLE_API_KEY environment variable before running.") |
| | genai.configure(api_key=API_KEY) |
| |
|
| | |
| | SYSTEM_PROMPT = """ |
| | You are a helpful AI assistant that can perform three main tasks: |
| | |
| | 1. ONBOARD – Help professionals create their profile by asking questions ONE AT A TIME in a natural conversation flow |
| | 2. SEARCH – Help find job opportunities based on user requirements |
| | 3. POST – Help clients create structured job postings |
| | |
| | ONBOARDING CONVERSATION FLOW: |
| | When onboarding a user, ask questions in this specific order, ONE QUESTION AT A TIME: |
| | 1. First, ask for their full name |
| | 2. Then ask what their current role/job title is |
| | 3. Then ask about their key skills (programming languages, technologies, etc.) |
| | 4. Then ask about their years of experience |
| | 5. Then ask about their preferred location or if they want remote work |
| | 6. Finally, summarize their profile and confirm if everything looks correct |
| | |
| | IMPORTANT ONBOARDING RULES: |
| | - Ask ONLY ONE question at a time |
| | - Wait for the user's response before asking the next question |
| | - Be conversational and friendly |
| | - If they provide multiple pieces of information at once, acknowledge what they shared and ask for what's still missing |
| | - Keep questions simple and clear |
| | - After collecting all information, show a summary and ask for confirmation |
| | |
| | SEARCH BEHAVIOR: |
| | For job searches, retrieve relevant listings and present them clearly with title, company, and key details. |
| | |
| | POST BEHAVIOR: |
| | For job postings, help create structured JSON format job listings. |
| | |
| | Always be helpful, conversational, and ask follow-up questions when needed. |
| | """ |
| |
|
| | class ChatbotState: |
| | """Manages the conversation state for onboarding flow.""" |
| | |
| | def __init__(self): |
| | self.reset() |
| | |
| | def reset(self): |
| | """Reset the conversation state.""" |
| | self.mode = None |
| | self.onboarding_step = 0 |
| | self.user_data = {} |
| | self.onboarding_complete = False |
| | |
| | def is_onboarding(self): |
| | """Check if currently in onboarding mode.""" |
| | return self.mode == "onboarding" and not self.onboarding_complete |
| | |
| | def get_next_onboarding_question(self): |
| | """Get the next question in the onboarding flow.""" |
| | questions = [ |
| | "What's your full name?", |
| | "What's your current role or job title?", |
| | "What are your key skills? (For example: Python, React, project management, etc.)", |
| | "How many years of experience do you have in your field?", |
| | "What's your preferred work location? (Or would you prefer remote work?)" |
| | ] |
| | |
| | if self.onboarding_step < len(questions): |
| | return questions[self.onboarding_step] |
| | return None |
| | |
| | def save_onboarding_data(self, field, value): |
| | """Save user response to onboarding data.""" |
| | fields = ["name", "role", "skills", "experience", "location"] |
| | if self.onboarding_step < len(fields): |
| | self.user_data[fields[self.onboarding_step]] = value |
| | |
| | def get_onboarding_summary(self): |
| | """Generate a summary of collected onboarding data.""" |
| | summary = "Here's your profile summary:\n" |
| | summary += f"• Name: {self.user_data.get('name', 'Not provided')}\n" |
| | summary += f"• Role: {self.user_data.get('role', 'Not provided')}\n" |
| | summary += f"• Skills: {self.user_data.get('skills', 'Not provided')}\n" |
| | summary += f"• Experience: {self.user_data.get('experience', 'Not provided')}\n" |
| | summary += f"• Location: {self.user_data.get('location', 'Not provided')}\n" |
| | summary += "\nDoes this look correct? (yes/no)" |
| | return summary |
| |
|
| | def save_user_profile(user_data, filename="data/user_profiles.jsonl"): |
| | """Save user profile to file.""" |
| | os.makedirs(os.path.dirname(filename), exist_ok=True) |
| | |
| | |
| | user_id = f"user_{int(time.time())}" |
| | |
| | profile_entry = { |
| | "id": user_id, |
| | "name": user_data.get("name", ""), |
| | "role": user_data.get("role", ""), |
| | "skills": user_data.get("skills", "").split(", ") if user_data.get("skills") else [], |
| | "experience": user_data.get("experience", ""), |
| | "location": user_data.get("location", ""), |
| | "timestamp": time.time() |
| | } |
| | |
| | try: |
| | with open(filename, "a", encoding="utf-8") as f: |
| | f.write(json.dumps(profile_entry) + "\n") |
| | return True |
| | except Exception as e: |
| | print(f"Error saving profile: {e}") |
| | return False |
| |
|
| | |
| | chatbot_state = ChatbotState() |
| | model = None |
| |
|
| | def initialize_model(model_name="gemini-1.5-flash"): |
| | """Initialize the Gemini model.""" |
| | global model |
| | model = genai.GenerativeModel(model_name) |
| | return model |
| |
|
| | def process_message(message, history, model_name): |
| | """Process user message and return bot response.""" |
| | global chatbot_state, model |
| | |
| | if model is None: |
| | initialize_model(model_name) |
| | |
| | try: |
| | |
| | if chatbot_state.is_onboarding(): |
| | |
| | if chatbot_state.onboarding_step < 5: |
| | |
| | chatbot_state.save_onboarding_data(None, message) |
| | chatbot_state.onboarding_step += 1 |
| | |
| | |
| | if chatbot_state.onboarding_step < 5: |
| | next_question = chatbot_state.get_next_onboarding_question() |
| | return f"Great! {next_question}" |
| | else: |
| | |
| | summary = chatbot_state.get_onboarding_summary() |
| | return summary |
| | |
| | elif chatbot_state.onboarding_step == 5: |
| | if message.lower() in ["yes", "y", "correct", "looks good"]: |
| | |
| | if save_user_profile(chatbot_state.user_data): |
| | response = "Perfect! Your profile has been saved successfully. You're now onboarded and ready to search for jobs! 🎉" |
| | else: |
| | response = "Your profile information has been recorded. You're now onboarded! 🎉" |
| | |
| | chatbot_state.onboarding_complete = True |
| | chatbot_state.mode = None |
| | return response |
| | |
| | elif message.lower() in ["no", "n", "incorrect", "wrong"]: |
| | chatbot_state.reset() |
| | chatbot_state.mode = "onboarding" |
| | chatbot_state.onboarding_step = 0 |
| | return "No problem! Let's start over. What's your full name?" |
| | |
| | else: |
| | return "Please answer 'yes' if the information is correct, or 'no' if you'd like to start over." |
| |
|
| | |
| | intent = detect_intent(message, model=model_name) |
| | print(f"[Debug] Detected intent: {intent}") |
| |
|
| | |
| | if intent == "ONBOARD": |
| | |
| | chatbot_state.reset() |
| | chatbot_state.mode = "onboarding" |
| | |
| | |
| | first_question = chatbot_state.get_next_onboarding_question() |
| | return f"I'd be happy to help you create your professional profile! Let's start with some basic information.\n\n{first_question}" |
| |
|
| | elif intent == "SEARCH": |
| | |
| | context_block = None |
| | job_results = retrieve_jobs(message, top_k=3) |
| | |
| | if job_results: |
| | lines = [] |
| | for idx, job in enumerate(job_results, start=1): |
| | lines.append(f"{idx}) {job['text']}") |
| | block_text = "\n".join(lines) |
| | context_block = f"Relevant job listings:\n{block_text}" |
| | print(f"[Debug] Found {len(job_results)} relevant jobs") |
| | else: |
| | print("[Debug] No relevant jobs found") |
| |
|
| | |
| | search_prompt = f""" |
| | {SYSTEM_PROMPT} |
| | |
| | {"Context Information: " + context_block if context_block else "No relevant jobs found in the database."} |
| | |
| | User is looking for jobs: {message} |
| | |
| | Please provide a helpful response about job opportunities. If jobs were found, present them clearly. If no jobs were found, provide helpful guidance on how they might refine their search. |
| | """ |
| |
|
| | response = model.generate_content(search_prompt) |
| | return response.text |
| |
|
| | elif intent == "POST": |
| | |
| | post_prompt = f""" |
| | {SYSTEM_PROMPT} |
| | |
| | User wants to create a job posting: {message} |
| | |
| | Help them create a structured job posting. Ask for any missing information needed to create a complete job post (title, description, required skills, budget, timeline, etc.). |
| | """ |
| |
|
| | response = model.generate_content(post_prompt) |
| | return response.text |
| |
|
| | else: |
| | |
| | general_prompt = f""" |
| | {SYSTEM_PROMPT} |
| | |
| | User message: {message} |
| | |
| | Respond helpfully. If they seem to want to get started with onboarding, job searching, or job posting, guide them appropriately. |
| | """ |
| |
|
| | response = model.generate_content(general_prompt) |
| | return response.text |
| |
|
| | except Exception as e: |
| | return f"Sorry, I encountered an error: {str(e)}. Please try again!" |
| |
|
| | def reset_conversation(): |
| | """Reset the conversation state.""" |
| | global chatbot_state |
| | chatbot_state.reset() |
| | return [], "" |
| |
|
| | def get_welcome_message(): |
| | """Get the initial welcome message.""" |
| | return """👋 **Welcome to AI Job Assistant!** |
| | |
| | I can help you with: |
| | • 🎯 **Creating your professional profile** - Let me guide you through a quick onboarding |
| | • 🔍 **Finding job opportunities** - Search for jobs that match your skills |
| | • 📝 **Creating job postings** - Help employers post structured job listings |
| | |
| | What would you like to do today?""" |
| |
|
| | |
| | custom_css = """ |
| | .gradio-container { |
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | } |
| | |
| | .chat-message { |
| | padding: 1rem; |
| | margin: 0.5rem 0; |
| | border-radius: 1rem; |
| | max-width: 80%; |
| | } |
| | |
| | .user-message { |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | margin-left: auto; |
| | } |
| | |
| | .bot-message { |
| | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | color: white; |
| | } |
| | |
| | /* Dark mode support */ |
| | .dark .chat-message { |
| | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); |
| | } |
| | |
| | .gradio-chatbot { |
| | height: 500px; |
| | } |
| | |
| | /* Custom button styles */ |
| | .primary-button { |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | border: none; |
| | color: white; |
| | padding: 0.5rem 1rem; |
| | border-radius: 0.5rem; |
| | font-weight: 500; |
| | transition: all 0.3s ease; |
| | } |
| | |
| | .primary-button:hover { |
| | transform: translateY(-2px); |
| | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); |
| | } |
| | """ |
| |
|
| | def create_interface(): |
| | """Create the Gradio interface.""" |
| | |
| | with gr.Blocks( |
| | css=custom_css, |
| | theme=gr.themes.Soft( |
| | primary_hue="blue", |
| | secondary_hue="purple", |
| | neutral_hue="slate", |
| | ), |
| | title="AI Job Assistant" |
| | ) as interface: |
| | |
| | gr.HTML(""" |
| | <div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 1rem; margin-bottom: 2rem;"> |
| | <h1 style="margin: 0; font-size: 2.5rem; font-weight: bold;">🤖 AI Job Assistant</h1> |
| | <p style="margin: 0.5rem 0 0 0; font-size: 1.2rem; opacity: 0.9;">Your intelligent career companion</p> |
| | </div> |
| | """) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=4): |
| | chatbot = gr.Chatbot( |
| | value=[{"role": "assistant", "content": get_welcome_message()}], |
| | height=500, |
| | show_label=False, |
| | container=True, |
| | avatar_images=("👤", "🤖"), |
| | type="messages" |
| | ) |
| | |
| | with gr.Row(): |
| | msg = gr.Textbox( |
| | placeholder="Type your message here...", |
| | show_label=False, |
| | scale=4, |
| | container=False |
| | ) |
| | send_btn = gr.Button( |
| | "Send", |
| | variant="primary", |
| | scale=1, |
| | elem_classes=["primary-button"] |
| | ) |
| | |
| | with gr.Column(scale=1, min_width=250): |
| | gr.HTML(""" |
| | <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 1.5rem; border-radius: 1rem; margin-bottom: 1rem;"> |
| | <h3 style="margin: 0 0 1rem 0;">🚀 Quick Actions</h3> |
| | <div style="font-size: 0.9rem; line-height: 1.6;"> |
| | <div style="margin-bottom: 0.8rem;"> |
| | <strong>💼 Get Started:</strong><br> |
| | "I want to create my profile" |
| | </div> |
| | <div style="margin-bottom: 0.8rem;"> |
| | <strong>🔍 Find Jobs:</strong><br> |
| | "Show me Python developer jobs" |
| | </div> |
| | <div> |
| | <strong>📝 Post Job:</strong><br> |
| | "I want to post a job opening" |
| | </div> |
| | </div> |
| | </div> |
| | """) |
| | |
| | model_selector = gr.Dropdown( |
| | choices=["gemini-1.5-flash", "gemini-1.5-pro"], |
| | value="gemini-1.5-flash", |
| | label="🧠 AI Model", |
| | info="Choose your preferred AI model" |
| | ) |
| | |
| | clear_btn = gr.Button( |
| | "🗑️ Clear Chat", |
| | variant="secondary", |
| | size="sm" |
| | ) |
| | |
| | gr.HTML(""" |
| | <div style="background: rgba(102, 126, 234, 0.1); padding: 1rem; border-radius: 0.5rem; margin-top: 1rem;"> |
| | <h4 style="margin: 0 0 0.5rem 0; color: #667eea;">ℹ️ Tips</h4> |
| | <ul style="margin: 0; padding-left: 1.2rem; font-size: 0.85rem; color: #666;"> |
| | <li>Be specific about your skills and preferences</li> |
| | <li>Use natural language - I understand context!</li> |
| | <li>Ask follow-up questions anytime</li> |
| | </ul> |
| | </div> |
| | """) |
| |
|
| | |
| | def respond(message, chat_history, model_name): |
| | if not message.strip(): |
| | return chat_history, "" |
| | |
| | bot_message = process_message(message, chat_history, model_name) |
| | chat_history.append({"role": "user", "content": message}) |
| | chat_history.append({"role": "assistant", "content": bot_message}) |
| | return chat_history, "" |
| |
|
| | def clear_chat(): |
| | reset_conversation() |
| | return [{"role": "assistant", "content": get_welcome_message()}], "" |
| |
|
| | |
| | msg.submit(respond, [msg, chatbot, model_selector], [chatbot, msg]) |
| | send_btn.click(respond, [msg, chatbot, model_selector], [chatbot, msg]) |
| | clear_btn.click(clear_chat, None, [chatbot, msg]) |
| | |
| | |
| | interface.load(lambda: gr.update(focus=True), None, msg) |
| |
|
| | return interface |
| |
|
| | def main(): |
| | """Main function to launch the Gradio interface.""" |
| | |
| | |
| | chosen_model = sys.argv[1] if len(sys.argv) > 1 else "gemini-1.5-flash" |
| | initialize_model(chosen_model) |
| | |
| | |
| | interface = create_interface() |
| | |
| | print("🚀 Launching AI Job Assistant...") |
| | print("🌐 Opening in your default browser...") |
| | |
| | interface.launch( |
| | server_name="0.0.0.0", |
| | server_port=7860, |
| | share=False, |
| | show_error=True |
| | ) |
| |
|
| | if __name__ == "__main__": |
| | main() |