Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
Hybrid AI Assistant -
|
4 |
-
A
|
5 |
"""
|
6 |
|
7 |
import os
|
@@ -39,8 +39,6 @@ class ConversationContext:
|
|
39 |
messages: List[Dict[str, str]] = field(default_factory=list)
|
40 |
detected_codes: List[str] = field(default_factory=list)
|
41 |
last_topic: Optional[str] = None
|
42 |
-
chat_history: List[Dict] = field(default_factory=list)
|
43 |
-
current_chat_id: str = "chat_1"
|
44 |
|
45 |
# ============= Healthcare Billing Database =============
|
46 |
|
@@ -116,6 +114,41 @@ class BillingCodesDB:
|
|
116 |
code_type='HCPCS',
|
117 |
additional_info='Cyanocobalamin up to 1000 mcg.',
|
118 |
category='Injections'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
)
|
120 |
}
|
121 |
|
@@ -165,9 +198,14 @@ class HybridAIAssistant:
|
|
165 |
def detect_intent(self, message: str) -> Dict[str, Any]:
|
166 |
"""Detect if the message is about billing codes or general conversation"""
|
167 |
lower_msg = message.lower()
|
|
|
|
|
168 |
codes = self.billing_db.search_codes(message)
|
169 |
|
170 |
-
|
|
|
|
|
|
|
171 |
is_billing = any(keyword in lower_msg for keyword in billing_keywords) or len(codes) > 0
|
172 |
|
173 |
return {
|
@@ -181,21 +219,21 @@ class HybridAIAssistant:
|
|
181 |
responses = []
|
182 |
|
183 |
if codes:
|
184 |
-
for code in codes[:3]:
|
185 |
info = self.billing_db.lookup(code)
|
186 |
if info:
|
187 |
-
response = f"
|
188 |
-
response += f"**Description:** {info.description}\n
|
189 |
if info.additional_info:
|
190 |
-
response += f"**Details:** {info.additional_info}\n
|
191 |
if info.category:
|
192 |
-
response += f"**Category:** {info.category}\n"
|
193 |
responses.append(response)
|
194 |
|
195 |
if responses:
|
196 |
final_response = "I found information about the billing code(s) you mentioned:\n\n"
|
197 |
-
final_response += "\n---\n
|
198 |
-
final_response += "\n\nπ‘ Need more details
|
199 |
return final_response
|
200 |
else:
|
201 |
return self.get_general_response(message, billing_context=True)
|
@@ -203,20 +241,24 @@ class HybridAIAssistant:
|
|
203 |
def get_general_response(self, message: str, billing_context: bool = False) -> str:
|
204 |
"""Get response from OpenRouter API for general queries"""
|
205 |
|
|
|
206 |
system_prompt = """You are a helpful, friendly AI assistant with expertise in healthcare billing codes.
|
207 |
You can assist with any topic - from casual conversation to complex questions.
|
208 |
When discussing medical billing codes, you provide accurate, detailed information.
|
209 |
-
Be conversational, helpful, and engaging.
|
210 |
-
|
211 |
|
212 |
if billing_context:
|
213 |
system_prompt += "\nThe user is asking about medical billing. Provide helpful information even if you don't have specific code details."
|
214 |
|
|
|
215 |
messages = [{'role': 'system', 'content': system_prompt}]
|
216 |
|
|
|
217 |
for msg in self.context.messages[-10:]:
|
218 |
messages.append(msg)
|
219 |
|
|
|
220 |
messages.append({'role': 'user', 'content': message})
|
221 |
|
222 |
try:
|
@@ -236,9 +278,11 @@ class HybridAIAssistant:
|
|
236 |
result = response.json()
|
237 |
ai_response = result['choices'][0]['message']['content']
|
238 |
|
|
|
239 |
self.context.messages.append({'role': 'user', 'content': message})
|
240 |
self.context.messages.append({'role': 'assistant', 'content': ai_response})
|
241 |
|
|
|
242 |
if len(self.context.messages) > 20:
|
243 |
self.context.messages = self.context.messages[-20:]
|
244 |
|
@@ -256,7 +300,8 @@ class HybridAIAssistant:
|
|
256 |
fallbacks = [
|
257 |
"I'm having trouble connecting right now, but I'm still here to help! Could you rephrase your question?",
|
258 |
"Let me think about that differently. What specific aspect would you like to know more about?",
|
259 |
-
"That's an interesting question! While I process that, is there anything specific you'd like to explore?"
|
|
|
260 |
]
|
261 |
return random.choice(fallbacks)
|
262 |
|
@@ -265,8 +310,10 @@ class HybridAIAssistant:
|
|
265 |
if not message.strip():
|
266 |
return "Feel free to ask me anything! I can help with general questions or healthcare billing codes. π"
|
267 |
|
|
|
268 |
intent = self.detect_intent(message)
|
269 |
|
|
|
270 |
if intent['is_billing'] and intent['codes_found']:
|
271 |
return self.handle_billing_query(message, intent['codes_found'])
|
272 |
else:
|
@@ -276,411 +323,273 @@ class HybridAIAssistant:
|
|
276 |
"""Reset conversation context"""
|
277 |
self.context = ConversationContext()
|
278 |
|
279 |
-
# =============
|
280 |
|
281 |
def create_interface():
|
282 |
assistant = HybridAIAssistant()
|
283 |
|
284 |
-
#
|
285 |
custom_css = """
|
286 |
-
/*
|
287 |
.gradio-container {
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
height: 100vh !important;
|
293 |
-
display: flex !important;
|
294 |
-
flex-direction: column !important;
|
295 |
-
font-family: 'SΓΆhne', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
|
296 |
-
background: #f9f9f9 !important;
|
297 |
}
|
298 |
|
299 |
-
/*
|
300 |
-
.
|
301 |
-
|
302 |
-
|
|
|
|
|
|
|
303 |
}
|
304 |
|
305 |
-
|
306 |
-
.main-container {
|
307 |
-
display: flex;
|
308 |
-
height: 100vh;
|
309 |
-
width: 100vw;
|
310 |
-
overflow: hidden;
|
311 |
-
background: #fff;
|
312 |
-
}
|
313 |
-
|
314 |
-
/* Sidebar */
|
315 |
-
.sidebar {
|
316 |
-
width: 260px;
|
317 |
-
background: #202123;
|
318 |
color: white;
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
height: 100vh;
|
323 |
-
overflow-y: auto;
|
324 |
-
}
|
325 |
-
|
326 |
-
.sidebar-header {
|
327 |
-
padding: 12px;
|
328 |
-
border-bottom: 1px solid rgba(255,255,255,0.1);
|
329 |
-
}
|
330 |
-
|
331 |
-
.new-chat-btn {
|
332 |
-
width: 100%;
|
333 |
-
padding: 12px 16px;
|
334 |
-
background: transparent;
|
335 |
-
border: 1px solid rgba(255,255,255,0.2);
|
336 |
-
color: white;
|
337 |
-
border-radius: 6px;
|
338 |
-
cursor: pointer;
|
339 |
-
font-size: 14px;
|
340 |
display: flex;
|
341 |
align-items: center;
|
342 |
-
|
343 |
-
|
344 |
-
}
|
345 |
-
|
346 |
-
.new-chat-btn:hover {
|
347 |
-
background: rgba(255,255,255,0.1);
|
348 |
-
}
|
349 |
-
|
350 |
-
.chat-history {
|
351 |
-
flex: 1;
|
352 |
-
padding: 8px;
|
353 |
-
overflow-y: auto;
|
354 |
-
}
|
355 |
-
|
356 |
-
.chat-item {
|
357 |
-
padding: 12px 16px;
|
358 |
-
margin: 2px 0;
|
359 |
-
border-radius: 6px;
|
360 |
-
cursor: pointer;
|
361 |
-
font-size: 14px;
|
362 |
-
color: rgba(255,255,255,0.8);
|
363 |
-
white-space: nowrap;
|
364 |
-
overflow: hidden;
|
365 |
-
text-overflow: ellipsis;
|
366 |
-
transition: background 0.2s;
|
367 |
-
}
|
368 |
-
|
369 |
-
.chat-item:hover {
|
370 |
-
background: rgba(255,255,255,0.05);
|
371 |
-
}
|
372 |
-
|
373 |
-
.chat-item.active {
|
374 |
-
background: rgba(255,255,255,0.1);
|
375 |
-
color: white;
|
376 |
}
|
377 |
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
background: #fff;
|
384 |
-
position: relative;
|
385 |
}
|
386 |
|
387 |
-
/* Chat
|
388 |
#chatbot {
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
flex-direction: column;
|
395 |
-
}
|
396 |
-
|
397 |
-
#chatbot > .wrap {
|
398 |
-
max-width: 48rem;
|
399 |
-
margin: 0 auto;
|
400 |
-
width: 100%;
|
401 |
-
padding: 2rem 1rem;
|
402 |
}
|
403 |
|
404 |
/* Message styling */
|
405 |
.message {
|
406 |
-
padding:
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
background: #fff;
|
412 |
-
}
|
413 |
-
|
414 |
-
.message.assistant {
|
415 |
-
background: #f7f7f8;
|
416 |
-
}
|
417 |
-
|
418 |
-
.message-content {
|
419 |
-
max-width: 48rem;
|
420 |
-
margin: 0 auto;
|
421 |
-
display: flex;
|
422 |
-
gap: 1.5rem;
|
423 |
-
padding: 0 1rem;
|
424 |
-
}
|
425 |
-
|
426 |
-
.avatar {
|
427 |
-
width: 32px;
|
428 |
-
height: 32px;
|
429 |
-
border-radius: 4px;
|
430 |
-
background: #5436DA;
|
431 |
-
display: flex;
|
432 |
-
align-items: center;
|
433 |
-
justify-content: center;
|
434 |
-
color: white;
|
435 |
-
font-weight: bold;
|
436 |
-
font-size: 14px;
|
437 |
-
flex-shrink: 0;
|
438 |
}
|
439 |
|
440 |
-
.
|
441 |
-
background: #
|
|
|
|
|
442 |
}
|
443 |
|
444 |
-
.message
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
color: #2d2d2d;
|
449 |
}
|
450 |
|
451 |
-
.message-text h1 { font-size: 1.8em; margin: 1em 0 0.5em; }
|
452 |
-
.message-text h2 { font-size: 1.5em; margin: 1em 0 0.5em; }
|
453 |
-
.message-text h3 { font-size: 1.2em; margin: 1em 0 0.5em; font-weight: 600; }
|
454 |
-
.message-text p { margin: 0.8em 0; }
|
455 |
-
.message-text ul, .message-text ol { margin: 0.8em 0; padding-left: 1.5em; }
|
456 |
-
.message-text li { margin: 0.4em 0; }
|
457 |
-
.message-text code {
|
458 |
-
background: #f3f4f6;
|
459 |
-
padding: 2px 6px;
|
460 |
-
border-radius: 4px;
|
461 |
-
font-family: 'Consolas', monospace;
|
462 |
-
font-size: 0.9em;
|
463 |
-
}
|
464 |
-
.message-text pre {
|
465 |
-
background: #1e1e1e;
|
466 |
-
color: #d4d4d4;
|
467 |
-
padding: 1em;
|
468 |
-
border-radius: 6px;
|
469 |
-
overflow-x: auto;
|
470 |
-
margin: 1em 0;
|
471 |
-
}
|
472 |
-
.message-text strong { font-weight: 600; }
|
473 |
-
|
474 |
/* Input area */
|
475 |
-
.input-container {
|
476 |
-
border-top: 1px solid #e5e5e5;
|
477 |
-
background: #fff;
|
478 |
-
padding: 1rem 0;
|
479 |
-
}
|
480 |
-
|
481 |
-
.input-wrapper {
|
482 |
-
max-width: 48rem;
|
483 |
-
margin: 0 auto;
|
484 |
-
padding: 0 1rem;
|
485 |
-
}
|
486 |
-
|
487 |
#input-box {
|
488 |
-
|
489 |
-
border:
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
background: #fff;
|
495 |
-
color: #2d2d2d;
|
496 |
-
outline: none;
|
497 |
-
box-shadow: 0 0 0 2px transparent;
|
498 |
-
transition: all 0.2s;
|
499 |
}
|
500 |
|
501 |
#input-box:focus {
|
502 |
-
border-color: #
|
503 |
-
box-shadow: 0 0 0
|
504 |
-
|
505 |
-
|
506 |
-
.send-button {
|
507 |
-
position: absolute;
|
508 |
-
right: 12px;
|
509 |
-
bottom: 12px;
|
510 |
-
background: #10a37f;
|
511 |
-
color: white;
|
512 |
-
border: none;
|
513 |
-
border-radius: 8px;
|
514 |
-
padding: 8px 12px;
|
515 |
-
cursor: pointer;
|
516 |
-
font-size: 14px;
|
517 |
-
font-weight: 500;
|
518 |
-
transition: background 0.2s;
|
519 |
}
|
520 |
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
color: #
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
border:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
561 |
border-radius: 12px;
|
562 |
-
padding:
|
563 |
-
|
564 |
-
transition: all 0.2s;
|
565 |
-
text-align: left;
|
566 |
}
|
567 |
|
568 |
-
|
569 |
-
background: #ececf1;
|
570 |
-
border-color: #d9d9e3;
|
571 |
-
}
|
572 |
-
|
573 |
-
.example-title {
|
574 |
-
font-weight: 600;
|
575 |
-
font-size: 14px;
|
576 |
-
color: #2d2d2d;
|
577 |
-
margin-bottom: 4px;
|
578 |
-
}
|
579 |
-
|
580 |
-
.example-text {
|
581 |
-
font-size: 13px;
|
582 |
-
color: #6e6e80;
|
583 |
-
}
|
584 |
-
|
585 |
-
/* Responsive */
|
586 |
@media (max-width: 768px) {
|
587 |
-
.
|
588 |
-
|
589 |
}
|
590 |
|
591 |
-
.
|
592 |
-
|
593 |
}
|
594 |
|
595 |
-
|
596 |
-
|
|
|
597 |
}
|
598 |
}
|
599 |
"""
|
600 |
|
601 |
-
with gr.Blocks(css=custom_css, theme=gr.themes.Base()
|
602 |
-
|
603 |
-
# Welcome message formatted with markdown
|
604 |
-
welcome_html = """
|
605 |
-
<div class="welcome-screen">
|
606 |
-
<h1 class="welcome-title">AI Assistant Plus</h1>
|
607 |
-
<p class="welcome-subtitle">Your intelligent companion for any question + Healthcare Billing Expert</p>
|
608 |
-
|
609 |
-
<div class="example-grid">
|
610 |
-
<div class="example-card" onclick="document.querySelector('#input-box textarea').value='What is medical billing code A0429?'; document.querySelector('#input-box textarea').dispatchEvent(new Event('input'));">
|
611 |
-
<div class="example-title">π₯ Medical Billing</div>
|
612 |
-
<div class="example-text">"Explain code A0429"</div>
|
613 |
-
</div>
|
614 |
-
<div class="example-card" onclick="document.querySelector('#input-box textarea').value='How does machine learning work?'; document.querySelector('#input-box textarea').dispatchEvent(new Event('input'));">
|
615 |
-
<div class="example-title">π€ Learn</div>
|
616 |
-
<div class="example-text">"Explain ML simply"</div>
|
617 |
-
</div>
|
618 |
-
<div class="example-card" onclick="document.querySelector('#input-box textarea').value='Write a professional email template'; document.querySelector('#input-box textarea').dispatchEvent(new Event('input'));">
|
619 |
-
<div class="example-title">βοΈ Write</div>
|
620 |
-
<div class="example-text">"Draft an email"</div>
|
621 |
-
</div>
|
622 |
-
<div class="example-card" onclick="document.querySelector('#input-box textarea').value='Give me a healthy recipe idea'; document.querySelector('#input-box textarea').dispatchEvent(new Event('input'));">
|
623 |
-
<div class="example-title">π³ Create</div>
|
624 |
-
<div class="example-text">"Recipe ideas"</div>
|
625 |
-
</div>
|
626 |
-
</div>
|
627 |
-
</div>
|
628 |
-
"""
|
629 |
-
|
630 |
-
# Main layout with HTML structure
|
631 |
gr.HTML("""
|
632 |
-
<div class="
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
<
|
639 |
-
</
|
640 |
-
</div>
|
641 |
-
<div class="chat-history">
|
642 |
-
<div class="chat-item active">Healthcare Billing Expert</div>
|
643 |
-
<div class="chat-item">General Assistant</div>
|
644 |
-
<div class="chat-item">Previous Chat</div>
|
645 |
-
</div>
|
646 |
-
</div>
|
647 |
-
|
648 |
-
<!-- Main chat area -->
|
649 |
-
<div class="chat-container">
|
650 |
""")
|
651 |
|
652 |
-
#
|
653 |
chatbot_ui = gr.Chatbot(
|
654 |
-
value=[
|
|
|
|
|
|
|
|
|
|
|
655 |
elem_id="chatbot",
|
656 |
show_label=False,
|
657 |
type="messages",
|
658 |
-
height=
|
659 |
-
render_markdown=True,
|
660 |
-
avatar_images=None
|
661 |
)
|
662 |
|
663 |
-
#
|
664 |
-
|
665 |
-
|
666 |
-
"
|
667 |
-
|
668 |
-
|
669 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
670 |
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
show_label=False,
|
677 |
-
elem_id="input-box",
|
678 |
-
lines=1,
|
679 |
-
max_lines=5,
|
680 |
-
autofocus=True
|
681 |
-
)
|
682 |
|
683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
684 |
|
685 |
# Event handlers
|
686 |
def respond(message, chat_history):
|
@@ -696,8 +605,29 @@ def create_interface():
|
|
696 |
|
697 |
return "", chat_history
|
698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
699 |
# Connect events
|
700 |
msg.submit(respond, [msg, chatbot_ui], [msg, chatbot_ui])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
|
702 |
return app
|
703 |
|
@@ -707,6 +637,5 @@ if __name__ == "__main__":
|
|
707 |
app.launch(
|
708 |
server_name="0.0.0.0",
|
709 |
server_port=7860,
|
710 |
-
share=False
|
711 |
-
favicon_path=None
|
712 |
)
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Hybrid AI Assistant - General Purpose + Healthcare Billing Expert
|
4 |
+
A ChatGPT-style assistant that can handle any conversation while specializing in healthcare billing codes
|
5 |
"""
|
6 |
|
7 |
import os
|
|
|
39 |
messages: List[Dict[str, str]] = field(default_factory=list)
|
40 |
detected_codes: List[str] = field(default_factory=list)
|
41 |
last_topic: Optional[str] = None
|
|
|
|
|
42 |
|
43 |
# ============= Healthcare Billing Database =============
|
44 |
|
|
|
114 |
code_type='HCPCS',
|
115 |
additional_info='Cyanocobalamin up to 1000 mcg.',
|
116 |
category='Injections'
|
117 |
+
),
|
118 |
+
'80053': CodeInfo(
|
119 |
+
code='80053',
|
120 |
+
description='Comprehensive metabolic panel',
|
121 |
+
code_type='CPT',
|
122 |
+
additional_info='14 blood tests including glucose, kidney, and liver function.',
|
123 |
+
category='Laboratory'
|
124 |
+
),
|
125 |
+
'70450': CodeInfo(
|
126 |
+
code='70450',
|
127 |
+
description='CT head/brain without contrast',
|
128 |
+
code_type='CPT',
|
129 |
+
additional_info='Computed tomography of head without contrast material.',
|
130 |
+
category='Radiology'
|
131 |
+
),
|
132 |
+
'90837': CodeInfo(
|
133 |
+
code='90837',
|
134 |
+
description='Psychotherapy, 60 minutes',
|
135 |
+
code_type='CPT',
|
136 |
+
additional_info='Individual psychotherapy session.',
|
137 |
+
category='Mental Health'
|
138 |
+
),
|
139 |
+
'36415': CodeInfo(
|
140 |
+
code='36415',
|
141 |
+
description='Venipuncture (blood draw)',
|
142 |
+
code_type='CPT',
|
143 |
+
additional_info='Collection of blood by needle.',
|
144 |
+
category='Laboratory'
|
145 |
+
),
|
146 |
+
'99282': CodeInfo(
|
147 |
+
code='99282',
|
148 |
+
description='Emergency department visit, low-moderate severity',
|
149 |
+
code_type='CPT',
|
150 |
+
additional_info='ED visit for problems of low to moderate severity.',
|
151 |
+
category='Emergency'
|
152 |
)
|
153 |
}
|
154 |
|
|
|
198 |
def detect_intent(self, message: str) -> Dict[str, Any]:
|
199 |
"""Detect if the message is about billing codes or general conversation"""
|
200 |
lower_msg = message.lower()
|
201 |
+
|
202 |
+
# Check for billing codes in the message
|
203 |
codes = self.billing_db.search_codes(message)
|
204 |
|
205 |
+
# Keywords that suggest billing/medical coding questions
|
206 |
+
billing_keywords = ['code', 'cpt', 'hcpcs', 'icd', 'drg', 'billing', 'medical code',
|
207 |
+
'healthcare code', 'diagnosis code', 'procedure code']
|
208 |
+
|
209 |
is_billing = any(keyword in lower_msg for keyword in billing_keywords) or len(codes) > 0
|
210 |
|
211 |
return {
|
|
|
219 |
responses = []
|
220 |
|
221 |
if codes:
|
222 |
+
for code in codes[:3]: # Limit to first 3 codes
|
223 |
info = self.billing_db.lookup(code)
|
224 |
if info:
|
225 |
+
response = f"**{info.code} ({info.code_type})**\n"
|
226 |
+
response += f"π **Description:** {info.description}\n"
|
227 |
if info.additional_info:
|
228 |
+
response += f"βΉοΈ **Details:** {info.additional_info}\n"
|
229 |
if info.category:
|
230 |
+
response += f"π·οΈ **Category:** {info.category}\n"
|
231 |
responses.append(response)
|
232 |
|
233 |
if responses:
|
234 |
final_response = "I found information about the billing code(s) you mentioned:\n\n"
|
235 |
+
final_response += "\n---\n".join(responses)
|
236 |
+
final_response += "\n\nπ‘ **Need more details?** Feel free to ask specific questions about these codes!"
|
237 |
return final_response
|
238 |
else:
|
239 |
return self.get_general_response(message, billing_context=True)
|
|
|
241 |
def get_general_response(self, message: str, billing_context: bool = False) -> str:
|
242 |
"""Get response from OpenRouter API for general queries"""
|
243 |
|
244 |
+
# Prepare system prompt
|
245 |
system_prompt = """You are a helpful, friendly AI assistant with expertise in healthcare billing codes.
|
246 |
You can assist with any topic - from casual conversation to complex questions.
|
247 |
When discussing medical billing codes, you provide accurate, detailed information.
|
248 |
+
Be conversational, helpful, and engaging. Use emojis occasionally to be friendly.
|
249 |
+
Keep responses concise but informative."""
|
250 |
|
251 |
if billing_context:
|
252 |
system_prompt += "\nThe user is asking about medical billing. Provide helpful information even if you don't have specific code details."
|
253 |
|
254 |
+
# Build conversation history for context
|
255 |
messages = [{'role': 'system', 'content': system_prompt}]
|
256 |
|
257 |
+
# Add recent conversation history (last 5 exchanges)
|
258 |
for msg in self.context.messages[-10:]:
|
259 |
messages.append(msg)
|
260 |
|
261 |
+
# Add current message
|
262 |
messages.append({'role': 'user', 'content': message})
|
263 |
|
264 |
try:
|
|
|
278 |
result = response.json()
|
279 |
ai_response = result['choices'][0]['message']['content']
|
280 |
|
281 |
+
# Update context
|
282 |
self.context.messages.append({'role': 'user', 'content': message})
|
283 |
self.context.messages.append({'role': 'assistant', 'content': ai_response})
|
284 |
|
285 |
+
# Keep only last 20 messages in context
|
286 |
if len(self.context.messages) > 20:
|
287 |
self.context.messages = self.context.messages[-20:]
|
288 |
|
|
|
300 |
fallbacks = [
|
301 |
"I'm having trouble connecting right now, but I'm still here to help! Could you rephrase your question?",
|
302 |
"Let me think about that differently. What specific aspect would you like to know more about?",
|
303 |
+
"That's an interesting question! While I process that, is there anything specific you'd like to explore?",
|
304 |
+
"I'm here to help! Could you provide a bit more detail about what you're looking for?"
|
305 |
]
|
306 |
return random.choice(fallbacks)
|
307 |
|
|
|
310 |
if not message.strip():
|
311 |
return "Feel free to ask me anything! I can help with general questions or healthcare billing codes. π"
|
312 |
|
313 |
+
# Detect intent
|
314 |
intent = self.detect_intent(message)
|
315 |
|
316 |
+
# Route to appropriate handler
|
317 |
if intent['is_billing'] and intent['codes_found']:
|
318 |
return self.handle_billing_query(message, intent['codes_found'])
|
319 |
else:
|
|
|
323 |
"""Reset conversation context"""
|
324 |
self.context = ConversationContext()
|
325 |
|
326 |
+
# ============= Gradio Interface =============
|
327 |
|
328 |
def create_interface():
|
329 |
assistant = HybridAIAssistant()
|
330 |
|
331 |
+
# ChatGPT-style CSS
|
332 |
custom_css = """
|
333 |
+
/* Main container */
|
334 |
.gradio-container {
|
335 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif !important;
|
336 |
+
max-width: 900px !important;
|
337 |
+
margin: auto !important;
|
338 |
+
background: #ffffff !important;
|
|
|
|
|
|
|
|
|
|
|
339 |
}
|
340 |
|
341 |
+
/* Header styling */
|
342 |
+
.header-container {
|
343 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
344 |
+
padding: 2rem;
|
345 |
+
border-radius: 15px 15px 0 0;
|
346 |
+
margin-bottom: 0;
|
347 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
348 |
}
|
349 |
|
350 |
+
.header-title {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
color: white;
|
352 |
+
font-size: 2rem;
|
353 |
+
font-weight: 700;
|
354 |
+
margin: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
display: flex;
|
356 |
align-items: center;
|
357 |
+
justify-content: center;
|
358 |
+
gap: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
}
|
360 |
|
361 |
+
.header-subtitle {
|
362 |
+
color: rgba(255,255,255,0.9);
|
363 |
+
font-size: 1rem;
|
364 |
+
margin-top: 0.5rem;
|
365 |
+
text-align: center;
|
|
|
|
|
366 |
}
|
367 |
|
368 |
+
/* Chat container */
|
369 |
#chatbot {
|
370 |
+
height: 500px !important;
|
371 |
+
border: 1px solid #e5e7eb !important;
|
372 |
+
border-radius: 12px !important;
|
373 |
+
box-shadow: 0 2px 6px rgba(0,0,0,0.05) !important;
|
374 |
+
background: #ffffff !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
}
|
376 |
|
377 |
/* Message styling */
|
378 |
.message {
|
379 |
+
padding: 1rem !important;
|
380 |
+
margin: 0.5rem !important;
|
381 |
+
border-radius: 12px !important;
|
382 |
+
font-size: 15px !important;
|
383 |
+
line-height: 1.6 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
384 |
}
|
385 |
|
386 |
+
.user-message {
|
387 |
+
background: #f3f4f6 !important;
|
388 |
+
border: 1px solid #e5e7eb !important;
|
389 |
+
margin-left: 20% !important;
|
390 |
}
|
391 |
|
392 |
+
.bot-message {
|
393 |
+
background: #ffffff !important;
|
394 |
+
border: 1px solid #e5e7eb !important;
|
395 |
+
margin-right: 20% !important;
|
|
|
396 |
}
|
397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
/* Input area */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
#input-box {
|
400 |
+
border: 2px solid #e5e7eb !important;
|
401 |
+
border-radius: 12px !important;
|
402 |
+
padding: 14px 16px !important;
|
403 |
+
font-size: 15px !important;
|
404 |
+
transition: all 0.3s ease !important;
|
405 |
+
background: #ffffff !important;
|
|
|
|
|
|
|
|
|
|
|
406 |
}
|
407 |
|
408 |
#input-box:focus {
|
409 |
+
border-color: #667eea !important;
|
410 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
|
411 |
+
outline: none !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
412 |
}
|
413 |
|
414 |
+
/* Buttons */
|
415 |
+
.primary-btn {
|
416 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
417 |
+
color: white !important;
|
418 |
+
border: none !important;
|
419 |
+
border-radius: 10px !important;
|
420 |
+
padding: 12px 24px !important;
|
421 |
+
font-weight: 600 !important;
|
422 |
+
font-size: 15px !important;
|
423 |
+
cursor: pointer !important;
|
424 |
+
transition: transform 0.2s ease !important;
|
425 |
+
}
|
426 |
+
|
427 |
+
.primary-btn:hover {
|
428 |
+
transform: translateY(-1px) !important;
|
429 |
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3) !important;
|
430 |
+
}
|
431 |
+
|
432 |
+
.secondary-btn {
|
433 |
+
background: #f3f4f6 !important;
|
434 |
+
color: #374151 !important;
|
435 |
+
border: 1px solid #e5e7eb !important;
|
436 |
+
border-radius: 10px !important;
|
437 |
+
padding: 10px 20px !important;
|
438 |
+
font-weight: 500 !important;
|
439 |
+
cursor: pointer !important;
|
440 |
+
transition: all 0.2s ease !important;
|
441 |
+
}
|
442 |
+
|
443 |
+
.secondary-btn:hover {
|
444 |
+
background: #e5e7eb !important;
|
445 |
+
border-color: #d1d5db !important;
|
446 |
+
}
|
447 |
+
|
448 |
+
/* Example chips */
|
449 |
+
.example-chip {
|
450 |
+
display: inline-block !important;
|
451 |
+
background: #ffffff !important;
|
452 |
+
border: 1px solid #e5e7eb !important;
|
453 |
+
border-radius: 20px !important;
|
454 |
+
padding: 8px 16px !important;
|
455 |
+
margin: 4px !important;
|
456 |
+
font-size: 14px !important;
|
457 |
+
color: #4b5563 !important;
|
458 |
+
cursor: pointer !important;
|
459 |
+
transition: all 0.2s ease !important;
|
460 |
+
}
|
461 |
+
|
462 |
+
.example-chip:hover {
|
463 |
+
background: #f9fafb !important;
|
464 |
+
border-color: #667eea !important;
|
465 |
+
color: #667eea !important;
|
466 |
+
transform: translateY(-1px) !important;
|
467 |
+
}
|
468 |
+
|
469 |
+
/* Info cards */
|
470 |
+
.info-card {
|
471 |
+
background: linear-gradient(135deg, #f6f8fb 0%, #f1f5f9 100%);
|
472 |
+
border: 1px solid #e5e7eb;
|
473 |
border-radius: 12px;
|
474 |
+
padding: 1rem;
|
475 |
+
margin: 1rem 0;
|
|
|
|
|
476 |
}
|
477 |
|
478 |
+
/* Responsive design */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
@media (max-width: 768px) {
|
480 |
+
.gradio-container {
|
481 |
+
padding: 0 !important;
|
482 |
}
|
483 |
|
484 |
+
.header-title {
|
485 |
+
font-size: 1.5rem;
|
486 |
}
|
487 |
|
488 |
+
.user-message, .bot-message {
|
489 |
+
margin-left: 5% !important;
|
490 |
+
margin-right: 5% !important;
|
491 |
}
|
492 |
}
|
493 |
"""
|
494 |
|
495 |
+
with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as app:
|
496 |
+
# Header
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
gr.HTML("""
|
498 |
+
<div class="header-container">
|
499 |
+
<h1 class="header-title">
|
500 |
+
<span>π€</span>
|
501 |
+
<span>AI Assistant</span>
|
502 |
+
<span style="font-size: 0.8em; background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px;">PLUS</span>
|
503 |
+
</h1>
|
504 |
+
<p class="header-subtitle">Your intelligent companion for any question + Healthcare Billing Expert</p>
|
505 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
""")
|
507 |
|
508 |
+
# Main chat interface
|
509 |
chatbot_ui = gr.Chatbot(
|
510 |
+
value=[
|
511 |
+
{
|
512 |
+
"role": "assistant",
|
513 |
+
"content": "π **Hello! I'm your AI Assistant!**\n\nI can help you with:\n\nπ₯ **Healthcare Billing Codes** - I'm an expert in CPT, HCPCS, ICD-10, and DRG codes\nπ¬ **General Conversation** - Ask me anything!\nπ **Learning & Education** - Help with various topics\nβοΈ **Writing & Creation** - Stories, emails, ideas\nπ§ **Problem Solving** - Let's work through challenges together\n\n**Try asking:**\nβ’ 'What is billing code A0429?'\nβ’ 'Help me write an email'\nβ’ 'Explain quantum physics simply'\nβ’ 'What's the weather like?'\n\nHow can I assist you today? π"
|
514 |
+
}
|
515 |
+
],
|
516 |
elem_id="chatbot",
|
517 |
show_label=False,
|
518 |
type="messages",
|
519 |
+
height=500
|
|
|
|
|
520 |
)
|
521 |
|
522 |
+
# Input section
|
523 |
+
with gr.Row():
|
524 |
+
msg = gr.Textbox(
|
525 |
+
placeholder="Ask me anything... (e.g., 'Explain code 99213' or 'Help me write a story')",
|
526 |
+
show_label=False,
|
527 |
+
elem_id="input-box",
|
528 |
+
scale=5,
|
529 |
+
lines=1,
|
530 |
+
max_lines=5
|
531 |
+
)
|
532 |
+
send_btn = gr.Button("Send", elem_classes="primary-btn", scale=1)
|
533 |
+
|
534 |
+
# Quick examples
|
535 |
+
gr.HTML("<div style='text-align: center; margin: 1rem 0; color: #6b7280; font-size: 14px;'>Quick Examples</div>")
|
536 |
+
|
537 |
+
with gr.Row():
|
538 |
+
ex_col1 = gr.Column(scale=1)
|
539 |
+
ex_col2 = gr.Column(scale=1)
|
540 |
+
ex_col3 = gr.Column(scale=1)
|
541 |
|
542 |
+
with ex_col1:
|
543 |
+
gr.HTML("<div style='color: #667eea; font-weight: 600; font-size: 13px; margin-bottom: 8px;'>π₯ Medical Billing</div>")
|
544 |
+
ex1 = gr.Button("What is code A0429?", elem_classes="example-chip", size="sm")
|
545 |
+
ex2 = gr.Button("Explain CPT 99213", elem_classes="example-chip", size="sm")
|
546 |
+
ex3 = gr.Button("DRG 470 details", elem_classes="example-chip", size="sm")
|
|
|
|
|
|
|
|
|
|
|
|
|
547 |
|
548 |
+
with ex_col2:
|
549 |
+
gr.HTML("<div style='color: #667eea; font-weight: 600; font-size: 13px; margin-bottom: 8px;'>π General Questions</div>")
|
550 |
+
ex4 = gr.Button("How does AI work?", elem_classes="example-chip", size="sm")
|
551 |
+
ex5 = gr.Button("Recipe for pasta", elem_classes="example-chip", size="sm")
|
552 |
+
ex6 = gr.Button("Python tutorial", elem_classes="example-chip", size="sm")
|
553 |
+
|
554 |
+
with ex_col3:
|
555 |
+
gr.HTML("<div style='color: #667eea; font-weight: 600; font-size: 13px; margin-bottom: 8px;'>βοΈ Creative Help</div>")
|
556 |
+
ex7 = gr.Button("Write a poem", elem_classes="example-chip", size="sm")
|
557 |
+
ex8 = gr.Button("Email template", elem_classes="example-chip", size="sm")
|
558 |
+
ex9 = gr.Button("Story ideas", elem_classes="example-chip", size="sm")
|
559 |
+
|
560 |
+
# Control buttons
|
561 |
+
with gr.Row():
|
562 |
+
clear_btn = gr.Button("π New Chat", elem_classes="secondary-btn", size="sm")
|
563 |
+
gr.HTML("<div style='flex-grow: 1;'></div>")
|
564 |
+
gr.HTML("""
|
565 |
+
<div style='text-align: right; color: #6b7280; font-size: 12px;'>
|
566 |
+
Powered by GPT-3.5 β’ Healthcare Billing Database
|
567 |
+
</div>
|
568 |
+
""")
|
569 |
+
|
570 |
+
# Footer info
|
571 |
+
gr.HTML("""
|
572 |
+
<div class="info-card" style="margin-top: 2rem;">
|
573 |
+
<div style="display: flex; justify-content: space-around; text-align: center;">
|
574 |
+
<div>
|
575 |
+
<div style="color: #667eea; font-size: 24px; font-weight: bold;">15+</div>
|
576 |
+
<div style="color: #6b7280; font-size: 12px;">Medical Codes</div>
|
577 |
+
</div>
|
578 |
+
<div>
|
579 |
+
<div style="color: #667eea; font-size: 24px; font-weight: bold;">β</div>
|
580 |
+
<div style="color: #6b7280; font-size: 12px;">Topics</div>
|
581 |
+
</div>
|
582 |
+
<div>
|
583 |
+
<div style="color: #667eea; font-size: 24px; font-weight: bold;">24/7</div>
|
584 |
+
<div style="color: #6b7280; font-size: 12px;">Available</div>
|
585 |
+
</div>
|
586 |
+
<div>
|
587 |
+
<div style="color: #667eea; font-size: 24px; font-weight: bold;">Fast</div>
|
588 |
+
<div style="color: #6b7280; font-size: 12px;">Responses</div>
|
589 |
+
</div>
|
590 |
+
</div>
|
591 |
+
</div>
|
592 |
+
""")
|
593 |
|
594 |
# Event handlers
|
595 |
def respond(message, chat_history):
|
|
|
605 |
|
606 |
return "", chat_history
|
607 |
|
608 |
+
def clear_chat():
|
609 |
+
assistant.reset_context()
|
610 |
+
welcome_msg = {
|
611 |
+
"role": "assistant",
|
612 |
+
"content": "π **Chat cleared! Ready for a new conversation.**\n\nI'm here to help with anything you need - from healthcare billing codes to general questions!\n\nWhat would you like to know? π"
|
613 |
+
}
|
614 |
+
return [welcome_msg]
|
615 |
+
|
616 |
# Connect events
|
617 |
msg.submit(respond, [msg, chatbot_ui], [msg, chatbot_ui])
|
618 |
+
send_btn.click(respond, [msg, chatbot_ui], [msg, chatbot_ui])
|
619 |
+
clear_btn.click(clear_chat, outputs=[chatbot_ui])
|
620 |
+
|
621 |
+
# Example button handlers
|
622 |
+
ex1.click(lambda: "What is healthcare billing code A0429?", outputs=msg)
|
623 |
+
ex2.click(lambda: "Can you explain CPT code 99213 in detail?", outputs=msg)
|
624 |
+
ex3.click(lambda: "Tell me about DRG 470", outputs=msg)
|
625 |
+
ex4.click(lambda: "How does artificial intelligence work?", outputs=msg)
|
626 |
+
ex5.click(lambda: "Give me a simple pasta recipe", outputs=msg)
|
627 |
+
ex6.click(lambda: "Teach me Python basics", outputs=msg)
|
628 |
+
ex7.click(lambda: "Write a short poem about nature", outputs=msg)
|
629 |
+
ex8.click(lambda: "Help me write a professional email template", outputs=msg)
|
630 |
+
ex9.click(lambda: "Give me creative story ideas", outputs=msg)
|
631 |
|
632 |
return app
|
633 |
|
|
|
637 |
app.launch(
|
638 |
server_name="0.0.0.0",
|
639 |
server_port=7860,
|
640 |
+
share=False
|
|
|
641 |
)
|