Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Hybrid AI Assistant - General Purpose + Healthcare Billing Expert | |
| Enhanced with Emotional UI and Voice Input/Output - HUGGING FACE SPACES VERSION | |
| """ | |
| import os | |
| import json | |
| import logging | |
| import re | |
| from typing import Dict, Optional, Tuple, List, Any | |
| from dataclasses import dataclass, field | |
| from enum import Enum | |
| import requests | |
| import gradio as gr | |
| from datetime import datetime | |
| import random | |
| import speech_recognition as sr | |
| from gtts import gTTS | |
| from io import BytesIO | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Set up environment - Hugging Face Spaces will handle API keys via secrets | |
| API_KEY = os.getenv('OPENROUTER_API_KEY', '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') | |
| # ============= Data Classes ============= | |
| class CodeInfo: | |
| code: str | |
| description: str | |
| code_type: str | |
| additional_info: Optional[str] = None | |
| category: Optional[str] = None | |
| class ConversationContext: | |
| messages: List[Dict[str, str]] = field(default_factory=list) | |
| detected_codes: List[str] = field(default_factory=list) | |
| last_topic: Optional[str] = None | |
| current_sentiment: str = "neutral" | |
| sentiment_history: List[str] = field(default_factory=list) | |
| class SentimentType(Enum): | |
| VERY_POSITIVE = "very_positive" | |
| POSITIVE = "positive" | |
| NEUTRAL = "neutral" | |
| NEGATIVE = "negative" | |
| VERY_NEGATIVE = "very_negative" | |
| ANXIOUS = "anxious" | |
| FRUSTRATED = "frustrated" | |
| EXCITED = "excited" | |
| CONFUSED = "confused" | |
| # ============= Healthcare Billing Database ============= | |
| class BillingCodesDB: | |
| def __init__(self): | |
| self.codes = { | |
| 'A0429': CodeInfo( | |
| code='A0429', | |
| description='Ambulance service, basic life support, emergency transport (BLS-emergency)', | |
| code_type='HCPCS', | |
| additional_info='Ground ambulance emergency transport with BLS level care. Used for emergency situations requiring immediate medical transport.', | |
| category='Ambulance Services' | |
| ), | |
| 'A0428': CodeInfo( | |
| code='A0428', | |
| description='Ambulance service, basic life support, non-emergency transport', | |
| code_type='HCPCS', | |
| additional_info='Scheduled or non-urgent medical transport with basic life support.', | |
| category='Ambulance Services' | |
| ), | |
| '99213': CodeInfo( | |
| code='99213', | |
| description='Office visit for established patient, low complexity', | |
| code_type='CPT', | |
| additional_info='Typically 20-29 minutes. For straightforward medical issues.', | |
| category='E&M Services' | |
| ), | |
| '99214': CodeInfo( | |
| code='99214', | |
| description='Office visit for established patient, moderate complexity', | |
| code_type='CPT', | |
| additional_info='Typically 30-39 minutes. For moderately complex medical issues.', | |
| category='E&M Services' | |
| ), | |
| '99215': CodeInfo( | |
| code='99215', | |
| description='Office visit for established patient, high complexity', | |
| code_type='CPT', | |
| additional_info='Typically 40-54 minutes. For complex medical decision making.', | |
| category='E&M Services' | |
| ), | |
| '93000': CodeInfo( | |
| code='93000', | |
| description='Electrocardiogram (ECG/EKG) with interpretation', | |
| code_type='CPT', | |
| additional_info='Complete 12-lead ECG including test, interpretation, and report.', | |
| category='Cardiovascular' | |
| ), | |
| 'DRG470': CodeInfo( | |
| code='DRG470', | |
| description='Major hip and knee joint replacement without complications', | |
| code_type='DRG', | |
| additional_info='Medicare payment group for joint replacement surgeries.', | |
| category='Orthopedic' | |
| ), | |
| 'Z79.899': CodeInfo( | |
| code='Z79.899', | |
| description='Other long term drug therapy', | |
| code_type='ICD-10', | |
| additional_info='Indicates patient is on long-term medication.', | |
| category='Diagnosis' | |
| ), | |
| 'E1399': CodeInfo( | |
| code='E1399', | |
| description='Durable medical equipment, miscellaneous', | |
| code_type='HCPCS', | |
| additional_info='For DME not elsewhere classified.', | |
| category='Equipment' | |
| ), | |
| 'J3420': CodeInfo( | |
| code='J3420', | |
| description='Vitamin B-12 injection', | |
| code_type='HCPCS', | |
| additional_info='Cyanocobalamin up to 1000 mcg.', | |
| category='Injections' | |
| ), | |
| '80053': CodeInfo( | |
| code='80053', | |
| description='Comprehensive metabolic panel', | |
| code_type='CPT', | |
| additional_info='14 blood tests including glucose, kidney, and liver function.', | |
| category='Laboratory' | |
| ), | |
| '70450': CodeInfo( | |
| code='70450', | |
| description='CT head/brain without contrast', | |
| code_type='CPT', | |
| additional_info='Computed tomography of head without contrast material.', | |
| category='Radiology' | |
| ), | |
| '90837': CodeInfo( | |
| code='90837', | |
| description='Psychotherapy, 60 minutes', | |
| code_type='CPT', | |
| additional_info='Individual psychotherapy session.', | |
| category='Mental Health' | |
| ), | |
| '36415': CodeInfo( | |
| code='36415', | |
| description='Venipuncture (blood draw)', | |
| code_type='CPT', | |
| additional_info='Collection of blood by needle.', | |
| category='Laboratory' | |
| ), | |
| '99282': CodeInfo( | |
| code='99282', | |
| description='Emergency department visit, low-moderate severity', | |
| code_type='CPT', | |
| additional_info='ED visit for problems of low to moderate severity.', | |
| category='Emergency' | |
| ) | |
| } | |
| def lookup(self, code: str) -> Optional[CodeInfo]: | |
| code = code.strip().upper() | |
| if code in self.codes: | |
| return self.codes[code] | |
| if code.isdigit() and len(code) == 3: | |
| drg_code = f"DRG{code}" | |
| if drg_code in self.codes: | |
| return self.codes[drg_code] | |
| return None | |
| def search_codes(self, text: str) -> List[str]: | |
| """Extract potential billing codes from text""" | |
| found_codes = [] | |
| patterns = [ | |
| r'\b([A-V][0-9]{4})\b', # HCPCS | |
| r'\b([0-9]{5})\b', # CPT | |
| r'\bDRG\s*([0-9]{3})\b', # DRG | |
| r'\b([A-Z][0-9]{2}\.?[0-9]{0,3})\b', # ICD-10 | |
| ] | |
| for pattern in patterns: | |
| matches = re.findall(pattern, text.upper()) | |
| for match in matches: | |
| if self.lookup(match): | |
| found_codes.append(match) | |
| return found_codes | |
| # ============= Sentiment Analysis ============= | |
| class SentimentAnalyzer: | |
| def __init__(self): | |
| self.positive_words = ['great', 'awesome', 'excellent', 'fantastic', 'wonderful', 'amazing', 'perfect', 'love', 'happy', 'excited', 'thank', 'thanks', 'good', 'nice', 'brilliant', 'outstanding'] | |
| self.negative_words = ['terrible', 'awful', 'horrible', 'bad', 'worst', 'hate', 'frustrated', 'angry', 'sad', 'disappointed', 'upset', 'confused', 'difficult', 'problem', 'issue', 'error', 'wrong'] | |
| self.anxious_words = ['worried', 'concerned', 'nervous', 'anxious', 'scared', 'afraid', 'stress', 'panic', 'uncertain', 'unsure'] | |
| self.excited_words = ['excited', 'thrilled', 'amazing', 'wow', 'incredible', 'fantastic', 'brilliant', 'awesome'] | |
| def analyze_sentiment(self, text: str) -> SentimentType: | |
| text_lower = text.lower() | |
| positive_count = sum(1 for word in self.positive_words if word in text_lower) | |
| negative_count = sum(1 for word in self.negative_words if word in text_lower) | |
| anxious_count = sum(1 for word in self.anxious_words if word in text_lower) | |
| excited_count = sum(1 for word in self.excited_words if word in text_lower) | |
| # Check for question marks (confusion indicator) | |
| question_marks = text.count('?') | |
| exclamation_marks = text.count('!') | |
| # Determine sentiment | |
| if excited_count > 0 or exclamation_marks > 1: | |
| return SentimentType.EXCITED | |
| elif anxious_count > 0: | |
| return SentimentType.ANXIOUS | |
| elif question_marks > 1 and negative_count > 0: | |
| return SentimentType.CONFUSED | |
| elif negative_count > positive_count and negative_count > 1: | |
| return SentimentType.VERY_NEGATIVE if negative_count > 2 else SentimentType.NEGATIVE | |
| elif positive_count > negative_count and positive_count > 1: | |
| return SentimentType.VERY_POSITIVE if positive_count > 2 else SentimentType.POSITIVE | |
| elif 'frustrated' in text_lower or 'frustrating' in text_lower: | |
| return SentimentType.FRUSTRATED | |
| else: | |
| return SentimentType.NEUTRAL | |
| # ============= AI Assistant Class ============= | |
| class HybridAIAssistant: | |
| def __init__(self): | |
| self.api_key = API_KEY | |
| self.billing_db = BillingCodesDB() | |
| self.sentiment_analyzer = SentimentAnalyzer() | |
| self.context = ConversationContext() | |
| self.headers = { | |
| 'Authorization': f'Bearer {self.api_key}', | |
| 'Content-Type': 'application/json', | |
| 'HTTP-Referer': 'https://huggingface.co', | |
| 'X-Title': 'Hybrid AI Assistant' | |
| } | |
| def detect_intent(self, message: str) -> Dict[str, Any]: | |
| """Detect if the message is about billing codes or general conversation""" | |
| lower_msg = message.lower() | |
| # Check for billing codes in the message | |
| codes = self.billing_db.search_codes(message) | |
| # Keywords that suggest billing/medical coding questions | |
| billing_keywords = ['code', 'cpt', 'hcpcs', 'icd', 'drg', 'billing', 'medical code', | |
| 'healthcare code', 'diagnosis code', 'procedure code'] | |
| is_billing = any(keyword in lower_msg for keyword in billing_keywords) or len(codes) > 0 | |
| return { | |
| 'is_billing': is_billing, | |
| 'codes_found': codes, | |
| 'message': message | |
| } | |
| def handle_billing_query(self, message: str, codes: List[str]) -> str: | |
| """Handle healthcare billing specific queries""" | |
| responses = [] | |
| if codes: | |
| for code in codes[:3]: # Limit to first 3 codes | |
| info = self.billing_db.lookup(code) | |
| if info: | |
| response = f"**{info.code} ({info.code_type})**\n" | |
| response += f"π **Description:** {info.description}\n" | |
| if info.additional_info: | |
| response += f"βΉοΈ **Details:** {info.additional_info}\n" | |
| if info.category: | |
| response += f"π·οΈ **Category:** {info.category}\n" | |
| responses.append(response) | |
| if responses: | |
| final_response = "I found information about the billing code(s) you mentioned:\n\n" | |
| final_response += "\n---\n".join(responses) | |
| final_response += "\n\nπ‘ **Need more details?** Feel free to ask specific questions about these codes!" | |
| return final_response | |
| else: | |
| return self.get_general_response(message, billing_context=True) | |
| def get_empathetic_response_prefix(self, sentiment: SentimentType) -> str: | |
| """Generate empathetic response based on sentiment""" | |
| prefixes = { | |
| SentimentType.VERY_POSITIVE: "I'm so glad to hear your enthusiasm! π ", | |
| SentimentType.POSITIVE: "That's wonderful! π ", | |
| SentimentType.EXCITED: "I can feel your excitement! π ", | |
| SentimentType.ANXIOUS: "I understand this might be causing some concern. Let me help ease your worries. π€ ", | |
| SentimentType.FRUSTRATED: "I can sense your frustration, and I'm here to help make this easier for you. π ", | |
| SentimentType.CONFUSED: "No worries, I'm here to clear things up for you! π§ ", | |
| SentimentType.NEGATIVE: "I hear that you're having some difficulties. Let me help you with that. π ", | |
| SentimentType.VERY_NEGATIVE: "I'm really sorry you're going through this. I'm here to support you. β€οΈ ", | |
| SentimentType.NEUTRAL: "" | |
| } | |
| return prefixes.get(sentiment, "") | |
| def get_general_response(self, message: str, billing_context: bool = False) -> str: | |
| """Get response from OpenRouter API for general queries""" | |
| # Analyze sentiment | |
| sentiment = self.sentiment_analyzer.analyze_sentiment(message) | |
| self.context.current_sentiment = sentiment.value | |
| self.context.sentiment_history.append(sentiment.value) | |
| # Keep only last 10 sentiments | |
| if len(self.context.sentiment_history) > 10: | |
| self.context.sentiment_history = self.context.sentiment_history[-10:] | |
| # Prepare system prompt with empathy | |
| system_prompt = """You are a helpful, friendly, and empathetic AI assistant with expertise in healthcare billing codes. | |
| You can assist with any topic - from casual conversation to complex questions. | |
| When discussing medical billing codes, you provide accurate, detailed information. | |
| Be conversational, helpful, and engaging. Show empathy and understanding. | |
| Adapt your tone based on the user's emotional state - be more supportive if they seem frustrated or anxious.""" | |
| if billing_context: | |
| system_prompt += "\nThe user is asking about medical billing. Provide helpful information even if you don't have specific code details." | |
| # Build conversation history for context | |
| messages = [{'role': 'system', 'content': system_prompt}] | |
| # Add recent conversation history (last 5 exchanges) | |
| for msg in self.context.messages[-10:]: | |
| messages.append(msg) | |
| # Add current message | |
| messages.append({'role': 'user', 'content': message}) | |
| try: | |
| response = requests.post( | |
| 'https://openrouter.ai/api/v1/chat/completions', | |
| headers=self.headers, | |
| json={ | |
| 'model': 'openai/gpt-3.5-turbo', | |
| 'messages': messages, | |
| 'temperature': 0.7, | |
| 'max_tokens': 500, | |
| 'stream': False | |
| }, | |
| timeout=30 | |
| ) | |
| if response.status_code == 200: | |
| result = response.json() | |
| ai_response = result['choices'][0]['message']['content'] | |
| # Add empathetic prefix based on sentiment | |
| empathy_prefix = self.get_empathetic_response_prefix(sentiment) | |
| if empathy_prefix: | |
| ai_response = empathy_prefix + ai_response | |
| # Update context | |
| self.context.messages.append({'role': 'user', 'content': message}) | |
| self.context.messages.append({'role': 'assistant', 'content': ai_response}) | |
| # Keep only last 20 messages in context | |
| if len(self.context.messages) > 20: | |
| self.context.messages = self.context.messages[-20:] | |
| return ai_response | |
| else: | |
| logger.error(f"API error: {response.status_code}") | |
| return self.get_fallback_response(message) | |
| except Exception as e: | |
| logger.error(f"Request failed: {e}") | |
| return self.get_fallback_response(message) | |
| def get_fallback_response(self, message: str) -> str: | |
| """Fallback responses when API fails""" | |
| sentiment = self.sentiment_analyzer.analyze_sentiment(message) | |
| empathy_prefix = self.get_empathetic_response_prefix(sentiment) | |
| fallbacks = [ | |
| "I'm having trouble connecting right now, but I'm still here to help! Could you rephrase your question?", | |
| "Let me think about that differently. What specific aspect would you like to know more about?", | |
| "That's an interesting question! While I process that, is there anything specific you'd like to explore?", | |
| "I'm here to help! Could you provide a bit more detail about what you're looking for?" | |
| ] | |
| return empathy_prefix + random.choice(fallbacks) | |
| def process_message(self, message: str) -> Tuple[str, str]: | |
| """Main method to process any message and return response with sentiment""" | |
| if not message.strip(): | |
| return "Feel free to ask me anything! I can help with general questions or healthcare billing codes. π", "neutral" | |
| # Detect intent | |
| intent = self.detect_intent(message) | |
| # Route to appropriate handler | |
| if intent['is_billing'] and intent['codes_found']: | |
| response = self.handle_billing_query(message, intent['codes_found']) | |
| else: | |
| response = self.get_general_response(message, billing_context=intent['is_billing']) | |
| return response, self.context.current_sentiment | |
| def reset_context(self): | |
| """Reset conversation context""" | |
| self.context = ConversationContext() | |
| # ============= Global Assistant Instance ============= | |
| assistant = HybridAIAssistant() | |
| # ============= Chat Functions for Gradio ============= | |
| def chat_with_assistant(message, history): | |
| """Main chat function for Gradio ChatInterface""" | |
| try: | |
| if not message or not message.strip(): | |
| return "Feel free to ask me anything! I can help with general questions or healthcare billing codes. π" | |
| # Process message and get response | |
| response, sentiment = assistant.process_message(message.strip()) | |
| return response | |
| except Exception as e: | |
| logger.error(f"Chat error: {e}") | |
| return "I apologize, but I encountered an error processing your message. Please try again!" | |
| def process_voice_input(audio_file): | |
| """Process voice input using speech recognition""" | |
| if audio_file is None: | |
| return "No audio received. Please try recording again.", None | |
| try: | |
| # Initialize recognizer | |
| recognizer = sr.Recognizer() | |
| # Load audio file | |
| with sr.AudioFile(audio_file) as source: | |
| audio = recognizer.record(source) | |
| # Recognize speech using Google Speech Recognition | |
| text = recognizer.recognize_google(audio) | |
| logger.info(f"Recognized text: {text}") | |
| # Process the recognized text through the assistant | |
| response, sentiment = assistant.process_message(text.strip()) | |
| # Generate audio response | |
| tts = gTTS(text=response, lang='en') | |
| audio_buffer = BytesIO() | |
| tts.write_to_fp(audio_buffer) | |
| audio_buffer.seek(0) | |
| return response, audio_buffer | |
| except sr.UnknownValueError: | |
| return "Sorry, I couldn't understand the audio. Please try speaking clearly or typing your question.", None | |
| except sr.RequestError as e: | |
| logger.error(f"Speech recognition error: {e}") | |
| return "Error processing audio. Please try again or type your question.", None | |
| except Exception as e: | |
| logger.error(f"Voice processing error: {e}") | |
| return "Error processing voice input. Please try again.", None | |
| def reset_conversation(): | |
| """Reset the conversation context""" | |
| try: | |
| assistant.reset_context() | |
| return "β Conversation reset successfully!", "" | |
| except Exception as e: | |
| logger.error(f"Reset error: {e}") | |
| return "Error resetting conversation.", "" | |
| # ============= Examples ============= | |
| examples = [ | |
| "What is healthcare billing code A0429?", | |
| "Can you explain CPT code 99213 in detail?", | |
| "Tell me about DRG 470", | |
| "I'm feeling frustrated with this billing issue", | |
| "This is confusing, can you help me understand?", | |
| "Thank you so much! This is exactly what I needed!", | |
| "How does artificial intelligence work?", | |
| "Give me a simple pasta recipe", | |
| "Write a short poem about nature" | |
| ] | |
| # ============= Custom CSS for Hugging Face Spaces ============= | |
| custom_css = """ | |
| /* Enhanced Hugging Face Spaces Compatible CSS */ | |
| .gradio-container { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; | |
| max-width: 1200px !important; | |
| margin: 0 auto !important; | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; | |
| padding: 1rem !important; | |
| } | |
| /* Header Styling */ | |
| .header-banner { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 2rem; | |
| border-radius: 15px; | |
| margin-bottom: 1.5rem; | |
| text-align: center; | |
| box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); | |
| } | |
| .header-banner h1 { | |
| margin: 0; | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| text-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
| } | |
| .header-banner p { | |
| margin: 0.5rem 0 0 0; | |
| font-size: 1.1rem; | |
| opacity: 0.9; | |
| } | |
| /* Enhanced Buttons */ | |
| .custom-button { | |
| background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%) !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| padding: 0.6rem 1.2rem !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .custom-button:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4) !important; | |
| } | |
| /* Chat Interface */ | |
| .gradio-chatbot { | |
| border-radius: 15px !important; | |
| box-shadow: 0 8px 25px rgba(0,0,0,0.1) !important; | |
| background: white !important; | |
| } | |
| /* Feature Cards */ | |
| .feature-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .feature-card { | |
| background: rgba(255,255,255,0.95); | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
| transition: transform 0.3s ease; | |
| text-align: center; | |
| } | |
| .feature-card:hover { | |
| transform: translateY(-5px); | |
| } | |
| .feature-icon { | |
| font-size: 2rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| /* Stats */ | |
| .stats-container { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .stat-item { | |
| background: white; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| text-align: center; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| border-top: 3px solid #667eea; | |
| } | |
| .stat-number { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| color: #667eea; | |
| } | |
| .stat-label { | |
| font-size: 0.8rem; | |
| color: #666; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .header-banner h1 { | |
| font-size: 2rem; | |
| } | |
| .feature-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .stats-container { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| } | |
| """ | |
| # ============= Main Gradio Interface ============= | |
| def create_gradio_interface(): | |
| """Create the main Gradio interface optimized for Hugging Face Spaces""" | |
| with gr.Blocks( | |
| css=custom_css, | |
| title="π₯ Hybrid AI Assistant - Healthcare Billing Expert" | |
| ) as demo: | |
| # Header Section | |
| gr.HTML(""" | |
| <div class="header-banner"> | |
| <h1>π₯ Hybrid AI Assistant</h1> | |
| <p>Your intelligent companion for healthcare billing codes and general assistance</p> | |
| <div style="margin-top: 1rem;"> | |
| <span style="background: rgba(255,255,255,0.2); padding: 0.3rem 0.8rem; border-radius: 15px; margin: 0 0.25rem; font-size: 0.9rem;">π¬ General AI</span> | |
| <span style="background: rgba(255,255,255,0.2); padding: 0.3rem 0.8rem; border-radius: 15px; margin: 0 0.25rem; font-size: 0.9rem;">π₯ Medical Billing</span> | |
| <span style="background: rgba(255,255,255,0.2); padding: 0.3rem 0.8rem; border-radius: 15px; margin: 0 0.25rem; font-size: 0.9rem;">π Emotional AI</span> | |
| <span style="background: rgba(255,255,255,0.2); padding: 0.3rem 0.8rem; border-radius: 15px; margin: 0 0.25rem; font-size: 0.9rem;">ποΈ Voice Ready</span> | |
| </div> | |
| </div> | |
| """) | |
| # Stats Section | |
| gr.HTML(""" | |
| <div class="stats-container"> | |
| <div class="stat-item"> | |
| <div class="stat-number">15+</div> | |
| <div class="stat-label">Billing Codes</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-number">9</div> | |
| <div class="stat-label">Sentiment Types</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-number">24/7</div> | |
| <div class="stat-label">Available</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-number">β</div> | |
| <div class="stat-label">Conversations</div> | |
| </div> | |
| </div> | |
| """) | |
| # Main Chat Interface | |
| chatbot = gr.ChatInterface( | |
| chat_with_assistant, | |
| examples=examples, | |
| title="", | |
| description="π¬ Start chatting! I can help with healthcare billing codes, general questions, and adapt to your emotional tone." | |
| ) | |
| # Voice Input/Output Section | |
| gr.Markdown("### ποΈ Voice Interaction") | |
| with gr.Row(): | |
| audio_input = gr.Audio( | |
| sources=["microphone"], | |
| type="filepath", | |
| label="Speak to the Assistant" | |
| ) | |
| audio_output = gr.Audio( | |
| label="Assistant's Response", | |
| type="filepath", | |
| interactive=False | |
| ) | |
| voice_btn = gr.Button("π€ Process Voice", elem_classes=["custom-button"]) | |
| # Features Section | |
| gr.HTML(""" | |
| <div class="feature-grid"> | |
| <div class="feature-card"> | |
| <div class="feature-icon">π§ </div> | |
| <h3>Smart AI Assistant</h3> | |
| <p>Advanced AI that understands context and provides intelligent responses.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon">π₯</div> | |
| <h3>Healthcare Billing Expert</h3> | |
| <p>Comprehensive database of CPT, HCPCS, ICD-10, and DRG codes.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon">π</div> | |
| <h3>Emotional Intelligence</h3> | |
| <p>Adapts responses based on your emotional state and tone.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon">ποΈ</div> | |
| <h3>Voice Interaction</h3> | |
| <p>Hands-free interaction with voice input and output.</p> | |
| </div> | |
| </div> | |
| """) | |
| # Control Section | |
| with gr.Row(): | |
| reset_btn = gr.Button("π Reset Conversation", elem_classes=["custom-button"]) | |
| status_output = gr.Textbox( | |
| label="Status", | |
| placeholder="System status will appear here...", | |
| lines=1, | |
| interactive=False | |
| ) | |
| # Event Handlers | |
| voice_btn.click( | |
| process_voice_input, | |
| inputs=[audio_input], | |
| outputs=[chatbot, audio_output] | |
| ) | |
| reset_btn.click( | |
| reset_conversation, | |
| outputs=[status_output, chatbot] | |
| ) | |
| return demo | |
| # ============= Launch Application ============= | |
| # Create and configure the interface | |
| demo = create_gradio_interface() | |
| # For Hugging Face Spaces, the app will be launched automatically | |
| # No need for manual demo.launch() in Hugging Face Spaces | |
| if __name__ == "__main__": | |
| # This block will be executed when running locally | |
| demo.launch(debug=True) |