Sobit commited on
Commit
c16bb21
·
verified ·
1 Parent(s): 8cc93d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -236
app.py CHANGED
@@ -59,57 +59,50 @@ def extract_text(uploaded_file):
59
  if not uploaded_file:
60
  return ""
61
  return extract_text_from_pdf(uploaded_file) if uploaded_file.type == "application/pdf" else extract_text_from_image(uploaded_file)
 
62
  def parse_resume(resume_text):
63
- """Extract key information from resume text"""
64
- parsed_info = {
65
- 'education': [],
66
- 'skills': [],
67
- 'experience': [],
68
- 'projects': [],
69
- 'publications': []
70
  }
71
 
72
- # Find education details
73
- edu_markers = ['Education:', 'EDUCATION', 'Academic Background']
74
- exp_markers = ['Experience:', 'EXPERIENCE', 'Work History', 'Employment']
75
- skill_markers = ['Skills:', 'SKILLS', 'Technical Skills', 'Technologies']
76
- proj_markers = ['Projects:', 'PROJECTS', 'Key Projects']
77
- pub_markers = ['Publications:', 'PUBLICATIONS', 'Research Papers']
78
 
79
- # Helper function to extract section content
80
- def extract_section(text, start_markers, end_markers):
81
- content = []
82
- for start in start_markers:
83
- start_idx = text.find(start)
84
- if start_idx != -1:
85
- section_start = start_idx + len(start)
86
- section_end = len(text)
87
-
88
- # Find the next section marker
89
- for end in end_markers:
90
- next_section = text.find(end, section_start)
91
- if next_section != -1:
92
- section_end = min(section_end, next_section)
93
-
94
- section_content = text[section_start:section_end].strip()
95
- content.append(section_content)
96
-
97
- return '\n'.join(content)
98
 
99
- # Extract sections
100
- all_markers = edu_markers + exp_markers + skill_markers + proj_markers + pub_markers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- parsed_info['education'] = extract_section(resume_text, edu_markers, all_markers)
103
- parsed_info['experience'] = extract_section(resume_text, exp_markers, all_markers)
104
- parsed_info['skills'] = extract_section(resume_text, skill_markers, all_markers)
105
- parsed_info['projects'] = extract_section(resume_text, proj_markers, all_markers)
106
- parsed_info['publications'] = extract_section(resume_text, pub_markers, all_markers)
107
 
108
  return parsed_info
109
 
110
  def extract_professor_details(text):
111
- professor_pattern = r"(Dr\.|Professor|Prof\.?)\s+([A-Z][a-z]+\s[A-Z][a-z]+)"
112
- university_pattern = r"(University|Institute|College|School of [A-Z][A-Za-z\s]+)"
113
 
114
  professor_match = re.search(professor_pattern, text)
115
  university_match = re.search(university_pattern, text)
@@ -117,85 +110,50 @@ def extract_professor_details(text):
117
  return (professor_match.group(0) if professor_match else "Not Found",
118
  university_match.group(0) if university_match else "Not Found")
119
 
120
- def clean_email_output(email_text):
121
- """Clean and format email content"""
122
- start_idx = email_text.find("Dear")
123
- if start_idx == -1:
124
- start_idx = 0
125
 
126
- end_markers = ["Best regards,", "Sincerely,", "Yours sincerely,", "Kind regards,"]
127
- end_idx = len(email_text)
128
- for marker in end_markers:
129
- idx = email_text.find(marker)
130
- if idx != -1:
131
- end_idx = email_text.find("\n\n", idx) if email_text.find("\n\n", idx) != -1 else len(email_text)
132
- break
133
-
134
- email_content = email_text[start_idx:end_idx].strip()
135
-
136
- if "Phone:" in email_text or "Email:" in email_text:
137
- contact_info = "\n\n" + "\n".join([
138
- line for line in email_text[end_idx:].split("\n")
139
- if any(info in line for info in ["Phone:", "Email:"])
140
- ]).strip()
141
- email_content += contact_info
142
 
143
- return email_content
144
-
145
- def clean_cover_letter_output(letter_text):
146
- """Clean and format cover letter content"""
147
- start_markers = ["Dear", "To Whom", "Hiring"]
148
- start_idx = len(letter_text)
149
- for marker in start_markers:
150
- idx = letter_text.find(marker)
151
- if idx != -1:
152
- start_idx = min(start_idx, idx)
153
 
