|
import gradio as gr |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.graph_objects as go |
|
import plotly.express as px |
|
from datetime import datetime, timedelta |
|
import random |
|
import json |
|
import os |
|
import time |
|
import requests |
|
from typing import List, Dict, Any, Optional |
|
import logging |
|
from dotenv import load_dotenv |
|
import pytz |
|
import uuid |
|
import re |
|
import base64 |
|
from io import BytesIO |
|
from PIL import Image |
|
|
|
|
|
from google import genai |
|
from google.genai import types |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "your-api-key") |
|
SERPER_API_KEY = os.getenv("SERPER_API_KEY", "your-serper-api-key") |
|
|
|
|
|
genai.configure(api_key=GOOGLE_API_KEY) |
|
|
|
|
|
client = genai.Client() |
|
|
|
|
|
MODEL_ID = "gemini-2.0-flash-001" |
|
|
|
|
|
EMOTIONS = ["Unmotivated", "Anxious", "Confused", "Excited", "Overwhelmed", "Discouraged"] |
|
GOAL_TYPES = ["Get a job at a big company", "Find an internship", "Change careers", "Improve skills", "Network better"] |
|
USER_DB_PATH = "user_database.json" |
|
RESUME_FOLDER = "user_resumes" |
|
PORTFOLIO_FOLDER = "user_portfolios" |
|
|
|
|
|
os.makedirs(RESUME_FOLDER, exist_ok=True) |
|
os.makedirs(PORTFOLIO_FOLDER, exist_ok=True) |
|
|
|
|
|
get_job_opportunities = types.FunctionDeclaration( |
|
name="get_job_opportunities", |
|
description="Get relevant job opportunities based on location and career goals", |
|
parameters={ |
|
"type": "OBJECT", |
|
"properties": { |
|
"location": { |
|
"type": "STRING", |
|
"description": "The city or country where the user is located", |
|
}, |
|
"career_goal": { |
|
"type": "STRING", |
|
"description": "The user's career goal or job interest", |
|
}, |
|
"max_results": { |
|
"type": "NUMBER", |
|
"description": "Maximum number of job opportunities to return", |
|
}, |
|
}, |
|
"required": ["location", "career_goal"], |
|
}, |
|
) |
|
|
|
generate_document = types.FunctionDeclaration( |
|
name="generate_document_template", |
|
description="Generate a document template for job applications", |
|
parameters={ |
|
"type": "OBJECT", |
|
"properties": { |
|
"document_type": { |
|
"type": "STRING", |
|
"description": "Type of document to generate (Resume, Cover Letter, Self-introduction)", |
|
}, |
|
"career_field": { |
|
"type": "STRING", |
|
"description": "The career field or industry the document is for", |
|
}, |
|
"experience_level": { |
|
"type": "STRING", |
|
"description": "User's experience level (Entry, Mid, Senior)", |
|
}, |
|
}, |
|
"required": ["document_type"], |
|
}, |
|
) |
|
|
|
create_routine = types.FunctionDeclaration( |
|
name="create_personalized_routine", |
|
description="Create a personalized career development routine", |
|
parameters={ |
|
"type": "OBJECT", |
|
"properties": { |
|
"emotion": { |
|
"type": "STRING", |
|
"description": "User's current emotional state", |
|
}, |
|
"goal": { |
|
"type": "STRING", |
|
"description": "User's career goal", |
|
}, |
|
"available_time_minutes": { |
|
"type": "NUMBER", |
|
"description": "Available time in minutes per day", |
|
}, |
|
"routine_length_days": { |
|
"type": "NUMBER", |
|
"description": "Length of routine in days", |
|
}, |
|
}, |
|
"required": ["emotion", "goal"], |
|
}, |
|
) |
|
|
|
analyze_resume = types.FunctionDeclaration( |
|
name="analyze_resume", |
|
description="Analyze a user's resume and provide feedback", |
|
parameters={ |
|
"type": "OBJECT", |
|
"properties": { |
|
"resume_text": { |
|
"type": "STRING", |
|
"description": "The full text of the user's resume", |
|
}, |
|
"career_goal": { |
|
"type": "STRING", |
|
"description": "The user's career goal or job interest", |
|
}, |
|
}, |
|
"required": ["resume_text"], |
|
}, |
|
) |
|
|
|
analyze_portfolio = types.FunctionDeclaration( |
|
name="analyze_portfolio", |
|
description="Analyze a user's portfolio and provide feedback", |
|
parameters={ |
|
"type": "OBJECT", |
|
"properties": { |
|
"portfolio_url": { |
|
"type": "STRING", |
|
"description": "URL to the user's portfolio", |
|
}, |
|
"portfolio_description": { |
|
"type": "STRING", |
|
"description": "Description of the portfolio content", |
|
}, |
|
"career_goal": { |
|
"type": "STRING", |
|
"description": "The user's career goal or job interest", |
|
}, |
|
}, |
|
"required": ["portfolio_description"], |
|
}, |
|
) |
|
|
|
|
|
job_tool = types.Tool(function_declarations=[get_job_opportunities]) |
|
document_tool = types.Tool(function_declarations=[generate_document]) |
|
routine_tool = types.Tool(function_declarations=[create_routine]) |
|
resume_tool = types.Tool(function_declarations=[analyze_resume]) |
|
portfolio_tool = types.Tool(function_declarations=[analyze_portfolio]) |
|
|
|
|
|
def load_user_database(): |
|
"""Load user database from JSON file or create if it doesn't exist""" |
|
try: |
|
with open(USER_DB_PATH, 'r') as file: |
|
return json.load(file) |
|
except (FileNotFoundError, json.JSONDecodeError): |
|
|
|
db = {'users': {}} |
|
save_user_database(db) |
|
return db |
|
|
|
def save_user_database(db): |
|
"""Save user database to JSON file""" |
|
with open(USER_DB_PATH, 'w') as file: |
|
json.dump(db, file, indent=4) |
|
|
|
def get_user_profile(user_id): |
|
"""Get user profile from database or create new one""" |
|
db = load_user_database() |
|
if user_id not in db['users']: |
|
db['users'][user_id] = { |
|
"user_id": user_id, |
|
"name": "", |
|
"location": "", |
|
"current_emotion": "", |
|
"career_goal": "", |
|
"progress_points": 0, |
|
"completed_tasks": [], |
|
"upcoming_events": [], |
|
"routine_history": [], |
|
"daily_emotions": [], |
|
"resume_path": "", |
|
"portfolio_path": "", |
|
"recommendations": [], |
|
"chat_history": [], |
|
"joined_date": datetime.now().strftime("%Y-%m-%d") |
|
} |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def update_user_profile(user_id, updates): |
|
"""Update user profile with new information""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
for key, value in updates.items(): |
|
db['users'][user_id][key] = value |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def add_task_to_user(user_id, task): |
|
"""Add a new task to user's completed tasks""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
if 'completed_tasks' not in db['users'][user_id]: |
|
db['users'][user_id]['completed_tasks'] = [] |
|
|
|
task_with_date = { |
|
"task": task, |
|
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
} |
|
db['users'][user_id]['completed_tasks'].append(task_with_date) |
|
db['users'][user_id]['progress_points'] += random.randint(10, 25) |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def add_emotion_record(user_id, emotion): |
|
"""Add a new emotion record to user's daily emotions""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
if 'daily_emotions' not in db['users'][user_id]: |
|
db['users'][user_id]['daily_emotions'] = [] |
|
|
|
emotion_record = { |
|
"emotion": emotion, |
|
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
} |
|
db['users'][user_id]['daily_emotions'].append(emotion_record) |
|
db['users'][user_id]['current_emotion'] = emotion |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def add_routine_to_user(user_id, routine): |
|
"""Add a new routine to user's routine history""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
if 'routine_history' not in db['users'][user_id]: |
|
db['users'][user_id]['routine_history'] = [] |
|
|
|
routine_with_date = { |
|
"routine": routine, |
|
"start_date": datetime.now().strftime("%Y-%m-%d"), |
|
"end_date": (datetime.now() + timedelta(days=routine.get('days', 7))).strftime("%Y-%m-%d"), |
|
"completion": 0 |
|
} |
|
db['users'][user_id]['routine_history'].append(routine_with_date) |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def save_user_resume(user_id, resume_text): |
|
"""Save user's resume to file and update profile""" |
|
|
|
filename = f"{user_id}_resume.txt" |
|
filepath = os.path.join(RESUME_FOLDER, filename) |
|
|
|
|
|
with open(filepath, 'w') as file: |
|
file.write(resume_text) |
|
|
|
|
|
update_user_profile(user_id, {"resume_path": filepath}) |
|
|
|
return filepath |
|
|
|
def save_user_portfolio(user_id, portfolio_content): |
|
"""Save user's portfolio info to file and update profile""" |
|
|
|
filename = f"{user_id}_portfolio.json" |
|
filepath = os.path.join(PORTFOLIO_FOLDER, filename) |
|
|
|
|
|
with open(filepath, 'w') as file: |
|
json.dump(portfolio_content, file, indent=4) |
|
|
|
|
|
update_user_profile(user_id, {"portfolio_path": filepath}) |
|
|
|
return filepath |
|
|
|
def add_recommendation_to_user(user_id, recommendation): |
|
"""Add a new recommendation to user's recommendations list""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
if 'recommendations' not in db['users'][user_id]: |
|
db['users'][user_id]['recommendations'] = [] |
|
|
|
recommendation_with_date = { |
|
"recommendation": recommendation, |
|
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
"status": "pending" |
|
} |
|
db['users'][user_id]['recommendations'].append(recommendation_with_date) |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
def add_chat_message(user_id, role, message): |
|
"""Add a message to the user's chat history""" |
|
db = load_user_database() |
|
if user_id in db['users']: |
|
if 'chat_history' not in db['users'][user_id]: |
|
db['users'][user_id]['chat_history'] = [] |
|
|
|
chat_message = { |
|
"role": role, |
|
"message": message, |
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
} |
|
db['users'][user_id]['chat_history'].append(chat_message) |
|
save_user_database(db) |
|
return db['users'][user_id] |
|
|
|
|
|
def search_jobs_with_serper(query, location, max_results=5): |
|
"""Search for job opportunities using Serper API""" |
|
try: |
|
headers = { |
|
'X-API-KEY': SERPER_API_KEY, |
|
'Content-Type': 'application/json' |
|
} |
|
|
|
params = { |
|
'q': f"{query} jobs in {location}", |
|
'num': max_results |
|
} |
|
|
|
response = requests.get( |
|
'https://serper.dev/search', |
|
headers=headers, |
|
params=params |
|
) |
|
|
|
if response.status_code == 200: |
|
data = response.json() |
|
|
|
job_results = [] |
|
|
|
|
|
if 'organic' in data: |
|
for item in data['organic']: |
|
if 'title' in item and 'link' in item and 'snippet' in item: |
|
|
|
if any(keyword in item['title'].lower() for keyword in ['job', 'career', 'position', 'hiring', 'work']): |
|
job_results.append({ |
|
'title': item['title'], |
|
'company': extract_company_from_title(item['title']), |
|
'description': item['snippet'], |
|
'link': item['link'], |
|
'location': location, |
|
'date_posted': 'Recent' |
|
}) |
|
|
|
return job_results |
|
else: |
|
logger.error(f"Error from Serper API: {response.status_code} - {response.text}") |
|
return [] |
|
except Exception as e: |
|
logger.error(f"Exception in search_jobs_with_serper: {str(e)}") |
|
return [] |
|
|
|
def extract_company_from_title(title): |
|
"""Extract company name from job title if possible""" |
|
|
|
if ' at ' in title: |
|
return title.split(' at ')[1].strip() |
|
if ' - ' in title: |
|
return title.split(' - ')[1].strip() |
|
return "Unknown Company" |
|
|
|
def get_ai_response(user_id, user_input, context=None, generate_recommendations=True): |
|
"""Get AI response using Google GenAI""" |
|
try: |
|
user_profile = get_user_profile(user_id) |
|
|
|
system_instruction = """ |
|
You are Aishura, an emotionally intelligent AI career assistant. Your goal is to empathize with the user's emotions |
|
and provide realistic information and actionable suggestions. Follow this structure: |
|
1. Recognize and acknowledge the user's emotion |
|
2. Respond with high-empathy message |
|
3. Suggest specific action based on their input |
|
4. Offer document support, job opportunities, or personalized routine |
|
|
|
Remember to be proactive and preemptive - suggest actions before the user asks. Your goal is to provide |
|
end-to-end support for the user's career journey, from emotional support to concrete action. |
|
|
|
If the user has shared a resume or portfolio, refer to insights from those documents to provide |
|
personalized guidance. |
|
""" |
|
|
|
|
|
contents = [] |
|
|
|
|
|
profile_context = f""" |
|
User Profile Information: |
|
- Name: {user_profile.get('name', '')} |
|
- Current emotion: {user_profile.get('current_emotion', '')} |
|
- Career goal: {user_profile.get('career_goal', '')} |
|
- Location: {user_profile.get('location', '')} |
|
""" |
|
|
|
|
|
if user_profile.get('resume_path') and os.path.exists(user_profile.get('resume_path')): |
|
try: |
|
with open(user_profile.get('resume_path'), 'r') as file: |
|
resume_text = file.read() |
|
profile_context += f"\nUser Resume Summary: The user has shared their resume. They have experience in {resume_text[:100]}..." |
|
except Exception as e: |
|
logger.error(f"Error reading resume: {str(e)}") |
|
|
|
|
|
if user_profile.get('portfolio_path') and os.path.exists(user_profile.get('portfolio_path')): |
|
try: |
|
with open(user_profile.get('portfolio_path'), 'r') as file: |
|
portfolio_data = json.load(file) |
|
profile_context += f"\nUser Portfolio: The user has shared their portfolio with URL: {portfolio_data.get('url', 'Not provided')}." |
|
except Exception as e: |
|
logger.error(f"Error reading portfolio: {str(e)}") |
|
|
|
|
|
user_context = types.Content( |
|
role="user", |
|
parts=[types.Part.from_text(profile_context)] |
|
) |
|
contents.append(user_context) |
|
|
|
|
|
if context: |
|
for msg in context: |
|
if msg["role"] == "user": |
|
contents.append(types.Content( |
|
role="user", |
|
parts=[types.Part.from_text(msg["message"])] |
|
)) |
|
else: |
|
contents.append(types.Content( |
|
role="model", |
|
parts=[types.Part.from_text(msg["message"])] |
|
)) |
|
|
|
|
|
contents.append(types.Content( |
|
role="user", |
|
parts=[types.Part.from_text(user_input)] |
|
)) |
|
|
|
|
|
tools = [job_tool, document_tool, routine_tool, resume_tool, portfolio_tool] |
|
|
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=contents, |
|
system_instruction=system_instruction, |
|
tools=tools, |
|
generation_config=types.GenerationConfig( |
|
temperature=0.7, |
|
max_output_tokens=2048, |
|
top_p=0.95, |
|
top_k=40 |
|
) |
|
) |
|
|
|
ai_response_text = response.text |
|
|
|
|
|
add_chat_message(user_id, "user", user_input) |
|
add_chat_message(user_id, "assistant", ai_response_text) |
|
|
|
|
|
if generate_recommendations: |
|
gen_recommendations(user_id, user_input, ai_response_text) |
|
|
|
return ai_response_text |
|
except Exception as e: |
|
logger.error(f"Error in get_ai_response: {str(e)}") |
|
return "I apologize, but I'm having trouble processing your request right now. Please try again later." |
|
|
|
def gen_recommendations(user_id, user_input, ai_response): |
|
"""Generate recommendations based on conversation""" |
|
try: |
|
user_profile = get_user_profile(user_id) |
|
|
|
prompt = f""" |
|
Based on the following conversation between a user and Aishura (an AI career assistant), |
|
generate 1-3 specific, actionable recommendations for the user's next steps in their career journey. |
|
|
|
User Profile: |
|
- Current emotion: {user_profile.get('current_emotion', '')} |
|
- Career goal: {user_profile.get('career_goal', '')} |
|
- Location: {user_profile.get('location', '')} |
|
|
|
Recent Conversation: |
|
User: {user_input} |
|
|
|
Aishura: {ai_response} |
|
|
|
Generate specific, actionable recommendations in JSON format: |
|
```json |
|
[ |
|
{{ |
|
"title": "Brief recommendation title", |
|
"description": "Detailed recommendation description", |
|
"action_type": "job_search|skill_building|networking|resume|portfolio|interview_prep|other", |
|
"priority": "high|medium|low" |
|
}} |
|
] |
|
``` |
|
|
|
Focus on immediate, practical next steps that align with the user's goals and emotional state. |
|
""" |
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
recommendation_text = response.text |
|
|
|
|
|
try: |
|
|
|
if "```json" in recommendation_text and "```" in recommendation_text.split("```json")[1]: |
|
json_str = recommendation_text.split("```json")[1].split("```")[0].strip() |
|
else: |
|
|
|
import re |
|
json_match = re.search(r'(\[.*\])', recommendation_text, re.DOTALL) |
|
if json_match: |
|
json_str = json_match.group(1) |
|
else: |
|
json_str = recommendation_text |
|
|
|
recommendations = json.loads(json_str) |
|
|
|
|
|
for rec in recommendations: |
|
add_recommendation_to_user(user_id, rec) |
|
|
|
return recommendations |
|
except json.JSONDecodeError: |
|
logger.error(f"Failed to parse JSON from AI response: {recommendation_text}") |
|
return [] |
|
except Exception as e: |
|
logger.error(f"Error in gen_recommendations: {str(e)}") |
|
return [] |
|
|
|
def create_personalized_routine_with_ai(user_id, emotion, goal, available_time=60, days=7): |
|
"""Create a personalized routine using AI""" |
|
try: |
|
user_profile = get_user_profile(user_id) |
|
|
|
prompt = f""" |
|
Create a personalized {days}-day career development routine for a user who is feeling {emotion} and has a goal to {goal}. |
|
They have about {available_time} minutes per day to dedicate to this routine. |
|
|
|
For each day, suggest 1-3 specific tasks that will help them make progress toward their goal while considering their emotional state. |
|
|
|
For each task provide: |
|
1. Task name |
|
2. Duration in minutes |
|
3. Points value (between 10-50) |
|
4. A brief description of why this task is valuable |
|
|
|
Format the routine as a JSON object with this structure: |
|
```json |
|
{{ |
|
"name": "Routine name", |
|
"description": "Brief description of the routine", |
|
"days": {days}, |
|
"daily_tasks": [ |
|
{{ |
|
"day": 1, |
|
"tasks": [ |
|
{{ |
|
"name": "Task name", |
|
"points": 20, |
|
"duration": 30, |
|
"description": "Why this task is valuable" |
|
}} |
|
] |
|
}} |
|
] |
|
}} |
|
``` |
|
""" |
|
|
|
|
|
if user_profile.get('resume_path') and os.path.exists(user_profile.get('resume_path')): |
|
try: |
|
with open(user_profile.get('resume_path'), 'r') as file: |
|
resume_text = file.read() |
|
prompt += f"\n\nTailor the routine based on the user's resume. Here's a summary: {resume_text[:500]}..." |
|
except Exception as e: |
|
logger.error(f"Error reading resume: {str(e)}") |
|
|
|
if user_profile.get('portfolio_path') and os.path.exists(user_profile.get('portfolio_path')): |
|
try: |
|
with open(user_profile.get('portfolio_path'), 'r') as file: |
|
portfolio_data = json.load(file) |
|
prompt += f"\n\nConsider the user's portfolio when creating the routine. Portfolio URL: {portfolio_data.get('url', 'Not provided')}" |
|
except Exception as e: |
|
logger.error(f"Error reading portfolio: {str(e)}") |
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
routine_text = response.text |
|
|
|
|
|
try: |
|
|
|
if "```json" in routine_text and "```" in routine_text.split("```json")[1]: |
|
json_str = routine_text.split("```json")[1].split("```")[0].strip() |
|
else: |
|
|
|
import re |
|
json_match = re.search(r'(\{.*\})', routine_text, re.DOTALL) |
|
if json_match: |
|
json_str = json_match.group(1) |
|
else: |
|
json_str = routine_text |
|
|
|
routine = json.loads(json_str) |
|
|
|
|
|
user_profile = add_routine_to_user(user_id, routine) |
|
return routine |
|
except json.JSONDecodeError: |
|
logger.error(f"Failed to parse JSON from AI response: {routine_text}") |
|
|
|
return generate_basic_routine(emotion, goal, available_time, days) |
|
except Exception as e: |
|
logger.error(f"Error in create_personalized_routine_with_ai: {str(e)}") |
|
|
|
return generate_basic_routine(emotion, goal, available_time, days) |
|
|
|
def generate_basic_routine(emotion, goal, available_time=60, days=7): |
|
"""Generate a basic routine as fallback""" |
|
routine_types = { |
|
"job_search": [ |
|
{"name": "Research target companies", "points": 10, "duration": 20, "description": "Identify potential employers that align with your career goals"}, |
|
{"name": "Update LinkedIn profile", "points": 15, "duration": 30, "description": "Keep your professional presence current and compelling"}, |
|
{"name": "Practice interview questions", "points": 20, "duration": 45, "description": "Build confidence and prepare for upcoming opportunities"}, |
|
{"name": "Reach out to a contact", "points": 25, "duration": 15, "description": "Grow your network and gather industry insights"} |
|
], |
|
"skill_building": [ |
|
{"name": "Complete one tutorial", "points": 20, "duration": 60, "description": "Develop practical skills in your field"}, |
|
{"name": "Read industry article", "points": 10, "duration": 15, "description": "Stay current with trends and developments"}, |
|
{"name": "Work on portfolio project", "points": 30, "duration": 90, "description": "Create tangible evidence of your abilities"}, |
|
{"name": "Watch expert talk", "points": 15, "duration": 30, "description": "Learn from leaders in your field"} |
|
], |
|
"motivation": [ |
|
{"name": "Write in gratitude journal", "points": 10, "duration": 10, "description": "Cultivate a positive mindset to enhance motivation"}, |
|
{"name": "Set 3 goals for the day", "points": 15, "duration": 15, "description": "Focus your energy on achievable tasks"}, |
|
{"name": "Exercise break", "points": 20, "duration": 20, "description": "Boost energy and mood with physical activity"}, |
|
{"name": "Reflect on progress", "points": 15, "duration": 15, "description": "Acknowledge achievements and identify next steps"} |
|
] |
|
} |
|
|
|
|
|
if "job" in goal.lower() or "company" in goal.lower(): |
|
routine_type = "job_search" |
|
elif "skill" in goal.lower() or "learn" in goal.lower(): |
|
routine_type = "skill_building" |
|
else: |
|
|
|
if emotion.lower() in ["unmotivated", "anxious", "confused", "overwhelmed", "discouraged"]: |
|
routine_type = "motivation" |
|
else: |
|
routine_type = random.choice(list(routine_types.keys())) |
|
|
|
|
|
daily_tasks = [] |
|
for day in range(1, days + 1): |
|
|
|
available_tasks = routine_types[routine_type].copy() |
|
random.shuffle(available_tasks) |
|
day_tasks = [] |
|
remaining_time = available_time |
|
|
|
for task in available_tasks: |
|
if task["duration"] <= remaining_time and len(day_tasks) < 3: |
|
day_tasks.append(task) |
|
remaining_time -= task["duration"] |
|
|
|
if remaining_time < 10 or len(day_tasks) >= 3: |
|
break |
|
|
|
daily_tasks.append({ |
|
"day": day, |
|
"tasks": day_tasks |
|
}) |
|
|
|
routine = { |
|
"name": f"{days}-Day {routine_type.replace('_', ' ').title()} Plan", |
|
"description": f"A personalized routine to help you {goal} while managing feelings of {emotion}.", |
|
"days": days, |
|
"daily_tasks": daily_tasks |
|
} |
|
|
|
return routine |
|
|
|
def generate_document_template_with_ai(document_type, career_field="", experience_level=""): |
|
"""Generate document templates using AI""" |
|
try: |
|
prompt = f""" |
|
Create a detailed template for a {document_type} for someone in the {career_field} field |
|
with {experience_level} experience level. |
|
|
|
The template should include all necessary sections and sample content that can be replaced. |
|
Format it in markdown. |
|
""" |
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
return response.text |
|
except Exception as e: |
|
logger.error(f"Error in generate_document_template_with_ai: {str(e)}") |
|
return f"Error generating {document_type} template. Please try again later." |
|
|
|
def analyze_resume_with_ai(user_id, resume_text): |
|
"""Analyze resume with AI and provide feedback""" |
|
try: |
|
user_profile = get_user_profile(user_id) |
|
|
|
prompt = f""" |
|
Analyze the following resume for a user who has the career goal of: {user_profile.get('career_goal', 'improving their career')} |
|
|
|
Resume Text: |
|
{resume_text} |
|
|
|
Provide detailed feedback on: |
|
1. Overall strengths and weaknesses |
|
2. Format and organization |
|
3. Content effectiveness for their career goal |
|
4. Specific improvement suggestions |
|
5. Keywords and skills that should be highlighted |
|
|
|
Format your analysis with markdown headings and bullet points. |
|
""" |
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
|
|
save_user_resume(user_id, resume_text) |
|
|
|
return response.text |
|
except Exception as e: |
|
logger.error(f"Error in analyze_resume_with_ai: {str(e)}") |
|
return "I apologize, but I'm having trouble analyzing your resume right now. Please try again later." |
|
|
|
def analyze_portfolio_with_ai(user_id, portfolio_url, portfolio_description): |
|
"""Analyze portfolio with AI and provide feedback""" |
|
try: |
|
user_profile = get_user_profile(user_id) |
|
|
|
prompt = f""" |
|
Analyze the following portfolio for a user who has the career goal of: {user_profile.get('career_goal', 'improving their career')} |
|
|
|
Portfolio URL: {portfolio_url} |
|
Portfolio Description: {portfolio_description} |
|
|
|
Based on the description provided, analyze: |
|
1. How well the portfolio aligns with their career goal |
|
2. Strengths of the portfolio |
|
3. Areas for improvement |
|
4. Specific suggestions to enhance the portfolio |
|
5. How to better showcase skills relevant to their goal |
|
|
|
Format your analysis with markdown headings and bullet points. |
|
""" |
|
|
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
|
|
portfolio_content = { |
|
"url": portfolio_url, |
|
"description": portfolio_description |
|
} |
|
save_user_portfolio(user_id, portfolio_content) |
|
|
|
return response.text |
|
except Exception as e: |
|
logger.error(f"Error in analyze_portfolio_with_ai: {str(e)}") |
|
return "I apologize, but I'm having trouble analyzing your portfolio right now. Please try again later." |
|
|
|
|
|
def create_emotion_chart(user_id): |
|
"""Create a chart of user's emotions over time""" |
|
user_profile = get_user_profile(user_id) |
|
emotion_records = user_profile.get('daily_emotions', []) |
|
|
|
if not emotion_records: |
|
|
|
fig = px.line(title="Emotion Tracking: No data available yet") |
|
return fig |
|
|
|
|
|
emotion_values = { |
|
"Unmotivated": 1, |
|
"Anxious": 2, |
|
"Confused": 3, |
|
"Discouraged": 4, |
|
"Overwhelmed": 5, |
|
"Excited": 6 |
|
} |
|
|
|
dates = [] |
|
emotion_scores = [] |
|
emotion_names = [] |
|
|
|
for record in emotion_records: |
|
dates.append(datetime.strptime(record['date'], "%Y-%m-%d %H:%M:%S")) |
|
emotion = record['emotion'] |
|
emotion_names.append(emotion) |
|
emotion_scores.append(emotion_values.get(emotion, 3)) |
|
|
|
df = pd.DataFrame({ |
|
'Date': dates, |
|
'Emotion Score': emotion_scores, |
|
'Emotion': emotion_names |
|
}) |
|
|
|
|
|
fig = px.line(df, x='Date', y='Emotion Score', markers=True, |
|
labels={"Emotion Score": "Emotional State"}, |
|
title="Your Emotional Journey") |
|
|
|
|
|
fig.update_traces(hovertemplate='%{x}<br>Feeling: %{text}', text=df['Emotion']) |
|
|
|
|
|
fig.update_yaxes( |
|
tickvals=list(emotion_values.values()), |
|
ticktext=list(emotion_values.keys()) |
|
) |
|
|
|
return fig |
|
|
|
def create_progress_chart(user_id): |
|
"""Create a chart showing user's progress over time""" |
|
user_profile = get_user_profile(user_id) |
|
tasks = user_profile.get('completed_tasks', []) |
|
|
|
if not tasks: |
|
|
|
fig = px.line(title="Progress Tracking: No data available yet") |
|
return fig |
|
|
|
|
|
dates = [] |
|
points = [] |
|
cumulative_points = 0 |
|
task_labels = [] |
|
|
|
for task in tasks: |
|
dates.append(datetime.strptime(task['date'], "%Y-%m-%d %H:%M:%S")) |
|
|
|
cumulative_points += 20 |
|
points.append(cumulative_points) |
|
task_labels.append(task['task']) |
|
|
|
df = pd.DataFrame({ |
|
'Date': dates, |
|
'Points': points, |
|
'Task': task_labels |
|
}) |
|
|
|
|
|
fig = px.line(df, x='Date', y='Points', markers=True, |
|
title="Your Career Journey Progress") |
|
|
|
|
|
fig.update_traces(hovertemplate='%{x}<br>Points: %{y}<br>Task: %{text}', text=df['Task']) |
|
|
|
return fig |
|
|
|
def create_routine_completion_gauge(user_id): |
|
"""Create a gauge chart showing routine completion percentage""" |
|
user_profile = get_user_profile(user_id) |
|
routines = user_profile.get('routine_history', []) |
|
|
|
if not routines: |
|
|
|
fig = go.Figure() |
|
fig.add_annotation(text="No active routines yet", showarrow=False) |
|
return fig |
|
|
|
|
|
latest_routine = routines[-1] |
|
completion = latest_routine.get('completion', 0) |
|
|
|
|
|
fig = go.Figure(go.Indicator( |
|
mode = "gauge+number", |
|
value = completion, |
|
domain = {'x': [0, 1], 'y': [0, 1]}, |
|
title = {'text': "Current Routine Completion"}, |
|
gauge = { |
|
'axis': {'range': [None, 100]}, |
|
'bar': {'color': "darkblue"}, |
|
'steps': [ |
|
{'range': [0, 30], 'color': "lightgray"}, |
|
{'range': [30, 70], 'color': "gray"}, |
|
{'range': [70, 100], 'color': "darkgray"} |
|
], |
|
'threshold': { |
|
'line': {'color': "red", 'width': 4}, |
|
'thickness': 0.75, |
|
'value': 90 |
|
} |
|
} |
|
)) |
|
|
|
return fig |
|
|
|
def create_skill_radar_chart(user_id): |
|
"""Create a radar chart of user's skills based on resume analysis""" |
|
user_profile = get_user_profile(user_id) |
|
|
|
|
|
if not user_profile.get('resume_path') or not os.path.exists(user_profile.get('resume_path')): |
|
fig = go.Figure() |
|
fig.add_annotation(text="No resume data available yet", showarrow=False) |
|
return fig |
|
|
|
|
|
try: |
|
with open(user_profile.get('resume_path'), 'r') as file: |
|
resume_text = file.read() |
|
except Exception as e: |
|
logger.error(f"Error reading resume: {str(e)}") |
|
fig = go.Figure() |
|
fig.add_annotation(text="Error reading resume data", showarrow=False) |
|
return fig |
|
|
|
|
|
prompt = f""" |
|
Based on the following resume, identify 5-8 key skills and rate them on a scale of 1-10. |
|
|
|
Resume: |
|
{resume_text[:2000]}... |
|
|
|
Return the results as a JSON object with this structure: |
|
```json |
|
{{ |
|
"skills": [ |
|
{{"name": "Skill Name", "score": 7}}, |
|
{{"name": "Another Skill", "score": 9}} |
|
] |
|
}} |
|
``` |
|
""" |
|
|
|
try: |
|
response = client.models.generate_content( |
|
model=MODEL_ID, |
|
contents=prompt |
|
) |
|
|
|
skill_text = response.text |
|
|
|
|
|
if "```json" in skill_text and "```" in skill_text.split("```json")[1]: |
|
json_str = skill_text.split("```json")[1].split("```")[0].strip() |
|
else: |
|
import re |
|
json_match = re.search(r'(\{.*\})', skill_text, re.DOTALL) |
|
if json_match: |
|
json_str = json_match.group(1) |
|
else: |
|
json_str = skill_text |
|
|
|
skill_data = json.loads(json_str) |
|
|
|
|
|
if 'skills' in skill_data and skill_data['skills']: |
|
skills = skill_data['skills'] |
|
|
|
|
|
categories = [skill['name'] for skill in skills] |
|
values = [skill['score'] for skill in skills] |
|
|
|
|
|
categories.append(categories[0]) |
|
values.append(values[0]) |
|
|
|
fig = go.Figure() |
|
|
|
fig.add_trace(go.Scatterpolar( |
|
r=values, |
|
theta=categories, |
|
fill='toself', |
|
name='Skills' |
|
)) |
|
|
|
fig.update_layout( |
|
polar=dict( |
|
radialaxis=dict( |
|
visible=True, |
|
range=[0, 10] |
|
) |
|
), |
|
showlegend=False, |
|
title="Skill Assessment Based on Resume" |
|
) |
|
|
|
return fig |
|
else: |
|
fig = go.Figure() |
|
fig.add_annotation(text="Could not extract skills from resume", showarrow=False) |
|
return fig |
|
|
|
except Exception as e: |
|
logger.error(f"Error creating skill radar chart: {str(e)}") |
|
fig = go.Figure() |
|
fig.add_annotation(text="Error analyzing skills", showarrow=False) |
|
return fig |
|
|
|
|
|
def create_interface(): |
|
"""Create the Gradio interface for Aishura MVP""" |
|
|
|
|
|
session_user_id = str(uuid.uuid4()) |
|
|
|
|
|
def welcome(name, location, emotion, goal): |
|
if not name or not location or not emotion or not goal: |
|
return ("Please fill out all fields to continue.", |
|
gr.update(visible=True), |
|
gr.update(visible=False)) |
|
|
|
|
|
update_user_profile(session_user_id, { |
|
"name": name, |
|
"location": location, |
|
"career_goal": goal |
|
}) |
|
|
|
|
|
add_emotion_record(session_user_id, emotion) |
|
|
|
|
|
response = get_ai_response( |
|
session_user_id, |
|
f"I'm {name} from {location}. I'm feeling {emotion} and my career goal is to {goal}." |
|
) |
|
|
|
return (response, |
|
gr.update(visible=False), |
|
gr.update(visible=True)) |
|
|
|
|
|
def chat(message, history): |
|
|
|
user_profile = get_user_profile(session_user_id) |
|
|
|
|
|
context = [] |
|
for h in history: |
|
context.append({"role": "user", "message": h[0]}) |
|
context.append({"role": "assistant", "message": h[1]}) |
|
|
|
|
|
response = get_ai_response(session_user_id, message, context) |
|
|
|
|
|
history.append((message, response)) |
|
return history, "" |
|
|
|
|
|
def search_jobs_interface(query, location, max_results=5): |
|
jobs = search_jobs_with_serper(query, location, int(max_results)) |
|
|
|
if not jobs: |
|
return "No job opportunities found. Try adjusting your search terms." |
|
|
|
result = "## Job Opportunities Found\n\n" |
|
for i, job in enumerate(jobs, 1): |
|
result += f"### {i}. {job['title']}\n" |
|
result += f"**Company:** {job['company']}\n" |
|
result += f"**Location:** {job['location']}\n" |
|
result += f"**Description:** {job['description']}\n" |
|
result += f"**Link:** [Apply Here]({job['link']})\n\n" |
|
|
|
return result |
|
|
|
|
|
def generate_template(document_type, career_field, experience_level): |
|
template = generate_document_template_with_ai(document_type, career_field, experience_level) |
|
return template |
|
|
|
|
|
def create_personal_routine(emotion, goal, available_time, days): |
|
routine = create_personalized_routine_with_ai( |
|
session_user_id, emotion, goal, int(available_time), int(days) |
|
) |
|
|
|
|
|
result = f"# Your {routine['name']}\n\n" |
|
result += f"{routine['description']}\n\n" |
|
|
|
for day_plan in routine['daily_tasks']: |
|
result += f"## Day {day_plan['day']}\n\n" |
|
for task in day_plan['tasks']: |
|
result += f"- **{task['name']}** ({task['duration']} mins, {task['points']} points)\n" |
|
result += f" *{task['description']}*\n\n" |
|
|
|
return result |
|
|
|
|
|
def analyze_resume_interface(resume_text): |
|
if not resume_text: |
|
return "Please enter your resume text." |
|
|
|
analysis = analyze_resume_with_ai(session_user_id, resume_text) |
|
|
|
|
|
skill_fig = create_skill_radar_chart(session_user_id) |
|
|
|
return analysis, skill_fig |
|
|
|
|
|
def analyze_portfolio_interface(portfolio_url, portfolio_description): |
|
if not portfolio_description: |
|
return "Please enter a description of your portfolio." |
|
|
|
analysis = analyze_portfolio_with_ai(session_user_id, portfolio_url, portfolio_description) |
|
return analysis |
|
|
|
|
|
def complete_task(task_name): |
|
if not task_name: |
|
return "Please enter a task name." |
|
|
|
user_profile = add_task_to_user(session_user_id, task_name) |
|
|
|
|
|
if user_profile.get('routine_history'): |
|
latest_routine = user_profile['routine_history'][-1] |
|
|
|
new_completion = min(100, latest_routine.get('completion', 0) + random.randint(5, 15)) |
|
latest_routine['completion'] = new_completion |
|
update_user_profile(session_user_id, {"routine_history": user_profile['routine_history']}) |
|
|
|
|
|
emotion_fig = create_emotion_chart(session_user_id) |
|
progress_fig = create_progress_chart(session_user_id) |
|
gauge_fig = create_routine_completion_gauge(session_user_id) |
|
|
|
return ( |
|
f"Task '{task_name}' completed! You earned {random.randint(10, 25)} points.", |
|
"", |
|
emotion_fig, |
|
progress_fig, |
|
gauge_fig |
|
) |
|
|
|
|
|
def update_emotion(emotion): |
|
add_emotion_record(session_user_id, emotion) |
|
|
|
|
|
emotion_fig = create_emotion_chart(session_user_id) |
|
|
|
return ( |
|
f"Your emotional state has been updated to: {emotion}", |
|
emotion_fig |
|
) |
|
|
|
|
|
def display_recommendations(): |
|
user_profile = get_user_profile(session_user_id) |
|
recommendations = user_profile.get('recommendations', []) |
|
|
|
if not recommendations: |
|
return "No recommendations available yet. Continue chatting with Aishura to receive personalized suggestions." |
|
|
|
|
|
recent_recs = recommendations[-5:] |
|
|
|
result = "# Your Personalized Recommendations\n\n" |
|
|
|
for i, rec in enumerate(recent_recs, 1): |
|
recommendation = rec['recommendation'] |
|
result += f"## {i}. {recommendation['title']}\n\n" |
|
result += f"{recommendation['description']}\n\n" |
|
result += f"**Priority:** {recommendation['priority'].title()}\n" |
|
result += f"**Type:** {recommendation['action_type'].replace('_', ' ').title()}\n\n" |
|
result += "---\n\n" |
|
|
|
return result |
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as app: |
|
gr.Markdown("# Aishura - Your AI Career Assistant") |
|
|
|
|
|
with gr.Group(visible=True) as welcome_group: |
|
gr.Markdown("## Welcome to Aishura") |
|
gr.Markdown("Let's start by getting to know you a little better.") |
|
|
|
name_input = gr.Textbox(label="Your Name") |
|
location_input = gr.Textbox(label="Your Location (City/Country)") |
|
emotion_dropdown = gr.Dropdown(choices=EMOTIONS, label="How are you feeling today?") |
|
goal_dropdown = gr.Dropdown(choices=GOAL_TYPES, label="What's your career goal?") |
|
|
|
welcome_button = gr.Button("Get Started") |
|
welcome_output = gr.Markdown() |
|
|
|
|
|
with gr.Group(visible=False) as main_interface: |
|
with gr.Tabs() as tabs: |
|
|
|
with gr.TabItem("Chat with Aishura"): |
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
chatbot = gr.Chatbot(height=500, avatar_images=["👤", "🤖"]) |
|
msg = gr.Textbox(show_label=False, placeholder="Type your message here...", container=False) |
|
|
|
with gr.Column(scale=1): |
|
gr.Markdown("## Your Recommendations") |
|
recommendation_output = gr.Markdown() |
|
refresh_recs_button = gr.Button("Refresh Recommendations") |
|
|
|
msg.submit(chat, [msg, chatbot], [chatbot, msg]) |
|
refresh_recs_button.click(display_recommendations, [], recommendation_output) |
|
|
|
|
|
with gr.TabItem("Profile & Analysis"): |
|
with gr.Tabs() as analysis_tabs: |
|
|
|
with gr.TabItem("Resume Analysis"): |
|
gr.Markdown("## Resume Analysis") |
|
resume_text = gr.Textbox(label="Paste your resume here", lines=10, placeholder="Copy and paste your entire resume here for analysis...") |
|
analyze_resume_button = gr.Button("Analyze Resume") |
|
resume_output = gr.Markdown() |
|
skill_chart = gr.Plot(label="Skill Assessment") |
|
|
|
analyze_resume_button.click( |
|
analyze_resume_interface, |
|
[resume_text], |
|
[resume_output, skill_chart] |
|
) |
|
|
|
|
|
with gr.TabItem("Portfolio Analysis"): |
|
gr.Markdown("## Portfolio Analysis") |
|
portfolio_url = gr.Textbox(label="Portfolio URL", placeholder="https://your-portfolio-website.com") |
|
portfolio_description = gr.Textbox(label="Describe your portfolio", lines=5, placeholder="Describe the content, structure, and purpose of your portfolio...") |
|
analyze_portfolio_button = gr.Button("Analyze Portfolio") |
|
portfolio_output = gr.Markdown() |
|
|
|
analyze_portfolio_button.click( |
|
analyze_portfolio_interface, |
|
[portfolio_url, portfolio_description], |
|
portfolio_output |
|
) |
|
|
|
|
|
with gr.TabItem("Find Opportunities"): |
|
gr.Markdown("## Search for Job Opportunities") |
|
job_query = gr.Textbox(label="What kind of job are you looking for?") |
|
job_location = gr.Textbox(label="Location") |
|
job_results = gr.Slider(minimum=5, maximum=20, value=10, step=5, label="Number of Results") |
|
|
|
search_button = gr.Button("Search") |
|
job_output = gr.Markdown() |
|
|
|
search_button.click(search_jobs_interface, [job_query, job_location, job_results], job_output) |
|
|
|
|
|
with gr.TabItem("Document Templates"): |
|
gr.Markdown("## Generate Document Templates") |
|
doc_type = gr.Dropdown( |
|
choices=["Resume", "Cover Letter", "Self-Introduction", "LinkedIn Profile", "Portfolio", "Interview Preparation"], |
|
label="Document Type" |
|
) |
|
career_field = gr.Textbox(label="Career Field/Industry") |
|
experience = gr.Dropdown( |
|
choices=["Entry Level", "Mid-Career", "Senior"], |
|
label="Experience Level" |
|
) |
|
|
|
template_button = gr.Button("Generate Template") |
|
template_output = gr.Markdown() |
|
|
|
template_button.click(generate_template, [doc_type, career_field, experience], template_output) |
|
|
|
|
|
with gr.TabItem("Personal Routine"): |
|
gr.Markdown("## Create Your Personal Development Routine") |
|
routine_emotion = gr.Dropdown(choices=EMOTIONS, label="Current Emotional State") |
|
routine_goal = gr.Textbox(label="What specific goal are you working toward?") |
|
time_available = gr.Slider(minimum=15, maximum=120, value=60, step=15, label="Minutes Available Per Day") |
|
routine_days = gr.Slider(minimum=3, maximum=30, value=7, step=1, label="Length of Routine (Days)") |
|
|
|
routine_button = gr.Button("Create Routine") |
|
routine_output = gr.Markdown() |
|
|
|
routine_button.click(create_personal_routine, |
|
[routine_emotion, routine_goal, time_available, routine_days], |
|
routine_output) |
|
|
|
|
|
with gr.TabItem("Track Progress"): |
|
with gr.Row(): |
|
with gr.Column(): |
|
gr.Markdown("## Mark Tasks as Complete") |
|
task_input = gr.Textbox(label="Enter Task Name") |
|
complete_button = gr.Button("Mark as Complete") |
|
task_output = gr.Markdown() |
|
|
|
with gr.Column(): |
|
gr.Markdown("## Update Your Emotional State") |
|
new_emotion = gr.Dropdown(choices=EMOTIONS, label="How are you feeling now?") |
|
emotion_button = gr.Button("Update") |
|
emotion_output = gr.Markdown() |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
emotion_chart = gr.Plot(label="Emotional Journey") |
|
|
|
with gr.Column(): |
|
progress_chart = gr.Plot(label="Progress Journey") |
|
|
|
with gr.Row(): |
|
gauge_chart = gr.Plot(label="Routine Completion") |
|
|
|
complete_button.click( |
|
complete_task, |
|
[task_input], |
|
[task_output, task_input, emotion_chart, progress_chart, gauge_chart] |
|
) |
|
|
|
emotion_button.click( |
|
update_emotion, |
|
[new_emotion], |
|
[emotion_output, emotion_chart] |
|
) |
|
|
|
|
|
welcome_button.click( |
|
welcome, |
|
[name_input, location_input, emotion_dropdown, goal_dropdown], |
|
[welcome_output, welcome_group, main_interface] |
|
) |
|
|
|
|
|
app.load( |
|
display_recommendations, |
|
[], |
|
recommendation_output |
|
) |
|
|
|
return app |
|
|
|
|
|
def main(): |
|
app = create_interface() |
|
app.launch(share=True) |
|
|
|
if __name__ == "__main__": |
|
main() |