ans123 commited on
Commit
1eb3ba2
·
verified ·
1 Parent(s): d61ddbe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +617 -65
app.py CHANGED
@@ -15,9 +15,14 @@ import logging
15
  from dotenv import load_dotenv
16
  import pytz
17
  import uuid
18
- import google.generativeai as genai
19
- from google.generativeai.types import HarmCategory, HarmBlockThreshold
20
- from google.generativeai.types import Tool, FunctionDeclaration, GenerateContentConfig
 
 
 
 
 
21
 
22
  # Load environment variables
23
  load_dotenv()
@@ -28,38 +33,33 @@ logging.basicConfig(level=logging.INFO,
28
  logger = logging.getLogger(__name__)
29
 
30
  # Configure API keys
31
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "your-gemini-api-key")
32
  SERPER_API_KEY = os.getenv("SERPER_API_KEY", "your-serper-api-key")
33
 
34
- # Configure Google Gemini API
35
- genai.configure(api_key=GEMINI_API_KEY)
 
 
 
 
 
 
36
 
37
  # Constants for global app
38
  EMOTIONS = ["Unmotivated", "Anxious", "Confused", "Excited", "Overwhelmed", "Discouraged"]
39
  GOAL_TYPES = ["Get a job at a big company", "Find an internship", "Change careers", "Improve skills", "Network better"]
40
  USER_DB_PATH = "user_database.json"
 
 
41
 
42
- # Gemini model configuration
43
- model = genai.GenerativeModel(
44
- model_name="gemini-2.0-flash",
45
- generation_config={
46
- "temperature": 0.7,
47
- "top_p": 0.95,
48
- "top_k": 40,
49
- "max_output_tokens": 2048,
50
- },
51
- safety_settings={
52
- HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
53
- HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
54
- HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
55
- HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
56
- }
57
- )
58
 