154
- end_markers = ["Sincerely,", "Best regards,", "Yours truly,", "Regards,"]
155
- end_idx = len(letter_text)
156
- for marker in end_markers:
157
- idx = letter_text.find(marker)
 
158
  if idx != -1:
159
- end_idx = letter_text.find("\n\n", idx) if letter_text.find("\n\n", idx) != -1 else len(letter_text)
160
  break
161
 
162
- return letter_text[start_idx:end_idx].strip()
163
-
164
- def clean_research_statement_output(statement_text):
165
- """Clean and format research statement content"""
166
- # Remove common headers
167
- headers = ["Research Statement", "Statement of Research", "Research Interests"]
168
- cleaned_text = statement_text
169
- for header in headers:
170
- if cleaned_text.startswith(header):
171
- cleaned_text = cleaned_text[len(header):].lstrip(":\n")
172
-
173
- # Remove any trailing references or bibliography sections
174
- end_markers = ["References", "Bibliography", "Citations"]
175
  for marker in end_markers:
176
- idx = cleaned_text.find(marker)
177
  if idx != -1:
178
- cleaned_text = cleaned_text[:idx].strip()
 
179
 
180
- return cleaned_text.strip()
181
-
182
- def clean_sop_output(sop_text):
183
- """Clean and format Statement of Purpose content"""
184
- # Remove common headers
185
- headers = ["Statement of Purpose", "Personal Statement", "Academic Statement"]
186
- cleaned_text = sop_text
187
- for header in headers:
188
- if cleaned_text.startswith(header):
189
- cleaned_text = cleaned_text[len(header):].lstrip(":\n")
190
 
191
- # Remove any trailing sections
192
- end_markers = ["Thank you", "References", "Additional Information"]
193
- for marker in end_markers:
194
- idx = cleaned_text.find(marker)
195
- if idx != -1:
196
- cleaned_text = cleaned_text[:idx].strip()
 
197
 
198
- return cleaned_text.strip()
199
 
200
  # Initialize session state
201
  if 'generated_content' not in st.session_state:
@@ -206,18 +164,18 @@ if 'generated_content' not in st.session_state:
206
  'sop': None
207
  }
208
 
209
- # Template Definitions
210
  templates = {
211
- 'email': PromptTemplate.from_template("""
212
  Write ONLY a formal cold email for a research position.
213
  Start with 'Dear Professor' and end with a signature.
214
 
215
  Use these specific details from the CV:
216
- Education: {education}
217
- Relevant Experience: {experience}
218
- Key Skills: {skills}
219
- Notable Projects: {projects}
220
- Publications: {publications}
221
 
222
  Additional Context:
223
  Professor: {professor_name}
@@ -227,83 +185,73 @@ Why This Lab: {reason}
227
 
228
  Guidelines:
229
  1. Keep the email concise (max 400 words)
230
- 2. Focus on the most relevant experience and skills that match the lab's research
231
- 3. Mention 1-2 specific projects or publications that align with the lab's work
232
- 4. Include a clear statement of interest and why you're a good fit
233
  5. End with your contact information
234
- """),
235
-
236
- 'cover_letter': PromptTemplate.from_template("""
237
- Write ONLY a professional cover letter.
238
- Use these specific details from the CV:
239
- Education: {education}
240
- Relevant Experience: {experience}
241
- Technical Skills: {skills}
242
- Notable Projects: {projects}
243
- Publications: {publications}
244
-
245
- Position Details:
246
- Job Title: {job_title}
247
- Company: {company}
248
  Required Skills: {key_skills}
249
 
250
  Guidelines:
251
  1. Start with a formal greeting
252
- 2. Focus on experiences and skills that directly match the job requirements
253
- 3. Provide specific examples from your projects and work history
254
- 4. Demonstrate how your background makes you an ideal candidate
255
- 5. End with a professional closing
256
- """),
257
-
258
- 'research_statement': PromptTemplate.from_template("""
259
- Write ONLY a research statement focused on your academic journey and research goals.
260
- Use these specific details from your background:
261
- Education: {education}
262
- Research Experience: {experience}
263
- Technical Skills: {skills}
264
- Research Projects: {projects}
265
- Publications: {publications}
266
-
267
- Additional Context:
268
- Research Background: {research_background}
269
- Key Projects: {key_projects}
270
  Future Goals: {future_goals}
271
 
272
  Guidelines:
273
- 1. Describe your research journey and motivation
274
- 2. Highlight key research achievements and findings
275
- 3. Connect past work to future research goals
276
- 4. Demonstrate technical expertise and methodological knowledge
277
- 5. End with your vision for future contributions to the field
278
- """),
279
-
280
- 'sop': PromptTemplate.from_template("""
281
- Write ONLY a Statement of Purpose (SOP).
282
- Use these specific details from your background:
283
- Education: {education}
284
- Research Experience: {experience}
285
- Technical Skills: {skills}
286
- Notable Projects: {projects}
287
- Publications: {publications}
288
-
289
- Additional Context:
290
  Motivation: {motivation}
291
- Academic Goals: {academic_background}
292
- Research Interests: {research_experiences}
293
- Career Objectives: {career_goals}
294
  Program Interest: {why_this_program}
295
 
296
  Guidelines:
297
- 1. Tell a coherent story about your academic journey
298
- 2. Connect your background to your future goals
299
- 3. Demonstrate why you're prepared for graduate study
300
- 4. Show alignment between your interests and the program
301
- 5. Make a compelling case for why you should be admitted
302
- """)
303
  }
