afouda commited on
Commit
7095aef
ยท
verified ยท
1 Parent(s): 627e74e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -63
app.py CHANGED
@@ -20,7 +20,7 @@ BASE_URL = "https://api.deepinfra.com/v1/openai"
20
  WEAVIATE_URL = "maf5cvz1saelnti3k34a.c0.europe-west3.gcp.weaviate.cloud"
21
  WEAVIATE_API_KEY = "cHFZK1JOaEg3K2p6K3JnQl9ZM1FEQ2NhMVU1SnBRVUpYWCtCVHlVU0J2Qmx1Mk9SaktpT09UQTNiU1hRPV92MjAw"
22
 
23
- # --- Helper function to create the Application schema ---
24
  def create_application_schema(client: weaviate.WeaviateClient):
25
  collection_name = "Application"
26
  if not client.collections.exists(collection_name):
@@ -38,9 +38,28 @@ def create_application_schema(client: weaviate.WeaviateClient):
38
  else:
39
  print(f"โœ… Collection '{collection_name}' already exists.")
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  # --- 2. CHATBOT CLASS ---
42
  class WeaviateChatbot:
43
- # ... (No changes needed in this class, code is omitted for brevity)
44
  def __init__(self, weaviate_url, weaviate_api_key, llm_api_key, llm_base_url):
45
  print("Connecting to clients...")
46
  self.weaviate_client = weaviate.connect_to_weaviate_cloud(
@@ -50,8 +69,10 @@ class WeaviateChatbot:
50
  )
51
  self.weaviate_client.connect()
52
  print("โœ… Successfully connected to Weaviate.")
53
-
 
54
  create_application_schema(self.weaviate_client)
 
55
 
56
  self.llm_client = OpenAI(api_key=llm_api_key, base_url=llm_base_url)
57
  print("โœ… Successfully connected to LLM client (DeepInfra).")
@@ -73,31 +94,29 @@ class WeaviateChatbot:
73
  except Exception as e:
74
  print(f"Could not query collection '{name}'. Error: {e}")
75
  return "\n---\n".join(all_results) if all_results else "No relevant information found in the database."
76
-
77
  def _generate_response(self, query: str, context: str) -> str:
