Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 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
|
| 171 |
app_state["cv_content"] = text
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
-
elif
|
| 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 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
|
|
|
| 197 |
response = chatbot_instance.ask(message)
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 215 |
with gr.Blocks(theme=gr.themes.Soft(), title="EduNatives Assistant") as demo:
|
| 216 |
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
"
|
| 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
|
| 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 |
-
|
|
|
|
| 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=[
|
| 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 |
+
|