Homoeopathy-Bot / ui_builder.py
yekkala's picture
Upload 4 files
e47681e verified
"""
Gradio UI Builder for Homeopathic Analyzer
"""
import gradio as gr
from config import APP_NAME, APP_DESCRIPTION, THEME_CONFIG
from analyzer import analyzer
from database import search_remedies, get_remedy_details
class UIBuilder:
def __init__(self):
self.css = self._get_css()
def _get_css(self):
"""Return custom CSS"""
return """
.gradio-container {
max-width: 1400px !important;
margin: 0 auto !important;
font-family: 'Inter', -apple-system, sans-serif !important;
}
.main-header {
background: linear-gradient(135deg, #1e40af 0%, #1e3a8a 100%);
padding: 32px;
color: white;
border-radius: 0 0 20px 20px;
margin: -20px -20px 30px -20px;
}
.tab-nav {
display: flex;
gap: 2px;
margin-bottom: 30px;
background: #f3f4f6;
padding: 4px;
border-radius: 12px;
}
.tab-button {
flex: 1;
padding: 16px;
border: none;
background: transparent;
font-weight: 500;
color: #6b7280;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-button:hover {
background: #e5e7eb;
}
.tab-button.active {
background: white;
color: #1e40af;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.analysis-card {
background: white;
border-radius: 12px;
padding: 28px;
margin-bottom: 24px;
border: 1px solid #e5e7eb;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.primary-btn {
background: linear-gradient(135deg, #1e40af 0%, #1e3a8a 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 10px;
font-weight: 600;
font-size: 16px;
width: 100%;
margin-top: 20px;
transition: all 0.3s ease;
}
.primary-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(30, 64, 175, 0.2);
}
.results-container {
margin-top: 30px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.medicine-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
}
.medicine-card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
}
.potency-badge {
display: inline-block;
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
margin: 2px;
}
.potency-high {
background: #d1fae5;
color: #065f46;
}
.potency-medium {
background: #fef3c7;
color: #92400e;
}
.potency-low {
background: #e5e7eb;
color: #6b7280;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
@media (max-width: 768px) {
.grid-2, .grid-3 {
grid-template-columns: 1fr;
}
}
"""
def create_header(self):
"""Create application header"""
return gr.Markdown(f"""
<div class="main-header">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<h1 style="margin: 0; font-size: 32px; font-weight: 700;">🏥 {APP_NAME}</h1>
<p style="margin: 8px 0 0 0; opacity: 0.9; font-size: 16px;">
{APP_DESCRIPTION}
</p>
</div>
<div style="font-size: 40px;">🌿</div>
</div>
</div>
""")
def create_analysis_tab(self):
"""Create symptom analysis tab"""
with gr.Column(elem_id="analysis-tab") as tab:
# Patient Information
with gr.Group(elem_classes="analysis-card"):
gr.Markdown("### 👤 Patient Information")
with gr.Row():
age = gr.Slider(label="Age", minimum=0, maximum=120, value=35, step=1)
gender = gr.Dropdown(label="Gender", choices=["Male", "Female", "Other"], value="Male")
constitution = gr.Dropdown(
label="Constitutional Type",
choices=["Lean/Nervous", "Average", "Robust/Plethoric", "Not sure"],
value="Average"
)
# Symptom Assessment
with gr.Group(elem_classes="analysis-card"):
gr.Markdown("### 🔍 Symptom Assessment")
chief_complaint = gr.Textbox(
label="Chief Complaint",
placeholder="Describe your main health concern...",
lines=3
)
with gr.Row():
location = gr.Textbox(
label="Location",
placeholder="Where exactly are symptoms?",
lines=2
)
sensation = gr.Textbox(
label="Sensation",
placeholder="What does it feel like?",
lines=2
)
intensity = gr.Slider(label="Intensity (1-10)", minimum=1, maximum=10, value=5)
# Modalities
with gr.Group(elem_classes="analysis-card"):
gr.Markdown("### 📈 Modalities")
with gr.Row():
aggravations = gr.Textbox(
label="Aggravating Factors",
placeholder="What makes it worse?",
lines=2
)
ameliorations = gr.Textbox(
label="Ameliorating Factors",
placeholder="What makes it better?",
lines=2
)
timing = gr.Textbox(
label="Timing & Periodicity",
placeholder="When do symptoms occur?",
lines=2
)
# Emotional State
with gr.Group(elem_classes="analysis-card"):
gr.Markdown("### 😊 Emotional & Mental State")
emotional_state = gr.Textbox(
label="Emotional State",
placeholder="Describe emotions, fears, mental symptoms...",
lines=2
)
generalities = gr.Textbox(
label="General Symptoms",
placeholder="Thermal preferences, food desires/aversions...",
lines=2
)
# Analysis Button
analyze_btn = gr.Button(
"🔬 Start Enhanced Analysis",
variant="primary",
elem_classes="primary-btn"
)
# Results Display
results = gr.HTML(
label="Analysis Results",
elem_classes="results-container"
)
# Analysis function
def analyze_symptoms(
age_val, gender_val, constitution_val,
complaint_val, location_val, sensation_val, intensity_val,
aggravations_val, ameliorations_val, timing_val,
emotional_val, generalities_val
):
patient_data = {
"age": age_val,
"gender": gender_val,
"constitution": constitution_val,
"chief_complaint": complaint_val,
"location": location_val,
"sensation": sensation_val,
"intensity": intensity_val,
"aggravations": aggravations_val,
"ameliorations": ameliorations_val,
"timing": timing_val,
"emotional_state": emotional_val,
"generalities": generalities_val
}
result = analyzer.analyze_case(patient_data)
return self._generate_results_html(result)
# Connect button
analyze_btn.click(
fn=analyze_symptoms,
inputs=[
age, gender, constitution,
chief_complaint, location, sensation, intensity,
aggravations, ameliorations, timing,
emotional_state, generalities
],
outputs=results
)
return tab
def create_medicine_tab(self):
"""Create medicine database tab"""
with gr.Column(elem_id="medicine-tab") as tab:
gr.Markdown("### 💊 Homeopathic Medicine Database")
gr.Markdown("Search and explore detailed information about homeopathic remedies")
# Search interface
with gr.Row():
search_input = gr.Textbox(
label="Search Remedies",
placeholder="Search by remedy name or indication (e.g., fever, headache, arnica)...",
scale=4
)
search_btn = gr.Button("Search", variant="primary", scale=1)
# Search results
search_results = gr.HTML(
label="Search Results",
elem_classes="results-container"
)
# Medicine details
medicine_details = gr.HTML(
label="Medicine Details",
visible=False
)
# Search function
def perform_search(query):
if not query or len(query.strip()) < 2:
return "", gr.HTML(visible=False)
results = search_remedies(query, max_results=10)
if not results:
return "<div style='text-align: center; padding: 40px; color: #666;'>No remedies found. Try a different search term.</div>", gr.HTML(visible=False)
html = "<div style='margin-bottom: 30px;'>"
html += f"<h3 style='color: #374151; margin-bottom: 20px;'>Found {len(results)} remedies:</h3>"
for name, data, match_type in results:
html += f"""
<div class='medicine-card' onclick='selectRemedy("{name}")' style='cursor: pointer;'>
<div style='display: flex; justify-content: space-between; align-items: start;'>
<div>
<h4 style='margin: 0 0 8px 0; color: #1e40af;'>{name}</h4>
<p style='margin: 0 0 8px 0; color: #6b7280; font-size: 14px;'>{data['description']}</p>
<div style='display: flex; gap: 8px; flex-wrap: wrap;'>
"""
for indication in data['indications'][:3]:
html += f'<span style="background: #f3f4f6; color: #4b5563; padding: 4px 8px; border-radius: 6px; font-size: 12px;">{indication}</span>'
html += f"""
</div>
</div>
<div style='background: #f0f9ff; color: #0369a1; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 500;'>
{data['potencies'].get('first_aid', '30C')}
</div>
</div>
</div>
"""
html += "</div>"
html += """
<script>
function selectRemedy(name) {
// This would trigger a Gradio event in a real implementation
console.log('Selected remedy:', name);
// For now, we'll use a simple approach
document.querySelectorAll('.medicine-card').forEach(card => {
card.style.borderColor = '#e5e7eb';
});
event.currentTarget.style.borderColor = '#3b82f6';
// In a full implementation, this would trigger a Gradio event
// For the demo, we'll just show an alert
alert('Click would show details for: ' + name);
}
</script>
"""
return html, gr.HTML(visible=False)
# Details function
def show_remedy_details(name):
if not name:
return ""
data = get_remedy_details(name)
if not data:
return "<div style='color: #666; text-align: center; padding: 40px;'>Remedy not found</div>"
return self._generate_medicine_details_html(name, data)
# Connect events
search_input.submit(
fn=perform_search,
inputs=[search_input],
outputs=[search_results, medicine_details]
)
search_btn.click(
fn=perform_search,
inputs=[search_input],
outputs=[search_results, medicine_details]
)
return tab
def _generate_results_html(self, analysis_result):
"""Generate HTML for analysis results"""
if not analysis_result.get("success", False):
return f"""
<div style="text-align: center; padding: 40px; background: #f8f9fa; border-radius: 12px;">
<div style="font-size: 48px; margin-bottom: 20px;">🏥</div>
<h3 style="color: #6b7280; margin-bottom: 10px;">{analysis_result.get('message', 'Analysis Failed')}</h3>
<p style="color: #9ca3af;">Please provide more detailed symptoms.</p>
</div>
"""
matches = analysis_result["matches"]
if not matches:
return """
<div style="text-align: center; padding: 40px; background: #f8f9fa; border-radius: 12px;">
<div style="font-size: 48px; margin-bottom: 20px;">🏥</div>
<h3 style="color: #6b7280; margin-bottom: 10px;">No Clear Matches Found</h3>
<p style="color: #9ca3af;">Try providing more specific symptom details.</p>
</div>
"""
top_match = matches[0]
case_type = analysis_result.get("case_type", "acute")
html = f"""
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
<!-- Report Header -->
<div style="background: linear-gradient(135deg, #1e40af 0%, #1e3a8a 100%);
padding: 28px; border-radius: 12px; margin-bottom: 24px; color: white;">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px;">
<div>
<h2 style="margin: 0; font-size: 24px; font-weight: 600;">Enhanced Homeopathic Analysis</h2>
<p style="margin: 8px 0 0 0; opacity: 0.9; font-size: 15px;">
Case Type: <strong>{case_type.upper()}</strong> • Confidence: <strong>{top_match['score']:.1f}%</strong>
</p>
</div>
<div style="display: flex; gap: 16px;">
<div style="background: rgba(255,255,255,0.15); padding: 12px 20px; border-radius: 10px; text-align: center;">
<div style="font-size: 12px; opacity: 0.8;">Top Match</div>
<div style="font-size: 20px; font-weight: 700;">{top_match['name']}</div>
</div>
</div>
</div>
</div>
<!-- Primary Recommendation -->
<div style="background: white; border-radius: 12px; padding: 28px; margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); border: 1px solid #e5e7eb;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; flex-wrap: wrap; gap: 16px;">
<div style="display: flex; align-items: center; gap: 16px;">
<div style="width: 60px; height: 60px; background: linear-gradient(135deg, #10b981, #059669);
border-radius: 12px; display: flex; align-items: center; justify-content: center;
color: white; font-size: 28px; font-weight: 700;">
1
</div>
<div>
<h3 style="margin: 0; color: #1f2937; font-size: 22px; font-weight: 600;">{top_match['name']}</h3>
<p style="margin: 4px 0 0 0; color: #6b7280; font-size: 16px;">{top_match['data']['description']}</p>
</div>
</div>
</div>
<!-- Recommendations -->
<div style="margin-bottom: 24px;">
<h4 style="color: #1e40af; margin: 0 0 16px 0; font-size: 18px;">💊 Recommendations</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px;">
"""
rec = top_match.get("recommendations", {})
if rec:
html += f"""
<div style="background: #f0f9ff; border-radius: 10px; padding: 20px; border-left: 4px solid #0ea5e9;">
<div style="font-weight: 600; color: #0369a1; margin-bottom: 8px;">Primary Potency</div>
<div style="font-size: 24px; font-weight: 700; color: #1e40af; margin-bottom: 4px;">{rec.get('primary_potency', '30C')}</div>
<div style="color: #6b7280; font-size: 14px;">For {case_type} cases</div>
</div>
<div style="background: #f8fafc; border-radius: 10px; padding: 20px;">
<div style="font-weight: 600; color: #4b5563; margin-bottom: 8px;">Administration</div>
<div style="color: #1f2937; font-size: 16px; font-weight: 500; margin-bottom: 4px;">{rec.get('frequency', '3 times daily')}</div>
<div style="color: #6b7280; font-size: 14px;">Duration: {rec.get('duration', '1-2 weeks')}</div>
</div>
<div style="background: #f8fafc; border-radius: 10px; padding: 20px;">
<div style="font-weight: 600; color: #4b5563; margin-bottom: 8px;">Dosage</div>
<div style="color: #6b7280; font-size: 14px;">{rec.get('dosage', '3-5 pellets sublingually')}</div>
</div>
"""
html += """
</div>
</div>
<!-- Match Reasons -->
<div style="margin-bottom: 24px;">
<h4 style="color: #1e40af; margin: 0 0 12px 0; font-size: 18px;">🔍 Key Match Factors</h4>
<div style="background: #f8fafc; border-radius: 10px; padding: 20px;">
<ul style="margin: 0; padding-left: 20px;">
"""
for reason in top_match.get("match_reasons", []):
html += f'<li style="margin-bottom: 8px; color: #4b5563;">{reason}</li>'
html += """
</ul>
</div>
</div>
</div>
<!-- Alternative Remedies -->
<div style="background: white; border-radius: 12px; padding: 28px; margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); border: 1px solid #e5e7eb;">
<h3 style="color: #1f2937; margin: 0 0 20px 0; font-size: 20px; font-weight: 600;">Alternative Considerations</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
"""
for i, match in enumerate(matches[1:4], 2):
color = "#f59e0b" if match["score"] > 60 else "#6b7280"
html += f"""
<div style="border: 1px solid #e5e7eb; border-radius: 10px; padding: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div style="display: flex; align-items: center; gap: 12px;">
<div style="width: 36px; height: 36px; background: {color}20; color: {color};
border-radius: 8px; display: flex; align-items: center; justify-content: center;
font-weight: 700;">
{i}
</div>
<div style="font-weight: 600; color: #1f2937;">{match['name']}</div>
</div>
<div style="background: {color}10; color: {color}; padding: 4px 12px; border-radius: 20px;
font-size: 13px; font-weight: 500;">
{match['score']:.1f}%
</div>
</div>
<div style="color: #6b7280; font-size: 14px; margin-bottom: 12px;">{match['data']['description']}</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
"""
for indication in match["data"]["indications"][:2]:
html += f'<span style="background: #f3f4f6; color: #6b7280; padding: 4px 8px; border-radius: 6px; font-size: 12px;">{indication}</span>'
html += """
</div>
</div>
"""
html += """
</div>
</div>
<!-- Disclaimer -->
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 20px;">
<div style="display: flex; align-items: flex-start;">
<div style="color: #dc2626; font-size: 24px; margin-right: 16px;">⚠️</div>
<div>
<div style="font-weight: 600; color: #991b1b; margin-bottom: 8px; font-size: 16px;">Medical Disclaimer</div>
<div style="color: #7f1d1d; font-size: 14px; line-height: 1.5;">
This analysis is generated by AI for educational purposes only. It is not a substitute for
professional medical advice, diagnosis, or treatment. Always consult a qualified homeopath
or medical professional for health concerns.
</div>
</div>
</div>
</div>
</div>
"""
return html
def _generate_medicine_details_html(self, name, data):
"""Generate HTML for medicine details"""
html = f"""
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
<div style="background: linear-gradient(135deg, #1e40af 0%, #1e3a8a 100%);
padding: 24px; border-radius: 12px; margin-bottom: 24px; color: white;">
<h2 style="margin: 0; font-size: 24px; font-weight: 600;">{name}</h2>
<p style="margin: 8px 0 0 0; opacity: 0.9; font-size: 16px;">{data['description']}</p>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 24px;">
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<h3 style="color: #1e40af; margin: 0 0 12px 0; font-size: 18px;">🔍 Key Indications</h3>
<ul style="margin: 0; padding-left: 20px;">
"""
for indication in data["indications"][:6]:
html += f"<li style='margin-bottom: 6px; color: #4b5563;'>{indication}</li>"
html += """
</ul>
</div>
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<h3 style="color: #1e40af; margin: 0 0 12px 0; font-size: 18px;">📈 Modalities</h3>
<div style="margin-bottom: 16px;">
<div style="color: #ef4444; font-weight: 600; margin-bottom: 4px;">Worse:</div>
<div style="color: #6b7280;">"""
html += ', '.join(data["modalities"]["worse"])
html += """</div>
</div>
<div>
<div style="color: #10b981; font-weight: 600; margin-bottom: 4px;">Better:</div>
<div style="color: #6b7280;">"""
html += ', '.join(data["modalities"]["better"])
html += """</div>
</div>
</div>
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<h3 style="color: #1e40af; margin: 0 0 12px 0; font-size: 18px;">💊 Potency Guide</h3>
<div style="display: grid; gap: 12px;">
"""
for case_type, potencies in data["potencies"].items():
if case_type != "recommended":
html += f"""
<div style="background: #f8fafc; padding: 12px; border-radius: 8px;">
<div style="font-weight: 600; color: #4b5563; text-transform: capitalize;">{case_type}:</div>
<div style="color: #6b7280;">
"""
if isinstance(potencies, list):
html += ', '.join(potencies)
else:
html += potencies
html += "</div></div>"
html += f"""
</div>
<div style="margin-top: 16px; padding: 12px; background: #f0f9ff; border-radius: 8px; border-left: 4px solid #0ea5e9;">
<div style="font-weight: 600; color: #0369a1;">Recommended:</div>
<div style="color: #0c4a6e;">{data['potencies'].get('recommended', '30C for acute cases')}</div>
</div>
</div>
</div>
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 20px; margin-top: 24px;">
<div style="color: #991b1b; font-size: 14px; line-height: 1.5;">
<strong>Disclaimer:</strong> This information is for educational purposes only.
Always consult a qualified homeopathic practitioner for treatment recommendations.
</div>
</div>
</div>
"""
return html