File size: 6,452 Bytes
8366946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592930d
8366946
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""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