Update app.py
Browse files
app.py
CHANGED
@@ -1,602 +1,642 @@
|
|
1 |
-
import json
|
2 |
-
import logging
|
3 |
-
|
4 |
-
from
|
5 |
-
from
|
6 |
-
from
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
logging
|
14 |
-
|
15 |
-
|
16 |
-
)
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
if
|
91 |
-
self.client = OpenAI(api_key=api_key)
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
raise Exception(f"Failed after {max_retries} attempts: {last_error}") #122
|
123 |
-
logger.warning(f"Attempt {attempt + 1} failed: {last_error}") #123
|
124 |
-
continue #124
|
125 |
-
#125
|
126 |
-
# Process response #126
|
127 |
-
ai_message = response.choices[0].message.content #127
|
128 |
-
self.conversation_history.append({ #128
|
129 |
-
"role": "assistant", #129
|
130 |
-
"content": ai_message, #130
|
131 |
-
"timestamp": datetime.now().isoformat() #131
|
132 |
-
}) #132
|
133 |
-
#133
|
134 |
-
# Analyze response and update state #134
|
135 |
-
self._update_conversation_state(ai_message) #135
|
136 |
-
#136
|
137 |
-
return { #137
|
138 |
-
"content": ai_message, #138
|
139 |
-
"type": "success", #139
|
140 |
-
"completion_status": self.get_completion_status(), #140
|
141 |
-
"timestamp": datetime.now().isoformat() #141
|
142 |
-
} #142
|
143 |
-
#143
|
144 |
-
except Exception as e: #144
|
145 |
-
error_msg = f"Error processing message: {str(e)}" #145
|
146 |
-
logger.error(error_msg) #146
|
147 |
-
self.state.last_error = error_msg #147
|
148 |
-
return { #148
|
149 |
-
"content": error_msg, #149
|
150 |
-
"type": "error", #150
|
151 |
-
"completion_status": self.get_completion_status(), #151
|
152 |
-
"timestamp": datetime.now().isoformat() #152
|
153 |
-
} #153
|
154 |
-
|
155 |
-
def _update_conversation_state(self, ai_message: str) -> None: #154
|
156 |
-
"""Update the conversation state based on AI response.""" #155
|
157 |
-
try: #156
|
158 |
-
# Create analysis prompt #157
|
159 |
-
analysis_prompt = """ #158
|
160 |
-
Review our conversation and identify: #159
|
161 |
-
1. What topics or aspects of their journey were discussed? #160
|
162 |
-
2. What areas need more exploration? #161
|
163 |
-
3. What's the current focus of discussion? #162
|
164 |
-
|
165 |
-
Response format:
|
166 |
-
{
|
167 |
-
"topics_discussed": [],
|
168 |
-
"areas_needing_exploration": [],
|
169 |
-
"current_focus": "",
|
170 |
-
"completion_estimate": 0.0
|
171 |
-
}
|
172 |
-
""" #163
|
173 |
-
|
174 |
-
# Get analysis from AI #164
|
175 |
-
response = self.client.chat.completions.create( #165
|
176 |
-
model="gpt-4o-mini", #166
|
177 |
-
messages=[ #167
|
178 |
-
{"role": "system", "content": SYSTEM_PROMPT}, #168
|
179 |
-
*self.conversation_history, #169
|
180 |
-
{"role": "user", "content": analysis_prompt} #170
|
181 |
-
], #171
|
182 |
-
temperature=0.3 #172
|
183 |
-
) #173
|
184 |
-
|
185 |
-
# Process analysis #174
|
186 |
-
try: #175
|
187 |
-
analysis = json.loads(response.choices[0].message.content) #176
|
188 |
-
|
189 |
-
# Update state based on analysis #177
|
190 |
-
self.state.sections_completed = analysis.get("topics_discussed", []) #178
|
191 |
-
self.state.sections_partial = analysis.get("areas_needing_exploration", []) #179
|
192 |
-
self.state.current_section = analysis.get("current_focus") #180
|
193 |
-
self.state.completion_percentage = analysis.get("completion_estimate", 0.0) #181
|
194 |
|
195 |
-
|
196 |
-
logger.error(f"Error parsing analysis JSON: {str(e)}") #183
|
197 |
-
# Set default values on error #184
|
198 |
-
self.state.completion_percentage = max( #185
|
199 |
-
self.state.completion_percentage, #186
|
200 |
-
len(self.conversation_history) * 5.0 # Rough estimate based on message count #187
|
201 |
-
) #188
|
202 |
|
203 |
-
|
204 |
-
|
205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
"topics_in_progress": self.state.sections_partial, #197
|
213 |
-
"current_focus": self.state.current_section, #198
|
214 |
-
"conversation_length": len(self.conversation_history), #199
|
215 |
-
"last_update": datetime.now().isoformat(), #200
|
216 |
-
"needs_attention": [ #201
|
217 |
-
topic for topic in self.state.sections_partial #202
|
218 |
-
if topic not in self.state.sections_completed #203
|
219 |
-
], #204
|
220 |
-
"status_summary": self._generate_status_summary() #205
|
221 |
-
} #206
|
222 |
-
|
223 |
-
if self.state.last_error: #207
|
224 |
-
status["last_error"] = self.state.last_error #208
|
225 |
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
232 |
|
233 |
-
|
234 |
-
|
235 |
-
# Add completion status #215
|
236 |
-
if self.state.completion_percentage > 0: #216
|
237 |
-
summary_parts.append( #217
|
238 |
-
f"Conversation is approximately {self.state.completion_percentage:.1f}% complete" #218
|
239 |
-
) #219
|
240 |
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
f"Currently focusing on: {self.state.current_section}" #227
|
250 |
-
) #228
|
251 |
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
|
|
|
|
|
|
|
|
256 |
|
257 |
-
|
258 |
-
|
259 |
-
def generate_json(self, api_key: str) -> Tuple[Optional[str], str]: #234
|
260 |
-
"""Generate a JSON profile from the conversation history.""" #235
|
261 |
-
try: #236
|
262 |
-
if not self.client: #237
|
263 |
-
self.client = OpenAI(api_key=api_key) #238
|
264 |
-
|
265 |
-
# Analysis prompt focused on understanding the conversation #239
|
266 |
-
analysis_prompt = """ #240
|
267 |
-
Review our conversation and create a JSON structure that captures the person's journey. #241
|
268 |
-
Focus on what was actually discussed, not fitting into predetermined categories. #242
|
269 |
-
Include: #243
|
270 |
-
1. Any experiences or achievements shared #244
|
271 |
-
2. Skills or competencies demonstrated #245
|
272 |
-
3. Timeline or progression points mentioned #246
|
273 |
-
4. Notable metrics or outcomes #247
|
274 |
-
5. Personal growth or learning moments #248
|
275 |
|
276 |
-
|
277 |
-
""
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
|
289 |
-
|
290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
|
292 |
-
#
|
293 |
-
|
294 |
-
|
295 |
-
Use this analysis as a guide: {json.dumps(analysis, indent=2)} #266
|
296 |
|
297 |
-
|
298 |
-
|
299 |
-
- Include both quantitative and qualitative information #269
|
300 |
-
- Preserve the context and significance of experiences #270
|
301 |
-
- Maintain natural flow and connections between topics #271
|
302 |
-
- Use descriptive section names that reflect the conversation #272
|
303 |
-
""" #273
|
304 |
|
305 |
-
#
|
306 |
-
|
307 |
-
|
308 |
-
messages=[ #277
|
309 |
-
{"role": "system", "content": SYSTEM_PROMPT}, #278
|
310 |
-
*self.conversation_history, #279
|
311 |
-
{"role": "user", "content": profile_prompt} #280
|
312 |
-
], #281
|
313 |
-
temperature=0.5 #282
|
314 |
-
) #283
|
315 |
|
316 |
-
#
|
317 |
-
|
318 |
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
|
329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
"
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
341 |
|
342 |
-
|
343 |
-
|
344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
|
346 |
-
try:
|
347 |
-
|
348 |
-
|
349 |
-
return (filename, json.dumps(profile_data, indent=2, ensure_ascii=False)) #312
|
350 |
-
except Exception as e: #313
|
351 |
-
logger.error(f"Error saving profile to file: {str(e)}") #314
|
352 |
-
return (None, json.dumps(profile_data, indent=2, ensure_ascii=False)) #315
|
353 |
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
"error_occurred": True #323
|
362 |
-
} #324
|
363 |
-
} #325
|
364 |
-
return (None, json.dumps(error_json, indent=2)) #326
|
365 |
-
|
366 |
-
def create_education_career_interface(): #327
|
367 |
-
"""Create Gradio interface for the education and career collector.""" #328
|
368 |
-
collector = EducationCareerCollector() #329
|
369 |
-
|
370 |
-
css = """ #330
|
371 |
-
.message { font-size: 16px; margin: 8px 0; } #331
|
372 |
-
.system-message { color: #444; font-style: italic; } #332
|
373 |
-
.user-message { color: #000; font-weight: 500; } #333
|
374 |
-
.alert { #334
|
375 |
-
padding: 12px; #335
|
376 |
-
margin: 8px 0; #336
|
377 |
-
border-radius: 4px; #337
|
378 |
-
} #338
|
379 |
-
.alert-info { #339
|
380 |
-
background-color: #e8f4f8; #340
|
381 |
-
border-left: 4px solid #4a90e2; #341
|
382 |
-
} #342
|
383 |
-
.alert-error { #343
|
384 |
-
background-color: #fde8e8; #344
|
385 |
-
border-left: 4px solid #f56565; #345
|
386 |
-
} #346
|
387 |
-
.alert-success { #347
|
388 |
-
background-color: #e8f8e8; #348
|
389 |
-
border-left: 4px solid #48bb78; #349
|
390 |
-
} #350
|
391 |
-
""" #351
|
392 |
-
|
393 |
-
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo: #352
|
394 |
-
gr.Markdown(""" #353
|
395 |
-
# 🐕 LOSS DOG - Profile Builder #354
|
396 |
-
|
397 |
-
Share your career and education journey naturally. #355
|
398 |
-
Tell your story in your own way - we'll capture what matters to you. #356
|
399 |
-
""") #357
|
400 |
-
|
401 |
-
with gr.Row(): #358
|
402 |
-
with gr.Column(scale=2): #359
|
403 |
-
# API Key Input #360
|
404 |
-
api_key = gr.Textbox( #361
|
405 |
-
label="OpenAI API Key", #362
|
406 |
-
type="password", #363
|
407 |
-
placeholder="Enter your OpenAI API key (sk-...)", #364
|
408 |
-
info="Your API key from platform.openai.com" #365
|
409 |
-
) #366
|
410 |
|
411 |
-
#
|
412 |
-
|
413 |
-
"
|
414 |
-
|
415 |
-
)
|
|
|
|
|
|
|
|
|
416 |
|
417 |
-
#
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
|
424 |
-
#
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
show_label=False, #383
|
430 |
-
scale=4 #384
|
431 |
-
) #385
|
432 |
-
submit = gr.Button("Send", variant="primary", scale=1) #386
|
433 |
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
"### Profile Progress\nStart sharing your story!", #394
|
442 |
-
elem_classes=["alert", "alert-info"] #395
|
443 |
-
) #396
|
444 |
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
451 |
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
|
|
|
|
457 |
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
""
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
result = collector.process_message(message, key) #424
|
478 |
-
|
479 |
-
# Update chat history #425
|
480 |
-
history.append((message, result["content"])) #426
|
481 |
-
|
482 |
-
# Generate status message #427
|
483 |
-
status = f"""Progress: {result['completion_status']['completion_percentage']:.1f}% #428
|
484 |
-
| Topics covered: {len(result['completion_status']['topics_covered'])}""" #429
|
485 |
-
|
486 |
-
return history, status #430
|
487 |
-
|
488 |
-
except Exception as e: #431
|
489 |
-
error_msg = f"Error: {str(e)}" #432
|
490 |
-
logger.error(error_msg) #433
|
491 |
-
return history, error_msg #434
|
492 |
-
|
493 |
-
def generate_profile(key: str) -> tuple: #435
|
494 |
-
"""Generate and return profile JSON.""" #436
|
495 |
-
try: #437
|
496 |
-
filename, json_content = collector.generate_json(key) #438
|
497 |
-
if filename: #439
|
498 |
-
return ( #440
|
499 |
-
filename, #441
|
500 |
-
json.loads(json_content), #442
|
501 |
-
"Profile generated successfully! 🎉" #443
|
502 |
-
) #444
|
503 |
-
return ( #445
|
504 |
-
None, #446
|
505 |
-
json.loads(json_content), #447
|
506 |
-
"Profile generated but couldn't save file." #448
|
507 |
-
) #449
|
508 |
-
except Exception as e: #450
|
509 |
-
error_msg = f"Error generating profile: {str(e)}" #451
|
510 |
-
logger.error(error_msg) #452
|
511 |
-
return None, {"error": error_msg}, error_msg #453
|
512 |
-
|
513 |
-
def clear_interface() -> tuple: #454
|
514 |
-
"""Reset the interface state.""" #455
|
515 |
-
return ( #456
|
516 |
-
[], # Clear chat history #457
|
517 |
-
"Ready to start! Share your journey...", # Reset status #458
|
518 |
-
"### Profile Progress\nStart sharing your story!", # Reset progress #459
|
519 |
-
None, # Clear JSON preview #460
|
520 |
-
None # Clear file output #461
|
521 |
-
) #462
|
522 |
-
|
523 |
-
def update_progress(history: list) -> str: #463
|
524 |
-
"""Update progress information based on conversation.""" #464
|
525 |
-
if not history: #465
|
526 |
-
return "### Profile Progress\nStart sharing your story!" #466
|
527 |
-
|
528 |
-
# Get completion status #467
|
529 |
-
status = collector.get_completion_status() #468
|
530 |
-
|
531 |
-
# Format progress message #469
|
532 |
-
progress_md = f"""### Profile Progress: {status['completion_percentage']:.1f}%\n\n""" #470
|
533 |
-
|
534 |
-
if status['topics_covered']: #471
|
535 |
-
progress_md += "✅ **Discussed:**\n" #472
|
536 |
-
for topic in status['topics_covered']: #473
|
537 |
-
progress_md += f"- {topic}\n" #474
|
538 |
-
|
539 |
-
if status['topics_in_progress']: #475
|
540 |
-
progress_md += "\n📝 **Currently exploring:**\n" #476
|
541 |
-
for topic in status['topics_in_progress']: #477
|
542 |
-
progress_md += f"- {topic}\n" #478
|
543 |
-
|
544 |
-
if status.get('needs_attention'): #479
|
545 |
-
progress_md += "\n❗ **Consider discussing:**\n" #480
|
546 |
-
for topic in status['needs_attention']: #481
|
547 |
-
progress_md += f"- {topic}\n" #482
|
548 |
|
549 |
-
return
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
msg
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
|
|
1 |
+
import json #1
|
2 |
+
import logging #2
|
3 |
+
import os #3
|
4 |
+
from datetime import datetime #4
|
5 |
+
from typing import Dict, List, Optional, Any, Tuple #5
|
6 |
+
from dataclasses import dataclass, field #6
|
7 |
+
from pathlib import Path #7
|
8 |
+
|
9 |
+
# Third-party imports
|
10 |
+
import gradio as gr #8
|
11 |
+
from openai import OpenAI #9
|
12 |
+
|
13 |
+
# Configure logging
|
14 |
+
logging.basicConfig( #10
|
15 |
+
level=logging.INFO, #11
|
16 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', #12
|
17 |
+
handlers=[ #13
|
18 |
+
logging.StreamHandler(), #14
|
19 |
+
logging.FileHandler('app.log') #15
|
20 |
+
] #16
|
21 |
+
) #17
|
22 |
+
logger = logging.getLogger(__name__) #18
|
23 |
+
|
24 |
+
# System prompt for the AI assistant
|
25 |
+
SYSTEM_PROMPT = """ #19
|
26 |
+
You are an Information Extraction Assistant, designed to help extract and organize
|
27 |
+
important information from conversations in a natural and engaging way.
|
28 |
+
|
29 |
+
Core Capabilities:
|
30 |
+
- Natural conversation while gathering specific information
|
31 |
+
- Flexible information extraction based on context
|
32 |
+
- Progress tracking and completion estimation
|
33 |
+
- Structured data organization with context preservation
|
34 |
+
|
35 |
+
Please maintain a friendly and professional tone while ensuring accurate information extraction.
|
36 |
+
""" #20
|
37 |
+
|
38 |
+
@dataclass #21
|
39 |
+
class ExtractedInfo: #22
|
40 |
+
"""Structure for storing extracted information.""" #23
|
41 |
+
text: str #24
|
42 |
+
category: str #25
|
43 |
+
confidence: float #26
|
44 |
+
timestamp: datetime = field(default_factory=datetime.now) #27
|
45 |
+
metadata: Dict[str, Any] = field(default_factory=dict) #28
|
46 |
+
|
47 |
+
@dataclass #29
|
48 |
+
class ConversationState: #30
|
49 |
+
"""Tracks the state and progress of the conversation.""" #31
|
50 |
+
extracted_items: List[ExtractedInfo] = field(default_factory=list) #32
|
51 |
+
categories_covered: List[str] = field(default_factory=list) #33
|
52 |
+
current_focus: Optional[str] = None #34
|
53 |
+
completion_percentage: float = 0.0 #35
|
54 |
+
last_error: Optional[str] = None #36
|
55 |
+
last_update: datetime = field(default_factory=datetime.now) #37
|
56 |
+
|
57 |
+
def add_extracted_info(self, info: ExtractedInfo) -> None: #38
|
58 |
+
"""Add new extracted information and update state.""" #39
|
59 |
+
self.extracted_items.append(info) #40
|
60 |
+
if info.category not in self.categories_covered: #41
|
61 |
+
self.categories_covered.append(info.category) #42
|
62 |
+
self.last_update = datetime.now() #43
|
63 |
+
|
64 |
+
class InformationExtractor: #44
|
65 |
+
"""Core class for handling information extraction from conversations.""" #45
|
66 |
+
|
67 |
+
def __init__(self): #46
|
68 |
+
self.conversation_history: List[Dict[str, str]] = [] #47
|
69 |
+
self.state = ConversationState() #48
|
70 |
+
self.client: Optional[OpenAI] = None #49
|
71 |
+
self.extraction_categories = [ #50
|
72 |
+
"personal_info", #51
|
73 |
+
"education", #52
|
74 |
+
"work_experience", #53
|
75 |
+
"skills", #54
|
76 |
+
"achievements" #55
|
77 |
+
] #56
|
78 |
+
|
79 |
+
def _validate_api_key(self, api_key: str) -> bool: #57
|
80 |
+
"""Validate OpenAI API key format.""" #58
|
81 |
+
if not api_key.strip(): #59
|
82 |
+
raise ValueError("API key cannot be empty") #60
|
83 |
+
if not api_key.startswith('sk-'): #61
|
84 |
+
raise ValueError("Invalid API key format") #62
|
85 |
+
return True #63
|
86 |
+
|
87 |
+
def _initialize_client(self, api_key: str) -> None: #64
|
88 |
+
"""Initialize OpenAI client with error handling.""" #65
|
89 |
+
try: #66
|
90 |
+
if self._validate_api_key(api_key): #67
|
91 |
+
self.client = OpenAI(api_key=api_key) #68
|
92 |
+
except Exception as e: #69
|
93 |
+
logger.error(f"Error initializing OpenAI client: {str(e)}") #70
|
94 |
+
raise #71
|
95 |
+
|
96 |
+
def _add_to_history(self, role: str, content: str) -> None: #72
|
97 |
+
"""Add a message to conversation history with timestamp.""" #73
|
98 |
+
self.conversation_history.append({ #74
|
99 |
+
"role": role, #75
|
100 |
+
"content": content, #76
|
101 |
+
"timestamp": datetime.now().isoformat() #77
|
102 |
+
}) #78
|
103 |
+
|
104 |
+
def _get_ai_response(self, retries: int = 3) -> str: #79
|
105 |
+
"""Get response from OpenAI with retry mechanism.""" #80
|
106 |
+
if not self.client: #81
|
107 |
+
raise ValueError("OpenAI client not initialized") #82
|
108 |
+
for attempt in range(retries): #83
|
109 |
+
try: #84
|
110 |
+
response = self.client.chat.completions.create( #85
|
111 |
+
model="gpt-4", #86
|
112 |
+
messages=[ #87
|
113 |
+
{"role": "system", "content": SYSTEM_PROMPT}, #88
|
114 |
+
*[{ #89
|
115 |
+
"role": msg["role"], #90
|
116 |
+
"content": msg["content"] #91
|
117 |
+
} for msg in self.conversation_history] #92
|
118 |
+
], #93
|
119 |
+
temperature=0.7, #94
|
120 |
+
max_tokens=2000 #95
|
121 |
+
) #96
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
+
return response.choices[0].message.content #97
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
+
except Exception as e: #98
|
126 |
+
logger.warning(f"Attempt {attempt + 1} failed: {str(e)}") #99
|
127 |
+
if attempt == retries - 1: #100
|
128 |
+
raise Exception(f"Failed after {retries} attempts: {str(e)}") #101
|
129 |
+
continue #102
|
130 |
+
|
131 |
+
def _extract_information(self, text: str) -> List[ExtractedInfo]: #103
|
132 |
+
"""Extract structured information from text.""" #104
|
133 |
+
try: #105
|
134 |
+
extraction_prompt = f""" #106
|
135 |
+
Analyze the following text and extract relevant information.
|
136 |
+
Categories to consider: {', '.join(self.extraction_categories)}
|
137 |
|
138 |
+
For each piece of information extracted, provide:
|
139 |
+
1. The exact text
|
140 |
+
2. The category it belongs to
|
141 |
+
3. Confidence level (0.0 to 1.0)
|
142 |
+
4. Any relevant context or metadata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
|
144 |
+
Format as JSON:
|
145 |
+
{{
|
146 |
+
"extracted_items": [
|
147 |
+
{{
|
148 |
+
"text": "extracted text",
|
149 |
+
"category": "category name",
|
150 |
+
"confidence": 0.95,
|
151 |
+
"metadata": {{}}
|
152 |
+
}}
|
153 |
+
]
|
154 |
+
}}
|
155 |
|
156 |
+
Text to analyze: {text}
|
157 |
+
""" #107
|
|
|
|
|
|
|
|
|
|
|
158 |
|
159 |
+
response = self.client.chat.completions.create( #108
|
160 |
+
model="gpt-4", #109
|
161 |
+
messages=[ #110
|
162 |
+
{"role": "system", "content": SYSTEM_PROMPT}, #111
|
163 |
+
{"role": "user", "content": extraction_prompt} #112
|
164 |
+
], #113
|
165 |
+
temperature=0.3 #114
|
166 |
+
) #115
|
167 |
|
168 |
+
# Parse response and create ExtractedInfo objects #116
|
169 |
+
analysis = json.loads(response.choices[0].message.content) #117
|
170 |
+
extracted_items = [] #118
|
|
|
|
|
171 |
|
172 |
+
for item in analysis.get("extracted_items", []): #119
|
173 |
+
extracted_info = ExtractedInfo( #120
|
174 |
+
text=item["text"], #121
|
175 |
+
category=item["category"], #122
|
176 |
+
confidence=item["confidence"], #123
|
177 |
+
metadata=item.get("metadata", {}) #124
|
178 |
+
) #125
|
179 |
+
extracted_items.append(extracted_info) #126
|
180 |
|
181 |
+
return extracted_items #127
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
+
except json.JSONDecodeError as e: #128
|
184 |
+
logger.error(f"Error parsing extraction response: {str(e)}") #129
|
185 |
+
return [] #130
|
186 |
+
except Exception as e: #131
|
187 |
+
logger.error(f"Error during information extraction: {str(e)}") #132
|
188 |
+
return [] #133
|
189 |
+
|
190 |
+
def _update_completion_status(self) -> None: #134
|
191 |
+
"""Update completion status based on extracted information.""" #135
|
192 |
+
total_categories = len(self.extraction_categories) #136
|
193 |
+
covered_categories = len(self.state.categories_covered) #137
|
194 |
+
|
195 |
+
# Calculate base completion percentage #138
|
196 |
+
base_completion = (covered_categories / total_categories) * 100 #139
|
197 |
+
|
198 |
+
# Adjust based on confidence levels #140
|
199 |
+
if self.state.extracted_items: #141
|
200 |
+
avg_confidence = sum(item.confidence for item in self.state.extracted_items) / len(self.state.extracted_items) #142
|
201 |
+
adjusted_completion = base_completion * avg_confidence #143
|
202 |
+
else: #144
|
203 |
+
adjusted_completion = 0.0 #145
|
204 |
|
205 |
+
self.state.completion_percentage = min(adjusted_completion, 100.0) #146
|
206 |
+
|
207 |
+
def process_message(self, message: str, api_key: str) -> Dict[str, Any]: #147
|
208 |
+
"""Process a user message and extract information.""" #148
|
209 |
+
try: #149
|
210 |
+
# Initialize client if needed #150
|
211 |
+
if not self.client: #151
|
212 |
+
self._initialize_client(api_key) #152
|
213 |
+
|
214 |
+
# Add user message to history #153
|
215 |
+
self._add_to_history("user", message) #154
|
216 |
|
217 |
+
# Get AI response #155
|
218 |
+
ai_response = self._get_ai_response() #156
|
219 |
+
self._add_to_history("assistant", ai_response) #157
|
|
|
220 |
|
221 |
+
# Extract information from the entire conversation #158
|
222 |
+
new_information = self._extract_information(message + "\n" + ai_response) #159
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
+
# Update state with new information #160
|
225 |
+
for info in new_information: #161
|
226 |
+
self.state.add_extracted_info(info) #162
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
|
228 |
+
# Update completion status #163
|
229 |
+
self._update_completion_status() #164
|
230 |
|
231 |
+
return { #165
|
232 |
+
"response": ai_response, #166
|
233 |
+
"extracted_info": [ #167
|
234 |
+
{ #168
|
235 |
+
"text": info.text, #169
|
236 |
+
"category": info.category, #170
|
237 |
+
"confidence": info.confidence #171
|
238 |
+
} for info in new_information #172
|
239 |
+
], #173
|
240 |
+
"completion_status": { #174
|
241 |
+
"percentage": self.state.completion_percentage, #175
|
242 |
+
"categories_covered": self.state.categories_covered, #176
|
243 |
+
"current_focus": self.state.current_focus #177
|
244 |
+
} #178
|
245 |
+
} #179
|
246 |
|
247 |
+
except Exception as e: #180
|
248 |
+
error_msg = f"Error processing message: {str(e)}" #181
|
249 |
+
logger.error(error_msg) #182
|
250 |
+
self.state.last_error = error_msg #183
|
251 |
+
return { #184
|
252 |
+
"error": error_msg, #185
|
253 |
+
"completion_status": { #186
|
254 |
+
"percentage": self.state.completion_percentage, #187
|
255 |
+
"categories_covered": self.state.categories_covered, #188
|
256 |
+
"current_focus": self.state.current_focus #189
|
257 |
+
} #190
|
258 |
+
} #191
|
259 |
+
def generate_output(self) -> Dict[str, Any]: #192
|
260 |
+
"""Generate structured output from all extracted information.""" #193
|
261 |
+
try: #194
|
262 |
+
# Organize extracted information by category #195
|
263 |
+
categorized_info = {} #196
|
264 |
+
for category in self.extraction_categories: #197
|
265 |
+
category_items = [ #198
|
266 |
+
{ #199
|
267 |
+
"text": item.text, #200
|
268 |
+
"confidence": item.confidence, #201
|
269 |
+
"timestamp": item.timestamp.isoformat(), #202
|
270 |
+
"metadata": item.metadata #203
|
271 |
+
} #204
|
272 |
+
for item in self.state.extracted_items #205
|
273 |
+
if item.category == category #206
|
274 |
+
] #207
|
275 |
+
if category_items: #208
|
276 |
+
categorized_info[category] = category_items #209
|
277 |
+
|
278 |
+
# Create output structure #210
|
279 |
+
output = { #211
|
280 |
+
"extracted_information": categorized_info, #212
|
281 |
+
"analysis_summary": { #213
|
282 |
+
"total_items": len(self.state.extracted_items), #214
|
283 |
+
"categories_covered": self.state.categories_covered, #215
|
284 |
+
"completion_percentage": self.state.completion_percentage #216
|
285 |
+
}, #217
|
286 |
+
"metadata": { #218
|
287 |
+
"generated_at": datetime.now().isoformat(), #219
|
288 |
+
"conversation_length": len(self.conversation_history), #220
|
289 |
+
"version": "2.0" #221
|
290 |
+
} #222
|
291 |
+
} #223
|
292 |
+
|
293 |
+
# Save to file #224
|
294 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") #225
|
295 |
+
filename = f"extracted_info_{timestamp}.json" #226
|
296 |
|
297 |
+
with open(filename, 'w', encoding='utf-8') as f: #227
|
298 |
+
json.dump(output, f, indent=2, ensure_ascii=False) #228
|
299 |
+
|
300 |
+
return { #229
|
301 |
+
"filename": filename, #230
|
302 |
+
"content": output, #231
|
303 |
+
"status": "success" #232
|
304 |
+
} #233
|
305 |
+
|
306 |
+
except Exception as e: #234
|
307 |
+
error_msg = f"Error generating output: {str(e)}" #235
|
308 |
+
logger.error(error_msg) #236
|
309 |
+
return { #237
|
310 |
+
"error": error_msg, #238
|
311 |
+
"status": "error" #239
|
312 |
+
} #240
|
313 |
+
|
314 |
+
def create_gradio_interface(): #241
|
315 |
+
"""Create the Gradio interface for information extraction.""" #242
|
316 |
+
extractor = InformationExtractor() #243
|
317 |
+
|
318 |
+
# Custom CSS for better styling #244
|
319 |
+
css = """ #245
|
320 |
+
.container { max-width: 900px; margin: auto; } #246
|
321 |
+
.message { padding: 1rem; margin: 0.5rem 0; border-radius: 0.5rem; } #247
|
322 |
+
.info-panel { background: #f5f5f5; padding: 1rem; border-radius: 0.5rem; } #248
|
323 |
+
.status-badge { #249
|
324 |
+
display: inline-block; #250
|
325 |
+
padding: 0.25rem 0.5rem; #251
|
326 |
+
border-radius: 0.25rem; #252
|
327 |
+
margin: 0.25rem; #253
|
328 |
+
background: #e0e0e0; #254
|
329 |
+
} #255
|
330 |
+
.extraction-highlight { #256
|
331 |
+
background: #e8f4f8; #257
|
332 |
+
border-left: 4px solid #4a90e2; #258
|
333 |
+
padding: 0.5rem; #259
|
334 |
+
margin: 0.5rem 0; #260
|
335 |
+
} #261
|
336 |
+
""" #262
|
337 |
+
|
338 |
+
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo: #263
|
339 |
+
gr.Markdown(""" #264
|
340 |
+
# 🔍 Information Extraction Assistant
|
341 |
+
|
342 |
+
Have a natural conversation while we extract and organize important information.
|
343 |
+
The system will automatically identify and categorize relevant details.
|
344 |
+
""") #265
|
345 |
+
|
346 |
+
with gr.Row(): #266
|
347 |
+
with gr.Column(scale=2): #267
|
348 |
+
# API Key input #268
|
349 |
+
api_key = gr.Textbox( #269
|
350 |
+
label="OpenAI API Key", #270
|
351 |
+
type="password", #271
|
352 |
+
placeholder="Enter your OpenAI API key (sk-...)", #272
|
353 |
+
show_label=True #273
|
354 |
+
) #274
|
355 |
+
|
356 |
+
# Chat interface #275
|
357 |
+
chatbot = gr.Chatbot( #276
|
358 |
+
value=[], #277
|
359 |
+
height=400, #278
|
360 |
+
type="messages", #279
|
361 |
+
show_label=False #280
|
362 |
+
) #281
|
363 |
+
|
364 |
+
# Message input #282
|
365 |
+
with gr.Row(): #283
|
366 |
+
msg = gr.Textbox( #284
|
367 |
+
label="Message", #285
|
368 |
+
placeholder="Type your message here...", #286
|
369 |
+
scale=4 #287
|
370 |
+
) #288
|
371 |
+
submit = gr.Button( #289
|
372 |
+
"Send", #290
|
373 |
+
variant="primary", #291
|
374 |
+
scale=1 #292
|
375 |
+
) #293
|
376 |
+
|
377 |
+
# Action buttons #294
|
378 |
+
with gr.Row(): #295
|
379 |
+
clear = gr.Button("Clear Chat", scale=1) #296
|
380 |
+
generate = gr.Button( #297
|
381 |
+
"Generate Report", #298
|
382 |
+
variant="secondary", #299
|
383 |
+
scale=2 #300
|
384 |
+
) #301
|
385 |
+
with gr.Column(scale=1): #302
|
386 |
+
# Extraction Status Panel #303
|
387 |
+
with gr.Group(visible=True) as status_panel: #304
|
388 |
+
gr.Markdown("### Extraction Progress") #305
|
389 |
+
|
390 |
+
# Progress indicator #306
|
391 |
+
progress = gr.Slider( #307
|
392 |
+
label="Completion", #308
|
393 |
+
minimum=0, #309
|
394 |
+
maximum=100, #310
|
395 |
+
value=0, #311
|
396 |
+
interactive=False #312
|
397 |
+
) #313
|
398 |
+
|
399 |
+
# Categories covered #314
|
400 |
+
categories_covered = gr.JSON( #315
|
401 |
+
label="Categories Covered", #316
|
402 |
+
value={"categories": []} #317
|
403 |
+
) #318
|
404 |
+
|
405 |
+
# Current focus #319
|
406 |
+
current_focus = gr.Textbox( #320
|
407 |
+
label="Current Focus", #321
|
408 |
+
value="Not started", #322
|
409 |
+
interactive=False #323
|
410 |
+
) #324
|
411 |
+
|
412 |
+
# Extraction Results #325
|
413 |
+
with gr.Tabs() as result_tabs: #326
|
414 |
+
with gr.Tab("Extracted Information"): #327
|
415 |
+
extracted_info = gr.JSON( #328
|
416 |
+
label="Extracted Details", #329
|
417 |
+
value={} #330
|
418 |
+
) #331
|
419 |
+
|
420 |
+
with gr.Tab("Download"): #332
|
421 |
+
file_output = gr.File( #333
|
422 |
+
label="Download Report" #334
|
423 |
+
) #335
|
424 |
+
|
425 |
+
with gr.Tab("Analysis"): #336
|
426 |
+
analysis_text = gr.Markdown( #337
|
427 |
+
"Analysis will appear here after processing." #338
|
428 |
+
) #339
|
429 |
+
|
430 |
+
# Helper Functions #340
|
431 |
+
def format_extraction_summary(extracted_items: List[Dict]) -> str: #341
|
432 |
+
"""Format extracted information for display.""" #342
|
433 |
+
if not extracted_items: #343
|
434 |
+
return "No information extracted yet." #344
|
435 |
|
436 |
+
summary = ["### Recently Extracted Information"] #345
|
437 |
+
for item in extracted_items: #346
|
438 |
+
summary.append( #347
|
439 |
+
f"- **{item['category']}** ({item['confidence']*100:.1f}% confidence)\n" #348
|
440 |
+
f" {item['text']}" #349
|
441 |
+
) #350
|
442 |
+
return "\n".join(summary) #351
|
443 |
+
|
444 |
+
def update_interface_state(state: Dict[str, Any]) -> tuple: #352
|
445 |
+
"""Update all interface components based on current state.""" #353
|
446 |
+
return ( #354
|
447 |
+
state['completion_status']['percentage'], #355
|
448 |
+
{"categories": state['completion_status']['categories_covered']}, #356
|
449 |
+
state['completion_status']['current_focus'] #357
|
450 |
+
) #358
|
451 |
+
|
452 |
+
# Event Handlers #359
|
453 |
+
def process_message(message: str, history: list, key: str) -> tuple: #360
|
454 |
+
"""Handle message processing and update interface.""" #361
|
455 |
+
if not message.strip(): #362
|
456 |
+
return history, 0, {}, "Please enter a message" #363
|
457 |
|
458 |
+
try: #364
|
459 |
+
# Process message #365
|
460 |
+
result = extractor.process_message(message, key) #366
|
|
|
|
|
|
|
|
|
461 |
|
462 |
+
if "error" in result: #367
|
463 |
+
return ( #368
|
464 |
+
history, #369
|
465 |
+
0, #370
|
466 |
+
{"categories": []}, #371
|
467 |
+
f"Error: {result['error']}" #372
|
468 |
+
) #373
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
469 |
|
470 |
+
# Update chat history #374
|
471 |
+
history.append({ #375
|
472 |
+
"role": "user", #376
|
473 |
+
"content": message #377
|
474 |
+
}) #378
|
475 |
+
history.append({ #379
|
476 |
+
"role": "assistant", #380
|
477 |
+
"content": result["response"] #381
|
478 |
+
}) #382
|
479 |
|
480 |
+
# Update status components #383
|
481 |
+
progress_value = result["completion_status"]["percentage"] #384
|
482 |
+
categories = { #385
|
483 |
+
"categories": result["completion_status"]["categories_covered"] #386
|
484 |
+
} #387
|
485 |
+
current_focus = result["completion_status"]["current_focus"] or "Processing..." #388
|
486 |
|
487 |
+
# Update extraction display #389
|
488 |
+
if result.get("extracted_info"): #390
|
489 |
+
analysis_text = format_extraction_summary(result["extracted_info"]) #391
|
490 |
+
else: #392
|
491 |
+
analysis_text = "No new information extracted." #393
|
|
|
|
|
|
|
|
|
492 |
|
493 |
+
return ( #394
|
494 |
+
history, #395
|
495 |
+
progress_value, #396
|
496 |
+
categories, #397
|
497 |
+
current_focus, #398
|
498 |
+
analysis_text #399
|
499 |
+
) #400
|
|
|
|
|
|
|
500 |
|
501 |
+
except Exception as e: #401
|
502 |
+
logger.error(f"Error in process_message: {str(e)}") #402
|
503 |
+
return ( #403
|
504 |
+
history, #404
|
505 |
+
0, #405
|
506 |
+
{"categories": []}, #406
|
507 |
+
f"Error: {str(e)}", #407
|
508 |
+
"An error occurred during processing." #408
|
509 |
+
) #409
|
510 |
+
def generate_report() -> tuple: #410
|
511 |
+
"""Generate and return report file.""" #411
|
512 |
+
try: #412
|
513 |
+
result = extractor.generate_output() #413
|
514 |
|
515 |
+
if result["status"] == "success": #414
|
516 |
+
# Update JSON preview #415
|
517 |
+
content_preview = { #416
|
518 |
+
"summary": result["content"]["analysis_summary"], #417
|
519 |
+
"categories": list(result["content"]["extracted_information"].keys()), #418
|
520 |
+
"total_items": len(result["content"]["extracted_information"]) #419
|
521 |
+
} #420
|
522 |
|
523 |
+
return ( #421
|
524 |
+
result["filename"], #422
|
525 |
+
content_preview, #423
|
526 |
+
"Report generated successfully! 🎉", #424
|
527 |
+
gr.update(value=format_extraction_summary( #425
|
528 |
+
[item for items in result["content"]["extracted_information"].values() #426
|
529 |
+
for item in items] #427
|
530 |
+
)) #428
|
531 |
+
) #429
|
532 |
+
else: #430
|
533 |
+
return ( #431
|
534 |
+
None, #432
|
535 |
+
{"error": result["error"]}, #433
|
536 |
+
f"Error generating report: {result['error']}", #434
|
537 |
+
"Failed to generate analysis." #435
|
538 |
+
) #436
|
539 |
|
540 |
+
except Exception as e: #437
|
541 |
+
logger.error(f"Error in generate_report: {str(e)}") #438
|
542 |
+
return ( #439
|
543 |
+
None, #440
|
544 |
+
{"error": str(e)}, #441
|
545 |
+
f"Error: {str(e)}", #442
|
546 |
+
"An error occurred during report generation." #443
|
547 |
+
) #444
|
548 |
+
|
549 |
+
def clear_interface() -> tuple: #445
|
550 |
+
"""Reset all interface components.""" #446
|
551 |
+
# Reset extractor state #447
|
552 |
+
global extractor #448
|
553 |
+
extractor = InformationExtractor() #449
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
|
555 |
+
return ( #450
|
556 |
+
[], # Clear chat history #451
|
557 |
+
0.0, # Reset progress #452
|
558 |
+
{"categories": []}, # Clear categories #453
|
559 |
+
"Not started", # Reset focus #454
|
560 |
+
{}, # Clear extracted info #455
|
561 |
+
None, # Clear file output #456
|
562 |
+
"Ready to start new extraction.", # Reset analysis #457
|
563 |
+
gr.update(value="") # Clear message input #458
|
564 |
+
) #459
|
565 |
+
|
566 |
+
# Event Bindings #460
|
567 |
+
msg.submit( #461
|
568 |
+
process_message, #462
|
569 |
+
inputs=[msg, chatbot, api_key], #463
|
570 |
+
outputs=[ #464
|
571 |
+
chatbot, #465
|
572 |
+
progress, #466
|
573 |
+
categories_covered, #467
|
574 |
+
current_focus, #468
|
575 |
+
analysis_text #469
|
576 |
+
] #470
|
577 |
+
).then( #471
|
578 |
+
lambda: "", #472
|
579 |
+
None, #473
|
580 |
+
msg #474
|
581 |
+
) #475
|
582 |
+
|
583 |
+
submit.click( #476
|
584 |
+
process_message, #477
|
585 |
+
inputs=[msg, chatbot, api_key], #478
|
586 |
+
outputs=[ #479
|
587 |
+
chatbot, #480
|
588 |
+
progress, #481
|
589 |
+
categories_covered, #482
|
590 |
+
current_focus, #483
|
591 |
+
analysis_text #484
|
592 |
+
] #485
|
593 |
+
).then( #486
|
594 |
+
lambda: "", #487
|
595 |
+
None, #488
|
596 |
+
msg #489
|
597 |
+
) #490
|
598 |
+
|
599 |
+
generate.click( #491
|
600 |
+
generate_report, #492
|
601 |
+
outputs=[ #493
|
602 |
+
file_output, #494
|
603 |
+
extracted_info, #495
|
604 |
+
current_focus, #496
|
605 |
+
analysis_text #497
|
606 |
+
] #498
|
607 |
+
) #499
|
608 |
+
|
609 |
+
clear.click( #500
|
610 |
+
clear_interface, #501
|
611 |
+
outputs=[ #502
|
612 |
+
chatbot, #503
|
613 |
+
progress, #504
|
614 |
+
categories_covered, #505
|
615 |
+
current_focus, #506
|
616 |
+
extracted_info, #507
|
617 |
+
file_output, #508
|
618 |
+
analysis_text, #509
|
619 |
+
msg #510
|
620 |
+
] #511
|
621 |
+
) #512
|
622 |
+
|
623 |
+
return demo #513
|
624 |
+
|
625 |
+
if __name__ == "__main__": #514
|
626 |
+
# Set up logging for the main application #515
|
627 |
+
logging.basicConfig( #516
|
628 |
+
level=logging.INFO, #517
|
629 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' #518
|
630 |
+
) #519
|
631 |
|
632 |
+
try: #520
|
633 |
+
demo = create_gradio_interface() #521
|
634 |
+
demo.launch( #522
|
635 |
+
server_name="0.0.0.0", #523
|
636 |
+
server_port=7860, #524
|
637 |
+
share=True, #525
|
638 |
+
show_api=False #526
|
639 |
+
) #527
|
640 |
+
except Exception as e: #528
|
641 |
+
logger.error(f"Application failed to start: {str(e)}") #529
|
642 |
+
raise #530
|