59
- # Function declarations for Gemini API
60
- search_jobs = FunctionDeclaration(
61
- name="search_jobs",
62
- description="Search for job opportunities based on location and career goals",
63
  parameters={
64
  "type": "OBJECT",
65
  "properties": {
@@ -80,7 +80,7 @@ search_jobs = FunctionDeclaration(
80
  },
81
  )
82
 
83
- generate_document = FunctionDeclaration(
84
  name="generate_document_template",
85
  description="Generate a document template for job applications",
86
  parameters={
@@ -103,7 +103,7 @@ generate_document = FunctionDeclaration(
103
  },
104
  )
105
 
106
- create_routine = FunctionDeclaration(
107
  name="create_personalized_routine",
108
  description="Create a personalized career development routine",
109
  parameters={
@@ -130,11 +130,54 @@ create_routine = FunctionDeclaration(
130
  },
131
  )
132
 
133
- tools = [
134
- Tool(function_declarations=[search_jobs]),
135
- Tool(function_declarations=[generate_document]),
136
- Tool(function_declarations=[create_routine])
137
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  # User database functions
140
  def load_user_database():
@@ -168,6 +211,10 @@ def get_user_profile(user_id):
168
  "upcoming_events": [],
169
  "routine_history": [],
170
  "daily_emotions": [],
 
 
 
 
171
  "joined_date": datetime.now().strftime("%Y-%m-%d")
172
  }
173
  save_user_database(db)
@@ -231,6 +278,68 @@ def add_routine_to_user(user_id, routine):
231
  save_user_database(db)
232
  return db['users'][user_id]
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  # API Helper Functions
235
  def search_jobs_with_serper(query, location, max_results=5):
236
  """Search for job opportunities using Serper API"""
@@ -288,8 +397,8 @@ def extract_company_from_title(title):
288
  return title.split(' - ')[1].strip()
289
  return "Unknown Company"
290
 
291
- def get_ai_response(user_id, user_input, context=None):
292
- """Get AI response using Google Gemini API"""
293
  try:
294
  user_profile = get_user_profile(user_id)
295
 
@@ -299,42 +408,178 @@ def get_ai_response(user_id, user_input, context=None):
299
  1. Recognize and acknowledge the user's emotion
300
  2. Respond with high-empathy message
301
  3. Suggest specific action based on their input
302
- 4. Offer document support or routine
 
 
 
303
 
304
- Remember to be proactive and preemptive - suggest actions before the user asks.
 
305
  """
306
 
307
  # Build conversation context
308
- messages = []
309
- if context:
310
- messages.extend(context)
311
 
312
  # Add user profile information as context
313
  profile_context = f"""
314
  User Profile Information:
 
315
  - Current emotion: {user_profile.get('current_emotion', '')}
316
  - Career goal: {user_profile.get('career_goal', '')}
317
  - Location: {user_profile.get('location', '')}
318
  """
319
 
320
- messages.append({"role": "user", "parts": [profile_context]})
321
- messages.append({"role": "user", "parts": [user_input]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
- response = model.generate_content(
324
- messages,
325
- generation_config={"temperature": 0.7},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  system_instruction=system_instruction,
327
- tools=tools
 
 
 
 
 
 
328
  )
329
 
330
- return response.text
 
 
 
 
 
 
 
 
 
 
331
  except Exception as e:
332
  logger.error(f"Error in get_ai_response: {str(e)}")
333
  return "I apologize, but I'm having trouble processing your request right now. Please try again later."
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  def create_personalized_routine_with_ai(user_id, emotion, goal, available_time=60, days=7):
336
  """Create a personalized routine using AI"""
337
  try:
 
 
338
  prompt = f"""
339
  Create a personalized {days}-day career development routine for a user who is feeling {emotion} and has a goal to {goal}.
340
  They have about {available_time} minutes per day to dedicate to this routine.
@@ -347,10 +592,51 @@ def create_personalized_routine_with_ai(user_id, emotion, goal, available_time=6
347
  3. Points value (between 10-50)
348
  4. A brief description of why this task is valuable
349
 
350
- Format the routine as a JSON object.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  """
352
 
353
- response = model.generate_content(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  routine_text = response.text
355
 
356
  # Extract JSON portion from the response
@@ -458,12 +744,88 @@ def generate_document_template_with_ai(document_type, career_field="", experienc
458
  Format it in markdown.
459
  """
460
 
461
- response = model.generate_content(prompt)
 
 
 
 
462
  return response.text
463
  except Exception as e:
464
  logger.error(f"Error in generate_document_template_with_ai: {str(e)}")
465
  return f"Error generating {document_type} template. Please try again later."
466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  # Chart and visualization functions
468
  def create_emotion_chart(user_id):
469
  """Create a chart of user's emotions over time"""
@@ -594,6 +956,109 @@ def create_routine_completion_gauge(user_id):
594
 
595
  return fig
596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  # Gradio interface components
598
  def create_interface():
599
  """Create the Gradio interface for Aishura MVP"""
@@ -630,19 +1095,20 @@ def create_interface():
630
 
631
  # Chat function
632
  def chat(message, history):
633
- # Record the user message for context
634
- if not history:
635
- history = []
636
 
637
- # Add user's message to history
638
- history.append({"role": "user", "parts": [message]})
 
 
 
639
 
640
  # Get AI response
641
- response = get_ai_response(session_user_id, message, history)
642
-
643
- # Add AI's response to history
644
- history.append({"role": "assistant", "parts": [response]})
645
 
 
 
646
  return history, ""
647
 
648
  # Function to search for jobs
@@ -683,11 +1149,28 @@ def create_interface():
683
  result += f"- **{task['name']}** ({task['duration']} mins, {task['points']} points)\n"
684
  result += f" *{task['description']}*\n\n"
685
 
686
- # Add the routine to user's profile
687
- add_routine_to_user(session_user_id, routine)
688
-
689
  return result
690
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
  # Function to mark a task as complete
692
  def complete_task(task_name):
693
  if not task_name:
@@ -728,6 +1211,29 @@ def create_interface():
728
  emotion_fig
729
  )
730
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  # Create the interface
732
  with gr.Blocks(theme=gr.themes.Soft()) as app:
733
  gr.Markdown("# Aishura - Your AI Career Assistant")
@@ -750,10 +1256,49 @@ def create_interface():
750
  with gr.Tabs() as tabs:
751
  # Chat tab
752
  with gr.TabItem("Chat with Aishura"):
753
- chatbot = gr.Chatbot(height=400, avatar_images=["👤", "🤖"])
754
- msg = gr.Textbox(show_label=False, placeholder="Type your message here...", container=False)
 
 
 
 
 
 
 
755
 
756
  msg.submit(chat, [msg, chatbot], [chatbot, msg])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
 
758
  # Job Search tab
759
  with gr.TabItem("Find Opportunities"):
@@ -771,7 +1316,7 @@ def create_interface():
771
  with gr.TabItem("Document Templates"):
772
  gr.Markdown("## Generate Document Templates")
773
  doc_type = gr.Dropdown(
774
- choices=["Resume", "Cover Letter", "Self-Introduction", "LinkedIn Profile", "Portfolio"],
775
  label="Document Type"
776
  )
777
  career_field = gr.Textbox(label="Career Field/Industry")
@@ -843,6 +1388,13 @@ def create_interface():
843
  [name_input, location_input, emotion_dropdown, goal_dropdown],
844
  [welcome_output, welcome_group, main_interface]
845
  )
 
 
 
 
 
 
 
846
 
847
  return app
848
 
 
15
  from dotenv import load_dotenv
16
  import pytz
17
  import uuid
18
+ import re
19
+ import base64
20
+ from io import BytesIO
21
+ from PIL import Image
22
+
23
+ # Import the updated Google GenAI SDK
24
+ from google import genai
25
+ from google.genai import types
26
 
27
  # Load environment variables
28
  load_dotenv()
 
33
  logger = logging.getLogger(__name__)
34
 
35
  # Configure API keys
36
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "your-api-key")
37
  SERPER_API_KEY = os.getenv("SERPER_API_KEY", "your-serper-api-key")
38
 
39
+ # Configure Google GenAI
40
+ genai.configure(api_key=GOOGLE_API_KEY)
41
+
42
+ # Init the client
43
+ client = genai.Client()
44
+
45
+ # Model configuration - using Gemini latest model
46
+ MODEL_ID = "gemini-2.0-flash-001"
47
 
48
  # Constants for global app
49
  EMOTIONS = ["Unmotivated", "Anxious", "Confused", "Excited", "Overwhelmed", "Discouraged"]
50
  GOAL_TYPES = ["Get a job at a big company", "Find an internship", "Change careers", "Improve skills", "Network better"]
51
  USER_DB_PATH = "user_database.json"
52
+ RESUME_FOLDER = "user_resumes"
53
+ PORTFOLIO_FOLDER = "user_portfolios"
54
 
55
+ # Ensure folders exist
56
+ os.makedirs(RESUME_FOLDER, exist_ok=True)
57
+ os.makedirs(PORTFOLIO_FOLDER, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ # Function declarations for tools
60
+ get_job_opportunities = types.FunctionDeclaration(
61
+ name="get_job_opportunities",
62
+ description="Get relevant job opportunities based on location and career goals",
63
  parameters={
64
  "type": "OBJECT",
65
  "properties": {
 
80
  },
81
  )
82
 
83
+ generate_document = types.FunctionDeclaration(
84
  name="generate_document_template",
85
  description="Generate a document template for job applications",
86
  parameters={
 
103
  },
104
  )
105
 
106
+ create_routine = types.FunctionDeclaration(
107
  name="create_personalized_routine",
108
  description="Create a personalized career development routine",
109
  parameters={
 
130
  },
131
  )
132
 
133
+ analyze_resume = types.FunctionDeclaration(
134
+ name="analyze_resume",
135
+ description="Analyze a user's resume and provide feedback",
136
+ parameters={
137
+ "type": "OBJECT",
138
+ "properties": {
139
+ "resume_text": {
140
+ "type": "STRING",
141
+ "description": "The full text of the user's resume",
142
+ },
143
+ "career_goal": {
144
+ "type": "STRING",
145
+ "description": "The user's career goal or job interest",
146
+ },
147
+ },
148
+ "required": ["resume_text"],
149
+ },
150
+ )
151
+
152
+ analyze_portfolio = types.FunctionDeclaration(
153
+ name="analyze_portfolio",
154
+ description="Analyze a user's portfolio and provide feedback",
155
+ parameters={
156
+ "type": "OBJECT",
157
+ "properties": {
158
+ "portfolio_url": {
159
+ "type": "STRING",
160
+ "description": "URL to the user's portfolio",
161
+ },
162
+ "portfolio_description": {
163
+ "type": "STRING",
164
+ "description": "Description of the portfolio content",
165
+ },
166
+ "career_goal": {
167
+ "type": "STRING",
168
+ "description": "The user's career goal or job interest",
169
+ },
170
+ },
171
+ "required": ["portfolio_description"],
172
+ },
173
+ )
174
+
175
+ # Combine tools
176
+ job_tool = types.Tool(function_declarations=[get_job_opportunities])
177
+ document_tool = types.Tool(function_declarations=[generate_document])
178
+ routine_tool = types.Tool(function_declarations=[create_routine])
179
+ resume_tool = types.Tool(function_declarations=[analyze_resume])
180
+ portfolio_tool = types.Tool(function_declarations=[analyze_portfolio])
181
 
182
  # User database functions
183
  def load_user_database():
 
211
  "upcoming_events": [],
212
  "routine_history": [],
213
  "daily_emotions": [],
214
+ "resume_path": "",
215
+ "portfolio_path": "",
216
+ "recommendations": [],
217
+ "chat_history": [],
218
  "joined_date": datetime.now().strftime("%Y-%m-%d")
219
  }
220
  save_user_database(db)
 
278
  save_user_database(db)
279
  return db['users'][user_id]
280
 
281
+ def save_user_resume(user_id, resume_text):
282
+ """Save user's resume to file and update profile"""
283
+ # Create filename
284
+ filename = f"{user_id}_resume.txt"
285
+ filepath = os.path.join(RESUME_FOLDER, filename)
286
+
287
+ # Save resume text to file
288
+ with open(filepath, 'w') as file:
289
+ file.write(resume_text)
290
+
291
+ # Update user profile
292
+ update_user_profile(user_id, {"resume_path": filepath})
293
+
294
+ return filepath
295
+
296
+ def save_user_portfolio(user_id, portfolio_content):
297
+ """Save user's portfolio info to file and update profile"""
298
+ # Create filename
299
+ filename = f"{user_id}_portfolio.json"
300
+ filepath = os.path.join(PORTFOLIO_FOLDER, filename)
301
+
302
+ # Save portfolio content to file
303
+ with open(filepath, 'w') as file:
304
+ json.dump(portfolio_content, file, indent=4)
305
+
306
+ # Update user profile
307
+ update_user_profile(user_id, {"portfolio_path": filepath})
308
+
309
+ return filepath
310
+
311
+ def add_recommendation_to_user(user_id, recommendation):
312
+ """Add a new recommendation to user's recommendations list"""
313
+ db = load_user_database()
314
+ if user_id in db['users']:
315
+ if 'recommendations' not in db['users'][user_id]:
316
+ db['users'][user_id]['recommendations'] = []
317
+
318
+ recommendation_with_date = {
319
+ "recommendation": recommendation,
320
+ "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
321
+ "status": "pending" # pending, completed, dismissed
322
+ }
323
+ db['users'][user_id]['recommendations'].append(recommendation_with_date)
324
+ save_user_database(db)
325
+ return db['users'][user_id]
326
+
327
+ def add_chat_message(user_id, role, message):
328
+ """Add a message to the user's chat history"""
329
+ db = load_user_database()
330
+ if user_id in db['users']:
331
+ if 'chat_history' not in db['users'][user_id]:
332
+ db['users'][user_id]['chat_history'] = []
333
+
334
+ chat_message = {
335
+ "role": role, # user or assistant
336
+ "message": message,
337
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
338
+ }
339
+ db['users'][user_id]['chat_history'].append(chat_message)
340
+ save_user_database(db)
341
+ return db['users'][user_id]
342
+
343
  # API Helper Functions
344
  def search_jobs_with_serper(query, location, max_results=5):
345
  """Search for job opportunities using Serper API"""
 
397
  return title.split(' - ')[1].strip()
398
  return "Unknown Company"
399
 
400
+ def get_ai_response(user_id, user_input, context=None, generate_recommendations=True):
401
+ """Get AI response using Google GenAI"""
402
  try:
403
  user_profile = get_user_profile(user_id)
404
 
 
408
  1. Recognize and acknowledge the user's emotion
409
  2. Respond with high-empathy message
410
  3. Suggest specific action based on their input
411
+ 4. Offer document support, job opportunities, or personalized routine
412
+
413
+ Remember to be proactive and preemptive - suggest actions before the user asks. Your goal is to provide
414
+ end-to-end support for the user's career journey, from emotional support to concrete action.
415
 
416
+ If the user has shared a resume or portfolio, refer to insights from those documents to provide
417
+ personalized guidance.
418
  """
419
 
420
  # Build conversation context
421
+ contents = []
 
 
422
 
423
  # Add user profile information as context
424
  profile_context = f"""
425
  User Profile Information:
426
+ - Name: {user_profile.get('name', '')}
427
  - Current emotion: {user_profile.get('current_emotion', '')}
428
  - Career goal: {user_profile.get('career_goal', '')}
429
  - Location: {user_profile.get('location', '')}
430
  """
431
 
432
+ # Add resume context if available
433
+ if user_profile.get('resume_path') and os.path.exists(user_profile.get('resume_path')):
434
+ try:
435
+ with open(user_profile.get('resume_path'), 'r') as file:
436
+ resume_text = file.read()
437
+ profile_context += f"\nUser Resume Summary: The user has shared their resume. They have experience in {resume_text[:100]}..."
438
+ except Exception as e:
439
+ logger.error(f"Error reading resume: {str(e)}")
440
+
441
+ # Add portfolio context if available
442
+ if user_profile.get('portfolio_path') and os.path.exists(user_profile.get('portfolio_path')):
443
+ try:
444
+ with open(user_profile.get('portfolio_path'), 'r') as file:
445
+ portfolio_data = json.load(file)
446
+ profile_context += f"\nUser Portfolio: The user has shared their portfolio with URL: {portfolio_data.get('url', 'Not provided')}."
447
+ except Exception as e:
448
+ logger.error(f"Error reading portfolio: {str(e)}")
449
+
450
+ # Start with context
451
+ user_context = types.Content(
452
+ role="user",
453
+ parts=[types.Part.from_text(profile_context)]
454
+ )
455
+ contents.append(user_context)
456
 
457
+ # Add previous context if provided
458
+ if context:
459
+ for msg in context:
460
+ if msg["role"] == "user":
461
+ contents.append(types.Content(
462
+ role="user",
463
+ parts=[types.Part.from_text(msg["message"])]
464
+ ))
465
+ else:
466
+ contents.append(types.Content(
467
+ role="model",
468
+ parts=[types.Part.from_text(msg["message"])]
469
+ ))
470
+
471
+ # Add current user input
472
+ contents.append(types.Content(
473
+ role="user",
474
+ parts=[types.Part.from_text(user_input)]
475
+ ))
476
+
477
+ # Configure tools
478
+ tools = [job_tool, document_tool, routine_tool, resume_tool, portfolio_tool]
479
+
480
+ # Get response
481
+ response = client.models.generate_content(
482
+ model=MODEL_ID,
483
+ contents=contents,
484
  system_instruction=system_instruction,
485
+ tools=tools,
486
+ generation_config=types.GenerationConfig(
487
+ temperature=0.7,
488
+ max_output_tokens=2048,
489
+ top_p=0.95,
490
+ top_k=40
491
+ )
492
  )
493
 
494
+ ai_response_text = response.text
495
+
496
+ # Log the message in chat history
497
+ add_chat_message(user_id, "user", user_input)
498
+ add_chat_message(user_id, "assistant", ai_response_text)
499
+
500
+ # Generate recommendations if enabled
501
+ if generate_recommendations:
502
+ gen_recommendations(user_id, user_input, ai_response_text)
503
+
504
+ return ai_response_text
505
  except Exception as e:
506
  logger.error(f"Error in get_ai_response: {str(e)}")
507
  return "I apologize, but I'm having trouble processing your request right now. Please try again later."
508
 
509
+ def gen_recommendations(user_id, user_input, ai_response):
510
+ """Generate recommendations based on conversation"""
511
+ try:
512
+ user_profile = get_user_profile(user_id)
513
+
514
+ prompt = f"""
515
+ Based on the following conversation between a user and Aishura (an AI career assistant),
516
+ generate 1-3 specific, actionable recommendations for the user's next steps in their career journey.
517
+
518
+ User Profile:
519
+ - Current emotion: {user_profile.get('current_emotion', '')}
520
+ - Career goal: {user_profile.get('career_goal', '')}
521
+ - Location: {user_profile.get('location', '')}
522
+
523
+ Recent Conversation:
524
+ User: {user_input}
525
+
526
+ Aishura: {ai_response}
527
+
528
+ Generate specific, actionable recommendations in JSON format:
529
+ ```json
530
+ [
531
+ {{
532
+ "title": "Brief recommendation title",
533
+ "description": "Detailed recommendation description",
534
+ "action_type": "job_search|skill_building|networking|resume|portfolio|interview_prep|other",
535
+ "priority": "high|medium|low"
536
+ }}
537
+ ]
538
+ ```
539
+
540
+ Focus on immediate, practical next steps that align with the user's goals and emotional state.
541
+ """
542
+
543
+ response = client.models.generate_content(
544
+ model=MODEL_ID,
545
+ contents=prompt
546
+ )
547
+
548
+ recommendation_text = response.text
549
+
550
+ # Extract JSON from response
551
+ try:
552
+ # Find JSON content between ```json and ``` if present
553
+ if "```json" in recommendation_text and "```" in recommendation_text.split("```json")[1]:
554
+ json_str = recommendation_text.split("```json")[1].split("```")[0].strip()
555
+ else:
556
+ # Otherwise try to find anything that looks like JSON array
557
+ import re
558
+ json_match = re.search(r'(\[.*\])', recommendation_text, re.DOTALL)
559
+ if json_match:
560
+ json_str = json_match.group(1)
561
+ else:
562
+ json_str = recommendation_text
563
+
564
+ recommendations = json.loads(json_str)
565
+
566
+ # Add recommendations to user profile
567
+ for rec in recommendations:
568
+ add_recommendation_to_user(user_id, rec)
569
+
570
+ return recommendations
571
+ except json.JSONDecodeError:
572
+ logger.error(f"Failed to parse JSON from AI response: {recommendation_text}")
573
+ return []
574
+ except Exception as e:
575
+ logger.error(f"Error in gen_recommendations: {str(e)}")
576
+ return []
577
+
578
  def create_personalized_routine_with_ai(user_id, emotion, goal, available_time=60, days=7):
579
  """Create a personalized routine using AI"""
580
  try:
581
+ user_profile = get_user_profile(user_id)
582
+
583
  prompt = f"""
584
  Create a personalized {days}-day career development routine for a user who is feeling {emotion} and has a goal to {goal}.
585
  They have about {available_time} minutes per day to dedicate to this routine.
 
592
  3. Points value (between 10-50)
593
  4. A brief description of why this task is valuable
594
 
595
+ Format the routine as a JSON object with this structure:
596
+ ```json
597
+ {{
598
+ "name": "Routine name",
599
+ "description": "Brief description of the routine",
600
+ "days": {days},
601
+ "daily_tasks": [
602
+ {{
603
+ "day": 1,
604
+ "tasks": [
605
+ {{
606
+ "name": "Task name",
607
+ "points": 20,
608
+ "duration": 30,
609
+ "description": "Why this task is valuable"
610
+ }}
611
+ ]
612
+ }}
613
+ ]
614
+ }}
615
+ ```
616
  """
617
 
618
+ # Use resume and portfolio info if available
619
+ if user_profile.get('resume_path') and os.path.exists(user_profile.get('resume_path')):
620
+ try:
621
+ with open(user_profile.get('resume_path'), 'r') as file:
622
+ resume_text = file.read()
623
+ prompt += f"\n\nTailor the routine based on the user's resume. Here's a summary: {resume_text[:500]}..."
624
+ except Exception as e:
625
+ logger.error(f"Error reading resume: {str(e)}")
626
+
627
+ if user_profile.get('portfolio_path') and os.path.exists(user_profile.get('portfolio_path')):
628
+ try:
629
+ with open(user_profile.get('portfolio_path'), 'r') as file:
630
+ portfolio_data = json.load(file)
631
+ prompt += f"\n\nConsider the user's portfolio when creating the routine. Portfolio URL: {portfolio_data.get('url', 'Not provided')}"
632
+ except Exception as e:
633
+ logger.error(f"Error reading portfolio: {str(e)}")
634
+
635
+ response = client.models.generate_content(
636
+ model=MODEL_ID,
637
+ contents=prompt
638
+ )
639
+
640
  routine_text = response.text
641
 
642
  # Extract JSON portion from the response
 
744
  Format it in markdown.
745
  """
746
 
747
+ response = client.models.generate_content(
748
+ model=MODEL_ID,
749
+ contents=prompt
750
+ )
751
+
752
  return response.text
753
  except Exception as e:
754
  logger.error(f"Error in generate_document_template_with_ai: {str(e)}")
755
  return f"Error generating {document_type} template. Please try again later."
756
 
757
+ def analyze_resume_with_ai(user_id, resume_text):
758
+ """Analyze resume with AI and provide feedback"""
759
+ try:
760
+ user_profile = get_user_profile(user_id)
761
+
762
+ prompt = f"""
763
+ Analyze the following resume for a user who has the career goal of: {user_profile.get('career_goal', 'improving their career')}
764
+
765
+ Resume Text:
766
+ {resume_text}
767
+
768
+ Provide detailed feedback on:
769
+ 1. Overall strengths and weaknesses
770
+ 2. Format and organization
771
+ 3. Content effectiveness for their career goal
772
+ 4. Specific improvement suggestions
773
+ 5. Keywords and skills that should be highlighted
774
+
775
+ Format your analysis with markdown headings and bullet points.
776
+ """
777
+
778
+ response = client.models.generate_content(
779
+ model=MODEL_ID,
780
+ contents=prompt
781
+ )
782
+
783
+ # Save resume
784
+ save_user_resume(user_id, resume_text)
785
+
786
+ return response.text
787
+ except Exception as e:
788
+ logger.error(f"Error in analyze_resume_with_ai: {str(e)}")
789
+ return "I apologize, but I'm having trouble analyzing your resume right now. Please try again later."
790
+
791
+ def analyze_portfolio_with_ai(user_id, portfolio_url, portfolio_description):
792
+ """Analyze portfolio with AI and provide feedback"""
793
+ try:
794
+ user_profile = get_user_profile(user_id)
795
+
796
+ prompt = f"""
797
+ Analyze the following portfolio for a user who has the career goal of: {user_profile.get('career_goal', 'improving their career')}
798
+
799
+ Portfolio URL: {portfolio_url}
800
+ Portfolio Description: {portfolio_description}
801
+
802
+ Based on the description provided, analyze:
803
+ 1. How well the portfolio aligns with their career goal
804
+ 2. Strengths of the portfolio
805
+ 3. Areas for improvement
806
+ 4. Specific suggestions to enhance the portfolio
807
+ 5. How to better showcase skills relevant to their goal
808
+
809
+ Format your analysis with markdown headings and bullet points.
810
+ """
811
+
812
+ response = client.models.generate_content(
813
+ model=MODEL_ID,
814
+ contents=prompt
815
+ )
816
+
817
+ # Save portfolio info
818
+ portfolio_content = {
819
+ "url": portfolio_url,
820
+ "description": portfolio_description
821
+ }
822
+ save_user_portfolio(user_id, portfolio_content)
823
+
824
+ return response.text
825
+ except Exception as e:
826
+ logger.error(f"Error in analyze_portfolio_with_ai: {str(e)}")
827
+ return "I apologize, but I'm having trouble analyzing your portfolio right now. Please try again later."
828
+
829
  # Chart and visualization functions
830
  def create_emotion_chart(user_id):
831
  """Create a chart of user's emotions over time"""
 
956
 
957
  return fig
958
 
959
+ def create_skill_radar_chart(user_id):
960
+ """Create a radar chart of user's skills based on resume analysis"""
961
+ user_profile = get_user_profile(user_id)
962
+
963
+ # If no resume, return empty chart
964
+ if not user_profile.get('resume_path') or not os.path.exists(user_profile.get('resume_path')):
965
+ fig = go.Figure()
966
+ fig.add_annotation(text="No resume data available yet", showarrow=False)
967
+ return fig
968
+
969
+ # Read resume
970
+ try:
971
+ with open(user_profile.get('resume_path'), 'r') as file:
972
+ resume_text = file.read()
973
+ except Exception as e:
974
+ logger.error(f"Error reading resume: {str(e)}")
975
+ fig = go.Figure()
976
+ fig.add_annotation(text="Error reading resume data", showarrow=False)
977
+ return fig
978
+
979
+ # Use AI to extract and score skills
980
+ prompt = f"""
981
+ Based on the following resume, identify 5-8 key skills and rate them on a scale of 1-10.
982
+
983
+ Resume:
984
+ {resume_text[:2000]}...
985
+
986
+ Return the results as a JSON object with this structure:
987
+ ```json
988
+ {{
989
+ "skills": [
990
+ {{"name": "Skill Name", "score": 7}},
991
+ {{"name": "Another Skill", "score": 9}}
992
+ ]
993
+ }}
994
+ ```
995
+ """
996
+
997
+ try:
998
+ response = client.models.generate_content(
999
+ model=MODEL_ID,
1000
+ contents=prompt
1001
+ )
1002
+
1003
+ skill_text = response.text
1004
+
1005
+ # Extract JSON
1006
+ if "```json" in skill_text and "```" in skill_text.split("```json")[1]:
1007
+ json_str = skill_text.split("```json")[1].split("```")[0].strip()
1008
+ else:
1009
+ import re
1010
+ json_match = re.search(r'(\{.*\})', skill_text, re.DOTALL)
1011
+ if json_match:
1012
+ json_str = json_match.group(1)
1013
+ else:
1014
+ json_str = skill_text
1015
+
1016
+ skill_data = json.loads(json_str)
1017
+
1018
+ # Create radar chart
1019
+ if 'skills' in skill_data and skill_data['skills']:
1020
+ skills = skill_data['skills']
1021
+
1022
+ # Prepare data for radar chart
1023
+ categories = [skill['name'] for skill in skills]
1024
+ values = [skill['score'] for skill in skills]
1025
+
1026
+ # Add the first point at the end to close the loop
1027
+ categories.append(categories[0])
1028
+ values.append(values[0])
1029
+
1030
+ fig = go.Figure()
1031
+
1032
+ fig.add_trace(go.Scatterpolar(
1033
+ r=values,
1034
+ theta=categories,
1035
+ fill='toself',
1036
+ name='Skills'
1037
+ ))
1038
+
1039
+ fig.update_layout(
1040
+ polar=dict(
1041
+ radialaxis=dict(
1042
+ visible=True,
1043
+ range=[0, 10]
1044
+ )
1045
+ ),
1046
+ showlegend=False,
1047
+ title="Skill Assessment Based on Resume"
1048
+ )
1049
+
1050
+ return fig
1051
+ else:
1052
+ fig = go.Figure()
1053
+ fig.add_annotation(text="Could not extract skills from resume", showarrow=False)
1054
+ return fig
1055
+
1056
+ except Exception as e:
1057
+ logger.error(f"Error creating skill radar chart: {str(e)}")
1058
+ fig = go.Figure()
1059
+ fig.add_annotation(text="Error analyzing skills", showarrow=False)
1060
+ return fig
1061
+
1062
  # Gradio interface components
1063
  def create_interface():
1064
  """Create the Gradio interface for Aishura MVP"""
 
1095
 
1096
  # Chat function
1097
  def chat(message, history):
1098
+ # Get user profile
1099
+ user_profile = get_user_profile(session_user_id)
 
1100
 
1101
+ # Convert history to the format expected by get_ai_response
1102
+ context = []
1103
+ for h in history:
1104
+ context.append({"role": "user", "message": h[0]})
1105
+ context.append({"role": "assistant", "message": h[1]})
1106
 
1107
  # Get AI response
1108
+ response = get_ai_response(session_user_id, message, context)
 
 
 
1109
 
1110
+ # Return updated history and empty message
1111
+ history.append((message, response))
1112
  return history, ""
1113
 
1114
  # Function to search for jobs
 
1149
  result += f"- **{task['name']}** ({task['duration']} mins, {task['points']} points)\n"
1150
  result += f" *{task['description']}*\n\n"
1151
 
 
 
 
1152
  return result
1153
 
1154
+ # Function to analyze resume
1155
+ def analyze_resume_interface(resume_text):
1156
+ if not resume_text:
1157
+ return "Please enter your resume text."
1158
+
1159
+ analysis = analyze_resume_with_ai(session_user_id, resume_text)
1160
+
1161
+ # Update skill chart
1162
+ skill_fig = create_skill_radar_chart(session_user_id)
1163
+
1164
+ return analysis, skill_fig
1165
+
1166
+ # Function to analyze portfolio
1167
+ def analyze_portfolio_interface(portfolio_url, portfolio_description):
1168
+ if not portfolio_description:
1169
+ return "Please enter a description of your portfolio."
1170
+
1171
+ analysis = analyze_portfolio_with_ai(session_user_id, portfolio_url, portfolio_description)
1172
+ return analysis
1173
+
1174
  # Function to mark a task as complete
1175
  def complete_task(task_name):
1176
  if not task_name:
 
1211
  emotion_fig
1212
  )
1213
 
1214
+ # Function to display recommendations
1215
+ def display_recommendations():
1216
+ user_profile = get_user_profile(session_user_id)
1217
+ recommendations = user_profile.get('recommendations', [])
1218
+
1219
+ if not recommendations:
1220
+ return "No recommendations available yet. Continue chatting with Aishura to receive personalized suggestions."
1221
+
1222
+ # Show the most recent 5 recommendations
1223
+ recent_recs = recommendations[-5:]
1224
+
1225
+ result = "# Your Personalized Recommendations\n\n"
1226
+
1227
+ for i, rec in enumerate(recent_recs, 1):
1228
+ recommendation = rec['recommendation']
1229
+ result += f"## {i}. {recommendation['title']}\n\n"
1230
+ result += f"{recommendation['description']}\n\n"
1231
+ result += f"**Priority:** {recommendation['priority'].title()}\n"
1232
+ result += f"**Type:** {recommendation['action_type'].replace('_', ' ').title()}\n\n"
1233
+ result += "---\n\n"
1234
+
1235
+ return result
1236
+
1237
  # Create the interface
1238
  with gr.Blocks(theme=gr.themes.Soft()) as app:
1239
  gr.Markdown("# Aishura - Your AI Career Assistant")
 
1256
  with gr.Tabs() as tabs:
1257
  # Chat tab
1258
  with gr.TabItem("Chat with Aishura"):
1259
+ with gr.Row():
1260
+ with gr.Column(scale=2):
1261
+ chatbot = gr.Chatbot(height=500, avatar_images=["👤", "🤖"])
1262
+ msg = gr.Textbox(show_label=False, placeholder="Type your message here...", container=False)
1263
+
1264
+ with gr.Column(scale=1):
1265
+ gr.Markdown("## Your Recommendations")
1266
+ recommendation_output = gr.Markdown()
1267
+ refresh_recs_button = gr.Button("Refresh Recommendations")
1268
 
1269
  msg.submit(chat, [msg, chatbot], [chatbot, msg])
1270
+ refresh_recs_button.click(display_recommendations, [], recommendation_output)
1271
+
1272
+ # Profile and Career Analysis tab
1273
+ with gr.TabItem("Profile & Analysis"):
1274
+ with gr.Tabs() as analysis_tabs:
1275
+ # Resume Analysis
1276
+ with gr.TabItem("Resume Analysis"):
1277
+ gr.Markdown("## Resume Analysis")
1278
+ resume_text = gr.Textbox(label="Paste your resume here", lines=10, placeholder="Copy and paste your entire resume here for analysis...")
1279
+ analyze_resume_button = gr.Button("Analyze Resume")
1280
+ resume_output = gr.Markdown()
1281
+ skill_chart = gr.Plot(label="Skill Assessment")
1282
+
1283
+ analyze_resume_button.click(
1284
+ analyze_resume_interface,
1285
+ [resume_text],
1286
+ [resume_output, skill_chart]
1287
+ )
1288
+
1289
+ # Portfolio Analysis
1290
+ with gr.TabItem("Portfolio Analysis"):
1291
+ gr.Markdown("## Portfolio Analysis")
1292
+ portfolio_url = gr.Textbox(label="Portfolio URL", placeholder="https://your-portfolio-website.com")
1293
+ portfolio_description = gr.Textbox(label="Describe your portfolio", lines=5, placeholder="Describe the content, structure, and purpose of your portfolio...")
1294
+ analyze_portfolio_button = gr.Button("Analyze Portfolio")
1295
+ portfolio_output = gr.Markdown()
1296
+
1297
+ analyze_portfolio_button.click(
1298
+ analyze_portfolio_interface,
1299
+ [portfolio_url, portfolio_description],
1300
+ portfolio_output
1301
+ )
1302
 
1303
  # Job Search tab
1304
  with gr.TabItem("Find Opportunities"):
 
1316
  with gr.TabItem("Document Templates"):
1317
  gr.Markdown("## Generate Document Templates")
1318
  doc_type = gr.Dropdown(
1319
+ choices=["Resume", "Cover Letter", "Self-Introduction", "LinkedIn Profile", "Portfolio", "Interview Preparation"],
1320
  label="Document Type"
1321
  )
1322
  career_field = gr.Textbox(label="Career Field/Industry")
 
1388
  [name_input, location_input, emotion_dropdown, goal_dropdown],
1389
  [welcome_output, welcome_group, main_interface]
1390
  )
1391
+
1392
+ # Load initial recommendations
1393
+ app.load(
1394
+ display_recommendations,
1395
+ [],
1396
+ recommendation_output
1397
+ )
1398
 
1399
  return app
1400