78
  prompt = f"""
79
- You are EduNatives Assistant.
80
- Your main goal is to help users find opportunities and apply for them.
81
- RULES:
82
- 1. First, answer the user's question based on the CONTEXT from the database.
83
- 2. If you list jobs:
84
- - By default: return them as a **numbered list with unique identifiers** like (job_001).
85
- - If the user explicitly asks for a "table": return the results in a clean **markdown table** with columns: Identifier | Title | Company | Location | Description.
86
- 3. If the user says they want to apply for a job, for example:
87
- "I want to apply for job_021"
88
- โ†’ Respond ONLY with the exact phrase:
89
- `STARTING_APPLICATION_PROCESS:job_021`
90
- 4. Keep answers concise, clear, and helpful.
91
- 5. Always prioritize opportunities from the CONTEXT.
92
-
93
- --- CONTEXT FROM DATABASE START ---
94
- {context}
95
- --- CONTEXT FROM DATABASE END ---
96
-
97
- User Question: {query}
98
 
99
- Answer:
100
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  response = self.llm_client.chat.completions.create(model=MODEL_NAME, messages=[{"role": "user", "content": prompt}], max_tokens=4096)
102
  return response.choices[0].message.content.strip()
103
 
@@ -129,10 +148,98 @@ class WeaviateChatbot:
129
  self.weaviate_client.close()
130
  print("\nWeaviate connection closed.")
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  # --- Helper to extract text from uploaded files ---
134
  def _extract_text_from_file(file_path):
135
- # ... (No changes needed in this function, code is omitted for brevity)
136
  print(f"Extracting text from: {file_path}")
137
  if file_path.endswith('.pdf'):
138
  try:
@@ -159,79 +266,155 @@ def _extract_text_from_file(file_path):
159
  chatbot_instance = WeaviateChatbot(WEAVIATE_URL, WEAVIATE_API_KEY, DEEPINFRA_API_KEY, BASE_URL)
160
  atexit.register(chatbot_instance.close_connections)
161
 
162
- # --- 4. GRADIO INTERFACE LOGIC (Simplified back) ---
163
  def chat_interface_func(message: str, history: list, app_state: dict, file_obj: object):
164
  history = history or []
 
165
 
 
 
 
 
166
  if file_obj is not None:
167
  file_path = file_obj.name
168
  text = _extract_text_from_file(file_path)
169
 
170
- if app_state.get("mode") == "APPLYING_CV":
171
  app_state["cv_content"] = text
172
- history.append((f"๐Ÿ“„ CV '{os.path.basename(file_path)}' uploaded.", "Great! Now, please upload your Cover Letter."))
173
- app_state["mode"] = "APPLYING_COVER_LETTER"
174
- return history, app_state, gr.update(visible=True, value=None)
 
 
 
175
 
176
- elif app_state.get("mode") == "APPLYING_COVER_LETTER":
177
  app_state["cover_letter_content"] = text
178
  history.append((f"๐Ÿ“„ Cover Letter '{os.path.basename(file_path)}' uploaded.", "Thank you! Submitting your application now..."))
179
 
180
  success = chatbot_instance.save_application(app_state)
181
- if success:
182
- final_message = f"โœ… Your application for job **{app_state.get('job_id')}** has been submitted successfully! What else can I help you with?"
183
- else:
184
- final_message = "โŒ Sorry, there was an error submitting your application. Please try again later."
185
-
186
  history.append((None, final_message))
187
  app_state = {"mode": "GENERAL"}
188
- return history, app_state, gr.update(visible=False, value=None)
189
 
 
190
  if message:
191
  history.append((message, None))
192
 
193
- if "APPLYING" in app_state.get("mode", "GENERAL"):
194
- history.append((None, "Please upload the requested document to continue."))
195
- return history, app_state, gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
 
197
  response = chatbot_instance.ask(message)
198
 
199
- if response.startswith("STARTING_APPLICATION_PROCESS:"):
200
- job_id = response.split(":")[1]
 
 
 
 
 
201
  app_state["mode"] = "APPLYING_CV"
202
  app_state["job_id"] = job_id
203
  bot_message = f"Starting application for job **{job_id}**. Please upload your CV."
204
  history.append((None, bot_message))
205
- return history, app_state, gr.update(visible=True)
 
 
 
 
 
 
 
206
  else:
207
  history.append((None, response))
208
- return history, app_state, gr.update(visible=False)
209
-
210
- # Default return if no message
211
- return history, app_state, gr.update(visible=False)
212
 
 
 
213
 
214
- # --- 5. BUILD GRADIO UI (Final Layout from Image) ---
 
215
  with gr.Blocks(theme=gr.themes.Soft(), title="EduNatives Assistant") as demo:
216
 
217
- application_state = gr.State({"mode": "GENERAL", "job_id": None, "cv_content": None, "cover_letter_content": None})
218
- file_uploader = gr.File(label="Upload Document", file_types=['.pdf', '.docx', '.txt'], visible=False)
 
 
 
219
 
220
  gr.Markdown(
221
  """
222
- # EduNatives Assistant
223
  Ask me anything about jobs, projects, or student availability. I can also help you navigate the EduNatives app.