304
 
305
-
306
- # Create LangChain instances
307
  chains = {key: LLMChain(llm=llm, prompt=template) for key, template in templates.items()}
308
 
309
  # Sidebar for Input Collection
@@ -311,7 +259,12 @@ with st.sidebar:
311
  st.subheader("📝 Input Details")
312
  job_opening_text = st.text_area("Job/Research Opening Details", height=150)
313
  cv_resume_file = st.file_uploader("Upload CV/Resume", type=["pdf", "png", "jpg", "jpeg"])
314
- cv_resume_text = extract_text(cv_resume_file)
 
 
 
 
 
315
 
316
  # Tab Layout
317
  tab1, tab2, tab3, tab4 = st.tabs(["Cold Email", "Cover Letter", "Research Statement", "SOP"])
@@ -326,48 +279,43 @@ with tab1:
326
  if job_opening_text and cv_resume_text:
327
  with st.spinner("Generating..."):
328
  try:
329
- # Parse resume information
330
- resume_info = parse_resume(cv_resume_text)
331
-
332
- # Generate email with parsed information
333
  generated_email = chains['email'].run({
 
334
  "professor_name": professor_name,
335
  "university_name": university_name,
336
  "research_interests": research_interests,
337
- "reason": reason,
338
- "education": resume_info['education'],
339
- "experience": resume_info['experience'],
340
- "skills": resume_info['skills'],
341
- "projects": resume_info['projects'],
342
- "publications": resume_info['publications']
343
  })
344
- st.session_state.generated_content['email'] = clean_email_output(generated_email)
345
  except Exception as e:
346
  st.error(f"Generation error: {e}")
347
  else:
348
  st.error("Please provide all required inputs")
 
 
 
 
 
 
 
349
 
350
  # Cover Letter Tab
351
  with tab2:
352
  job_title = st.text_input("Job Title")
353
  company_name = university_name if university_name != "Not Found" else st.text_input("Company/University")
354
- key_skills = st.text_input("Key Skills")
355
 
356
  if st.button("Generate Cover Letter", key="cover_letter_btn"):
357
  if job_opening_text and cv_resume_text:
358
  with st.spinner("Generating..."):
359
  try:
360
- resume_info = parse_resume(cv_resume_text)
361
  generated_letter = chains['cover_letter'].run({
 
362
  "job_title": job_title,
363
  "company": company_name,
364
- "key_skills": key_skills,
365
- "reason": reason,
366
- "skills": resume_info['skills'],
367
- "education": resume_info['education'],
368
- "experience": resume_info['experience']
369
  })
370
- st.session_state.generated_content['cover_letter'] = clean_cover_letter_output(generated_letter)
371
  except Exception as e:
372
  st.error(f"Generation error: {e}")
373
  else:
@@ -382,28 +330,23 @@ with tab2:
382
 
383
  # Research Statement Tab
384
  with tab3:
385
-
386
  key_projects = st.text_input("Key Research Projects")
387
  future_goals = st.text_input("Future Research Goals")
388
 
389
  if st.button("Generate Research Statement", key="research_stmt_btn"):
390
- with st.spinner("Generating..."):
391
- try:
392
- resume_info = parse_resume(cv_resume_text)
393
- generated_statement = chains['research_statement'].run({
394
- "reason": reason,
395
- "education": resume_info['education'],
396
- "experience": resume_info['experience'],
397
- "skills": resume_info['skills'],
398
- "projects": resume_info['projects'],
399
- "publications": resume_info['publications'],
400
- "research_background": resume_info['publications'],
401
- "key_projects": key_projects,
402
- "future_goals": future_goals
403
- })
404
- st.session_state.generated_content['research_statement'] = clean_research_statement_output(generated_statement)
405
- except Exception as e:
406
- st.error(f"Generation error: {e}")
407
 
