snapr / src /ui /gradio_app.py
lisekarimi's picture
fix: rename CHANGELOG link
592930d verified
"""Launches a Gradio app that runs an AI planning agent to find smart online deals.
Uses Python's built-in logging system for consistent logs.
Includes demo mode restrictions: MAX_DEMO_RUNS_PER_DAY runs per day.
"""
import os
from typing import Tuple
import gradio as gr
from src.agents.pipeline import run_and_stream_logs
from src.config.constants import (
IS_DEMO_VERSION,
MAX_CATEGORY_SELECTION,
MAX_DEMO_RUNS_PER_DAY,
PROJECT_NAME,
VERSION,
)
from src.config.feeds import CATEGORY_FEEDS
from src.ui.formatting import format_deals_table
from src.utils.logger import console
from src.utils.state_manager import can_run_app
PROJECT_NAME_CAP = PROJECT_NAME.capitalize()
REPO_URL = f"https://github.com/lisekarimi/{PROJECT_NAME}"
DOC_URL = f"https://lisekarimi.github.io/{PROJECT_NAME}"
def build_ui() -> gr.Blocks:
"""Constructs and returns the Gradio UI interface, with error handling."""
try:
with open(
os.path.join(os.path.dirname(__file__), "assets", "styles.css"), "r"
) as f:
css = f.read()
except Exception as e:
css = ""
console.print(f"[bold yellow]⚠️ Failed to load CSS:[/] {e}")
# Set initial UI state here
disable_btn = False
initial_status = "Loading demo status..."
# Building the UI
try:
with gr.Blocks(css=css, title=f"🏷️{PROJECT_NAME_CAP}") as ui:
with gr.Column(elem_id="app-container"):
# 🔝 Top content
gr.Markdown(f"<h1 id='app-title'>🏷️ {PROJECT_NAME_CAP} </h1>")
gr.Markdown(
"<h2 id='app-subtitle'>Autonomous AI Agents Snapping the Best "
"Deals Online</h2>"
)
gr.HTML(
f"""
<div id="intro-text">
<p>🏷️ <strong>Snapr</strong>,
<strong>an Agentic AI System</strong>,
discovers the best online deals for you—smart,
simple, and automatic.
Let <strong>AI</strong> do the work, so you can save money
without any effort.</p>
<h4>🤖 How It Works:</h4>
<p>1️⃣ Choose up to {MAX_CATEGORY_SELECTION}
categories to search.</p>
<p>2️⃣ Click "Find Smart Deals" — AI scans, estimates prices,
and filters top discounts.</p>
<p>3️⃣ See the best deals in a table with prices, discounts,
and direct links.</p>
</div>
"""
)
if IS_DEMO_VERSION:
gr.Markdown(
f"""
<p>⚠️ This is a demo version — limited to
{MAX_DEMO_RUNS_PER_DAY} global runs per day for all users.</p>
"""
)
gr.Markdown(
f"""
<p class="custom-links">📦 Want more?
You can <a href="{REPO_URL}"
target="_blank">run it locally</a>
with full <a href="{DOC_URL}/technical/localdev/"
target="_blank">instructions</a> and
<a href="{DOC_URL}"
target="_blank">documentation</a>.</p>
"""
)
with gr.Column(elem_id="left-col"):
category_selector = gr.Dropdown(
choices=list(CATEGORY_FEEDS.keys()),
value="Electronics",
multiselect=True,
label=(
f"🧭 Select up to {MAX_CATEGORY_SELECTION} Deal Categories"
),
elem_id="category-selector",
elem_classes="custom-dropdown",
)
run_btn = gr.Button(
"🔍 Find Smart Deals",
elem_id="run-btn",
variant="primary",
elem_classes="run-button",
interactive=not disable_btn,
)
status_msg = gr.HTML(value=initial_status, elem_id="status-message")
# Logs + deals
logs_output = gr.HTML(
value="""
<div id="logs-label">📜 Live Agent Logs</div>
<div id="scrollContent">
🕵️‍♀️ Click "🔍 Find Smart Deals" to wake the agents and
stream logs here!
</div>
"""
)
deals_output = gr.HTML(
value=format_deals_table([]), elem_id="deal-table"
)
# Connect button
run_btn.click(
fn=run_and_stream_logs,
inputs=[category_selector],
outputs=[logs_output, deals_output, run_btn, status_msg],
)
# Status update on load
@ui.load(outputs=[status_msg, run_btn])
def update_status_on_load() -> Tuple[str, gr.update]:
"""Sets demo status and button state on UI load."""
can_run, status = can_run_app()
btn_state = gr.update(
interactive=can_run,
elem_classes=(
["run-button", "btn-disabled"]
if not can_run
else ["run-button"]
),
)
return status, btn_state
# Bottom: version info
gr.Markdown(
f"""
<p class="version-banner">
🔖 <strong>
<a href="{DOC_URL}/CHANGELOG"
target="_blank">Version {VERSION}</a>
</strong>
</p>
"""
)
return ui
except Exception as e:
console.print(f"[bold red]❌ Failed to build UI:[/] {e}")
raise