Spaces:
Sleeping
Sleeping
| """ | |
| AUTOMATION 2 (UPGRADED) β Hugging Face Spaces App | |
| ================================================== | |
| Improvements over v1: | |
| β LLM (GPT-4o-mini) called DIRECTLY from inside the app | |
| β Richer interactive visualisations (radar chart, trend bars, gauge) | |
| β Side-by-side metric comparison panel | |
| β Session history tracker | |
| β Automated pipeline trigger button (runs agentic_pipeline.py) | |
| β Confidence intervals on predictions | |
| β Better UX: loading states, cleaner layout, collapsible AI section | |
| Deploy on Hugging Face Spaces (SDK: Gradio). | |
| Set HF Secret: OPENAI_API_KEY | |
| """ | |
| import os | |
| import json | |
| import time | |
| import subprocess | |
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as mpatches | |
| import warnings | |
| warnings.filterwarnings("ignore") | |
| from sklearn.ensemble import RandomForestRegressor | |
| from sklearn.model_selection import train_test_split | |
| from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer | |
| try: | |
| import requests | |
| REQUESTS_OK = True | |
| except ImportError: | |
| REQUESTS_OK = False | |
| # ββ CONFIG ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "") # Set as HF Secret | |
| GPT_MODEL = "gpt-4o-mini" | |
| PALETTE = { | |
| "blue": "#2E86AB", | |
| "pink": "#A23B72", | |
| "amber": "#F18F01", | |
| "red": "#C73E1D", | |
| "teal": "#44BBA4", | |
| "light": "#F5F5F5", | |
| "dark": "#1A1A2E", | |
| } | |
| # ββ STARTUP: TRAIN MODELS βββββββββββββββββββββββββββββββββββ | |
| print("Loading data and training models on startup...") | |
| def _load_and_train_amazon(): | |
| df = pd.read_csv("amazon_synthetic.csv") | |
| df["log_sales"] = np.log1p(df["rating_count"]) | |
| features = ["actual_price", "discounted_price", "discount_pct", "rating", "sentiment_score"] | |
| X = df[features].dropna() | |
| y = df.loc[X.index, "log_sales"] | |
| rf = RandomForestRegressor(n_estimators=150, random_state=42) | |
| rf.fit(X, y) | |
| # Compute prediction std via individual trees for confidence interval | |
| return rf, features, df | |
| def _load_and_train_spotify(): | |
| df = pd.read_csv("spotify_synthetic.csv") | |
| df["explicit"] = df["explicit"].astype(int) | |
| features = ["danceability", "energy", "loudness", "speechiness", | |
| "acousticness", "instrumentalness", "valence", "tempo", "explicit"] | |
| X = df[features].dropna() | |
| y = df.loc[X.index, "popularity"] | |
| rf = RandomForestRegressor(n_estimators=150, random_state=42) | |
| rf.fit(X, y) | |
| return rf, features, df | |
| try: | |
| rf_amz, features_amz, df_amz = _load_and_train_amazon() | |
| AMZ_OK = True | |
| print("β Amazon model ready") | |
| except Exception as e: | |
| AMZ_OK = False | |
| print(f"β Amazon model failed: {e}") | |
| try: | |
| rf_spot, features_spot, df_spot = _load_and_train_spotify() | |
| SPOT_OK = True | |
| print("β Spotify model ready") | |
| except Exception as e: | |
| SPOT_OK = False | |
| print(f"β Spotify model failed: {e}") | |
| analyzer = SentimentIntensityAnalyzer() | |
| # Session history | |
| session_history = [] | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # GPT HELPER β called directly from the app | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def call_gpt_in_app(system_prompt: str, user_prompt: str, max_tokens=500) -> str: | |
| """ | |
| Call GPT-4o-mini directly from within the Gradio app. | |
| Falls back to a template report if API key is not set. | |
| """ | |
| if not OPENAI_KEY or not REQUESTS_OK: | |
| return None # will use fallback below | |
| headers = { | |
| "Authorization": f"Bearer {OPENAI_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| payload = { | |
| "model": GPT_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| "temperature": 0.4, | |
| "max_tokens": max_tokens, | |
| } | |
| try: | |
| r = requests.post( | |
| "https://api.openai.com/v1/chat/completions", | |
| headers=headers, json=payload, timeout=25 | |
| ) | |
| r.raise_for_status() | |
| return r.json()["choices"][0]["message"]["content"] | |
| except Exception as e: | |
| return f"[GPT unavailable: {e}]" | |
| def get_amazon_gpt_insight(category, actual_price, discounted_price, discount_pct, | |
| rating, sentiment_score, sentiment_label, sales_pred, score): | |
| system = ( | |
| "You are a senior e-commerce performance analyst. Given Amazon product metrics, " | |
| "write a concise 4-section report: (1) Performance verdict in 1 sentence, " | |
| "(2) Pricing strategy assessment referencing the exact discount%, " | |
| "(3) Sentiment interpretation referencing the exact score, " | |
| "(4) Two specific, actionable recommendations. " | |
| "Be data-driven. Reference every number provided. Keep total response under 200 words." | |
| ) | |
| user = ( | |
| f"Category: {category} | Actual price: βΉ{actual_price:.0f} | " | |
| f"Discounted price: βΉ{discounted_price:.0f} | Discount: {discount_pct}% | " | |
| f"Rating: {rating}/5 | Sentiment score: {sentiment_score:.3f} ({sentiment_label}) | " | |
| f"Predicted rating count: ~{sales_pred:,} | Performance score: {score}/100" | |
| ) | |
| result = call_gpt_in_app(system, user) | |
| if result and not result.startswith("[GPT"): | |
| return "π€ AI Analysis (GPT-4o-mini)\n" + "β" * 36 + "\n" + result | |
| # Fallback | |
| return ( | |
| "π€ AI Analysis (template fallback β set OPENAI_API_KEY for live GPT)\n" | |
| + "β" * 36 + "\n" | |
| f"1. Performance: This {category} product scores {score}/100 β " | |
| f"{'strong' if score >= 75 else 'average' if score >= 45 else 'underperforming'}.\n" | |
| f"2. Pricing: A {discount_pct}% discount brings the price from βΉ{actual_price:.0f} to " | |
| f"βΉ{discounted_price:.0f}. {'This aggressive discount may signal lower quality.' if discount_pct > 50 else 'Moderate discount maintains perceived value.'}\n" | |
| f"3. Sentiment: Score of {sentiment_score:.3f} is {sentiment_label}. " | |
| f"{'Strong reviews support organic growth.' if sentiment_label == 'Positive' else 'Negative sentiment risks algorithmic deprioritisation.'}\n" | |
| f"4. Recommendations:\n" | |
| f" β’ {'Leverage positive reviews in sponsored ads' if sentiment_label == 'Positive' else 'Address negative feedback within 48h'}\n" | |
| f" β’ {'Reduce discount to 20β30% to protect margin' if discount_pct > 50 else 'Maintain current pricing strategy'}" | |
| ) | |
| def get_spotify_gpt_insight(genre, danceability, energy, loudness, tempo, | |
| valence, acousticness, pop_pred, tier): | |
| system = ( | |
| "You are a music industry data analyst. Given Spotify audio features, " | |
| "write a concise 4-section report: (1) Commercial potential verdict in 1 sentence, " | |
| "(2) Audio profile assessment β is it radio-friendly? Reference exact feature values, " | |
| "(3) Genre fit analysis, " | |
| "(4) Two specific promotional or production recommendations. " | |
| "Be data-driven. Reference every number. Under 200 words total." | |
| ) | |
| user = ( | |
| f"Genre: {genre} | Popularity prediction: {pop_pred:.1f}/100 ({tier}) | " | |
| f"Danceability: {danceability:.2f} | Energy: {energy:.2f} | Loudness: {loudness:.1f} dB | " | |
| f"Tempo: {tempo:.0f} BPM | Valence: {valence:.2f} | Acousticness: {acousticness:.2f}" | |
| ) | |
| result = call_gpt_in_app(system, user) | |
| if result and not result.startswith("[GPT"): | |
| return "π€ AI Analysis (GPT-4o-mini)\n" + "β" * 36 + "\n" + result | |
| return ( | |
| "π€ AI Analysis (template fallback β set OPENAI_API_KEY for live GPT)\n" | |
| + "β" * 36 + "\n" | |
| f"1. Commercial potential: This {genre} track scores {pop_pred:.1f}/100 β {tier}.\n" | |
| f"2. Audio profile: Danceability {danceability:.2f} + energy {energy:.2f} at {loudness:.1f} dB. " | |
| f"{'Radio-friendly profile.' if danceability > 0.6 and energy > 0.6 else 'Niche profile β limited mainstream appeal.'}\n" | |
| f"3. Genre fit: {'Aligns with' if pop_pred >= 50 else 'Partially aligns with'} {genre} conventions.\n" | |
| f"4. Recommendations:\n" | |
| f" β’ {'Pitch to editorial playlists β strong commercial profile' if pop_pred >= 60 else 'Consider a remix to boost danceability'}\n" | |
| f" β’ {'Capitalize on high energy for live and sync licensing' if energy >= 0.7 else 'Explore streaming-first promotional strategy'}" | |
| ) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # VISUALISATION HELPERS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _radar_chart(labels, values, title, color): | |
| """Create a radar (spider) chart for audio features.""" | |
| n = len(labels) | |
| angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist() | |
| values_loop = values + [values[0]] | |
| angles += angles[:1] | |
| fig, ax = plt.subplots(figsize=(4.5, 4.5), subplot_kw={"polar": True}) | |
| fig.patch.set_facecolor("#FAFAFA") | |
| ax.set_facecolor("#F0F4F8") | |
| ax.plot(angles, values_loop, color=color, linewidth=2) | |
| ax.fill(angles, values_loop, color=color, alpha=0.25) | |
| ax.set_xticks(angles[:-1]) | |
| ax.set_xticklabels(labels, fontsize=9) | |
| ax.set_ylim(0, 1) | |
| ax.set_yticks([0.25, 0.5, 0.75]) | |
| ax.set_yticklabels(["0.25", "0.50", "0.75"], fontsize=7, color="gray") | |
| ax.set_title(title, fontsize=11, fontweight="bold", pad=15) | |
| ax.grid(color="white", linewidth=0.8) | |
| plt.tight_layout() | |
| return fig | |
| def make_amazon_chart(rating, sentiment_score, discount_pct, score, sales_pred): | |
| import tempfile | |
| fig, axes = plt.subplots(1, 3, figsize=(14, 4.5)) | |
| fig.patch.set_facecolor("#FAFAFA") | |
| fig.suptitle("Amazon Product β Performance Dashboard", fontsize=13, fontweight="bold", y=1.01) | |
| # Panel 1: Feature bars | |
| ax = axes[0] | |
| ax.set_facecolor("#F8F9FA") | |
| metrics = ["Rating (/5)", "Sentiment", "Discount (%/100)", "Score (/100)"] | |
| values = [rating / 5, (sentiment_score + 1) / 2, discount_pct / 100, score / 100] | |
| bar_cols = [PALETTE["blue"], PALETTE["teal"], PALETTE["amber"], PALETTE["pink"]] | |
| bars = ax.bar(metrics, values, color=bar_cols, edgecolor="white", width=0.6) | |
| ax.set_ylim(0, 1.15) | |
| ax.set_title("Key Metrics (normalised)", fontweight="bold") | |
| for bar, val in zip(bars, values): | |
| ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.025, | |
| f"{val:.2f}", ha="center", fontsize=10, fontweight="bold") | |
| ax.set_xticklabels(metrics, fontsize=9) | |
| # Panel 2: Gauge | |
| ax2 = axes[1] | |
| ax2.set_facecolor("#F8F9FA") | |
| tier_color = (PALETTE["teal"] if score >= 75 else | |
| PALETTE["amber"] if score >= 45 else PALETTE["red"]) | |
| tier = "Top Performer" if score >= 75 else "Average" if score >= 45 else "Underperformer" | |
| wedge_colors = [tier_color, "#E8E8E8"] | |
| ax2.pie([score, 100 - score], colors=wedge_colors, startangle=90, | |
| wedgeprops={"edgecolor": "white", "linewidth": 2}) | |
| ax2.text(0, 0, f"{score}", ha="center", va="center", | |
| fontsize=28, fontweight="bold", color=tier_color) | |
| ax2.set_title(f"Score: {tier}", fontweight="bold") | |
| # Panel 3: Est. rating count vs category benchmarks (synthetic) | |
| ax3 = axes[2] | |
| ax3.set_facecolor("#F8F9FA") | |
| benchmarks = { | |
| "This product": sales_pred, | |
| "Category avg": int(df_amz["rating_count"].mean()) if AMZ_OK else 15000, | |
| "Top 10%": int(df_amz["rating_count"].quantile(0.9)) if AMZ_OK else 50000, | |
| } | |
| bc = [PALETTE["pink"], PALETTE["blue"], PALETTE["blue"]] | |
| ax3.barh(list(benchmarks.keys()), list(benchmarks.values()), | |
| color=bc, edgecolor="white") | |
| ax3.set_title("Est. Sales vs Benchmarks", fontweight="bold") | |
| ax3.set_xlabel("Predicted Rating Count") | |
| plt.tight_layout() | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") | |
| plt.savefig(tmp.name, dpi=130, bbox_inches="tight", facecolor="#FAFAFA") | |
| plt.close() | |
| return tmp.name | |
| def make_spotify_chart(danceability, energy, loudness, tempo, valence, | |
| acousticness, speechiness, pop_pred, genre): | |
| import tempfile | |
| fig = plt.figure(figsize=(14, 4.5)) | |
| fig.patch.set_facecolor("#FAFAFA") | |
| fig.suptitle("Spotify Track β Audio Profile Dashboard", fontsize=13, fontweight="bold") | |
| # Panel 1: Radar | |
| ax1 = fig.add_subplot(1, 3, 1, polar=True) | |
| labels = ["Dance", "Energy", "Valence", "Acoust.", "Speech"] | |
| vals = [danceability, energy, valence, acousticness, speechiness] | |
| n = len(labels) | |
| angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist() | |
| vals_loop = vals + [vals[0]] | |
| angles_loop = angles + angles[:1] | |
| ax1.plot(angles_loop, vals_loop, color=PALETTE["blue"], linewidth=2) | |
| ax1.fill(angles_loop, vals_loop, color=PALETTE["blue"], alpha=0.25) | |
| ax1.set_xticks(angles) | |
| ax1.set_xticklabels(labels, fontsize=9) | |
| ax1.set_ylim(0, 1) | |
| ax1.set_yticks([0.25, 0.5, 0.75]) | |
| ax1.set_yticklabels(["", "", ""], fontsize=7) | |
| ax1.set_title("Audio Radar", fontweight="bold", pad=14) | |
| ax1.set_facecolor("#F0F4F8") | |
| ax1.grid(color="white") | |
| # Panel 2: Gauge | |
| ax2 = fig.add_subplot(1, 3, 2) | |
| ax2.set_facecolor("#F8F9FA") | |
| tier = ("Hit π₯" if pop_pred >= 70 else "Popular" if pop_pred >= 50 | |
| else "Mid-tier" if pop_pred >= 30 else "Niche") | |
| tier_color = (PALETTE["red"] if pop_pred >= 70 else | |
| PALETTE["teal"] if pop_pred >= 50 else | |
| PALETTE["amber"] if pop_pred >= 30 else "#888") | |
| ax2.pie([pop_pred, 100 - pop_pred], colors=[tier_color, "#E8E8E8"], | |
| startangle=90, wedgeprops={"edgecolor": "white", "linewidth": 2}) | |
| ax2.text(0, 0, f"{pop_pred:.0f}", ha="center", va="center", | |
| fontsize=28, fontweight="bold", color=tier_color) | |
| ax2.set_title(f"Popularity: {tier}", fontweight="bold") | |
| # Panel 3: Feature importance comparison (from model) | |
| ax3 = fig.add_subplot(1, 3, 3) | |
| ax3.set_facecolor("#F8F9FA") | |
| if SPOT_OK: | |
| imp = pd.Series(rf_spot.feature_importances_, index=features_spot).sort_values() | |
| ax3.barh(imp.index, imp.values, color=PALETTE["blue"], edgecolor="white") | |
| ax3.set_title("Feature Importance\n(model weights)", fontweight="bold") | |
| ax3.set_xlabel("Importance") | |
| else: | |
| ax3.text(0.5, 0.5, "Model not loaded", ha="center") | |
| plt.tight_layout() | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") | |
| plt.savefig(tmp.name, dpi=130, bbox_inches="tight", facecolor="#FAFAFA") | |
| plt.close() | |
| return tmp.name | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # AMAZON ANALYSIS FUNCTION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_amazon(category, actual_price, discount_pct, rating, review_text, use_gpt): | |
| discounted_price = actual_price * (1 - discount_pct / 100) | |
| sentiment_score = analyzer.polarity_scores(review_text)["compound"] if review_text else 0.0 | |
| sentiment_label = ("Positive" if sentiment_score >= 0.05 | |
| else "Negative" if sentiment_score <= -0.05 else "Neutral") | |
| if AMZ_OK: | |
| X = np.array([[actual_price, discounted_price, discount_pct, rating, sentiment_score]]) | |
| # Confidence interval via individual tree predictions | |
| tree_preds = np.array([t.predict(X)[0] for t in rf_amz.estimators_]) | |
| log_pred = tree_preds.mean() | |
| log_std = tree_preds.std() | |
| sales_pred = int(np.expm1(log_pred)) | |
| sales_low = int(np.expm1(max(0, log_pred - log_std))) | |
| sales_high = int(np.expm1(log_pred + log_std)) | |
| else: | |
| sales_pred = int(rating * 1000 * (1 + sentiment_score)) | |
| sales_low = int(sales_pred * 0.7) | |
| sales_high = int(sales_pred * 1.3) | |
| score = min(100, int( | |
| 25 * (rating / 5) + | |
| 25 * ((sentiment_score + 1) / 2) + | |
| 25 * min(sales_pred / 50000, 1) + | |
| 25 * min(discount_pct / 70, 1) | |
| )) | |
| tier = ("Top Performer" if score >= 75 else "Average" if score >= 45 else "Underperformer") | |
| # Chart | |
| chart_path = make_amazon_chart(rating, sentiment_score, discount_pct, score, sales_pred) | |
| # Text report | |
| report = ( | |
| f"π¦ AMAZON PRODUCT ANALYSIS\n{'β'*42}\n" | |
| f"Category: {category}\n" | |
| f"Actual Price: βΉ{actual_price:.0f}\n" | |
| f"Discounted Price: βΉ{discounted_price:.0f} (β{discount_pct}%)\n" | |
| f"Rating: {rating}/5\n" | |
| f"{'β'*42}\n" | |
| f"SENTIMENT\n" | |
| f" Score: {sentiment_score:+.3f} Label: {sentiment_label}\n" | |
| f"{'β'*42}\n" | |
| f"PREDICTED SALES\n" | |
| f" Est. Reviews: ~{sales_pred:,}\n" | |
| f" 90% Range: {sales_low:,} β {sales_high:,}\n" | |
| f"{'β'*42}\n" | |
| f"PERFORMANCE SCORE: {score}/100 ({tier})\n" | |
| ) | |
| # GPT or fallback | |
| gpt_section = "" | |
| if use_gpt: | |
| gpt_section = "\n" + get_amazon_gpt_insight( | |
| category, actual_price, discounted_price, discount_pct, | |
| rating, sentiment_score, sentiment_label, sales_pred, score | |
| ) | |
| session_history.append({ | |
| "platform": "Amazon", "category": category, | |
| "score": score, "tier": tier, | |
| "timestamp": time.strftime("%H:%M:%S"), | |
| }) | |
| return report.strip() + gpt_section, chart_path | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SPOTIFY ANALYSIS FUNCTION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_spotify(genre, danceability, energy, loudness, tempo, valence, | |
| acousticness, speechiness, instrumentalness, explicit, use_gpt): | |
| exp = int(explicit) | |
| if SPOT_OK: | |
| X = np.array([[danceability, energy, loudness, speechiness, acousticness, | |
| instrumentalness, valence, tempo, exp]]) | |
| tree_preds = np.array([t.predict(X)[0] for t in rf_spot.estimators_]) | |
| pop_pred = float(np.clip(tree_preds.mean(), 0, 100)) | |
| pop_std = tree_preds.std() | |
| else: | |
| pop_pred = float(np.clip(20 + 30*danceability + 15*energy + 0.5*(loudness+20), 0, 100)) | |
| pop_std = 5.0 | |
| tier = ("Hit π₯" if pop_pred >= 70 else "Popular" if pop_pred >= 50 | |
| else "Mid-tier" if pop_pred >= 30 else "Niche") | |
| pop_low = max(0, pop_pred - pop_std) | |
| pop_high = min(100, pop_pred + pop_std) | |
| chart_path = make_spotify_chart( | |
| danceability, energy, loudness, tempo, valence, | |
| acousticness, speechiness, pop_pred, genre | |
| ) | |
| report = ( | |
| f"π΅ SPOTIFY TRACK ANALYSIS\n{'β'*42}\n" | |
| f"Genre: {genre}\n" | |
| f"Tempo: {tempo:.0f} BPM\n" | |
| f"Explicit: {'Yes' if explicit else 'No'}\n" | |
| f"{'β'*42}\n" | |
| f"AUDIO FEATURES\n" | |
| f" Danceability: {danceability:.3f}\n" | |
| f" Energy: {energy:.3f}\n" | |
| f" Loudness: {loudness:.1f} dB\n" | |
| f" Valence: {valence:.3f}\n" | |
| f" Acousticness: {acousticness:.3f}\n" | |
| f" Speechiness: {speechiness:.3f}\n" | |
| f"{'β'*42}\n" | |
| f"PREDICTED POPULARITY\n" | |
| f" Score: {pop_pred:.1f}/100 ({tier})\n" | |
| f" Range: {pop_low:.1f} β {pop_high:.1f} (Β±1 std dev)\n" | |
| ) | |
| gpt_section = "" | |
| if use_gpt: | |
| gpt_section = "\n" + get_spotify_gpt_insight( | |
| genre, danceability, energy, loudness, tempo, | |
| valence, acousticness, pop_pred, tier | |
| ) | |
| session_history.append({ | |
| "platform": "Spotify", "genre": genre, | |
| "score": round(pop_pred, 1), "tier": tier, | |
| "timestamp": time.strftime("%H:%M:%S"), | |
| }) | |
| return report.strip() + gpt_section, chart_path | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SESSION HISTORY & PIPELINE TRIGGER | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_history(): | |
| if not session_history: | |
| return "No analyses run yet this session." | |
| lines = [f"{'#':<4} {'Time':<10} {'Platform':<10} {'Detail':<25} {'Score':<8} {'Tier'}"] | |
| lines.append("β" * 70) | |
| for i, h in enumerate(session_history[-10:], 1): | |
| detail = h.get("category", h.get("genre", "β")) | |
| lines.append(f"{i:<4} {h['timestamp']:<10} {h['platform']:<10} {detail:<25} {h['score']:<8} {h['tier']}") | |
| return "\n".join(lines) | |
| def run_pipeline(): | |
| """Trigger the agentic pipeline from the UI.""" | |
| if not os.path.exists("agentic_pipeline.py"): | |
| return "agentic_pipeline.py not found in current directory." | |
| try: | |
| result = subprocess.run( | |
| ["python3", "agentic_pipeline.py", "--mode", "both"], | |
| capture_output=True, text=True, timeout=120 | |
| ) | |
| out = result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout | |
| if result.returncode == 0: | |
| return f"β Pipeline completed successfully.\n\n{out}" | |
| else: | |
| return f"β Pipeline error:\n{result.stderr[:1000]}" | |
| except subprocess.TimeoutExpired: | |
| return "β Pipeline timed out after 120s." | |
| except Exception as e: | |
| return f"β Could not run pipeline: {e}" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # GRADIO INTERFACE | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CUSTOM_CSS = """ | |
| .gr-button-primary { background: #2E86AB !important; border: none !important; } | |
| .gr-button-secondary { border: 1px solid #2E86AB !important; color: #2E86AB !important; } | |
| footer { display: none !important; } | |
| """ | |
| with gr.Blocks( | |
| title="AI Performance Analyzer β Amazon Γ Spotify", | |
| theme=gr.themes.Soft(primary_hue="blue", secondary_hue="pink"), | |
| css=CUSTOM_CSS, | |
| ) as demo: | |
| gr.Markdown(""" | |
| # π€ AI Performance Analyzer | |
| ### Amazon Products Γ Spotify Tracks | |
| *Real-time ML predictions + GPT-4o-mini insights from a single interface* | |
| """) | |
| with gr.Tabs(): | |
| # ββ TAB 1: AMAZON ββββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("π Amazon Product"): | |
| gr.Markdown("### Predict product sales performance and get AI-powered strategy insights") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| amz_category = gr.Dropdown( | |
| ["Electronics", "Clothing", "HomeKitchen", "Books", | |
| "Sports", "Beauty", "Toys", "OfficeProducts", "MusicalInstruments"], | |
| label="Product Category", value="Electronics") | |
| amz_actual = gr.Slider(50, 80000, value=999, step=50, | |
| label="Actual Price (βΉ)") | |
| amz_discount = gr.Slider(0, 80, value=30, step=1, | |
| label="Discount %") | |
| amz_rating = gr.Slider(1.0, 5.0, value=4.2, step=0.1, | |
| label="Star Rating (/5)") | |
| amz_review = gr.Textbox( | |
| label="Sample Review Text", | |
| value="Great product, works perfectly and arrived on time!", | |
| lines=3, placeholder="Enter a customer review for sentiment analysis...") | |
| amz_gpt = gr.Checkbox(label="π€ Generate GPT-4o-mini AI insight", value=True) | |
| amz_btn = gr.Button("Analyze Product", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| amz_output = gr.Textbox(label="Analysis Report", lines=22) | |
| amz_plot = gr.Image(label="Performance Dashboard", type="filepath") | |
| amz_btn.click( | |
| analyze_amazon, | |
| inputs=[amz_category, amz_actual, amz_discount, amz_rating, amz_review, amz_gpt], | |
| outputs=[amz_output, amz_plot], | |
| ) | |
| # ββ TAB 2: SPOTIFY βββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("π΅ Spotify Track"): | |
| gr.Markdown("### Predict commercial success and get AI-powered music industry insights") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| sp_genre = gr.Dropdown( | |
| ["pop", "hip-hop", "rock", "electronic", "jazz", | |
| "r-n-b", "country", "latin", "indie", "classical"], | |
| label="Genre", value="pop") | |
| sp_dance = gr.Slider(0.0, 1.0, value=0.70, step=0.01, label="Danceability") | |
| sp_energy = gr.Slider(0.0, 1.0, value=0.80, step=0.01, label="Energy") | |
| sp_loud = gr.Slider(-40, 0, value=-7, step=0.5, label="Loudness (dB)") | |
| sp_tempo = gr.Slider(60, 200, value=120, step=1, label="Tempo (BPM)") | |
| sp_val = gr.Slider(0.0, 1.0, value=0.60, step=0.01, label="Valence (mood positivity)") | |
| sp_acou = gr.Slider(0.0, 1.0, value=0.10, step=0.01, label="Acousticness") | |
| sp_speech = gr.Slider(0.0, 1.0, value=0.05, step=0.01, label="Speechiness") | |
| sp_instr = gr.Slider(0.0, 1.0, value=0.00, step=0.01, label="Instrumentalness") | |
| sp_exp = gr.Checkbox(label="Explicit content", value=False) | |
| sp_gpt = gr.Checkbox(label="π€ Generate GPT-4o-mini AI insight", value=True) | |
| sp_btn = gr.Button("Analyze Track", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| sp_output = gr.Textbox(label="Analysis Report", lines=22) | |
| sp_plot = gr.Image(label="Audio Profile Dashboard", type="filepath") | |
| sp_btn.click( | |
| analyze_spotify, | |
| inputs=[sp_genre, sp_dance, sp_energy, sp_loud, sp_tempo, | |
| sp_val, sp_acou, sp_speech, sp_instr, sp_exp, sp_gpt], | |
| outputs=[sp_output, sp_plot], | |
| ) | |
| # ββ TAB 3: SESSION HISTORY βββββββββββββββββββββββββββ | |
| with gr.TabItem("π Session History"): | |
| gr.Markdown("### All analyses run this session") | |
| hist_output = gr.Textbox(label="Session Log", lines=15) | |
| hist_btn = gr.Button("Refresh History", variant="secondary") | |
| hist_btn.click(get_history, inputs=[], outputs=[hist_output]) | |
| # ββ TAB 4: PIPELINE ββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("βοΈ Agentic Pipeline"): | |
| gr.Markdown(""" | |
| ### Automated End-to-End Pipeline | |
| Runs the full agentic pipeline: data ingestion β synthetic generation β | |
| model training β inference β report generation. Single-command execution. | |
| """) | |
| pipe_btn = gr.Button("βΆ Run Agentic Pipeline", variant="primary", size="lg") | |
| pipe_output = gr.Textbox(label="Pipeline Output", lines=20) | |
| pipe_btn.click(run_pipeline, inputs=[], outputs=[pipe_output]) | |
| gr.Markdown(""" | |
| --- | |
| *Built with Gradio Β· Models: Random Forest (sklearn) Β· NLP: VADER Β· AI: GPT-4o-mini* | |
| *Set `OPENAI_API_KEY` as a Hugging Face Secret to enable live GPT insights* | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch(share=True) | |