Distopia22's picture
Updated the frontend styling
c953011
Raw
History Blame
16.2 kB
import gradio as gr
import requests
import json
# ==================== API Functions ====================
def check_api_health(api_url):
"""Check if the backend API is healthy"""
try:
response = requests.get(f"{api_url}/api/v1/health", timeout=10)
response.raise_for_status()
health_data = response.json()
return f"βœ… API is healthy and running!\n\nService: {health_data.get('service', 'ICD-CPT Coding API')}"
except requests.exceptions.Timeout:
return "❌ API Health Check Failed: Request timed out"
except requests.exceptions.RequestException as e:
return f"❌ API Health Check Failed: {str(e)}"
except Exception as e:
return f"❌ Unexpected Error: {str(e)}"
def analyze_provider_notes(provider_notes, api_url, progress=gr.Progress()):
"""Send provider notes to backend API for analysis"""
if not provider_notes or not provider_notes.strip():
return generate_empty_response()
progress(0, desc="Starting analysis...")
try:
payload = {"provider_notes": provider_notes}
progress(0.3, desc="Sending request to API...")
response = requests.post(
f"{api_url}/api/v1/analyze",
json=payload,
headers={"Content-Type": "application/json"},
timeout=60
)
progress(0.6, desc="Processing response...")
response.raise_for_status()
result = response.json()
progress(0.9, desc="Formatting results...")
# Format complete response
formatted_response = format_complete_response(result)
progress(1.0, desc="Complete!")
return formatted_response
except requests.exceptions.Timeout:
return "❌ **Request Timeout**\n\nThe API is taking too long to respond. Please try again."
except requests.exceptions.HTTPError as e:
return f"❌ **HTTP Error {e.response.status_code}**\n\n{e.response.text}"
except requests.exceptions.RequestException as e:
return f"❌ **Request Error**\n\n{str(e)}"
except Exception as e:
return f"❌ **Unexpected Error**\n\n{str(e)}"
def generate_empty_response():
"""Generate empty response message"""
return """
### πŸ’¬ Welcome to ICD-10 & CPT Coding Assistant
Please enter provider notes in the chat box and click **Send** to analyze.
**Tips:**
- Provide detailed clinical documentation
- Include symptoms, diagnoses, and procedures
- Be specific about treatments and prescriptions
"""
def format_complete_response(result):
"""Format complete analysis response"""
output = "---\n\n"
# Overall Summary
if result.get("overall_summary"):
output += f"### πŸ“‹ Overall Summary\n\n{result['overall_summary']}\n\n---\n\n"
# ICD-10 Codes
output += "### πŸ₯ ICD-10 Diagnostic Codes\n\n"
icd_codes = result.get("icd_codes", [])
if icd_codes:
for idx, icd in enumerate(icd_codes, 1):
output += f"**{idx}. {icd.get('code', 'N/A')}** - {icd.get('description', 'N/A')}\n\n"
output += f"*Explanation:* {icd.get('explanation', 'N/A')}\n\n"
else:
output += "*No ICD-10 codes identified*\n\n"
output += "---\n\n"
# CPT Codes
output += "### πŸ’Ό CPT Procedure Codes\n\n"
cpt_codes = result.get("cpt_codes", [])
if cpt_codes:
for idx, cpt in enumerate(cpt_codes, 1):
output += f"**{idx}. {cpt.get('code', 'N/A')}** - {cpt.get('description', 'N/A')}\n\n"
output += f"*Explanation:* {cpt.get('explanation', 'N/A')}\n\n"
else:
output += "*No CPT codes identified*\n\n"
return output
# ==================== Example Notes ====================
EXAMPLES = {
"Acute Bronchitis": """Patient presents with acute bronchitis. Cough for 5 days, productive with yellow sputum. Lung exam reveals diffuse wheezing. Prescribed azithromycin 500mg.""",
"Type 2 Diabetes": """Patient with type 2 diabetes mellitus, uncontrolled. HbA1c 9.2%. Discussed diet and medication compliance. Adjusted insulin dosing. Referred to diabetes educator.""",
"Hypertension Follow-up": """Follow-up visit for hypertension. Blood pressure 145/92. Patient reports good medication compliance. Continue current antihypertensive regimen. Return in 3 months.""",
"Annual Physical": """Annual physical examination for 45-year-old patient. Comprehensive metabolic panel ordered. Discussed preventive health measures. No acute concerns. Patient in good health.""",
"Acute Pharyngitis": """Patient with sore throat for 3 days, fever 101.5Β°F. Physical exam shows erythematous pharynx with exudate. Rapid strep test positive. Prescribed amoxicillin 500mg TID for 10 days.""",
}
def load_example(example_name):
"""Load example provider notes"""
return EXAMPLES.get(example_name, "")
# ==================== Custom CSS ====================
custom_css = """
/* Global Styles */
.gradio-container {
max-width: 100% !important;
padding: 0 !important;
background: linear-gradient(135deg, #0d1117 0%, #1a1f2e 100%) !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
}
/* Header Styling */
.header-container {
background: linear-gradient(90deg, #00d4aa 0%, #00a896 100%);
padding: 25px;
text-align: center;
border-radius: 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
margin-bottom: 0;
}
.header-container h1 {
color: white;
font-size: 2rem;
font-weight: 700;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.header-container p {
color: #e0f7f4;
font-size: 1rem;
margin: 8px 0 0 0;
}
/* Sidebar Styling */
.sidebar {
background: #1a1f2e !important;
border-right: 1px solid #00d4aa !important;
padding: 20px !important;
height: 100vh !important;
overflow-y: auto !important;
}
.sidebar-title {
color: #00d4aa !important;
font-size: 1.3rem !important;
font-weight: 600 !important;
margin-bottom: 20px !important;
padding-bottom: 10px !important;
border-bottom: 2px solid #00d4aa !important;
}
/* Example Buttons */
.example-btn {
background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important;
color: white !important;
border: none !important;
border-radius: 8px !important;
padding: 12px 16px !important;
margin: 8px 0 !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
width: 100% !important;
text-align: left !important;
}
.example-btn:hover {
background: linear-gradient(135deg, #00ffcc 0%, #00d4aa 100%) !important;
transform: translateX(5px) !important;
box-shadow: 0 4px 12px rgba(0, 212, 170, 0.4) !important;
}
/* Chat Container */
.chat-container {
background: #0d1117 !important;
border-radius: 12px !important;
padding: 20px !important;
height: calc(100vh - 200px) !important;
display: flex !important;
flex-direction: column !important;
}
/* Input Area */
.input-row {
background: #1a1f2e !important;
border: 2px solid #00d4aa !important;
border-radius: 12px !important;
padding: 15px !important;
margin-top: 10px !important;
}
.input-row textarea {
background: #0d1117 !important;
color: #e6e6e6 !important;
border: 1px solid #2d3748 !important;
border-radius: 8px !important;
font-size: 1rem !important;
padding: 12px !important;
}
.input-row textarea:focus {
border-color: #00d4aa !important;
outline: none !important;
box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.1) !important;
}
/* Send Button */
.send-button {
background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important;
color: white !important;
border: none !important;
border-radius: 8px !important;
padding: 12px 32px !important;
font-size: 1rem !important;
font-weight: 600 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
margin-top: 10px !important;
}
.send-button:hover {
background: linear-gradient(135deg, #00ffcc 0%, #00d4aa 100%) !important;
box-shadow: 0 6px 20px rgba(0, 212, 170, 0.4) !important;
transform: translateY(-2px) !important;
}
/* Clear Button */
.clear-button {
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%) !important;
color: white !important;
border: 1px solid #4a5568 !important;
border-radius: 8px !important;
padding: 12px 32px !important;
font-size: 1rem !important;
font-weight: 600 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
margin-top: 10px !important;
}
.clear-button:hover {
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%) !important;
border-color: #718096 !important;
transform: translateY(-2px) !important;
}
/* Output Area */
.output-container {
background: #1a1f2e !important;
border: 1px solid #2d3748 !important;
border-radius: 12px !important;
padding: 20px !important;
color: #e6e6e6 !important;
overflow-y: auto !important;
flex-grow: 1 !important;
margin-bottom: 15px !important;
}
.output-container h3 {
color: #00d4aa !important;
border-bottom: 2px solid #00d4aa !important;
padding-bottom: 8px !important;
margin-top: 20px !important;
}
.output-container strong {
color: #00ffcc !important;
}
.output-container hr {
border: none !important;
border-top: 1px solid #2d3748 !important;
margin: 20px 0 !important;
}
/* API Config Section */
.api-config {
background: #1a1f2e !important;
border: 1px solid #2d3748 !important;
border-radius: 8px !important;
padding: 15px !important;
margin-bottom: 15px !important;
}
.api-config input {
background: #0d1117 !important;
color: #e6e6e6 !important;
border: 1px solid #2d3748 !important;
border-radius: 6px !important;
padding: 10px !important;
}
.api-config input:focus {
border-color: #00d4aa !important;
outline: none !important;
}
/* Check API Button */
.check-api-button {
background: linear-gradient(135deg, #00a896 0%, #008577 100%) !important;
color: white !important;
border: none !important;
border-radius: 6px !important;
padding: 10px 20px !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
}
.check-api-button:hover {
background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important;
box-shadow: 0 4px 12px rgba(0, 168, 150, 0.4) !important;
}
/* Footer */
.footer {
text-align: center;
padding: 20px;
color: #718096;
font-size: 0.9rem;
background: #0d1117;
border-top: 1px solid #2d3748;
margin-top: 20px;
}
.footer strong {
color: #00d4aa;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #0d1117;
}
::-webkit-scrollbar-thumb {
background: #00d4aa;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #00ffcc;
}
/* Accordion Styling */
.accordion {
background: #1a1f2e !important;
border: 1px solid #2d3748 !important;
border-radius: 8px !important;
}
.accordion summary {
color: #00d4aa !important;
font-weight: 600 !important;
padding: 12px !important;
cursor: pointer !important;
}
/* Responsive */
@media (max-width: 768px) {
.header-container h1 {
font-size: 1.5rem;
}
.sidebar {
height: auto !important;
border-right: none !important;
border-bottom: 1px solid #00d4aa !important;
}
}
"""
# ==================== Gradio Interface ====================
with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="ICD-10 & CPT Coding Assistant") as demo:
# Hidden state for API URL
api_url_state = gr.State(value="https://Distopia22-icd-cpt-coding-api.hf.space")
# Header
gr.HTML("""
<div class="header-container">
<h1>πŸ₯ ICD-10 & CPT Coding Assistant</h1>
<p>AI-Powered Medical Coding Analysis using Groq LLaMA 3.3 70B</p>
</div>
""")
# Main Layout
with gr.Row():
# Left Sidebar
with gr.Column(scale=1, elem_classes="sidebar"):
gr.HTML('<div class="sidebar-title">πŸ“š Example Cases</div>')
# Example buttons
for example_name in EXAMPLES.keys():
gr.Button(
f"πŸ“„ {example_name}",
elem_classes="example-btn"
).click(
fn=lambda name=example_name: load_example(name),
outputs=gr.Textbox(elem_id="provider_notes_input", visible=False)
)
gr.Markdown("---")
# API Configuration
with gr.Accordion("βš™οΈ API Configuration", open=False, elem_classes="api-config"):
api_url_input = gr.Textbox(
label="Backend API URL",
value="https://Distopia22-icd-cpt-coding-api.hf.space",
placeholder="https://your-backend-api.hf.space",
interactive=True
)
check_api_btn = gr.Button("πŸ” Check API Status", elem_classes="check-api-button", size="sm")
api_status_output = gr.Textbox(label="Status", lines=3, interactive=False)
check_api_btn.click(
fn=check_api_health,
inputs=[api_url_input],
outputs=[api_status_output]
)
# Update state when URL changes
api_url_input.change(
fn=lambda x: x,
inputs=[api_url_input],
outputs=[api_url_state]
)
# Right Chat Area
with gr.Column(scale=3, elem_classes="chat-container"):
# Output/Chat History
output_area = gr.Markdown(
value=generate_empty_response(),
elem_classes="output-container",
label="Analysis Results"
)
# Input Area
with gr.Row(elem_classes="input-row"):
with gr.Column(scale=5):
provider_notes_input = gr.Textbox(
label="",
placeholder="Enter clinical provider notes here...",
lines=4,
max_lines=8,
elem_id="provider_notes_input"
)
with gr.Column(scale=1):
send_btn = gr.Button("πŸš€ Send", elem_classes="send-button", size="lg")
clear_btn = gr.Button("πŸ—‘οΈ Clear", elem_classes="clear-button", size="lg")
# Footer
gr.HTML("""
<div class="footer">
<p>Powered by <strong>Groq LLaMA 3.3 70B</strong> | <strong>FastAPI</strong> | <strong>Gradio</strong></p>
<p>Β© 2025 ICD-CPT Coding Assistant - Secure & HIPAA Compliant</p>
</div>
""")
# ==================== Event Handlers ====================
# Send button - Analyze notes
send_btn.click(
fn=analyze_provider_notes,
inputs=[provider_notes_input, api_url_state],
outputs=[output_area]
)
# Clear button
clear_btn.click(
fn=lambda: ("", generate_empty_response()),
outputs=[provider_notes_input, output_area]
)
# Example buttons - Load example into input
for example_name in EXAMPLES.keys():
example_btn = gr.Button(f"πŸ“„ {example_name}", visible=False)
example_btn.click(
fn=lambda name=example_name: load_example(name),
outputs=[provider_notes_input]
)
# Launch the app
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False
)