Spaces:
Running
Running
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,6 +39,8 @@ 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 |
|
43 |
# ============= Healthcare Billing Database =============
|
44 |
|
@@ -114,41 +116,6 @@ class BillingCodesDB:
|
|
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,14 +165,9 @@ class HybridAIAssistant:
|
|
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 |
-
|
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,21 +181,21 @@ class HybridAIAssistant:
|
|
219 |
responses = []
|
220 |
|
221 |
if codes:
|
222 |
-
for code in codes[:3]:
|
223 |
info = self.billing_db.lookup(code)
|
224 |
if info:
|
225 |
-
response = f"
|
226 |
-
response += f"
|
227 |
if info.additional_info:
|
228 |
-
response += f"
|
229 |
if info.category:
|
230 |
-
response += f"
|
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💡
|
237 |
return final_response
|
238 |
else:
|
239 |
return self.get_general_response(message, billing_context=True)
|
@@ -241,24 +203,20 @@ class HybridAIAssistant:
|
|
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.
|
249 |
-
|
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,11 +236,9 @@ class HybridAIAssistant:
|
|
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,8 +256,7 @@ class HybridAIAssistant:
|
|
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,10 +265,8 @@ class HybridAIAssistant:
|
|
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,273 +276,411 @@ class HybridAIAssistant:
|
|
323 |
"""Reset conversation context"""
|
324 |
self.context = ConversationContext()
|
325 |
|
326 |
-
# =============
|
327 |
|
328 |
def create_interface():
|
329 |
assistant = HybridAIAssistant()
|
330 |
|
331 |
-
#
|
332 |
custom_css = """
|
333 |
-
/*
|
334 |
.gradio-container {
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
|
|
|
|
|
|
|
|
|
|
339 |
}
|
340 |
|
341 |
-
/*
|
342 |
-
.
|
343 |
-
|
344 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
color: white;
|
352 |
-
|
353 |
-
|
354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
display: flex;
|
356 |
align-items: center;
|
357 |
-
|
358 |
-
|
359 |
}
|
360 |
|
361 |
-
.
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
366 |
}
|
367 |
|
368 |
-
/* Chat
|
369 |
#chatbot {
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
}
|
376 |
|
377 |
/* Message styling */
|
378 |
.message {
|
379 |
-
padding:
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
384 |
}
|
385 |
|
386 |
-
.
|
387 |
-
background: #
|
388 |
-
border: 1px solid #e5e7eb !important;
|
389 |
-
margin-left: 20% !important;
|
390 |
}
|
391 |
|
392 |
-
.
|
393 |
-
|
394 |
-
|
395 |
-
|
|
|
396 |
}
|
397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
/* Input area */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
#input-box {
|
400 |
-
|
401 |
-
border
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
|
|
|
|
|
|
|
|
|
|
406 |
}
|
407 |
|
408 |
#input-box:focus {
|
409 |
-
border-color: #
|
410 |
-
box-shadow: 0 0 0
|
411 |
-
outline: none !important;
|
412 |
}
|
413 |
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
cursor: pointer
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
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 |
-
/* 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:
|
475 |
-
|
|
|
|
|
476 |
}
|
477 |
|
478 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
@media (max-width: 768px) {
|
480 |
-
.
|
481 |
-
|
482 |
}
|
483 |
|
484 |
-
.
|
485 |
-
|
486 |
}
|
487 |
|
488 |
-
|
489 |
-
|
490 |
-
margin-right: 5% !important;
|
491 |
}
|
492 |
}
|
493 |
"""
|
494 |
|
495 |
-
with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as app:
|
496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
gr.HTML("""
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
<
|
505 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
""")
|
507 |
|
508 |
-
#
|
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=
|
|
|
|
|
520 |
)
|
521 |
|
522 |
-
#
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
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 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
559 |
|
560 |
-
|
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,29 +696,8 @@ def create_interface():
|
|
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,5 +707,6 @@ if __name__ == "__main__":
|
|
637 |
app.launch(
|
638 |
server_name="0.0.0.0",
|
639 |
server_port=7860,
|
640 |
-
share=False
|
|
|
641 |
)
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Hybrid AI Assistant - ChatGPT Style Full Screen Interface
|
4 |
+
A full-screen ChatGPT clone with healthcare billing expertise
|
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 |
+
chat_history: List[Dict] = field(default_factory=list)
|
43 |
+
current_chat_id: str = "chat_1"
|
44 |
|
45 |
# ============= Healthcare Billing Database =============
|
46 |
|
|
|
116 |
code_type='HCPCS',
|
117 |
additional_info='Cyanocobalamin up to 1000 mcg.',
|
118 |
category='Injections'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
)
|
120 |
}
|
121 |
|
|
|
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 |
+
billing_keywords = ['code', 'cpt', 'hcpcs', 'icd', 'drg', 'billing', 'medical code']
|
|
|
|
|
|
|
171 |
is_billing = any(keyword in lower_msg for keyword in billing_keywords) or len(codes) > 0
|
172 |
|
173 |
return {
|
|
|
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"### {info.code} ({info.code_type})\n\n"
|
188 |
+
response += f"**Description:** {info.description}\n\n"
|
189 |
if info.additional_info:
|
190 |
+
response += f"**Details:** {info.additional_info}\n\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\n".join(responses)
|
198 |
+
final_response += "\n\n💡 Need more details? Feel free to ask specific questions about these codes!"
|
199 |
return final_response
|
200 |
else:
|
201 |
return self.get_general_response(message, billing_context=True)
|
|
|
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. Format your responses with markdown for better readability.
|
210 |
+
Use headers (###), bold (**text**), and bullet points where appropriate."""
|
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 |
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 |
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 |
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 |
"""Reset conversation context"""
|
277 |
self.context = ConversationContext()
|
278 |
|
279 |
+
# ============= Create ChatGPT-Style Interface =============
|
280 |
|
281 |
def create_interface():
|
282 |
assistant = HybridAIAssistant()
|
283 |
|
284 |
+
# Full-screen ChatGPT CSS
|
285 |
custom_css = """
|
286 |
+
/* Full screen layout */
|
287 |
.gradio-container {
|
288 |
+
margin: 0 !important;
|
289 |
+
padding: 0 !important;
|
290 |
+
width: 100vw !important;
|
291 |
+
max-width: 100vw !important;
|
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 |
+
/* Remove all Gradio default spacing */
|
300 |
+
.contain, .wrapper, .wrap, .block {
|
301 |
+
border: none !important;
|
302 |
+
background: transparent !important;
|
|
|
|
|
|
|
303 |
}
|
304 |
|
305 |
+
/* Main container layout */
|
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 |
+
padding: 0;
|
320 |
+
display: flex;
|
321 |
+
flex-direction: column;
|
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 |
+
gap: 12px;
|
343 |
+
transition: background 0.2s;
|
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 |
+
/* Main chat area */
|
379 |
+
.chat-container {
|
380 |
+
flex: 1;
|
381 |
+
display: flex;
|
382 |
+
flex-direction: column;
|
383 |
+
background: #fff;
|
384 |
+
position: relative;
|
385 |
}
|
386 |
|
387 |
+
/* Chat messages area */
|
388 |
#chatbot {
|
389 |
+
flex: 1;
|
390 |
+
overflow-y: auto;
|
391 |
+
padding: 0;
|
392 |
+
background: #fff;
|
393 |
+
display: flex;
|
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: 1.5rem 0;
|
407 |
+
border-bottom: 1px solid #f0f0f0;
|
408 |
+
}
|
409 |
+
|
410 |
+
.message.user {
|
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 |
+
.avatar.assistant {
|
441 |
+
background: #19c37d;
|
|
|
|
|
442 |
}
|
443 |
|
444 |
+
.message-text {
|
445 |
+
flex: 1;
|
446 |
+
line-height: 1.6;
|
447 |
+
font-size: 15px;
|
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 |
+
width: 100%;
|
489 |
+
border: 1px solid #d9d9e3;
|
490 |
+
border-radius: 12px;
|
491 |
+
padding: 12px 48px 12px 16px;
|
492 |
+
font-size: 15px;
|
493 |
+
resize: none;
|
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: #10a37f;
|
503 |
+
box-shadow: 0 0 0 2px rgba(16,163,127,0.1);
|
|
|
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 |
+
.send-button:hover {
|
522 |
+
background: #0d8f6e;
|
523 |
+
}
|
524 |
+
|
525 |
+
.send-button:disabled {
|
526 |
+
background: #e5e5e5;
|
527 |
+
cursor: not-allowed;
|
528 |
+
}
|
529 |
+
|
530 |
+
/* Welcome screen */
|
531 |
+
.welcome-screen {
|
532 |
+
max-width: 48rem;
|
533 |
+
margin: 0 auto;
|
534 |
+
padding: 3rem 1rem;
|
535 |
+
text-align: center;
|
536 |
+
}
|
537 |
+
|
538 |
+
.welcome-title {
|
539 |
+
font-size: 2rem;
|
540 |
+
font-weight: 600;
|
541 |
+
color: #2d2d2d;
|
542 |
+
margin-bottom: 1rem;
|
543 |
+
}
|
544 |
+
|
545 |
+
.welcome-subtitle {
|
546 |
+
font-size: 1rem;
|
547 |
+
color: #6e6e80;
|
548 |
+
margin-bottom: 3rem;
|
549 |
+
}
|
550 |
+
|
551 |
+
.example-grid {
|
552 |
+
display: grid;
|
553 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
554 |
+
gap: 12px;
|
555 |
+
margin-top: 2rem;
|
556 |
+
}
|
557 |
+
|
558 |
+
.example-card {
|
559 |
+
background: #f7f7f8;
|
560 |
+
border: 1px solid #e5e5e5;
|
|
|
|
|
|
|
|
|
561 |
border-radius: 12px;
|
562 |
+
padding: 16px;
|
563 |
+
cursor: pointer;
|
564 |
+
transition: all 0.2s;
|
565 |
+
text-align: left;
|
566 |
}
|
567 |
|
568 |
+
.example-card:hover {
|
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 |
+
.sidebar {
|
588 |
+
display: none;
|
589 |
}
|
590 |
|
591 |
+
.input-wrapper {
|
592 |
+
padding: 0 0.5rem;
|
593 |
}
|
594 |
|
595 |
+
#chatbot > .wrap {
|
596 |
+
padding: 1rem 0.5rem;
|
|
|
597 |
}
|
598 |
}
|
599 |
"""
|
600 |
|
601 |
+
with gr.Blocks(css=custom_css, theme=gr.themes.Base(), title="AI Assistant") as app:
|
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="main-container">
|
633 |
+
<!-- Sidebar -->
|
634 |
+
<div class="sidebar">
|
635 |
+
<div class="sidebar-header">
|
636 |
+
<button class="new-chat-btn">
|
637 |
+
<span>+</span>
|
638 |
+
<span>New chat</span>
|
639 |
+
</button>
|
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 |
+
# Chat interface
|
653 |
chatbot_ui = gr.Chatbot(
|
654 |
+
value=[],
|
|
|
|
|
|
|
|
|
|
|
655 |
elem_id="chatbot",
|
656 |
show_label=False,
|
657 |
type="messages",
|
658 |
+
height=600,
|
659 |
+
render_markdown=True,
|
660 |
+
avatar_images=None
|
661 |
)
|
662 |
|
663 |
+
# Set initial welcome message
|
664 |
+
chatbot_ui.value = [
|
665 |
+
{
|
666 |
+
"role": "assistant",
|
667 |
+
"content": welcome_html
|
668 |
+
}
|
669 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
670 |
|
671 |
+
# Input area
|
672 |
+
with gr.Row(elem_classes="input-container"):
|
673 |
+
with gr.Column(elem_classes="input-wrapper"):
|
674 |
+
msg = gr.Textbox(
|
675 |
+
placeholder="Message AI Assistant...",
|
676 |
+
show_label=False,
|
677 |
+
elem_id="input-box",
|
678 |
+
lines=1,
|
679 |
+
max_lines=5,
|
680 |
+
autofocus=True
|
681 |
+
)
|
682 |
|
683 |
+
gr.HTML("</div></div>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
684 |
|
685 |
# Event handlers
|
686 |
def respond(message, chat_history):
|
|
|
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 |
app.launch(
|
708 |
server_name="0.0.0.0",
|
709 |
server_port=7860,
|
710 |
+
share=False,
|
711 |
+
favicon_path=None
|
712 |
)
|