408
  if st.session_state.generated_content['research_statement']:
409
  st.markdown('<div class="output-container">', unsafe_allow_html=True)
@@ -415,29 +358,24 @@ with tab3:
415
  # SOP Tab
416
  with tab4:
417
  motivation = st.text_input("Motivation for Graduate Studies")
418
-
419
-
420
  career_goals = st.text_input("Career Goals")
421
  why_this_program = st.text_input("Why This Program")
422
 
423
  if st.button("Generate SOP", key="sop_btn"):
424
- with st.spinner("Generating..."):
425
- try:
426
- resume_info = parse_resume(cv_resume_text)
427
- generated_sop = chains['sop'].run({
428
- "motivation": motivation,
429
- "academic_background": resume_info['education'],
430
- "research_experiences": resume_info['publications'],
431
- "career_goals": career_goals,
432
- "why_this_program": why_this_program,
433
- "experience": resume_info['experience'],
434
- "skills": resume_info['skills'],
435
- "projects": resume_info['projects']
436
-
437
- })
438
- st.session_state.generated_content['sop'] = clean_sop_output(generated_sop)
439
- except Exception as e:
440
- st.error(f"Generation error: {e}")
441
 
442
  if st.session_state.generated_content['sop']:
443
  st.markdown('<div class="output-container">', unsafe_allow_html=True)
 
59
  if not uploaded_file:
60
  return ""
61
  return extract_text_from_pdf(uploaded_file) if uploaded_file.type == "application/pdf" else extract_text_from_image(uploaded_file)
62
+
63
  def parse_resume(resume_text):
64
+ """Extract key information from resume text using improved parsing"""
65
+ sections = {
66
+ 'education': ['Education:', 'EDUCATION', 'Academic Background'],
67
+ 'experience': ['Experience:', 'EXPERIENCE', 'Work History', 'Employment'],
68
+ 'skills': ['Skills:', 'SKILLS', 'Technical Skills', 'Technologies'],
69
+ 'projects': ['Projects:', 'PROJECTS', 'Key Projects'],
70
+ 'publications': ['Publications:', 'PUBLICATIONS', 'Research Papers']
71
  }
72
 
73
+ parsed_info = {key: '' for key in sections}
 
 
 
 
 
74
 
75
+ # Convert text to lines for better parsing
76
+ lines = resume_text.split('\n')
77
+ current_section = None
78
+ section_content = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ for line in lines:
81
+ line = line.strip()
82
+ if not line:
83
+ continue
84
+
85
+ # Check if this line is a section header
86
+ for section, headers in sections.items():
87
+ if any(header.lower() in line.lower() for header in headers):
88
+ if current_section:
89
+ parsed_info[current_section] = '\n'.join(section_content)
90
+ current_section = section
91
+ section_content = []
92
+ break
93
+ else:
94
+ if current_section:
95
+ section_content.append(line)
96
 
97
+ # Add the last section
98
+ if current_section and section_content:
99
+ parsed_info[current_section] = '\n'.join(section_content)
 
 
100
 
101
  return parsed_info
102
 
103
  def extract_professor_details(text):
104
+ professor_pattern = r"(Dr\.|Professor|Prof\.?)\s+([A-Z][a-z]+(?:\s[A-Z][a-z]+)*)"
105
+ university_pattern = r"(University|Institute|College|School) of [A-Z][A-Za-z\s]+"
106
 
107
  professor_match = re.search(professor_pattern, text)
108
  university_match = re.search(university_pattern, text)
 
110
  return (professor_match.group(0) if professor_match else "Not Found",
111
  university_match.group(0) if university_match else "Not Found")
112
 
113
+ def clean_output(text, type_="general"):
114
+ """Unified cleaning function for all document types"""
115
+ if not text:
116
+ return ""
 
117
 
118
+ # Common start markers
119
+ start_markers = {
120
+ "email": ["Dear"],
121
+ "cover_letter": ["Dear", "To Whom", "Hiring"],
122
+ "research_statement": ["Research Statement", "Statement of Research"],
123
+ "sop": ["Statement of Purpose", "Personal Statement"]
124
+ }
 
 
 
 
 
 
 
 
 
125
 
