Spaces:
Sleeping
Sleeping
| import dash | |
| from dash import html, dcc, Output, Input, State | |
| import pickle | |
| import pandas as pd | |
| import numpy as np | |
| import re | |
| import os | |
| # Load preprocessed data | |
| with open('processed_data.pkl', 'rb') as f: | |
| data = pickle.load(f) | |
| df = data['df'] | |
| similarity_matrix = data['similarity_matrix'] | |
| # Clean genres (remove empty) | |
| all_genres = sorted({genre for genres in df['genres'] for genre in genres if genre.strip() != ''}) | |
| genre_tabs = [{'label': genre, 'value': genre} for genre in all_genres] | |
| genre_tabs.insert(0, {'label': 'Most Popular', 'value': '__popular__'}) | |
| # Helper for recommendations | |
| def extract_series(name): | |
| return re.split(r'[:\-]', name)[0].strip().lower() | |
| def get_recommendations(game_id): | |
| idx = df[df['id'] == game_id].index[0] | |
| sim_scores = list(enumerate(similarity_matrix[idx])) | |
| sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)[1:] | |
| selected = [] | |
| seen_series = set() | |
| for i, score in sim_scores: | |
| name = df.iloc[i]['name'] | |
| series = extract_series(name) | |
| if series not in seen_series: | |
| selected.append({ | |
| 'id': int(df.iloc[i]['id']), | |
| 'name': name, | |
| 'image': df.iloc[i]['background_image'], | |
| 'rating': df.iloc[i]['rating'], | |
| 'platforms': df.iloc[i]['platforms'], | |
| }) | |
| seen_series.add(series) | |
| if len(selected) >= 5: | |
| break | |
| return selected | |
| app = dash.Dash(__name__) | |
| server = app.server | |
| app.title = "Game Recommender 🎮" | |
| # Layout | |
| app.layout = html.Div(className="app-background", children=[ | |
| html.Div(className="layout-container", children=[ | |
| html.Div(className="sidebar-panel", children=[ | |
| html.H2("Discover Games", className="sidebar-title"), | |
| dcc.Tabs( | |
| id='genre-tabs', | |
| value='__popular__', | |
| vertical=True, | |
| children=[ | |
| dcc.Tab(label=tab['label'], value=tab['value'], | |
| className="tab", selected_className="selected-tab") | |
| for tab in genre_tabs | |
| ], | |
| className="tabs-container" | |
| ) | |
| ]), | |
| html.Div(className="main-container", children=[ | |
| html.H1("🎮 Game Recommender", className="fancy-title"), | |
| dcc.Dropdown( | |
| id='game-dropdown', | |
| options=[{'label': row['name'], 'value': row['id']} for _, row in df.iterrows()], | |
| placeholder="Search for a game", | |
| className="custom-dropdown" | |
| ), | |
| dcc.Store(id='mode-store', data='discovery'), | |
| dcc.Store(id='cards-data-store'), | |
| dcc.Store(id='recommendations-data-store'), | |
| html.Div(id='search-output'), | |
| html.Div(id='grid-display'), | |
| ]) | |
| ]) | |
| ]) | |
| # 1️⃣ Render grid and store grid card metadata | |
| def render_grid(selected_tab, mode): | |
| if mode != 'discovery': | |
| return "", dash.no_update | |
| if selected_tab == '__popular__': | |
| filtered = df.sort_values(by='rating', ascending=False).head(20) | |
| else: | |
| filtered = df[df['genres'].apply(lambda genres: selected_tab in genres)].sort_values(by='rating', ascending=False).head(20) | |
| cards_data = [] | |
| grid_cards = [] | |
| for idx, row in enumerate(filtered.itertuples()): | |
| card_info = { | |
| 'id': row.id, | |
| 'name': row.name, | |
| 'image': row.background_image, | |
| 'rating': row.rating | |
| } | |
| cards_data.append(card_info) | |
| grid_cards.append( | |
| html.Div([ | |
| html.Img(src=row.background_image, className="rec-image"), | |
| html.Div([ | |
| html.H4(row.name, className="rec-title"), | |
| html.P(f"{row.rating} ⭐", className="rec-rating") | |
| ], className="rec-info") | |
| ], | |
| className="rec-card", | |
| n_clicks=0, | |
| id={'type': 'grid-card', 'index': idx}) | |
| ) | |
| return [ | |
| html.H3("Games", className="subtitle"), | |
| html.Div(grid_cards, className="recommendations-container") | |
| ], cards_data | |
| # 2️⃣ Render search output and store recommendation metadata | |
| def render_search(selected_id, mode): | |
| if mode != 'search' or selected_id is None: | |
| return "", dash.no_update | |
| row = df[df['id'] == selected_id].iloc[0] | |
| platforms_display = row['platforms'][:4] | |
| platforms_hidden = ', '.join(row['platforms']) | |
| main_game = html.Div([ | |
| html.Div([ | |
| html.Img(src=row['background_image'], className='main-image'), | |
| html.Div([ | |
| html.H2(row['name'], className='main-title'), | |
| html.P(f"Rating: {row['rating']} ⭐"), | |
| html.P(f"Metacritic: {row['metacritic']} 🎯"), | |
| html.P(f"Genres: {', '.join(row['genres'])}"), | |
| html.P("Platforms:"), | |
| html.Div([ | |
| html.Span(', '.join(platforms_display), title=platforms_hidden, className="platform-text") | |
| ]) | |
| ], className="main-details") | |
| ], className="main-card-inner") | |
| ], className="main-card") | |
| recs = get_recommendations(selected_id) | |
| rec_cards = [] | |
| rec_data = [] | |
| for idx, rec in enumerate(recs): | |
| rec_cards.append( | |
| html.Div([ | |
| html.Img(src=rec['image'], className="rec-image"), | |
| html.Div([ | |
| html.H4(rec['name'], className="rec-title"), | |
| html.P(f"{rec['rating']} ⭐", className="rec-rating") | |
| ], className="rec-info") | |
| ], className="rec-card", | |
| n_clicks=0, | |
| id={'type': 'rec-card', 'index': idx}) | |
| ) | |
| rec_data.append({'id': rec['id'], 'name': rec['name']}) | |
| return html.Div([ | |
| main_game, | |
| html.H3("You May Also Like:", className="subtitle"), | |
| html.Div(rec_cards, className="recommendations-container") | |
| ]), rec_data | |
| # 3️⃣ Unified callback handling all clicks + dropdown + tabs | |
| def handle_inputs(dropdown_value, tab_value, grid_clicks, rec_clicks, cards_data, rec_data): | |
| ctx = dash.callback_context | |
| if not ctx.triggered: | |
| raise dash.exceptions.PreventUpdate | |
| triggered = ctx.triggered[0]['prop_id'] | |
| # Handle grid card click | |
| if "grid-card" in triggered: | |
| for i, clicks in enumerate(grid_clicks): | |
| if clicks and clicks > 0: | |
| clicked_game_id = cards_data[i]['id'] | |
| return clicked_game_id, 'search' | |
| # Handle recommendation card click | |
| if "rec-card" in triggered: | |
| for i, clicks in enumerate(rec_clicks): | |
| if clicks and clicks > 0: | |
| clicked_game_id = rec_data[i]['id'] | |
| return clicked_game_id, 'search' | |
| # Handle search dropdown | |
| if 'game-dropdown' in triggered and dropdown_value is not None: | |
| return dropdown_value, 'search' | |
| # Handle tab change | |
| if 'genre-tabs' in triggered: | |
| return dash.no_update, 'discovery' | |
| return dash.no_update, dash.no_update | |
| if __name__ == '__main__': | |
| app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), debug=False) | |