224
  """
225
  )
226
 
227
  chatbot_window = gr.Chatbot(height=450, label="Chat Window", bubble_full_width=False)
228
 
229
- # The container for the grid of example buttons
230
  with gr.Column() as examples_container:
231
  examples_list = [
232
  "What jobs are available at Google?",
233
  "Find students with experience in Python and Machine Learning.",
234
- "Tell me about the 'AI-Powered Medical Imaging Analysis' project.",
235
  "ูƒูŠู ูŠู…ูƒู†ู†ูŠ ูƒุทุงู„ุจ ุงู„ุชุณุฌูŠู„ ููŠ ุงู„ุชุทุจูŠู‚ุŸ",
236
  "I'm a company, how can I post an internship?"
237
  ]
@@ -245,15 +428,18 @@ with gr.Blocks(theme=gr.themes.Soft(), title="EduNatives Assistant") as demo:
245
 
246
  example_buttons = [btn1, btn2, btn3, btn4, btn5]
247
 
248
- # The main input bar, now always visible
249
  with gr.Row() as main_input_row:
250
- text_input = gr.Textbox(placeholder="Ask your question here...", container=False, scale=7)
251
  submit_btn = gr.Button("Send", variant="primary", scale=1)
 
 
 
252
 
253
  # --- Event Handlers ---
254
- outputs_list = [chatbot_window, application_state, file_uploader]
 
255
 
256
- # Event handlers for the main text input
257
  submit_btn.click(
258
  fn=chat_interface_func,
259
  inputs=[text_input, chatbot_window, application_state, file_uploader],
@@ -265,26 +451,31 @@ with gr.Blocks(theme=gr.themes.Soft(), title="EduNatives Assistant") as demo:
265
  outputs=outputs_list
266
  )
267
 
268
- # Event handlers for each of the example buttons
269
  for btn in example_buttons:
 
 
 
270
  btn.click(
 
 
 
 
271
  fn=chat_interface_func,
272
- inputs=[btn, chatbot_window, application_state, file_uploader],
273
  outputs=outputs_list
274
  )
275
 
276
- # Event handler for file uploads
277
  file_uploader.upload(
278
  fn=chat_interface_func,
279
  inputs=[gr.Textbox(value="", visible=False), chatbot_window, application_state, file_uploader],
280
  outputs=outputs_list
281
  )
282
 
283
- # Clear textbox after submit
284
  submit_btn.click(lambda: "", outputs=text_input)
285
  text_input.submit(lambda: "", outputs=text_input)
286
 
287
 
288
  # --- 6. LAUNCH APP ---
289
  if __name__ == "__main__":
290
- demo.launch(debug=True)
 
 
20
  WEAVIATE_URL = "maf5cvz1saelnti3k34a.c0.europe-west3.gcp.weaviate.cloud"
21
  WEAVIATE_API_KEY = "cHFZK1JOaEg3K2p6K3JnQl9ZM1FEQ2NhMVU1SnBRVUpYWCtCVHlVU0J2Qmx1Mk9SaktpT09UQTNiU1hRPV92MjAw"
22
 
23
+ # --- Helper function to create schemas ---
24
  def create_application_schema(client: weaviate.WeaviateClient):
25
  collection_name = "Application"
26
  if not client.collections.exists(collection_name):
 
38
  else:
39
  print(f"โœ… Collection '{collection_name}' already exists.")
40
 
41
+ def create_project_schema(client: weaviate.WeaviateClient):
42
+ collection_name = "Project"
43
+ if not client.collections.exists(collection_name):
44
+ print(f"Creating collection: {collection_name}")
45
+ client.collections.create(
46
+ name=collection_name,
47
+ properties=[
48
+ weaviate.classes.config.Property(name="project_name", data_type=weaviate.classes.config.DataType.TEXT),
49
+ weaviate.classes.config.Property(name="description", data_type=weaviate.classes.config.DataType.TEXT),
50
+ weaviate.classes.config.Property(name="required_skills", data_type=weaviate.classes.config.DataType.TEXT_ARRAY),
51
+ weaviate.classes.config.Property(name="team_members", data_type=weaviate.classes.config.DataType.TEXT_ARRAY),
52
+ weaviate.classes.config.Property(name="max_team_size", data_type=weaviate.classes.config.DataType.NUMBER),
53
+ weaviate.classes.config.Property(name="creator_id", data_type=weaviate.classes.config.DataType.TEXT),
54
+ weaviate.classes.config.Property(name="is_recruiting", data_type=weaviate.classes.config.DataType.BOOL),
55
+ ]
56
+ )
57
+ print(f"โœ… Collection '{collection_name}' created successfully.")
58
+ else:
59
+ print(f"โœ… Collection '{collection_name}' already exists.")
60
+
61
  # --- 2. CHATBOT CLASS ---
62
  class WeaviateChatbot:
 
63
  def __init__(self, weaviate_url, weaviate_api_key, llm_api_key, llm_base_url):
64
  print("Connecting to clients...")
65
  self.weaviate_client = weaviate.connect_to_weaviate_cloud(
 
69
  )
70
  self.weaviate_client.connect()
71
  print("โœ… Successfully connected to Weaviate.")
72
+
73
+ # Create schemas on startup
74
  create_application_schema(self.weaviate_client)
75
+ create_project_schema(self.weaviate_client)
76
 
77
  self.llm_client = OpenAI(api_key=llm_api_key, base_url=llm_base_url)
78
  print("โœ… Successfully connected to LLM client (DeepInfra).")
 
94
  except Exception as e:
95
  print(f"Could not query collection '{name}'. Error: {e}")
96
  return "\n---\n".join(all_results) if all_results else "No relevant information found in the database."
97
+
98
  def _generate_response(self, query: str, context: str) -> str:
99
  prompt = f"""