126
+ # Common end markers
127
+ end_markers = ["Best regards,", "Sincerely,", "Yours sincerely,", "Kind regards,", "Thank you"]
 
 
 
 
 
 
 
 
128
 
129
+ # Find start of content
130
+ start_idx = 0
131
+ relevant_starts = start_markers.get(type_, start_markers["email"])
132
+ for marker in relevant_starts:
133
+ idx = text.find(marker)
134
  if idx != -1:
135
+ start_idx = idx
136
  break
137
 
138
+ # Find end of content
139
+ end_idx = len(text)
 
 
 
 
 
 
 
 
 
 
 
140
  for marker in end_markers:
141
+ idx = text.find(marker)
142
  if idx != -1:
143
+ end_idx = text.find("\n\n", idx) if text.find("\n\n", idx) != -1 else len(text)
144
+ break
145
 
146
+ cleaned_text = text[start_idx:end_idx].strip()
 
 
 
 
 
 
 
 
 
147
 
148
+ # Add contact information for emails
149
+ if type_ == "email" and ("Phone:" in text or "Email:" in text):
150
+ contact_info = "\n\n" + "\n".join([
151
+ line for line in text[end_idx:].split("\n")
152
+ if any(info in line for info in ["Phone:", "Email:"])
153
+ ]).strip()
154
+ cleaned_text += contact_info
155
 
156
+ return cleaned_text
157
 
158
  # Initialize session state
159
  if 'generated_content' not in st.session_state:
 
164
  'sop': None
165
  }
166
 
167
+ # Template Definitions (simplified and standardized)
168
  templates = {
169
+ 'email': """
170
  Write ONLY a formal cold email for a research position.
171
  Start with 'Dear Professor' and end with a signature.
172
 
173
  Use these specific details from the CV:
174
+ {education}
175
+ {experience}
176
+ {skills}
177
+ {projects}
178
+ {publications}
179
 
180
  Additional Context:
181
  Professor: {professor_name}
 
185
 
186
  Guidelines:
187
  1. Keep the email concise (max 400 words)
188
+ 2. Focus on the most relevant experience and skills
189
+ 3. Mention 1-2 specific projects that align with the lab's work
190
+ 4. Include a clear statement of interest
191
  5. End with your contact information
192
+ """,
193
+ 'cover_letter': """
194
+ Write ONLY a professional cover letter for {job_title} at {company}.
195
+ Use these specific details:
196
+ {education}
197
+ {experience}
198
+ {skills}
199
+ {projects}
200
+
 
 
 
 
 
201
  Required Skills: {key_skills}
202
 
203
  Guidelines:
204
  1. Start with a formal greeting
205
+ 2. Focus on experiences matching job requirements
206
+ 3. Provide specific examples
207
+ 4. Show why you're an ideal candidate
208
+ 5. End professionally
209
+ """,
210
+ 'research_statement': """
211
+ Write ONLY a research statement focused on your academic journey and future goals.
212
+ Background:
213
+ {education}
214
+ {experience}
215
+ {skills}
216
+ {projects}
217
+ {publications}
218
+
219
+ Research Focus:
220
+ {key_projects}
 
 
221
  Future Goals: {future_goals}
222
 
223
  Guidelines:
224
+ 1. Describe your research journey
225
+ 2. Highlight key achievements
226
+ 3. Connect past work to future goals
227
+ 4. Show technical expertise
228
+ 5. Present your research vision
229
+ """,
230
+ 'sop': """
231
+ Write ONLY a Statement of Purpose (SOP) for graduate studies.
232
+ Background:
233
+ {education}
234
+ {experience}
235
+ {skills}
236
+ {projects}
237
+ {publications}
238
+
239
+ Context:
 
240
  Motivation: {motivation}
241
+ Career Goals: {career_goals}
 
 
242
  Program Interest: {why_this_program}
243
 
244
  Guidelines:
245
+ 1. Tell your academic journey
246
+ 2. Connect background to goals
247
+ 3. Show preparation for graduate study
248
+ 4. Demonstrate program alignment
249
+ 5. Make a compelling case
250
+ """
251
  }
252
 
253
+ # Convert templates to PromptTemplate objects
254
+ templates = {k: PromptTemplate.from_template(v) for k, v in templates.items()}
255
  chains = {key: LLMChain(llm=llm, prompt=template) for key, template in templates.items()}
256
 
257
  # Sidebar for Input Collection
 
259
  st.subheader("📝 Input Details")
260
  job_opening_text = st.text_area("Job/Research Opening Details", height=150)
