"""
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"""
🏥 {APP_NAME}
{APP_DESCRIPTION}
🌿
""")
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 "No remedies found. Try a different search term.
", gr.HTML(visible=False)
html = ""
html += f"
Found {len(results)} remedies:
"
for name, data, match_type in results:
html += f"""
{name}
{data['description']}
"""
for indication in data['indications'][:3]:
html += f'{indication}'
html += f"""
{data['potencies'].get('first_aid', '30C')}
"""
html += "
"
html += """
"""
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 "Remedy not found
"
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"""
🏥
{analysis_result.get('message', 'Analysis Failed')}
Please provide more detailed symptoms.
"""
matches = analysis_result["matches"]
if not matches:
return """
🏥
No Clear Matches Found
Try providing more specific symptom details.
"""
top_match = matches[0]
case_type = analysis_result.get("case_type", "acute")
html = f"""
Enhanced Homeopathic Analysis
Case Type: {case_type.upper()} • Confidence: {top_match['score']:.1f}%
Top Match
{top_match['name']}
1
{top_match['name']}
{top_match['data']['description']}
💊 Recommendations
"""
rec = top_match.get("recommendations", {})
if rec:
html += f"""
Primary Potency
{rec.get('primary_potency', '30C')}
For {case_type} cases
Administration
{rec.get('frequency', '3 times daily')}
Duration: {rec.get('duration', '1-2 weeks')}
Dosage
{rec.get('dosage', '3-5 pellets sublingually')}
"""
html += """
🔍 Key Match Factors
"""
for reason in top_match.get("match_reasons", []):
html += f'- {reason}
'
html += """
Alternative Considerations
"""
for i, match in enumerate(matches[1:4], 2):
color = "#f59e0b" if match["score"] > 60 else "#6b7280"
html += f"""
{match['data']['description']}
"""
for indication in match["data"]["indications"][:2]:
html += f'{indication}'
html += """
"""
html += """
⚠️
Medical Disclaimer
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.
"""
return html
def _generate_medicine_details_html(self, name, data):
"""Generate HTML for medicine details"""
html = f"""
{name}
{data['description']}
🔍 Key Indications
"""
for indication in data["indications"][:6]:
html += f"- {indication}
"
html += """
📈 Modalities
Worse:
"""
html += ', '.join(data["modalities"]["worse"])
html += """
Better:
"""
html += ', '.join(data["modalities"]["better"])
html += """
💊 Potency Guide
"""
for case_type, potencies in data["potencies"].items():
if case_type != "recommended":
html += f"""
{case_type}:
"""
if isinstance(potencies, list):
html += ', '.join(potencies)
else:
html += potencies
html += "
"
html += f"""
Recommended:
{data['potencies'].get('recommended', '30C for acute cases')}
Disclaimer: This information is for educational purposes only.
Always consult a qualified homeopathic practitioner for treatment recommendations.
"""
return html