100
+ You are EduNatives Assistant. Your main goal is to help users find opportunities, apply for them, or manage projects.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ * First, answer the user's question based on the CONTEXT from the database.
103
+ * IMPORTANT*:
104
+
105
+ * If you list jobs, always include a clear identifier like (job_021).
106
+ * Show jobs as a simple list by default.
107
+ * Only return jobs as a **table** if the user explicitly asks for "table" or "ุฌุฏูˆู„".
108
+ * If the user says they want to apply for a job (e.g., "I want to apply for job_021"), you MUST respond ONLY with the exact phrase:
109
+ `STARTING_APPLICATION_PROCESS:job_021`
110
+ * If the user wants to create a project (e.g., "create a new project"), respond ONLY with the exact phrase:
111
+ `STARTING_PROJECT_CREATION`.
112
+ --- CONTEXT FROM DATABASE START ---
113
+ {context}
114
+ --- CONTEXT FROM_DATABASE END ---
115
+
116
+ User Question: {query}
117
+
118
+ Answer:
119
+ """
120
  response = self.llm_client.chat.completions.create(model=MODEL_NAME, messages=[{"role": "user", "content": prompt}], max_tokens=4096)
121
  return response.choices[0].message.content.strip()
122
 
 
148
  self.weaviate_client.close()
149
  print("\nWeaviate connection closed.")
150
 
151
+ def _get_job_details(self, job_id: str) -> dict:
152
+ try:
153
+ jobs = self.weaviate_client.collections.get("Job")
154
+ response = jobs.query.fetch_objects(
155
+ limit=1,
156
+ filters=weaviate.classes.query.Filter.by_property("job_id").equal(job_id)
157
+ )
158
+ if response.objects:
159
+ return response.objects[0].properties
160
+ except Exception as e:
161
+ print(f"Error fetching job details for {job_id}: {e}")
162
+ return None
163
+
164
+ def generate_cover_letter(self, cv_content: str, job_id: str) -> str:
165
+ print(f"Generating Cover Letter for job: {job_id}")
166
+ job_details = self._get_job_details(job_id)
167
+
168
+ if not job_details:
169
+ # Fallback prompt: Generate a generic letter if job details are not found
170
+ print(f"โš ๏ธ Job details for '{job_id}' not found. Generating a generic cover letter based on CV.")
171
+ prompt = f"""
172
+ You are an expert career coach. A user has provided their CV but the specific job details for job '{job_id}' could not be found.
173
+
174
+ **Goal:** Write a strong, general-purpose cover letter based ONLY on the user's CV.
175
+
176
+ **Instructions:**
177
+ 1. **Analyze the User's CV:** Scan the user's CV to identify their key skills, main role (e.g., "Software Developer", "Project Manager"), and most impressive accomplishments.
178
+ 2. **Generalize:** Write a cover letter that showcases these strengths for a typical role in their field. For instance, if the CV is for a React developer, write a cover letter for a generic "React Developer" position.
179
+ 3. **Structure and Tone:**
180
+ - Start with "Dear Hiring Manager,".
181
+ - Maintain a professional and enthusiastic tone.
182
+ - Highlight 2-3 key skills or projects from the CV.
183
+ - Conclude by expressing a strong interest in discussing their qualifications further.
184
+ - **Important:** Add a note at the end: "[This is a general cover letter as the specific job details for '{job_id}' were not found. For a more targeted letter, please ensure the job exists in the database.]"
185
+
186
+ --- USER CV CONTENT START ---
187
+ {cv_content}
188
+ --- USER CV CONTENT END ---
189
+
190
+ Now, write the general-purpose cover letter.
191
+ """
192
+ else:
193
+ # Original, preferred prompt: Use specific job details to write a targeted letter
194
+ prompt = f"""
195
+ You are an expert career coach specializing in crafting impactful cover letters. Your primary task is to act as a bridge between a candidate's CV and a job's requirements.
196
+
197
+ **Goal:** Write a professional, personalized, and enthusiastic cover letter.
198
+
199
+ **Instructions:**
200
+ 1. **Analyze the Job Description:** Carefully read the provided job description to understand the key responsibilities and required skills.
201
+ 2. **Analyze the User's CV:** Scan the user's CV to identify their skills, experiences, and accomplishments.
202
+ 3. **Connect the Dots:** Your most important task is to explicitly connect the user's qualifications from the CV to the requirements of the job. For example, if the job requires 'Python' and the CV mentions a 'Python project', you must highlight this connection.
203
+ 4. **Structure and Tone:**
204
+ - Start with "Dear Hiring Manager,".
205
+ - Maintain a professional and enthusiastic tone.
206
+ - Structure the letter with a clear introduction, body (where you make the connections), and conclusion.
207
+ - Do not invent skills or experiences the user does not have. Base everything strictly on the provided texts.
208
+
209
+ --- JOB DESCRIPTION START ---
210
+ {json.dumps(job_details, indent=2)}
211
+ --- JOB DESCRIPTION END ---
212
+
213
+ --- USER CV CONTENT START ---
214
+ {cv_content}
215
+ --- USER CV CONTENT END ---
216
+
217
+ Now, write the cover letter that perfectly bridges the candidate's CV with the job description.
218
+ """
219
+ response = self.llm_client.chat.completions.create(model=MODEL_NAME, messages=[{"role": "user", "content": prompt}], max_tokens=2048)
220
+ return response.choices[0].message.content.strip()
221
+
222
+ def create_project(self, project_data: dict):
223
+ print("Saving new project to Weaviate...")
224
+ try:
225
+ projects = self.weaviate_client.collections.get("Project")
226
+ project_uuid = projects.data.insert({
227
+ "project_name": project_data.get("project_name"),
228
+ "description": project_data.get("description"),
229
+ "required_skills": project_data.get("required_skills"),
230
+ "max_team_size": project_data.get("max_team_size"),
231
+ "creator_id": "default_user", # In a real app, this would be a logged-in user's ID
232
+ "is_recruiting": True,
233
+ "team_members": []
234
+ })
235
+ print(f"โœ… Project saved with UUID: {project_uuid}")
236
+ return True
237
+ except Exception as e:
238
+ print(f"โŒ Failed to save project: {e}")
239
+ return False
240
 
241
  # --- Helper to extract text from uploaded files ---
242
  def _extract_text_from_file(file_path):
 
243
  print(f"Extracting text from: {file_path}")
244
  if file_path.endswith('.pdf'):
245
  try:
 
266
  chatbot_instance = WeaviateChatbot(WEAVIATE_URL, WEAVIATE_API_KEY, DEEPINFRA_API_KEY, BASE_URL)
267
  atexit.register(chatbot_instance.close_connections)
268
 
269
+ # --- 4. GRADIO INTERFACE LOGIC ---
270
  def chat_interface_func(message: str, history: list, app_state: dict, file_obj: object):
271
  history = history or []
272
+ current_mode = app_state.get("mode", "GENERAL")
273
 
274
+ # After any interaction, the examples will be hidden
275
+ hide_examples = gr.update(visible=False)
276
+
277
+ # --- Part 1: Handle File Uploads ---
278
  if file_obj is not None:
279
  file_path = file_obj.name
280
  text = _extract_text_from_file(file_path)
281
 
282
+ if current_mode == "APPLYING_CV":
283
  app_state["cv_content"] = text
284
+ bot_message = (f"๐Ÿ“„ CV '{os.path.basename(file_path)}' uploaded. "
285
+ f"Would you like me to help you write a cover letter for job **{app_state.get('job_id')}**, "
286
+ "or would you prefer to upload your own?")
287
+ history.append((None, bot_message))
288
+ app_state["mode"] = "APPLYING_COVER_LETTER_CHOICE"
289
+ return history, app_state, gr.update(visible=True, value=None), hide_examples
290
 
291
+ elif current_mode == "APPLYING_COVER_LETTER_UPLOAD":
292
  app_state["cover_letter_content"] = text
293
  history.append((f"๐Ÿ“„ Cover Letter '{os.path.basename(file_path)}' uploaded.", "Thank you! Submitting your application now..."))
294
 
295
  success = chatbot_instance.save_application(app_state)
296
+ final_message = f"โœ… Your application for job **{app_state.get('job_id')}** has been submitted successfully!" if success else "โŒ Sorry, there was an error submitting your application."
 
 
 
 
297
  history.append((None, final_message))
298
  app_state = {"mode": "GENERAL"}
299
+ return history, app_state, gr.update(visible=False, value=None), hide_examples
300
 
301
+ # --- Part 2: Handle Text Messages ---
302
  if message:
303
  history.append((message, None))
304
 
305
+ # --- Project Creation Flow ---
306
+ if current_mode == "CREATING_PROJECT_NAME":
307
+ app_state["project_name"] = message
308
+ app_state["mode"] = "CREATING_PROJECT_DESC"
309
+ history.append((None, "Great! Now, please provide a short description for your project."))
310
+ return history, app_state, gr.update(visible=False), hide_examples
311
+ elif current_mode == "CREATING_PROJECT_DESC":
312
+ app_state["description"] = message
313
+ app_state["mode"] = "CREATING_PROJECT_SKILLS"
314
+ history.append((None, "What skills are required for this project? (e.g., Python, UI/UX, Marketing)"))
315
+ return history, app_state, gr.update(visible=False), hide_examples
316
+ elif current_mode == "CREATING_PROJECT_SKILLS":
317
+ app_state["required_skills"] = [skill.strip() for skill in message.split(',')]
318
+ app_state["mode"] = "CREATING_PROJECT_SIZE"
319
+ history.append((None, "Perfect. How many members are you looking for in the team? (Please enter a number)"))
320
+ return history, app_state, gr.update(visible=False), hide_examples
321
+ elif current_mode == "CREATING_PROJECT_SIZE":
322
+ try:
323
+ app_state["max_team_size"] = int(message)
324
+ success = chatbot_instance.create_project(app_state)
325
+ bot_message = f"๐Ÿš€ Fantastic! Your project **'{app_state.get('project_name')}'** has been created." if success else "โŒ Sorry, there was an error creating your project."
326
+ history.append((None, bot_message))
327
+ app_state = {"mode": "GENERAL"}
328
+ return history, app_state, gr.update(visible=False), hide_examples
329
+ except ValueError:
330
+ history.append((None, "Please enter a valid number for the team size."))
331
+ return history, app_state, gr.update(visible=False), hide_examples
332
+
333
+ # --- Cover Letter & Submission Flow ---
334
+ if current_mode == "APPLYING_COVER_LETTER_CHOICE":
335
+ positive_keywords = ["help", "generate", "write", "yes", "ok", "sure", "please"]
336
+
337
+ if any(keyword in message.lower() for keyword in positive_keywords) and "upload" not in message.lower():
338
+ history.append((None, "Of course! I'm generating a draft for you now... This might take a moment."))
339
+ cover_letter = chatbot_instance.generate_cover_letter(app_state["cv_content"], app_state["job_id"])
340
+ history.append((None, f"Here is a draft for your cover letter:\n\n---\n{cover_letter}\n\n---\n\nIf you are happy with this, please type 'submit' to send the application."))
341
+ app_state["cover_letter_content"] = cover_letter
342
+ app_state["mode"] = "CONFIRM_SUBMISSION"
343
+ return history, app_state, gr.update(visible=False), hide_examples
344
+ elif "upload" in message.lower():
345
+ history.append((None, "Okay, please upload your cover letter file."))
346
+ app_state["mode"] = "APPLYING_COVER_LETTER_UPLOAD"
347
+ return history, app_state, gr.update(visible=True), hide_examples
348
+ else:
349
+ history.append((None, "I'm sorry, I didn't quite understand. Do you want me to **write** a letter for you, or would you prefer to **upload** your own?"))
350
+ return history, app_state, gr.update(visible=True), hide_examples
351
+
352
+ if current_mode == "CONFIRM_SUBMISSION":
353
+ if "submit" in message.lower():
354
+ history.append((None, "Thank you! Submitting your application now..."))
355
+ success = chatbot_instance.save_application(app_state)
356
+ final_message = f"โœ… Your application for job **{app_state.get('job_id')}** has been submitted successfully!" if success else "โŒ Sorry, there was an error submitting your application."
357
+ history.append((None, final_message))
358
+ app_state = {"mode": "GENERAL"}
359
+ return history, app_state, gr.update(visible=False), hide_examples
360
+ else:
361
+ history.append((None, "Please type 'submit' to confirm and send your application."))
362
+ return history, app_state, gr.update(visible=False), hide_examples
363
 
364
+ # --- General Chat & Starting New Flows ---
365
  response = chatbot_instance.ask(message)
366
 
367
+ print(f"DEBUG: LLM Raw Response was -> '{response}'") # For debugging
368
+
369
+ app_match = re.search(r"STARTING_APPLICATION_PROCESS:([\w-]+)", response)
370
+ project_match = "STARTING_PROJECT_CREATION" in response
371
+
372
+ if app_match:
373
+ job_id = app_match.group(1)
374
  app_state["mode"] = "APPLYING_CV"
375
  app_state["job_id"] = job_id
376
  bot_message = f"Starting application for job **{job_id}**. Please upload your CV."
377
  history.append((None, bot_message))
378
+ return history, app_state, gr.update(visible=True), hide_examples
379
+
380
+ elif project_match:
381
+ app_state["mode"] = "CREATING_PROJECT_NAME"
382
+ bot_message = "Awesome! Let's create a new project. What would you like to name it?"
383
+ history.append((None, bot_message))
384
+ return history, app_state, gr.update(visible=False), hide_examples
385
+
386
  else:
387
  history.append((None, response))
388
+ return history, app_state, gr.update(visible=False), hide_examples
 
 
 
389
 
390
+ # Default return if no message and no file
391
+ return history, app_state, gr.update(visible=False), gr.update()
392
 
393
+
394
+ # --- 5. BUILD GRADIO UI ---
395
  with gr.Blocks(theme=gr.themes.Soft(), title="EduNatives Assistant") as demo:
396
 
397
+ initial_state = {
398
+ "mode": "GENERAL", "job_id": None, "cv_content": None, "cover_letter_content": None,
399
+ "project_name": None, "description": None, "required_skills": None, "max_team_size": None
400
+ }
401
+ application_state = gr.State(initial_state)
402
 
403
  gr.Markdown(
404
  """