261
  cv_resume_file = st.file_uploader("Upload CV/Resume", type=["pdf", "png", "jpg", "jpeg"])
262
+ cv_resume_text = extract_text(cv_resume_file) if cv_resume_file else ""
263
+
264
+ # Parse resume once for all tabs
265
+ resume_info = parse_resume(cv_resume_text) if cv_resume_text else {
266
+ 'education': '', 'experience': '', 'skills': '', 'projects': '', 'publications': ''
267
+ }
268
 
269
  # Tab Layout
270
  tab1, tab2, tab3, tab4 = st.tabs(["Cold Email", "Cover Letter", "Research Statement", "SOP"])
 
279
  if job_opening_text and cv_resume_text:
280
  with st.spinner("Generating..."):
281
  try:
 
 
 
 
282
  generated_email = chains['email'].run({
283
+ **resume_info,
284
  "professor_name": professor_name,
285
  "university_name": university_name,
286
  "research_interests": research_interests,
287
+ "reason": reason
 
 
 
 
 
288
  })
289
+ st.session_state.generated_content['email'] = clean_output(generated_email, "email")
290
  except Exception as e:
291
  st.error(f"Generation error: {e}")
292
  else:
293
  st.error("Please provide all required inputs")
294
+
295
+ if st.session_state.generated_content['email']:
296
+ st.markdown('<div class="output-container">', unsafe_allow_html=True)
297
+ st.markdown(st.session_state.generated_content['email'])
298
+ st.download_button("Download Email", st.session_state.generated_content['email'],
299
+ file_name="email.txt", key="email_download")
300
+ st.markdown('</div>', unsafe_allow_html=True)
301
 
302
  # Cover Letter Tab
303
  with tab2:
304
  job_title = st.text_input("Job Title")
305
  company_name = university_name if university_name != "Not Found" else st.text_input("Company/University")
306
+ key_skills = st.text_input("Key Skills Required")
307
 
308
  if st.button("Generate Cover Letter", key="cover_letter_btn"):
309
  if job_opening_text and cv_resume_text:
310
  with st.spinner("Generating..."):
311
  try:
 
312
  generated_letter = chains['cover_letter'].run({
313
+ **resume_info,
314
  "job_title": job_title,
315
  "company": company_name,
316
+ "key_skills": key_skills
 
 
 
 
317
  })
318
+ st.session_state.generated_content['cover_letter'] = clean_output(generated_letter, "cover_letter")
319
  except Exception as e:
320
  st.error(f"Generation error: {e}")
321
  else:
 
330
 
331
  # Research Statement Tab
332
  with tab3:
 
333
  key_projects = st.text_input("Key Research Projects")
334
  future_goals = st.text_input("Future Research Goals")
335
 
336
  if st.button("Generate Research Statement", key="research_stmt_btn"):
337
+ if cv_resume_text:
338
+ with st.spinner("Generating..."):
339
+ try:
340
+ generated_statement = chains['research_statement'].run({
341
+ **resume_info,
342
+ "key_projects": key_projects,
343
+ "future_goals": future_goals
344
+ })
345
+ st.session_state.generated_content['research_statement'] = clean_output(generated_statement, "research_statement")
346
+ except Exception as e:
347
+ st.error(f"Generation error: {e}")
348
+ else:
349
+ st.error("Please upload your CV/Resume")
 
 
 
 
350
 
351
  if st.session_state.generated_content['research_statement']:
352
  st.markdown('<div class="output-container">', unsafe_allow_html=True)
 
358
  # SOP Tab
359
  with tab4:
360
  motivation = st.text_input("Motivation for Graduate Studies")
 
 
361
  career_goals = st.text_input("Career Goals")
362
  why_this_program = st.text_input("Why This Program")
363
 
364
  if st.button("Generate SOP", key="sop_btn"):
365
+ if cv_resume_text:
366
+ with st.spinner("Generating..."):
367
+ try:
368
+ generated_sop = chains['sop'].run({
369
+ **resume_info,
370
+ "motivation": motivation,
371
+ "career_goals": career_goals,
372
+ "why_this_program": why_this_program
373
+ })
374
+ st.session_state.generated_content['sop'] = clean_output(generated_sop, "sop")
375
+ except Exception as e:
376
+ st.error(f"Generation error: {e}")
377
+ else:
378
+ st.error("Please upload your CV/Resume")
 
 
 
379
 
380
  if st.session_state.generated_content['sop']:
381
  st.markdown('<div class="output-container">', unsafe_allow_html=True)