kosmoscpp commited on
Commit
1de1d01
·
verified ·
1 Parent(s): 5d44843

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -146
app.py CHANGED
@@ -1,152 +1,244 @@
1
- import streamlit as st
2
  import pandas as pd
 
3
  import re
4
 
5
- # Load and clean data
6
- @st.cache_data
7
- def load_data():
8
- df = pd.read_csv("amazon_eco-friendly_products.csv")
 
9
 
10
- # --- Clean price ---
11
- def clean_price(p):
12
- if isinstance(p, str):
13
- match = re.search(r"(\d+(\.\d{1,2})?)", p.replace(",", ""))
14
- return float(match.group(1)) if match else None
15
  return None
16
-
17
- df["price"] = df["price"].apply(clean_price)
18
-
19
- # Remove rows with invalid price
20
- df = df[df["price"].notna()]
21
-
22
- # --- Clean rating ---
23
- def clean_rating(r):
24
- if isinstance(r, str):
25
- match = re.search(r"(\d+(\.\d)?)", r)
26
- return float(match.group(1)) if match else None
27
- return r
28
-
29
- df["rating"] = df["rating"].apply(clean_rating)
30
-
31
- # --- Clean stock ---
32
- df["inStock"] = df["inStock"].astype(str).str.strip().str.lower().isin(["true", "yes", "1"])
33
-
34
- return df
35
-
36
- df = load_data()
37
-
38
- # --- UI ---
39
- st.set_page_config(page_title="Eco-Friendly Products", layout="wide")
40
-
41
- # Custom CSS
42
- st.markdown(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  """
44
- <style>
45
- body { background: linear-gradient(135deg,#1e3c72,#2a5298); color: white; }
46
- .stApp { background: transparent; }
47
- .glass {
48
- background: rgba(255,255,255,0.1);
49
- backdrop-filter: blur(12px);
50
- border-radius: 20px;
51
- padding: 20px;
52
- margin-bottom: 20px;
53
- transition: all 0.3s ease-in-out;
54
- }
55
- .glass:hover {
56
- transform: scale(1.02);
57
- box-shadow: 0 8px 20px rgba(0,0,0,0.3);
58
- }
59
- .footer {
60
- background: rgba(255,255,255,0.15);
61
- backdrop-filter: blur(15px);
62
- border-radius: 15px;
63
- padding: 15px;
64
- text-align: center;
65
- color: #fff;
66
- font-weight: bold;
67
- margin-top: 30px;
68
- animation: glow 2s infinite alternate;
69
- }
70
- @keyframes glow {
71
- from { text-shadow: 0 0 5px #00ffcc, 0 0 10px #00ffcc; }
72
- to { text-shadow: 0 0 20px #00ffcc, 0 0 30px #00ffcc; }
73
- }
74
- .category-pill {
75
- display:inline-block;
76
- padding:8px 18px;
77
- margin:5px;
78
- border-radius:25px;
79
- background:rgba(255,255,255,0.15);
80
- color:white;
81
- cursor:pointer;
82
- transition:0.2s;
83
- }
84
- .category-pill:hover {
85
- background:rgba(255,255,255,0.3);
86
- transform:scale(1.1);
87
- }
88
- </style>
89
- """,
90
- unsafe_allow_html=True,
91
- )
92
-
93
- st.title("🌍 Eco-Friendly Products")
94
-
95
- # --- Filters ---
96
- col1, col2, col3 = st.columns([2, 2, 2])
97
- with col1:
98
- category = st.selectbox("Category", ["All"] + sorted(df["category"].dropna().unique().tolist()))
99
- with col2:
100
- min_price, max_price = st.slider("Price range ($)", 0.0, float(df["price"].max()), (0.0, float(df["price"].max())))
101
- with col3:
102
- min_rating = st.slider("Minimum Rating ⭐", 0.0, 5.0, 0.0, 0.1)
103
-
104
- in_stock_only = st.checkbox("Show In-Stock Only", True)
105
-
106
- # --- Sorting ---
107
- sort_by = st.selectbox("Sort By", ["Relevance", "Price: Low → High", "Price: High → Low", "Rating: High → Low"])
108
-
109
- # --- Filter logic ---
110
- filtered = df.copy()
111
-
112
- if category != "All":
113
- filtered = filtered[filtered["category"] == category]
114
-
115
- filtered = filtered[
116
- (filtered["price"] >= min_price) &
117
- (filtered["price"] <= max_price) &
118
- (filtered["rating"].fillna(0) >= min_rating)
119
- ]
120
-
121
- if in_stock_only:
122
- filtered = filtered[filtered["inStock"]]
123
-
124
- # Sorting
125
- if sort_by == "Price: Low High":
126
- filtered = filtered.sort_values("price", ascending=True)
127
- elif sort_by == "Price: High → Low":
128
- filtered = filtered.sort_values("price", ascending=False)
129
- elif sort_by == "Rating: High → Low":
130
- filtered = filtered.sort_values("rating", ascending=False)
131
-
132
- # --- Display ---
133
- if filtered.empty:
134
- st.warning("No products match your filters.")
135
- else:
136
- cols = st.columns(3)
137
- for i, (_, row) in enumerate(filtered.iterrows()):
138
- with cols[i % 3]:
139
- st.markdown(
140
- f"""
141
- <div class="glass">
142
- <h4>{row['name']}</h4>
143
- <p>💲 <b>${row['price']:.2f}</b></p>
144
- <p>⭐ {row['rating'] if not pd.isna(row['rating']) else "N/A"}</p>
145
- <p>📦 {"In Stock" if row['inStock'] else "Out of Stock"}</p>
146
- </div>
147
- """,
148
- unsafe_allow_html=True,
149
- )
150
-
151
- # --- Footer ---
152
- st.markdown('<div class="footer">Made with ❤️ for a Greener World 🌱</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pandas as pd
2
+ import gradio as gr
3
  import re
4
 
5
+ # =============================
6
+ # Load & Clean Data
7
+ # =============================
8
+ df = pd.read_csv("amazon_eco-friendly_products.csv")
9
+ df.fillna('', inplace=True)
10
 
11
+ # --- Price Cleaning ---
12
+ def parse_price(price_str):
13
+ if not isinstance(price_str, str):
 
 
14
  return None
15
+ match = re.search(r"[\d,.]+", price_str)
16
+ if match:
17
+ try:
18
+ return float(match.group().replace(",", ""))
19
+ except:
20
+ return None
21
+ return None
22
+
23
+ df["price_num"] = df["price"].apply(parse_price)
24
+ df = df[df["price_num"].notnull()] # drop invalid prices
25
+
26
+ # --- Rating Cleaning ---
27
+ df["rating_num"] = pd.to_numeric(df["rating"], errors="coerce").fillna(0)
28
+
29
+ # --- Stock Normalization ---
30
+ df["inStockBool"] = df["inStockText"].str.contains("In Stock", case=False, na=False)
31
+
32
+ # Categories
33
+ categories = sorted(df['category'].dropna().unique().tolist())
34
+
35
+ # =============================
36
+ # UI Helpers
37
+ # =============================
38
+ def product_card(prod):
39
+ """Render a single product card with hover popup"""
40
+ return f"""
41
+ <div class="card">
42
+ <a href='{prod["url"]}' target='_blank'>
43
+ <img src='{prod["img_url"]}' class="prod-img">
44
+ <div class="card-title">{prod["title"]}</div>
45
+ </a>
46
+ <div class="card-meta">
47
+ <span class="price">${prod["price_num"]:.2f}</span><br>
48
+ ⭐ {prod["rating_num"]}/5<br>
49
+ {prod["inStockText"]}
50
+ </div>
51
+ <div class="popup">
52
+ <b>Brand:</b> {prod.get("brand","")}<br>
53
+ <b>Category:</b> {prod.get("category","")}<br>
54
+ <b>Price:</b> ${prod["price_num"]:.2f}<br>
55
+ <b>Rating:</b> {prod["rating_num"]}/5<br>
56
+ <b>Status:</b> {prod["inStockText"]}
57
+ </div>
58
+ </div>
59
  """
60
+
61
+ def render_products(products_df):
62
+ if products_df.empty:
63
+ return """
64
+ <div class="no-results">
65
+ <h3>😕 No products found</h3>
66
+ <p>Try adjusting filters or searching again.</p>
67
+ </div>
68
+ """
69
+ return "<div class='grid'>" + "".join(product_card(row) for _, row in products_df.iterrows()) + "</div>"
70
+
71
+ # =============================
72
+ # Core Functions
73
+ # =============================
74
+ batch_size = 8
75
+ shown_ids = []
76
+
77
+ def home_tab(category=None):
78
+ global shown_ids
79
+ if category:
80
+ filtered = df[df['category'].str.contains(category, case=False, na=False)]
81
+ else:
82
+ filtered = df
83
+ shown_ids = []
84
+ sample = filtered.sample(min(batch_size, len(filtered)), random_state=1)
85
+ shown_ids = sample['id'].tolist()
86
+ return render_products(sample)
87
+
88
+ def load_more(category=None):
89
+ global shown_ids
90
+ if category:
91
+ filtered = df[df['category'].str.contains(category, case=False, na=False)]
92
+ else:
93
+ filtered = df
94
+ remaining = filtered[~filtered['id'].isin(shown_ids)]
95
+ if remaining.empty:
96
+ return render_products(filtered[filtered['id'].isin(shown_ids)])
97
+ next_batch = remaining.sample(min(batch_size, len(remaining)))
98
+ shown_ids += next_batch['id'].tolist()
99
+ return render_products(df[df['id'].isin(shown_ids)])
100
+
101
+ def search_products(query):
102
+ filtered = df[
103
+ df['title'].str.contains(query, case=False, na=False) |
104
+ df['brand'].str.contains(query, case=False, na=False) |
105
+ df['category'].str.contains(query, case=False, na=False)
106
+ ]
107
+ return render_products(filtered)
108
+
109
+ def filter_sort(category=None, price_min=0, price_max=1000, min_rating=0, in_stock=False, sort_by="Price: Low→High"):
110
+ filtered = df.copy()
111
+ if category:
112
+ filtered = filtered[filtered['category'].str.contains(category, case=False, na=False)]
113
+ # Fix slider swap
114
+ if price_min > price_max:
115
+ price_min, price_max = price_max, price_min
116
+ filtered = filtered[(filtered['price_num'] >= price_min) & (filtered['price_num'] <= price_max)]
117
+ filtered = filtered[filtered['rating_num'] >= min_rating]
118
+ if in_stock:
119
+ filtered = filtered[filtered['inStockBool'] == True]
120
+ if sort_by == "Price: Low→High":
121
+ filtered = filtered.sort_values("price_num")
122
+ elif sort_by == "Price: High→Low":
123
+ filtered = filtered.sort_values("price_num", ascending=False)
124
+ elif sort_by == "Rating: High→Low":
125
+ filtered = filtered.sort_values("rating_num", ascending=False)
126
+ return render_products(filtered)
127
+
128
+ # =============================
129
+ # Gradio App
130
+ # =============================
131
+ with gr.Blocks(css="""
132
+ .grid {
133
+ display: grid;
134
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
135
+ gap: 20px;
136
+ }
137
+ .card {
138
+ position: relative;
139
+ padding: 12px;
140
+ border-radius: 16px;
141
+ background: rgba(255, 255, 255, 0.15);
142
+ backdrop-filter: blur(12px);
143
+ text-align: center;
144
+ transition: transform 0.3s, box-shadow 0.3s;
145
+ }
146
+ .card:hover {
147
+ transform: translateY(-6px) scale(1.03);
148
+ box-shadow: 0 8px 20px rgba(0,0,0,0.25);
149
+ }
150
+ .prod-img {
151
+ width: 160px;
152
+ height: 160px;
153
+ object-fit: cover;
154
+ border-radius: 12px;
155
+ }
156
+ .card-title {
157
+ font-weight: bold;
158
+ margin: 10px 0;
159
+ }
160
+ .card-meta {
161
+ font-size: 14px;
162
+ }
163
+ .popup {
164
+ visibility: hidden;
165
+ opacity: 0;
166
+ transition: opacity 0.3s;
167
+ position: absolute;
168
+ top: 0;
169
+ left: 0;
170
+ width: 100%;
171
+ height: 100%;
172
+ border-radius: 16px;
173
+ background: rgba(0,0,0,0.85);
174
+ color: white;
175
+ padding: 12px;
176
+ font-size: 14px;
177
+ }
178
+ .card:hover .popup {
179
+ visibility: visible;
180
+ opacity: 1;
181
+ }
182
+ .no-results {
183
+ text-align:center;
184
+ padding:30px;
185
+ border-radius: 16px;
186
+ background: rgba(255,255,255,0.15);
187
+ backdrop-filter: blur(10px);
188
+ }
189
+ """) as app:
190
+
191
+ # Header
192
+ gr.Markdown("""
193
+ <div style='background: linear-gradient(90deg, #6dd5ed, #2193b0); padding:20px; border-radius:14px; text-align:center; color:white;'>
194
+ <h1 style='margin:0;'>🌿 Ecoducts Online</h1>
195
+ <p style='margin:0;'>Discover eco-friendly alternatives at one place</p>
196
+ </div>
197
+ """)
198
+
199
+ with gr.Tabs():
200
+ # Home
201
+ with gr.TabItem("🏠 Home"):
202
+ with gr.Row():
203
+ cat_buttons = []
204
+ for cat in categories[:8]:
205
+ btn = gr.Button(cat, elem_classes="pill")
206
+ cat_buttons.append(btn)
207
+ home_output = gr.HTML(home_tab())
208
+ load_btn = gr.Button("Load More")
209
+
210
+ # Search
211
+ with gr.TabItem("🔎 Search"):
212
+ query_in = gr.Textbox(label="Search products")
213
+ query_out = gr.HTML()
214
+ query_in.submit(search_products, inputs=query_in, outputs=query_out)
215
+ gr.Button("Search").click(search_products, inputs=query_in, outputs=query_out)
216
+
217
+ # Filters
218
+ with gr.TabItem("⚙ Filters & Sort"):
219
+ c_dd = gr.Dropdown(choices=categories, label="Category")
220
+ price_min = gr.Number(value=0, label="Min Price ($)")
221
+ price_max = gr.Number(value=1000, label="Max Price ($)")
222
+ rating_slider = gr.Slider(0, 5, value=0, step=0.1, label="Minimum Rating")
223
+ stock_cb = gr.Checkbox(label="In Stock Only")
224
+ sort_dd = gr.Dropdown(choices=["Price: Low→High","Price: High→Low","Rating: High→Low"], value="Price: Low→High", label="Sort By")
225
+ filter_out = gr.HTML()
226
+ gr.Button("Apply Filters").click(filter_sort,
227
+ inputs=[c_dd, price_min, price_max, rating_slider, stock_cb, sort_dd],
228
+ outputs=filter_out)
229
+
230
+ # Footer
231
+ gr.HTML("""
232
+ <div style='margin-top:30px; padding:15px; text-align:center;
233
+ border-radius:14px; background: linear-gradient(90deg,#6dd5ed,#2193b0); color:white;'>
234
+ <h3>Made with ❤️ by <b>Krishna Jha</b></h3>
235
+ <a href='https://instagram.com/kosmos.cpp' target='_blank' style='color:white;text-decoration:none;'>@kosmos.cpp</a>
236
+ </div>
237
+ """)
238
+
239
+ # Link buttons
240
+ for btn in cat_buttons:
241
+ btn.click(home_tab, inputs=btn, outputs=home_output)
242
+ load_btn.click(load_more, outputs=home_output)
243
+
244
+ app.launch()