405
+ # ๐Ÿค– EduNatives Assistant
406
  Ask me anything about jobs, projects, or student availability. I can also help you navigate the EduNatives app.
407
  """
408
  )
409
 
410
  chatbot_window = gr.Chatbot(height=450, label="Chat Window", bubble_full_width=False)
411
 
412
+ # The container for the example buttons, will be hidden after first use
413
  with gr.Column() as examples_container:
414
  examples_list = [
415
  "What jobs are available at Google?",
416
  "Find students with experience in Python and Machine Learning.",
417
+ "I want to create a new project",
418
  "ูƒูŠู ูŠู…ูƒู†ู†ูŠ ูƒุทุงู„ุจ ุงู„ุชุณุฌูŠู„ ููŠ ุงู„ุชุทุจูŠู‚ุŸ",
419
  "I'm a company, how can I post an internship?"
420
  ]
 
428
 
429
  example_buttons = [btn1, btn2, btn3, btn4, btn5]
430
 
431
+ # The main input bar
432
  with gr.Row() as main_input_row:
433
+ text_input = gr.Textbox(placeholder="Ask your question here or type 'create a new project'...", container=False, scale=7)
434
  submit_btn = gr.Button("Send", variant="primary", scale=1)
435
+
436
+ # The file uploader is now at the bottom, initially hidden
437
+ file_uploader = gr.File(label="Upload Document", file_types=['.pdf', '.docx', '.txt'], visible=False)
438
 
439
  # --- Event Handlers ---
440
+ # The list of outputs now includes the examples_container to control its visibility
441
+ outputs_list = [chatbot_window, application_state, file_uploader, examples_container]
442
 
 
443
  submit_btn.click(
444
  fn=chat_interface_func,
445
  inputs=[text_input, chatbot_window, application_state, file_uploader],
 
451
  outputs=outputs_list
452
  )
453
 
 
454
  for btn in example_buttons:
455
+ def trigger_example(value):
456
+ return value, []
457
+
458
  btn.click(
459
+ fn=trigger_example,
460
+ inputs=btn,
461
+ outputs=[text_input, chatbot_window]
462
+ ).then(
463
  fn=chat_interface_func,
464
+ inputs=[text_input, chatbot_window, application_state, file_uploader],
465
  outputs=outputs_list
466
  )
467
 
 
468
  file_uploader.upload(
469
  fn=chat_interface_func,
470
  inputs=[gr.Textbox(value="", visible=False), chatbot_window, application_state, file_uploader],
471
  outputs=outputs_list
472
  )
473
 
 
474
  submit_btn.click(lambda: "", outputs=text_input)
475
  text_input.submit(lambda: "", outputs=text_input)
476
 
477
 
478
  # --- 6. LAUNCH APP ---
479
  if __name__ == "__main__":
480
+ demo.launch(debug=True)
481
+