"""
EcoVision — Smart Sustainability Analyzer
Gradio 6. Background thread trains model at startup.
All click handlers wrapped in try/except.
"""
import os, io, tempfile, warnings, threading
from datetime import datetime
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import gradio as gr
warnings.filterwarnings("ignore")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
from eco_score import calculate_eco_score, get_score_description
from ml_model import predict_impact, train_model
from llm_analysis import generate_eco_explanation, compare_products_llm
from vision_model import classify_image
# ── Load CSV (instant) ────────────────────────────────────────────────────────
DF = pd.read_csv(os.path.join(BASE_DIR, "dataset", "products.csv"))
ALL_PRODUCTS = sorted(DF["product_name"].tolist())
ALL_CATS = sorted(DF["category"].unique().tolist())
PACKAGING_OPTS = [
"Plastic Bottle","Glass Bottle","Cardboard Box","Compostable",
"Recycled Plastic","Single-use Plastic","Paper Wrapper","Other",
]
# ── Train model in background thread so UI is never blocked ───────────────────
_model_ready = threading.Event()
_model_error = None
def _train_bg():
global _model_error
try:
model_path = os.path.join(BASE_DIR, "models", "eco_classifier.pkl")
os.makedirs(os.path.join(BASE_DIR, "models"), exist_ok=True)
if not os.path.exists(model_path):
train_model()
_model_ready.set()
print("✅ Model ready")
except Exception as e:
_model_error = str(e)
_model_ready.set() # unblock anyway so clicks don't hang forever
print(f"❌ Model training failed: {e}")
threading.Thread(target=_train_bg, daemon=True).start()
def wait_for_model():
"""Wait up to 120s for model, return error string or None."""
_model_ready.wait(timeout=120)
return _model_error
# ── Charts ────────────────────────────────────────────────────────────────────
def _bar_color(v):
if v > 0.5: return "#C62828"
if v > 0.25: return "#E65100"
return "#2E7D32"
BASE_LAYOUT = dict(
paper_bgcolor="#FFFFFF",
plot_bgcolor="#F4FBF4",
font=dict(color="#1A1A1A", family="sans-serif"),
margin=dict(t=50, b=30, l=55, r=20),
)
def gauge_chart(score, grade):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(grade,"#2E7D32")
fig = go.Figure(go.Indicator(
mode="gauge+number", value=score,
title=dict(text=f"Eco Score • Grade {grade}",
font=dict(size=14, color="#1B5E20")),
number=dict(font=dict(size=46, color=gc), suffix=" / 10"),
gauge=dict(
axis=dict(range=[1,10], tickfont=dict(color="#607D63")),
bar=dict(color=gc, thickness=0.28),
bgcolor="#F4FBF4", borderwidth=1, bordercolor="#C8E6C9",
steps=[dict(range=[1,3], color="#FFEBEE"),
dict(range=[3,5], color="#FFF3E0"),
dict(range=[5,7], color="#F1F8E9"),
dict(range=[7,10],color="#E8F5E9")],
threshold=dict(line=dict(color=gc,width=4),thickness=0.8,value=score),
),
))
fig.update_layout(**BASE_LAYOUT, height=260)
return fig
def metric_bar(label, value, icon):
color = _bar_color(value)
fig = go.Figure(go.Bar(
x=[label], y=[value], marker_color=[color],
text=[f"{value:.0%}"], textposition="outside",
textfont=dict(color="#1A1A1A", size=14), width=[0.4],
))
fig.update_layout(
**BASE_LAYOUT,
title=dict(text=f"{icon} {label}", font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%",
tickfont=dict(color="#607D63")),
xaxis=dict(showgrid=False),
height=220,
)
return fig
def prob_bar(probs):
fig = go.Figure(go.Bar(
x=list(probs.keys()), y=list(probs.values()),
marker_color=["#2E7D32","#43A047","#66BB6A"][:len(probs)],
text=[f"{v:.0%}" for v in probs.values()],
textposition="outside", textfont=dict(color="#1A1A1A",size=12),
))
fig.update_layout(
**BASE_LAYOUT,
title=dict(text="ML Confidence", font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
height=220,
)
return fig
def comparison_bar(n1, n2, eco1, eco2):
labels = ["Deforestation","Pollution","Biodiversity"]
v1 = [eco1["metrics"]["deforestation_risk"],
eco1["metrics"]["pollution_level"],
eco1["metrics"]["biodiversity_impact"]]
v2 = [eco2["metrics"]["deforestation_risk"],
eco2["metrics"]["pollution_level"],
eco2["metrics"]["biodiversity_impact"]]
fig = go.Figure(data=[
go.Bar(name=n1[:18], x=labels, y=v1,
text=[f"{v:.0%}" for v in v1], textposition="outside",
marker_color="#2E7D32"),
go.Bar(name=n2[:18], x=labels, y=v2,
text=[f"{v:.0%}" for v in v2], textposition="outside",
marker_color="#C62828"),
])
fig.update_layout(
**BASE_LAYOUT, barmode="group", height=280,
title=dict(text="Side-by-Side Metrics",font=dict(color="#1B5E20",size=13)),
yaxis=dict(range=[0,1.4], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
legend=dict(bgcolor="#F4FBF4", bordercolor="#C8E6C9", borderwidth=1),
margin=dict(t=55,b=35,l=55,r=20),
)
return fig
# ── HTML helpers ──────────────────────────────────────────────────────────────
def err_html(msg):
return (f"
❌ {msg}
")
def ok_html(msg):
return (f"✅ {msg}
")
def score_card(name, eco, ml):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(eco["grade"],"#2E7D32")
impact = ml["predicted_impact"]
ibg = {"Low Impact":"#E8F5E9","Medium Impact":"#FFF3E0",
"High Impact":"#FFEBEE"}.get(impact,"#F5F5F5")
ifg = {"Low Impact":"#2E7D32","Medium Impact":"#E65100",
"High Impact":"#C62828"}.get(impact,"#333")
badges = []
if eco["metrics"]["deforestation_risk"] < 0.2: badges.append("🌲 Forest Protector")
if eco["metrics"]["pollution_level"] < 0.3: badges.append("💧 Clean Waters")
if eco["metrics"]["biodiversity_impact"] < 0.2: badges.append("🦋 Biodiversity Guard")
if eco["eco_score"] >= 7: badges.append("⭐ Eco Star")
if eco["eco_score"] >= 9: badges.append("🌟 Green Leader")
badge_html = "".join(
f"{b}"
for b in badges
) or "No badges yet"
def pill(lbl, val):
bg = "#FFEBEE" if val>0.5 else "#FFF8E1" if val>0.3 else "#E8F5E9"
fg = "#C62828" if val>0.5 else "#E65100" if val>0.3 else "#2E7D32"
return (f"")
return f"""
{name}
{impact}
ML confidence: {ml["confidence"]:.0%}
{eco["eco_score"]}
out of 10
Grade {eco["grade"]}
{eco["grade_label"]}
{get_score_description(eco["eco_score"])}
{pill("Deforestation", eco["metrics"]["deforestation_risk"])}
{pill("Pollution", eco["metrics"]["pollution_level"])}
{pill("Biodiversity", eco["metrics"]["biodiversity_impact"])}
"""
def alts_card(category, current=""):
rows = DF[(DF["category"]==category) &
(DF["eco_label"]=="Low Impact") &
(DF["product_name"]!=current)].head(3)
if rows.empty:
return ""
items = ""
for _, r in rows.iterrows():
ae = calculate_eco_score(r["deforestation_risk"],
r["pollution_level"], r["biodiversity_impact"])
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828"}.get(ae["grade"],"#2E7D32")
items += (
f""
f"
{r['product_name']}
"
f"
📦 {r['packaging_type']}
"
f"
{ae['eco_score']}"
f"/10
"
)
return (f""
f"
"
f"🌱 Eco-Friendly Alternatives
{items}")
def llm_card(text, model_name=""):
out = ""
for line in text.split("\n"):
s = line.strip()
if not s: continue
if s.startswith("## "):
out += (f"{s[3:]}
")
elif len(s)>2 and s[0].isdigit() and s[1]==".":
c = s[3:].replace("**","",1).replace("**","",1)
out += (f"#{s[0]} {c}
")
elif s.startswith("- "):
out += f"{s[2:]}"
else:
s2 = s.replace("**","",1).replace("**","",1)
out += (f"{s2}
")
badge = (f"{model_name}"
) if model_name else ""
return (f""
f"
"
f"🧠 AI Environmental Analysis {badge}
{out}
")
def compare_card(p, eco, ml, winner):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(eco["grade"],"#2E7D32")
border = "#2E7D32" if winner else "#E0E0E0"
pills = "".join(
f""
for l,v in [("🌲 Deforest.",eco["metrics"]["deforestation_risk"]),
("💨 Pollution",eco["metrics"]["pollution_level"]),
("🦋 Biodiv.",eco["metrics"]["biodiversity_impact"])]
)
return (f""
f"
"
f"{'🏆 ' if winner else ''}{p['product_name']}
"
f"
"
f"{p['category']} · {p['packaging_type']}
"
f"
"
f"
"
f"
"
f"{eco['eco_score']}
"
f"
SCORE
"
f"
"
f"
"
f"{eco['grade']}
"
f"
GRADE
"
f"
{pills}
"
f"
"
f"ML: {ml['predicted_impact']} · "
f"Conf: {ml['confidence']:.0%}
")
# ── PDF ───────────────────────────────────────────────────────────────────────
def save_pdf(name, category, eco, ml, explanation, packaging=""):
try:
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.colors import HexColor
from reportlab.platypus import (SimpleDocTemplate, Paragraph,
Spacer, Table, TableStyle)
from reportlab.lib.units import inch
buf = io.BytesIO()
doc = SimpleDocTemplate(buf, pagesize=letter, topMargin=inch,
bottomMargin=inch, leftMargin=inch, rightMargin=inch)
TT = ParagraphStyle("TT",fontSize=20,fontName="Helvetica-Bold",
textColor=HexColor("#1B5E20"),spaceAfter=6)
HH = ParagraphStyle("HH",fontSize=13,fontName="Helvetica-Bold",
textColor=HexColor("#2E7D32"),spaceAfter=4,spaceBefore=8)
BB = ParagraphStyle("BB",fontSize=10,fontName="Helvetica",
textColor=HexColor("#1A1A1A"),spaceAfter=3,leading=14)
content = [
Paragraph("EcoVision Sustainability Report", TT),
Paragraph(f"Generated: {datetime.now().strftime('%B %d, %Y %H:%M')}", BB),
Spacer(1,0.15*inch), Paragraph("Product Overview", HH),
]
rows = [["Product",name],["Category",category],
["Eco Score",f"{eco['eco_score']}/10 Grade {eco['grade']}"],
["Impact",ml.get("predicted_impact","N/A")],["Packaging",packaging]]
tbl = Table(rows, colWidths=[1.8*inch, 4.7*inch])
tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,-1),HexColor("#E8F5E9")),
("TEXTCOLOR",(0,0),(0,-1),HexColor("#1B5E20")),
("TEXTCOLOR",(1,0),(1,-1),HexColor("#1A1A1A")),
("FONTNAME",(0,0),(-1,-1),"Helvetica"),
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),10),
("GRID",(0,0),(-1,-1),0.5,HexColor("#C8E6C9")),
("ROWBACKGROUNDS",(0,0),(-1,-1),[HexColor("#FFFFFF"),HexColor("#F7F9F4")]),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
]))
content += [tbl, Spacer(1,0.12*inch), Paragraph("Analysis", HH)]
for line in explanation.replace("**","").replace("## ","").split("\n")[:30]:
if line.strip():
content.append(Paragraph(line.strip(), BB))
doc.build(content)
data = buf.getvalue()
except Exception:
data = (f"EcoVision Report\nProduct: {name}\n"
f"Eco Score: {eco['eco_score']}/10\nGrade: {eco['grade']}\n"
f"Impact: {ml.get('predicted_impact')}\n\n{explanation}").encode("utf-8")
ext = "pdf" if data[:4]==b"%PDF" else "txt"
path = os.path.join(tempfile.gettempdir(),
f"eco_{name[:20].replace(' ','_')}.{ext}")
with open(path,"wb") as f: f.write(data)
return path
# ── Core pipeline ─────────────────────────────────────────────────────────────
def run_eco(name, category, deforest, pollution, biodiversity,
packaging, ingredients, groq_key):
if groq_key:
os.environ["GROQ_API_KEY"] = groq_key.strip()
eco = calculate_eco_score(deforest, pollution, biodiversity)
ml = predict_impact(category, deforest, pollution, biodiversity)
llm = generate_eco_explanation(
name, category, deforest, pollution, biodiversity,
eco["eco_score"], ml["predicted_impact"], packaging, ingredients,
)
return eco, ml, llm
EMPTY9 = ("", None, None, None, None, None, "", "", None)
# ── Tab handlers ──────────────────────────────────────────────────────────────
def search_fn(query, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY9[1:]
if not query.strip():
return (err_html("Please enter a product name."),) + EMPTY9[1:]
q = query.lower()
m = DF[DF["product_name"].str.lower().str.contains(q, na=False)]
if m.empty:
m = DF[DF["category"].str.lower().str.contains(q, na=False)]
if m.empty:
return (err_html(f"'{query}' not found. Try: Patagonia · Lays · "
f"Head & Shoulders · Shein · Ethique"),) + EMPTY9[1:]
row = m.iloc[0]
eco, ml, llm = run_eco(
row["product_name"], row["category"],
float(row["deforestation_risk"]), float(row["pollution_level"]),
float(row["biodiversity_impact"]), row["packaging_type"],
row["ingredients"], groq_key,
)
pdf = save_pdf(row["product_name"], row["category"], eco, ml,
llm["explanation"], row["packaging_type"])
return (
score_card(row["product_name"], eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk", eco["metrics"]["deforestation_risk"], "🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "💨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"🦋"),
prob_bar(ml["all_probabilities"]),
llm_card(llm["explanation"], llm.get("model","")),
alts_card(row["category"], row["product_name"]),
pdf,
)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY9[1:]
def manual_fn(name, category, packaging, ingredients,
deforest, pollution, biodiversity, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY9[1:]
if not name.strip():
return (err_html("Please enter a product name."),) + EMPTY9[1:]
eco, ml, llm = run_eco(name, category, deforest, pollution, biodiversity,
packaging, ingredients, groq_key)
pdf = save_pdf(name, category, eco, ml, llm["explanation"], packaging)
return (
score_card(name, eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk", eco["metrics"]["deforestation_risk"], "🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "💨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"🦋"),
prob_bar(ml["all_probabilities"]),
llm_card(llm["explanation"], llm.get("model","")),
alts_card(category, ""),
pdf,
)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY9[1:]
EMPTY_IMG = ("", None, "", None, None, None, None, "", None)
def image_fn(image_path, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY_IMG[1:]
if not image_path:
return EMPTY_IMG
vision = classify_image(image_path)
detected = vision["predicted_category"]
cat_rows = DF[DF["category"]==detected]
if cat_rows.empty:
return EMPTY_IMG
avg = cat_rows[["deforestation_risk","pollution_level","biodiversity_impact"]].mean()
rep = cat_rows.iloc[0]
eco, ml, llm = run_eco(
f"Detected: {detected}", detected,
float(avg["deforestation_risk"]), float(avg["pollution_level"]),
float(avg["biodiversity_impact"]), rep["packaging_type"], "", groq_key,
)
scores = vision["all_scores"]
fig_v = go.Figure(go.Bar(
x=list(scores.keys()), y=list(scores.values()),
marker_color=["#2E7D32" if k==detected else "#A5D6A7" for k in scores],
text=[f"{v:.0%}" for v in scores.values()],
textposition="outside", textfont=dict(color="#1A1A1A", size=12),
))
fig_v.update_layout(
**BASE_LAYOUT, height=220,
title=dict(text=f"Vision — '{detected}' detected",
font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
)
vhtml = (f""
f"
Detected
"
f"
"
f"📷 {detected}
"
f"
"
f"Confidence: "
f"{vision['confidence']:.0%} · {vision['method']}
")
pdf = save_pdf(f"Detected {detected}", detected, eco, ml,
llm["explanation"], rep["packaging_type"])
return (vhtml, fig_v, score_card(f"Detected {detected}", eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk",eco["metrics"]["deforestation_risk"],"🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "💨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"🦋"),
llm_card(llm["explanation"], llm.get("model","")), pdf)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY_IMG[1:]
EMPTY_CMP = ("","",None,None,None,"")
def compare_fn(n1, n2, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY_CMP[1:]
if groq_key:
os.environ["GROQ_API_KEY"] = groq_key.strip()
p1 = DF[DF["product_name"]==n1].iloc[0]
p2 = DF[DF["product_name"]==n2].iloc[0]
eco1 = calculate_eco_score(float(p1["deforestation_risk"]),
float(p1["pollution_level"]),
float(p1["biodiversity_impact"]))
eco2 = calculate_eco_score(float(p2["deforestation_risk"]),
float(p2["pollution_level"]),
float(p2["biodiversity_impact"]))
ml1 = predict_impact(p1["category"],float(p1["deforestation_risk"]),
float(p1["pollution_level"]),float(p1["biodiversity_impact"]))
ml2 = predict_impact(p2["category"],float(p2["deforestation_risk"]),
float(p2["pollution_level"]),float(p2["biodiversity_impact"]))
d1 = dict(name=n1, category=p1["category"],
eco_score=eco1["eco_score"], **eco1["metrics"])
d2 = dict(name=n2, category=p2["category"],
eco_score=eco2["eco_score"], **eco2["metrics"])
comp = compare_products_llm(d1, d2)
w1 = eco1["eco_score"] >= eco2["eco_score"]
return (compare_card(p1,eco1,ml1,w1), compare_card(p2,eco2,ml2,not w1),
gauge_chart(eco1["eco_score"],eco1["grade"]),
gauge_chart(eco2["eco_score"],eco2["grade"]),
comparison_bar(n1,n2,eco1,eco2),
llm_card(comp["comparison"],comp.get("model","")))
except Exception as e:
return (err_html(f"Error: {str(e)}"), "",None,None,None,"")
# ── Build UI ──────────────────────────────────────────────────────────────────
with gr.Blocks(title="EcoVision — Smart Sustainability Analyzer") as demo:
gr.HTML("""
🌿 EcoVision
Smart Sustainability Analyzer · ML · Computer Vision · LLM
✅ Rule-based Scoring
🤖 RandomForest ML
📷 MobileNetV2 CV
🧠 Groq LLM
""")
with gr.Accordion("🔑 Groq API Key (optional)", open=False):
gr.HTML(""
"Free at console.groq.com — "
"works without it using template explanations.
")
groq_key = gr.Textbox(label="Groq API Key", type="password", placeholder="gsk_…")
with gr.Tabs():
# ── Tab 1: Search ─────────────────────────────────────────────────────
with gr.Tab("🔍 Product Search"):
gr.HTML(""
"Search any of our 53+ products"
" — shampoos, snacks, clothing, cosmetics, plastics.
")
with gr.Row():
s_in = gr.Textbox(
label="Product name or category",
placeholder="e.g. Head & Shoulders | Patagonia | Lays | Shein",
scale=4,
)
s_btn = gr.Button("🔬 Analyze", variant="primary", scale=1)
with gr.Accordion("📋 Browse all products", open=False):
gr.Dataframe(
value=DF[["product_name","category","eco_label","packaging_type"]].rename(
columns={"product_name":"Product","category":"Category",
"eco_label":"Impact","packaging_type":"Packaging"}),
interactive=False,
)
s_card = gr.HTML()
with gr.Row():
s_gauge = gr.Plot()
s_prob = gr.Plot()
with gr.Row():
s_b1 = gr.Plot()
s_b2 = gr.Plot()
s_b3 = gr.Plot()
s_llm = gr.HTML()
s_alts = gr.HTML()
s_pdf = gr.File(label="📄 Download Report")
s_btn.click(
fn=search_fn,
inputs=[s_in, groq_key],
outputs=[s_card, s_gauge, s_b1, s_b2, s_b3, s_prob,
s_llm, s_alts, s_pdf],
)
# ── Tab 2: Manual ─────────────────────────────────────────────────────
with gr.Tab("✏️ Manual Input"):
gr.HTML(""
"Analyse any custom product by entering details manually.
")
with gr.Row():
with gr.Column():
m_name = gr.Textbox(label="Product Name",
placeholder="e.g. My Eco Shampoo")
m_cat = gr.Dropdown(ALL_CATS, label="Category", value="Shampoo")
m_pkg = gr.Dropdown(PACKAGING_OPTS, label="Packaging",
value="Plastic Bottle")
m_ingr = gr.Textbox(label="Ingredients / Materials",
placeholder="e.g. Organic Cotton, Natural Dyes",
lines=3)
with gr.Column():
m_defo = gr.Slider(0.0,1.0,value=0.30,step=0.05,label="🌲 Deforestation Risk")
m_poll = gr.Slider(0.0,1.0,value=0.50,step=0.05,label="💨 Pollution Level")
m_bio = gr.Slider(0.0,1.0,value=0.40,step=0.05,label="🦋 Biodiversity Impact")
m_btn = gr.Button("🔬 Analyse Product", variant="primary")
m_card = gr.HTML()
with gr.Row():
m_gauge = gr.Plot()
m_prob = gr.Plot()
with gr.Row():
m_b1 = gr.Plot()
m_b2 = gr.Plot()
m_b3 = gr.Plot()
m_llm = gr.HTML()
m_alts = gr.HTML()
m_pdf = gr.File(label="📄 Download Report")
m_btn.click(
fn=manual_fn,
inputs=[m_name, m_cat, m_pkg, m_ingr,
m_defo, m_poll, m_bio, groq_key],
outputs=[m_card, m_gauge, m_b1, m_b2, m_b3, m_prob,
m_llm, m_alts, m_pdf],
)
# ── Tab 3: Image ──────────────────────────────────────────────────────
with gr.Tab("📷 Image Scan"):
gr.HTML(""
"Upload a product photo — MobileNetV2 detects the category.
")
with gr.Row():
with gr.Column():
i_img = gr.Image(type="filepath", label="Upload Product Image",
height=250)
i_btn = gr.Button("🔭 Scan & Analyse", variant="primary")
with gr.Column():
i_detect = gr.HTML()
i_vchart = gr.Plot()
i_card = gr.HTML()
with gr.Row():
i_gauge = gr.Plot()
with gr.Row():
i_b1 = gr.Plot()
i_b2 = gr.Plot()
i_b3 = gr.Plot()
i_llm = gr.HTML()
i_pdf = gr.File(label="📄 Download Report")
i_btn.click(
fn=image_fn,
inputs=[i_img, groq_key],
outputs=[i_detect, i_vchart, i_card, i_gauge,
i_b1, i_b2, i_b3, i_llm, i_pdf],
)
# ── Tab 4: Compare ────────────────────────────────────────────────────
with gr.Tab("⚖️ Compare Products"):
gr.HTML(""
"Compare any two products side-by-side.
")
with gr.Row():
c_p1 = gr.Dropdown(ALL_PRODUCTS, label="🅰️ Product A",
value=ALL_PRODUCTS[0], scale=5)
gr.HTML("VS
")
c_p2 = gr.Dropdown(ALL_PRODUCTS, label="🅱️ Product B",
value=ALL_PRODUCTS[5], scale=5)
c_btn = gr.Button("⚖️ Compare", variant="primary", scale=2)
with gr.Row():
c_card1 = gr.HTML()
c_card2 = gr.HTML()
with gr.Row():
c_g1 = gr.Plot()
c_g2 = gr.Plot()
c_bar = gr.Plot()
c_llm = gr.HTML()
c_btn.click(
fn=compare_fn,
inputs=[c_p1, c_p2, groq_key],
outputs=[c_card1, c_card2, c_g1, c_g2, c_bar, c_llm],
)
gr.HTML(""
"🌿 EcoVision · Gradio · scikit-learn · "
"PyTorch MobileNetV2 · Groq LLM · Plotly
")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)