VJyzCELERY commited on
Commit
5450dc1
·
1 Parent(s): 6f8c133

First Commit

Browse files
Files changed (5) hide show
  1. GameRecommender.py +334 -0
  2. app.py +232 -0
  3. component.py +301 -0
  4. requirements.txt +193 -0
  5. style.css +208 -0
GameRecommender.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from sklearn.neighbors import KNeighborsClassifier
4
+ from sklearn.preprocessing import MultiLabelBinarizer,LabelEncoder,MinMaxScaler
5
+ from sklearn.feature_extraction.text import TfidfVectorizer
6
+ import joblib
7
+ from sklearn.decomposition import TruncatedSVD
8
+ from sklearn.metrics import classification_report
9
+ from xgboost import XGBClassifier
10
+ import nltk
11
+ from nltk.tokenize import word_tokenize
12
+ from nltk.corpus import stopwords
13
+ from nltk.stem import WordNetLemmatizer
14
+ from nltk.tag import pos_tag
15
+ import string
16
+ import re
17
+ import os
18
+ nltk.download('punkt')
19
+ nltk.download('averaged_perceptron_tagger_eng')
20
+ nltk.download('wordnet')
21
+
22
+ class CollaborativeRecommender:
23
+ def __init__(self, svd_matrix, item_to_index, index_to_item):
24
+ """
25
+ svd_matrix: 2D numpy array (items x latent features)
26
+ item_to_index: dict mapping app_id to row index in svd_matrix
27
+ index_to_item: dict mapping row index to app_id
28
+ """
29
+ self.svd_matrix : TruncatedSVD = svd_matrix
30
+ self.item_to_index = item_to_index
31
+ self.index_to_item = index_to_item
32
+
33
+ def save(self, path: str):
34
+ """Save the entire model as a single file using joblib."""
35
+ joblib.dump(self, path)
36
+
37
+ @staticmethod
38
+ def load(path: str):
39
+ """Load the entire model from a joblib file."""
40
+ return joblib.load(path)
41
+
42
+ def _get_item_vector(self, app_id):
43
+ idx = self.item_to_index.get(app_id)
44
+ if idx is None:
45
+ raise ValueError(f"app_id {app_id} not found in the model.")
46
+ return self.svd_matrix[idx]
47
+
48
+ def _cosine_similarity(self, vec, matrix):
49
+ # Cosine similarity between vec and all rows in matrix
50
+ vec_norm = np.linalg.norm(vec)
51
+ matrix_norms = np.linalg.norm(matrix, axis=1)
52
+ similarity = (matrix @ vec) / (matrix_norms * vec_norm + 1e-10)
53
+ return similarity
54
+
55
+ def get_similarities(self, app_ids,top_n=None):
56
+ """
57
+ Input: app_ids - single app_id or list of app_ids
58
+ Output: DataFrame with columns ['app_id', 'similarity'] sorted by similarity descending
59
+ """
60
+ if isinstance(app_ids, (str, int)):
61
+ app_ids = [app_ids]
62
+ elif not isinstance(app_ids, (list, tuple, np.ndarray)):
63
+ raise TypeError("app_ids must be a string/int or a list of such")
64
+
65
+ valid_vectors = []
66
+ missing_ids = []
67
+ for app_id in app_ids:
68
+ try:
69
+ vec = self._get_item_vector(app_id)
70
+ valid_vectors.append(vec)
71
+ except ValueError:
72
+ missing_ids.append(app_id)
73
+
74
+ if len(valid_vectors) == 0:
75
+ raise ValueError("None of the input app_ids were found in the model.")
76
+
77
+ # Aggregate vectors by averaging if multiple inputs
78
+ aggregated_vec = np.mean(valid_vectors, axis=0)
79
+
80
+ # Compute similarity with all items
81
+ similarities = self._cosine_similarity(aggregated_vec, self.svd_matrix)
82
+
83
+ # Build DataFrame of results
84
+ result_df = pd.DataFrame({
85
+ 'app_id': [self.index_to_item[i] for i in range(len(similarities))],
86
+ 'collaborative_similarity': similarities
87
+ })
88
+
89
+ # Exclude the input app_ids themselves from results
90
+ result_df = result_df[~result_df['app_id'].isin(app_ids)]
91
+
92
+ # Sort descending by similarity
93
+ result_df = result_df.sort_values('collaborative_similarity', ascending=False).reset_index(drop=True)
94
+
95
+ # If any input app_ids were missing, notify user (optional)
96
+ if missing_ids:
97
+ print(f"Warning: These app_ids were not found in the model and ignored: {missing_ids}")
98
+ if top_n:
99
+ return result_df.head(top_n)
100
+ else:
101
+ return result_df
102
+
103
+ class GameContentRecommender:
104
+ def __init__(self,model,genre_encoder,category_encoder,price_range_encoder,scaler,app_id_encoder):
105
+ self.model : KNeighborsClassifier = model
106
+ self.genre_encoder : MultiLabelBinarizer = genre_encoder
107
+ self.category_encoder : MultiLabelBinarizer = category_encoder
108
+ self.price_range_encoder : LabelEncoder = price_range_encoder
109
+ self.scaler : MinMaxScaler = scaler
110
+ self.app_id_encoder : LabelEncoder = app_id_encoder
111
+
112
+ def save(self, path: str):
113
+ """Save the entire model as a single file using joblib."""
114
+ joblib.dump(self, path)
115
+
116
+ @staticmethod
117
+ def load(path: str):
118
+ """Load the entire model from a joblib file."""
119
+ return joblib.load(path)
120
+
121
+ def predict(self, price_range, year_release, average_playtime, game_score, dlc_count, genres, categories, top_n=None):
122
+ genre_dict = {g: 0 for g in self.genre_encoder.classes_}
123
+ categories_dict = {c: 0 for c in self.category_encoder.classes_}
124
+
125
+ for genre in genres:
126
+ if genre != 'Unknown' and genre in genre_dict:
127
+ genre_dict[genre] = 1
128
+
129
+ for category in categories:
130
+ if category != 'Unknown' and category in categories_dict:
131
+ categories_dict[category] = 1
132
+
133
+ price_range = self.price_range_encoder.transform(np.array(price_range).reshape(-1, 1))
134
+ scaled_features = self.scaler.transform(np.array([[year_release, average_playtime, game_score, dlc_count]]))[0]
135
+
136
+ user_vector = list(scaled_features) + list(price_range) + list(genre_dict.values()) + list(categories_dict.values())
137
+
138
+ user_df = pd.DataFrame([user_vector])
139
+
140
+ distances, indices = self.model.kneighbors(user_df)
141
+ distances = distances.flatten()
142
+ indices = indices.flatten()
143
+
144
+ similarity = 1 / (1 + distances)
145
+
146
+ app_ids = self.app_id_encoder.inverse_transform(indices)
147
+
148
+ prediction = pd.DataFrame({
149
+ 'app_id': app_ids,
150
+ 'content_probability': similarity
151
+ })
152
+
153
+ if top_n:
154
+ prediction = prediction.head(top_n)
155
+
156
+ return prediction
157
+
158
+
159
+
160
+ class TextBasedRecommendation():
161
+ def __init__(self,classifier,vectorizer,app_id_encoder,history):
162
+ self.classifier : XGBClassifier = classifier
163
+ self.vectorizer : TfidfVectorizer = vectorizer
164
+ self.app_id_encoder : LabelEncoder = app_id_encoder
165
+ self.history = history
166
+
167
+ def save(self, path_prefix: str):
168
+ self.classifier.save_model(f"{path_prefix}_xgb.json")
169
+
170
+ classifier_backup = self.classifier
171
+ self.classifier = None
172
+
173
+ joblib.dump(self, f"{path_prefix}_preprocessor.joblib")
174
+
175
+ self.classifier = classifier_backup
176
+
177
+ @staticmethod
178
+ def load(path_prefix: str):
179
+ obj = joblib.load(f"{path_prefix}_preprocessor.joblib")
180
+ xgb = XGBClassifier()
181
+ xgb.load_model(f"{path_prefix}_xgb.json")
182
+ obj.classifier = xgb
183
+
184
+ return obj
185
+
186
+ def preprocess(self,text : str):
187
+ stopword = stopwords.words('english')
188
+ lemmatizer = WordNetLemmatizer()
189
+ def convert_postag(postag:str):
190
+ if postag.startswith('V'):
191
+ return 'v'
192
+ elif postag.startswith('R'):
193
+ return 'r'
194
+ elif postag.startswith('J'):
195
+ return 'a'
196
+ return 'n'
197
+
198
+ def clean_space(text : str):
199
+ if not isinstance(text, str):
200
+ return ''
201
+ cleaned = re.sub(r'\s+', ' ', text.replace('\n', ' ')).strip()
202
+ return cleaned
203
+
204
+ def tokenize(text : str):
205
+ text = text.lower()
206
+ text = clean_space(text)
207
+ token = word_tokenize(text)
208
+ token = [word for word in token if word not in
209
+ string.punctuation and word not in stopword and word.isalpha()]
210
+ return token
211
+
212
+ # lemmatize
213
+ def lemmatizing(token : str):
214
+ postag = pos_tag(token)
215
+ lemmatized = [lemmatizer.lemmatize(word,convert_postag(tag)) for word,tag in postag]
216
+ return lemmatized
217
+
218
+ token = tokenize(text)
219
+ token = lemmatizing(token)
220
+ return " ".join(token)
221
+
222
+ def get_accuracy(self,X_test,y_test):
223
+ y_pred = self.classifier.predict(self.vectorizer.transform(X_test))
224
+ y_test = self.app_id_encoder.transform(y_test)
225
+ print(classification_report(y_test,y_pred))
226
+
227
+ def predict(self,text,top_n=None):
228
+ cleaned_text = self.preprocess(text)
229
+ vectorized_text = self.vectorizer.transform([cleaned_text])
230
+ proba = self.classifier.predict_proba(vectorized_text)[0]
231
+ class_indices = np.argsort(proba)[::-1]
232
+ if top_n is not None:
233
+ class_indices = class_indices[:top_n]
234
+ class_labels = self.app_id_encoder.inverse_transform(class_indices)
235
+ class_probs = proba[class_indices]
236
+ return pd.DataFrame({
237
+ 'app_id': class_labels,
238
+ 'text_probability': class_probs
239
+ })
240
+
241
+ class GameRecommendationEnsemble:
242
+ def __init__(self,game_content_recommeder,collaborative_recommender,text_based_recommender):
243
+ self.game_content_recommeder : GameContentRecommender=game_content_recommeder
244
+ self.collaborative_recommender : CollaborativeRecommender=collaborative_recommender
245
+ self.text_based_recommender : TextBasedRecommendation = text_based_recommender
246
+
247
+ def save(self, dir_path: str):
248
+ os.makedirs(dir_path, exist_ok=True)
249
+ self.game_content_recommeder.save(os.path.join(dir_path, "game_content_recommender.joblib"))
250
+ self.collaborative_recommender.save(os.path.join(dir_path, "collaborative_recommender.joblib"))
251
+ self.text_based_recommender.save(os.path.join(dir_path, "text_based_recommender"))
252
+
253
+ @staticmethod
254
+ def load(dir_path: str):
255
+ game_content_recommender = GameContentRecommender.load(os.path.join(dir_path, "game_content_recommender.joblib"))
256
+ collaborative_recommender = CollaborativeRecommender.load(os.path.join(dir_path, "collaborative_recommender.joblib"))
257
+ text_based_recommender = TextBasedRecommendation.load(os.path.join(dir_path, "text_based_recommender"))
258
+
259
+ return GameRecommendationEnsemble(
260
+ game_content_recommender,
261
+ collaborative_recommender,
262
+ text_based_recommender
263
+ )
264
+
265
+ def scale_proba(self,series):
266
+ if len(series)<=1:
267
+ return pd.Series([1.0] * len(series), index=series.index)
268
+ scaler = MinMaxScaler()
269
+ scaled = scaler.fit_transform(series.values.reshape(-1, 1)).flatten()
270
+ return pd.Series(scaled, index=series.index)
271
+
272
+ def predict(self, description=None, app_ids=None, price_range=None, year_release=None,
273
+ average_playtime=None, game_score=None, dlc_count=None,
274
+ genres=None, categories=None, top_n=None,
275
+ weight_text=1.0, weight_collab=1.0, weight_content=1.0):
276
+
277
+ merge_dfs = []
278
+ if description is not None:
279
+ text_proba = self.text_based_recommender.predict(description)
280
+ text_proba['app_id'] = text_proba['app_id'].astype(str)
281
+ text_proba['text_probability'] = self.scale_proba(text_proba['text_probability'])
282
+ merge_dfs.append(text_proba)
283
+ else:
284
+ weight_text=0
285
+
286
+ # Collaborative similarity (only if app_ids is provided)
287
+ if app_ids is not None:
288
+ similar_app = self.collaborative_recommender.get_similarities(app_ids)
289
+ similar_app['app_id'] = similar_app['app_id'].astype(str)
290
+ similar_app['collaborative_similarity'] = self.scale_proba(similar_app['collaborative_similarity'])
291
+ merge_dfs.append(similar_app)
292
+ else:
293
+ weight_collab = 0 # No weight if not used
294
+
295
+ if None in (price_range, year_release,average_playtime,game_score,dlc_count, genres, categories):
296
+ weight_content=0
297
+ else:
298
+ similar_content = self.game_content_recommeder.predict(price_range, year_release,average_playtime,game_score,dlc_count, genres, categories)
299
+ similar_content['app_id'] = similar_content['app_id'].astype(str)
300
+ similar_content['content_probability'] = self.scale_proba(similar_content['content_probability'])
301
+ merge_dfs.append(similar_content)
302
+
303
+ if not merge_dfs:
304
+ return None
305
+
306
+ from functools import reduce
307
+ merged = reduce(lambda left, right: pd.merge(left, right, on='app_id', how='outer'), merge_dfs)
308
+
309
+ # Fill missing values
310
+ merged = merged.fillna(0)
311
+
312
+ # Final score calculation
313
+ def compute_aggregated_score(df, w_text, w_collab, w_content):
314
+ # Normalize weights (prevent divide-by-zero if one or more weights are 0)
315
+ total_weight = w_text + w_collab + w_content
316
+ if total_weight == 0:
317
+ raise ValueError("All weights are zero. At least one weight must be positive.")
318
+
319
+ w_text /= total_weight
320
+ w_collab /= total_weight
321
+ w_content /= total_weight
322
+
323
+ df['final_score'] = (
324
+ df.get('text_probability', 0) * w_text +
325
+ df.get('collaborative_similarity', 0) * w_collab +
326
+ df.get('content_probability', 0) * w_content
327
+ )
328
+
329
+ return df.sort_values(by='final_score', ascending=False).reset_index(drop=True)
330
+ final_df = compute_aggregated_score(merged, weight_text, weight_collab, weight_content)
331
+ if top_n:
332
+ return final_df.head(top_n)
333
+ else:
334
+ return final_df
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import os
4
+ from component import *
5
+ from GameRecommender import *
6
+ import gc
7
+ from sklearn.model_selection import train_test_split
8
+ from huggingface_hub import snapshot_download
9
+ from sklearn.preprocessing import MultiLabelBinarizer,LabelEncoder,MinMaxScaler
10
+
11
+ DATA_BASE_PATH = 'data'
12
+ # MODEL_BASE_PATH = 'models'
13
+ MODEL_BASE_PATH = snapshot_download(
14
+ repo_id="VJyzCELERY/SteamGameRecommender",
15
+ repo_type="model",
16
+ allow_patterns=["GameRecommender/*"]
17
+ )
18
+ SEED = 42
19
+ RAW_GAMES_DATAPATH = os.path.join(DATA_BASE_PATH,'converted.csv')
20
+ GAMES_DATAPATH = os.path.join(DATA_BASE_PATH,'Cleaned_games.csv')
21
+ REVIEWS_DATAPATH = os.path.join(DATA_BASE_PATH,'MergedFragmentData_SAMPLE.csv')
22
+ TRIMMED_REVIEW_DATAPATH = os.path.join(DATA_BASE_PATH,'Trimmed_Dataset.csv')
23
+ USER_PREFERENCE_DATAPATH = os.path.join(DATA_BASE_PATH,'UserPreferenceDF.csv')
24
+ MODEL_PATH = os.path.join(MODEL_BASE_PATH,'GameRecommender')
25
+ from datasets import load_dataset
26
+
27
+ GAMES_DS = load_dataset("VJyzCELERY/Cleaned_games")
28
+
29
+
30
+ # load dataset
31
+
32
+ model = GameRecommendationEnsemble.load(MODEL_PATH)
33
+ vectorizer=model.text_based_recommender.vectorizer
34
+ review_app_id_encoder=model.text_based_recommender.app_id_encoder
35
+ genres = model.game_content_recommeder.genre_encoder.classes_.tolist()
36
+ genres = [genre for genre in genres if genre != 'Unknown']
37
+ categories = model.game_content_recommeder.category_encoder.classes_.tolist()
38
+ categories = [cat for cat in categories if cat != 'Unknown']
39
+ price_ranges = model.game_content_recommeder.price_range_encoder.classes_.tolist()
40
+ selectable_app_ids = list(model.collaborative_recommender.item_to_index.keys())
41
+ # df_games = pd.read_csv(GAMES_DATAPATH,index_col=False)
42
+
43
+ df_games = GAMES_DS['train'].to_pandas()
44
+ available_names = df_games[df_games['app_id'].astype(str).isin(selectable_app_ids)]['Name'].tolist()
45
+
46
+ def recommend_game(description=None, app_name=None, price_range=None, year_release=None,
47
+ excpected_playtime=None, game_score=None, dlc_count=None,
48
+ genres=None, categories=None, top_n=5,weight_text=1.0, weight_collab=1.0, weight_content=1.0):
49
+ if app_name:
50
+ if isinstance(app_name, (str)):
51
+ app_name = [app_name]
52
+ app_ids = df_games[df_games['Name'].isin(app_name)]['app_id'].astype(str).tolist()
53
+ else:
54
+ app_ids = None
55
+ prediction = model.predict(description=description,app_ids=app_ids,price_range=price_range,year_release=year_release,average_playtime=excpected_playtime,game_score=game_score,
56
+ dlc_count=dlc_count,genres=genres,categories=categories,top_n=top_n,weight_text=weight_text,weight_collab=weight_collab,weight_content=weight_content)
57
+ app_ids = prediction['app_id'].tolist()
58
+ output = df_games.loc[df_games['app_id'].astype(str).isin(app_ids)].reset_index()
59
+ return gr.DataFrame(value=output)
60
+
61
+ # Load external CSS file
62
+ with open('style.css', 'r') as f:
63
+ custom_css = f.read()
64
+ # for nav
65
+ def set_active_section(btn_id):
66
+ """
67
+ button active function and handle visibility section
68
+ """
69
+ # First set all sections to invisible
70
+ updates = [gr.update(visible=False) for _ in sections]
71
+
72
+ # Then set the selected section to visible
73
+ if btn_id in sections:
74
+ index = list(sections.keys()).index(btn_id)
75
+ updates[index] = gr.update(visible=True)
76
+
77
+ # Also update button active states
78
+ button_states = []
79
+ for btn in nav_buttons:
80
+ state = ("active" if btn.elem_id == btn_id else "")
81
+ button_states.append(gr.update(elem_classes=f"nav-btn {state}"))
82
+
83
+ return updates + button_states
84
+
85
+ """
86
+ MAIN DEMO
87
+ """
88
+ with gr.Blocks(css = custom_css) as demo:
89
+ # container
90
+ with gr.Row(elem_classes="container"):
91
+ # navbar
92
+ with gr.Sidebar(elem_classes="navbar"):
93
+
94
+ # nav header
95
+ with gr.Column(elem_classes="nav-header"):
96
+ gr.Markdown("# Game Recommendation by Your Preference")
97
+
98
+ # nav button container
99
+ with gr.Column(elem_classes="nav-buttons"):
100
+ # nav button list
101
+ nav_buttons = []
102
+ sections = [
103
+ ('Home', 'home'),
104
+ ("Dataset", "dataset"),
105
+ ("Exploratory Data Analysis", "eda"),
106
+ ("Preprocessing Data", "preprocess"),
107
+ ("Training Result", "training"),
108
+ ("Our System", "system")
109
+ ]
110
+ # create button
111
+ for label, section_id in sections:
112
+ button = gr.Button(label, elem_classes="nav-btn", elem_id=f"btn-{section_id}")
113
+ nav_buttons.append(button)
114
+
115
+ # Recommendation system
116
+ with gr.Column(elem_id="system", elem_classes='content-section', visible=False) as system_section:
117
+ # special for this section
118
+ gr.HTML('<h1 class="header-title">Game Recommendation System</h1>', elem_id='system')
119
+ with gr.Row():
120
+ with gr.Column(min_width=500, elem_classes='input-column'):
121
+
122
+ app_name = input_choice(
123
+ Label='Select games that you liked',
124
+ Choices=available_names,
125
+ Multiselect=True
126
+ )
127
+
128
+ year = input_number(
129
+ Label='Year Release',
130
+ Precision=0,
131
+ minimum=0
132
+ )
133
+
134
+ expected_playtime = input_number(
135
+ Label='Expected Playtime (Hours)',
136
+ Precision=2,
137
+ minimum=0
138
+ )
139
+
140
+ expected_score = input_number(
141
+ Label='Expected Score (% Positive)',
142
+ Precision=2,
143
+ minimum=0
144
+ )
145
+
146
+ dlc_count = input_number(
147
+ Label='DLC Count',
148
+ Precision=0,
149
+ minimum=0
150
+ )
151
+
152
+ description = input_paragaph_textbox('Description', 'Describe the game (max 1200 characters)...')
153
+
154
+ genre = input_choice(
155
+ Label="Select Your Genre (Multiple Choice)",
156
+ Choices=genres,
157
+ Multiselect=True
158
+ )
159
+
160
+ categories = input_choice(
161
+ Label="Select Your Categories (Multiple Choice)",
162
+ Choices=categories,
163
+ Multiselect=True
164
+ )
165
+
166
+ # single selection (multiselect=False)
167
+ price_range = input_choice(
168
+ Label="Select Your Price Range (Only Single Choice)",
169
+ Choices=price_ranges,
170
+ Multiselect=False
171
+ )
172
+
173
+ top_n= input_number(
174
+ Label='Output amount',
175
+ Precision=0,
176
+ minimum=0,
177
+ value=10
178
+ )
179
+ weight_text = input_number(
180
+ Label='Weight Text',
181
+ Precision=2,
182
+ minimum=0,
183
+ maximum=1,
184
+ value=0.5,
185
+ step=0.01
186
+ )
187
+ weight_collab = input_number(
188
+ Label='Weight Of Collaborative Model',
189
+ Precision=2,
190
+ minimum=0,
191
+ maximum=1,
192
+ value=0.5,
193
+ step=0.01
194
+ )
195
+ weight_content = input_number(
196
+ Label='Weight Of Content Based Model',
197
+ Precision=2,
198
+ minimum=0,
199
+ maximum=1,
200
+ value=0.5,
201
+ step=0.01
202
+ )
203
+ submit_btn = gr.Button("Get Recommendations", variant="primary", elem_id="submit-btn")
204
+
205
+ # Results column
206
+ with gr.Column(min_width=500, elem_classes='results-column'):
207
+ h2('Result')
208
+ with gr.Column(elem_id='Output'):
209
+ # Results column using the modular component
210
+ h2('Recommended Game')
211
+ recommended_game = gr.DataFrame()
212
+ # click button logic
213
+ submit_btn.click(
214
+ fn=recommend_game,
215
+ inputs=[description,app_name,price_range,year,expected_playtime,expected_score,dlc_count, genre, categories,top_n,weight_text,weight_collab,weight_content],
216
+ outputs=recommended_game
217
+ )
218
+
219
+ # Navigation logic
220
+ sections = {
221
+ "btn-system": system_section
222
+ }
223
+
224
+ # Set click events for navigation buttons
225
+ for btn in nav_buttons:
226
+ btn.click(
227
+ set_active_section,
228
+ inputs=gr.State(btn.elem_id),
229
+ outputs=list(sections.values()) + nav_buttons
230
+ )
231
+
232
+ demo.launch()
component.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import matplotlib
4
+ matplotlib.use("Agg")
5
+ import matplotlib.pyplot as plt
6
+ import inspect
7
+ import io
8
+
9
+
10
+ # style formating for Header
11
+ def header(input:str):
12
+ """
13
+ Usage:
14
+ header('your text')
15
+ Output:
16
+ <h1 class="header"> {input} <h1>
17
+ output will be bold. use for container header only
18
+ Args:
19
+ input (str): _header_Title_
20
+ """
21
+ gr.Markdown(f"# {input}", elem_classes='header')
22
+
23
+ # style formating for Header2
24
+ def h2(input:str):
25
+ """
26
+ Usage:
27
+ h2('your text')
28
+ Output:
29
+ <h2 class="subheader"> {input} <h2>
30
+ output will be bold. use for optional
31
+ Args:
32
+ input (str): _subheader_Title_
33
+ """
34
+ gr.Markdown(f'<h2 class="subheader" style="black">{input}</h2>')
35
+
36
+ # style formating for Text
37
+ def p(input:str):
38
+ """
39
+ Usage:
40
+ p('''
41
+ text <br>
42
+ text
43
+ ''')
44
+
45
+ or
46
+
47
+ p('text')
48
+ Outputs:
49
+ Multiple <p class="desc">...</p> blocks, one per paragraph.
50
+ """
51
+ paragraphs = input.strip().split("<br>")
52
+ text = ''.join(f'<p class="desc">{para.strip()}</p>' for para in paragraphs if para.strip())
53
+ return gr.Markdown(text)
54
+
55
+ # this for displaying dataframe and also provied downlaod csv
56
+ def Dataset(df,title, source, key=None):
57
+ """
58
+ Creates a reusable dataset display component.
59
+ This is displaying title, dataframe, and provide download button
60
+ file path means file
61
+ Args:
62
+ df (pd.DataFrame): Dataset to display
63
+ title (str): Title for the dataset display
64
+ file_path (str): Path to the CSV file for download (the file name following the path)
65
+ key (str): Optional unique identifier for Gradio components
66
+ """
67
+ def get_file():
68
+ return source
69
+
70
+ with gr.Column(elem_classes='dataframe-layout', elem_id=f"dataset-{key}" if key else None):
71
+ # Title and download button in a row
72
+ with gr.Row():
73
+ gr.Markdown(f'<h1 class="subtitle">{title}</h1>') # title formating
74
+ download_btn = gr.DownloadButton(
75
+ label="Download CSV",
76
+ value=get_file,
77
+ elem_id=f"download-{key}" if key else None
78
+ )
79
+
80
+ # Dataframe display
81
+ df_display=gr.Dataframe(
82
+ value=df.head(100),
83
+ headers=list(df.columns),
84
+ elem_id=f"table-{key}" if key else None,
85
+ interactive=False, # read only
86
+ # disable the warp for reduce height of data
87
+ # wrap=True
88
+ )
89
+ return df_display
90
+
91
+ def describe_value_counts(series):
92
+ description = series.describe().to_frame(name='value')
93
+ description = description.reset_index() # Move index (stat name) into column
94
+ description.columns = ['Statistic', 'Value']
95
+ return description
96
+
97
+ # this is for EDA, preprocess
98
+ def plot_distribution(df, column):
99
+ """
100
+ Generates a matplotlib plot (bar chart or histogram) showing the distribution
101
+ of values in a selected column from the dataframe.
102
+
103
+ Parameters:
104
+ -----------
105
+ df : pd.DataFrame
106
+ The dataframe to plot from.
107
+ column : str
108
+ The column name to visualize.
109
+
110
+ Returns:
111
+ --------
112
+ matplotlib.figure.Figure
113
+ A figure object representing the distribution plot.
114
+ """
115
+ fig, ax = plt.subplots(figsize=(10, 5))
116
+
117
+ if df[column].dtype == 'object' or df[column].nunique() < 20:
118
+ # Bar plot for categorical/small unique values
119
+ value_counts = df[column].value_counts().head(20)
120
+ ax.bar(value_counts.index, value_counts.values)
121
+ ax.set_xticklabels(value_counts.index, rotation=45, ha='right')
122
+ ax.set_ylabel('Count')
123
+ ax.set_title(f'Distribution of {column}')
124
+ else:
125
+ # Histogram for numerical
126
+ ax.hist(df[column].dropna(), bins=100, edgecolor='black')
127
+ ax.set_title(f'Distribution of {column}')
128
+ ax.set_xlabel(column)
129
+ ax.set_ylabel('Frequency')
130
+
131
+ fig.tight_layout()
132
+ return fig
133
+
134
+ ## this is for eda, preprocess, and training
135
+ def code_cell(code):
136
+ """
137
+ simply syntax for gr.code
138
+ Usage :
139
+ Code_cell('df = pd.read_csv(path)')
140
+ or
141
+ using triple string for multiple line
142
+ code_cell("""""")
143
+ """
144
+ gr.Code(inspect.cleandoc(code), language='python')
145
+
146
+ ## This for EDA, Preprocess, and training
147
+ def plot_training_results(results: dict):
148
+ """
149
+ Plots the training metrics: merror and mlogloss from the result dictionary.
150
+
151
+ This function generates a line plot that visualizes the model's training
152
+ performance over time (e.g., across epochs or folds), using the merror
153
+ (training error) and mlogloss (log loss) values.
154
+
155
+ Args:
156
+ results (dict): A dictionary containing two keys:
157
+ - 'merror': list of training error values.
158
+ - 'mlogloss': list of log loss values.
159
+ Example:
160
+ {
161
+ "merror": [0.12, 0.10, 0.08],
162
+ "mlogloss": [0.35, 0.32, 0.30]
163
+ }
164
+
165
+ Returns:
166
+ matplotlib.figure.Figure: A Matplotlib figure showing the trends of
167
+ training error and log loss as line plots.
168
+
169
+ Example:
170
+ results = {
171
+ "merror": [0.12, 0.10, 0.08],
172
+ "mlogloss": [0.35, 0.32, 0.30]
173
+ }
174
+ plot_output = gr.Plot()
175
+ btn = gr.Button("Generate Plot")
176
+ btn.click(fn=lambda:plot_training_results(results), inputs=[], outputs=plot_output, preprocess=False)
177
+ """
178
+ epochs = list(range(1, len(results["merror"]) + 1))
179
+
180
+ plt.figure(figsize=(8, 5))
181
+ plt.plot(epochs, results["merror"], marker='o', label='Training Error (merror)', color='blue')
182
+ plt.plot(epochs, results["mlogloss"], marker='s', label='Log Loss (mlogloss)', color='orange')
183
+
184
+ plt.title('Training Metrics Over Time')
185
+ plt.xlabel('Epoch / Fold')
186
+ plt.ylabel('Value')
187
+ plt.legend()
188
+ plt.grid(True)
189
+ plt.tight_layout()
190
+
191
+ return plt.gcf()
192
+
193
+ # for Recommendation section
194
+ def input_name_textbox(Label:str, Placeholder:str):
195
+ """
196
+ usage:
197
+ app_name = input_name_textbox('Input Your App', 'Enter game title...')
198
+ Args:
199
+ Label (str): Title textbox
200
+ Placeholder (str): placeholder text
201
+
202
+ Returns:
203
+ variable : str
204
+ """
205
+
206
+ inputbox = gr.Textbox(
207
+ label=Label,
208
+ placeholder=Placeholder,
209
+ elem_classes="text-input"
210
+ )
211
+ return inputbox
212
+
213
+ def input_number(Label:str,Precision = 0,**kwargs):
214
+ """
215
+ usage:
216
+ app_name = input_number('Input Number', 'Enter game number...')
217
+ Args:
218
+ Label (str): Title textbox
219
+ Placeholder (str): placeholder text
220
+
221
+ Returns:
222
+ variable : str
223
+ """
224
+
225
+ inputbox = gr.Number(
226
+ label=Label,
227
+ elem_classes="text-input",
228
+ precision=Precision,
229
+ **kwargs
230
+ )
231
+ return inputbox
232
+
233
+ def input_paragaph_textbox(Label:str, Placeholder:str):
234
+ """
235
+ usage:
236
+ paragraph = input_paragaph_textbox('Your Story', 'Type your text...')
237
+ Args:
238
+ Label (str): Title textbox
239
+ Placeholder (str): placeholder text
240
+
241
+ Returns:
242
+ variable : str
243
+ """
244
+ paragraph = gr.Textbox(
245
+ label=Label,
246
+ placeholder=Placeholder,
247
+ lines=5,
248
+ max_lines=8,
249
+ max_length=1200,
250
+ elem_classes="text-input"
251
+ )
252
+ return paragraph
253
+
254
+ def input_choice(Label:str, Choices:list, Multiselect:bool):
255
+ """Allow user to select choices\n
256
+ Multiselect True for multiple choices\n
257
+ Multiselect False for single choices\n
258
+ Usage:\n
259
+ genre = gr.Dropdown(\n
260
+ label="Select Your Genre (Multiple Choice)",\n
261
+ choices=[\n
262
+ 'Action', 'Adventure', 'RPG', 'Strategy', 'Simulation',\n
263
+ 'Casual', 'Indie', 'Sports', 'Racing', 'Fighting',\n
264
+ 'Puzzle', 'Shooter', 'Platformer', 'MMO', 'Horror',\n
265
+ 'Survival', 'Open World', 'Visual Novel', 'Point & Click',\n
266
+ 'Sandbox', 'Metroidvania', 'Tactical', 'Rhythm',\n
267
+ 'Stealth', 'Rogue-like', 'Rogue-lite'\n
268
+ ],\n
269
+ multiselect=True,\n
270
+ value=[],\n
271
+ elem_classes="dropdown"\n
272
+ )\n
273
+
274
+ or only single choice \n
275
+
276
+ price_range_input = gr.Dropdown(\n
277
+ label="Select Your Price Range (Only Single Choice)",\n
278
+ choices=[\n
279
+ 'Free',\n
280
+ '5$ - 10%',\n
281
+ '10$ - 50%',\n
282
+ '50$ - 100%',\n
283
+ '100$ - 500%',\n
284
+ 'above 500%',\n
285
+ ],
286
+ multiselect=False,\n
287
+ value=[],\n
288
+ elem_classes="dropdown"\n
289
+ )\n
290
+ Args:\n
291
+ Label (str): _description_\n
292
+ Choices (list): _description_\n
293
+ """
294
+ multiple_choice = gr.Dropdown(
295
+ label=Label,
296
+ choices=Choices,
297
+ multiselect=Multiselect, # True Allowing multi select
298
+ value=[] if Multiselect else None, # the choosen value will be passed here
299
+ elem_classes="dropdown"
300
+ )
301
+ return multiple_choice
requirements.txt ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file may be used to create an environment using:
2
+ # $ conda create --name <env> --file <this file>
3
+ # platform: win-64
4
+ # created-by: conda 25.1.1
5
+ _openmp_mutex=4.5=2_gnu
6
+ aiofiles=24.1.0=pypi_0
7
+ aiohappyeyeballs=2.6.1=pypi_0
8
+ aiohttp=3.12.9=pypi_0
9
+ aiosignal=1.3.2=pypi_0
10
+ annotated-types=0.7.0=pypi_0
11
+ anyio=4.9.0=pypi_0
12
+ asttokens=3.0.0=pyhd8ed1ab_1
13
+ async-timeout=5.0.1=pypi_0
14
+ attrs=25.3.0=pypi_0
15
+ blis=0.7.11=pypi_0
16
+ bzip2=1.0.8=h2bbff1b_6
17
+ ca-certificates=2025.4.26=h4c7d964_0
18
+ catalogue=2.0.10=pypi_0
19
+ certifi=2025.4.26=pypi_0
20
+ charset-normalizer=3.4.2=pypi_0
21
+ click=8.2.0=pypi_0
22
+ cloudpickle=3.1.1=pypi_0
23
+ colorama=0.4.6=pyhd8ed1ab_1
24
+ comm=0.2.2=pyhd8ed1ab_1
25
+ confection=0.1.5=pypi_0
26
+ cpython=3.10.17=py310hd8ed1ab_0
27
+ cuda-version=12.9=h4f385c5_3
28
+ cycler=0.12.1=pypi_0
29
+ cymem=2.0.11=pypi_0
30
+ cython=0.29.32=pypi_0
31
+ dask=2025.5.1=pypi_0
32
+ datasets=3.6.0=pypi_0
33
+ debugpy=1.8.14=py310h9e98ed7_0
34
+ decorator=5.2.1=pyhd8ed1ab_0
35
+ dill=0.3.8=pypi_0
36
+ en-core-web-sm=3.5.0=pypi_0
37
+ exceptiongroup=1.3.0=pyhd8ed1ab_0
38
+ executing=2.2.0=pyhd8ed1ab_0
39
+ fastapi=0.115.12=pypi_0
40
+ ffmpy=0.6.0=pypi_0
41
+ filelock=3.18.0=pypi_0
42
+ fonttools=4.58.0=pypi_0
43
+ frozenlist=1.6.2=pypi_0
44
+ fsspec=2025.3.0=pypi_0
45
+ fst-pso=1.8.1=pypi_0
46
+ fuzzytm=2.0.9=pypi_0
47
+ gensim=4.3.0=pypi_0
48
+ gradio=5.32.1=pypi_0
49
+ gradio-client=1.10.2=pypi_0
50
+ groovy=0.1.2=pypi_0
51
+ h11=0.16.0=pypi_0
52
+ httpcore=1.0.9=pypi_0
53
+ httpx=0.28.1=pypi_0
54
+ huggingface-hub=0.32.4=pypi_0
55
+ idna=3.10=pypi_0
56
+ importlib-metadata=8.6.1=pyha770c72_0
57
+ inquirerpy=0.3.4=pypi_0
58
+ intel-openmp=2024.2.1=h57928b3_1083
59
+ ipykernel=6.29.5=pyh4bbf305_0
60
+ ipython=8.36.0=pyh9ab4c32_0
61
+ jedi=0.19.2=pyhd8ed1ab_1
62
+ jinja2=3.1.6=pypi_0
63
+ joblib=1.5.0=pyhd8ed1ab_0
64
+ jupyter_client=8.6.3=pyhd8ed1ab_1
65
+ jupyter_core=5.7.2=pyh5737063_1
66
+ kiwisolver=1.4.8=pypi_0
67
+ krb5=1.21.3=hdf4eb48_0
68
+ langcodes=3.5.0=pypi_0
69
+ langdetect=1.0.9=pypi_0
70
+ language-data=1.3.0=pypi_0
71
+ libblas=3.9.0=31_h641d27c_mkl
72
+ libcblas=3.9.0=31_h5e41251_mkl
73
+ libffi=3.4.4=hd77b12b_1
74
+ libgomp=15.1.0=h1383e82_2
75
+ libhwloc=2.11.2=default_ha69328c_1001
76
+ libiconv=1.18=h135ad9c_1
77
+ liblapack=3.9.0=31_h1aa476e_mkl
78
+ libsodium=1.0.20=hc70643c_0
79
+ libwinpthread=12.0.0.r4.gg4f2fc60ca=h57928b3_9
80
+ libxgboost=3.0.1=cuda128_hace5437_0
81
+ libxml2=2.13.8=h866ff63_0
82
+ locket=1.0.0=pypi_0
83
+ marisa-trie=1.2.1=pypi_0
84
+ markdown-it-py=3.0.0=pypi_0
85
+ markupsafe=3.0.2=pypi_0
86
+ matplotlib=3.5.3=pypi_0
87
+ matplotlib-inline=0.1.7=pyhd8ed1ab_1
88
+ mdurl=0.1.2=pypi_0
89
+ miniful=0.0.6=pypi_0
90
+ mkl=2024.2.2=h66d3029_15
91
+ mpmath=1.3.0=pypi_0
92
+ multidict=6.4.4=pypi_0
93
+ multiprocess=0.70.16=pypi_0
94
+ murmurhash=1.0.12=pypi_0
95
+ nest-asyncio=1.6.0=pyhd8ed1ab_1
96
+ networkx=3.4.2=pypi_0
97
+ nltk=3.8.1=pypi_0
98
+ numpy=1.25.2=py310hd02465a_0
99
+ openssl=3.5.0=ha4e3fda_1
100
+ orjson=3.10.18=pypi_0
101
+ packaging=25.0=pyh29332c3_1
102
+ pandas=2.1.4=pypi_0
103
+ parso=0.8.4=pyhd8ed1ab_1
104
+ partd=1.4.2=pypi_0
105
+ pathlib-abc=0.1.1=pypi_0
106
+ pathy=0.11.0=pypi_0
107
+ pfzy=0.3.4=pypi_0
108
+ pickleshare=0.7.5=pyhd8ed1ab_1004
109
+ pillow=9.5.0=pypi_0
110
+ pip=25.1=pyhc872135_2
111
+ platformdirs=4.3.8=pyhe01879c_0
112
+ preshed=3.0.9=pypi_0
113
+ prompt-toolkit=3.0.51=pyha770c72_0
114
+ propcache=0.3.1=pypi_0
115
+ psutil=7.0.0=py310ha8f682b_0
116
+ pure_eval=0.2.3=pyhd8ed1ab_1
117
+ py-xgboost=3.0.1=cuda128_pyhee1328b_0
118
+ pyarrow=20.0.0=pypi_0
119
+ pycountry=24.6.1=pypi_0
120
+ pydantic=2.11.5=pypi_0
121
+ pydantic-core=2.33.2=pypi_0
122
+ pydub=0.25.1=pypi_0
123
+ pyfume=0.3.1=pypi_0
124
+ pygments=2.19.1=pyhd8ed1ab_0
125
+ pyparsing=3.2.3=pypi_0
126
+ python=3.10.16=h4607a30_1
127
+ python-dateutil=2.9.0.post0=pyhff2d567_1
128
+ python-multipart=0.0.20=pypi_0
129
+ python-tzdata=2025.2=pyhd8ed1ab_0
130
+ python_abi=3.10=2_cp310
131
+ pytz=2025.2=pyhd8ed1ab_0
132
+ pywin32=307=py310h9e98ed7_3
133
+ pyyaml=6.0.2=pypi_0
134
+ pyzmq=26.4.0=py310h656833d_0
135
+ regex=2024.11.6=pypi_0
136
+ requests=2.32.3=pypi_0
137
+ rich=14.0.0=pypi_0
138
+ ruff=0.11.12=pypi_0
139
+ safehttpx=0.1.6=pypi_0
140
+ safetensors=0.5.3=pypi_0
141
+ scikit-learn=1.3.0=pypi_0
142
+ scipy=1.11.4=pypi_0
143
+ seaborn=0.13.2=pypi_0
144
+ semantic-version=2.10.0=pypi_0
145
+ sentence-transformers=4.1.0=pypi_0
146
+ setuptools=78.1.1=py310haa95532_0
147
+ shellingham=1.5.4=pypi_0
148
+ simpful=2.12.0=pypi_0
149
+ six=1.17.0=pyhd8ed1ab_0
150
+ smart-open=6.4.0=pypi_0
151
+ sniffio=1.3.1=pypi_0
152
+ spacy=3.5.3=pypi_0
153
+ spacy-legacy=3.0.12=pypi_0
154
+ spacy-loggers=1.0.5=pypi_0
155
+ sqlite=3.45.3=h2bbff1b_0
156
+ srsly=2.5.1=pypi_0
157
+ stack_data=0.6.3=pyhd8ed1ab_1
158
+ starlette=0.46.2=pypi_0
159
+ swifter=1.4.0=pypi_0
160
+ sympy=1.14.0=pypi_0
161
+ tbb=2021.13.0=h62715c5_1
162
+ thinc=8.1.12=pypi_0
163
+ threadpoolctl=3.6.0=pyhecae5ae_0
164
+ tk=8.6.14=h0416ee5_0
165
+ tokenizers=0.21.1=pypi_0
166
+ tomlkit=0.13.2=pypi_0
167
+ toolz=1.0.0=pypi_0
168
+ torch=2.7.0=pypi_0
169
+ tornado=6.4.2=py310ha8f682b_0
170
+ tqdm=4.67.1=pypi_0
171
+ traitlets=5.14.3=pyhd8ed1ab_1
172
+ transformers=4.51.3=pypi_0
173
+ typer=0.16.0=pypi_0
174
+ typing-inspection=0.4.1=pypi_0
175
+ typing_extensions=4.13.2=pyh29332c3_0
176
+ tzdata=2025b=h04d1e81_0
177
+ ucrt=10.0.22621.0=h57928b3_1
178
+ urllib3=2.4.0=pypi_0
179
+ uvicorn=0.34.3=pypi_0
180
+ vc=14.42=haa95532_5
181
+ vc14_runtime=14.42.34438=hfd919c2_26
182
+ vs2015_runtime=14.42.34438=h7142326_26
183
+ wasabi=1.1.3=pypi_0
184
+ wcwidth=0.2.13=pyhd8ed1ab_1
185
+ websockets=15.0.1=pypi_0
186
+ wheel=0.45.1=py310haa95532_0
187
+ xgboost=3.0.1=cuda128_pyh68bd8d9_0
188
+ xxhash=3.5.0=pypi_0
189
+ xz=5.6.4=h4754444_1
190
+ yarl=1.20.0=pypi_0
191
+ zeromq=4.3.5=ha9f60a1_7
192
+ zipp=3.21.0=pyhd8ed1ab_1
193
+ zlib=1.2.13=h8cc25b3_1
style.css ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .container {
2
+ /* display: flex; */
3
+ width: 100%;
4
+ /* min-height: 90vh; */
5
+ font-family: 'Arial', 'sans-serif';
6
+ }
7
+
8
+ .navbar {
9
+ width: 200px;
10
+ height: 100%;
11
+ border-right: 5px solid #34495e;
12
+
13
+ display: flex;
14
+ flex-direction: column;
15
+ padding: 0 10px;
16
+ /* justify-content: center; */
17
+ justify-content: flex-start;
18
+ background-color: #2c3e50;
19
+ }
20
+
21
+ .nav-header {
22
+ margin-top: 1rem;
23
+ margin-bottom: 2rem;
24
+ }
25
+
26
+ .nav-header h1 {
27
+ color: #fcdf1e;
28
+ }
29
+
30
+ .nav-buttons {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: 0.5rem;
34
+ padding: 0 5px;
35
+ }
36
+
37
+ .nav-btn {
38
+ text-align: left;
39
+ padding: 10px 15px;
40
+ width: 100%;
41
+ background-color: #34495e;
42
+ color: #ecf0f1;
43
+ border: none;
44
+ border-radius: 4px;
45
+ cursor: pointer;
46
+ transition: all 0.3s ease;
47
+ font-weight: bold;
48
+ }
49
+
50
+ .nav-btn:hover {
51
+ background-color: #3d566e;
52
+ color: #fcdf1e;
53
+ }
54
+
55
+ .nav-btn.active {
56
+ background-color: #f39c12;
57
+ color: #2c3e50;
58
+ }
59
+
60
+ .main-content {
61
+ flex-grow: 1;
62
+ padding: 1rem;
63
+ display: flex;
64
+ flex-direction: column;
65
+ }
66
+
67
+ /* Section layout styling */
68
+ .content-section {
69
+ border: 2px solid #ccc;
70
+ padding: 1rem !important;
71
+ margin-bottom: 1rem;
72
+ background-color: #f9f9f9;
73
+ border-radius: 8px;
74
+
75
+ height: auto !important;
76
+ min-height: 80vh;
77
+ overflow: visible !important;
78
+ /* padding: 20px !important; */
79
+ }
80
+
81
+ .content-section .header h1,
82
+ .content-section .header * h1 {
83
+ color: #3d3d3c !important;
84
+ font-size: 1.5rem;
85
+ font-weight: bold;
86
+ border-bottom: 2px solid #ccc;
87
+ padding-bottom: 0.5rem;
88
+ margin-bottom: 1rem;
89
+ }
90
+
91
+ .content {
92
+ border: 2px solid #ccc;
93
+ padding: 0.5rem;
94
+ height: 80vh; /* Fixed height */
95
+ margin-bottom: 1rem;
96
+ background-color: #f9f9f9;
97
+ border-radius: 8px;
98
+ overflow-y: auto;
99
+ }
100
+
101
+ p.desc {
102
+ color: #3d3d3c !important;
103
+ /* color: white; */
104
+ }
105
+
106
+ /* dataset display */
107
+ /* Dataset Container */
108
+ .datasets-container {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 30px;
112
+ width: 100%;
113
+ }
114
+
115
+ /* Dataset Layout */
116
+ .dataframe-layout {
117
+ border: 1px solid #e0e0e0;
118
+ border-radius: 8px;
119
+ padding: 20px;
120
+ background-color: #fff;
121
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
122
+ }
123
+
124
+ /* Title Styling */
125
+ .subtitle {
126
+ font-size: 1.2rem !important;
127
+ font-weight: 600;
128
+ color: #2c3e50;
129
+ margin: 0 !important;
130
+ padding: 0 !important;
131
+ }
132
+
133
+ /* Download Button */
134
+ .download-button {
135
+ background-color: #3498db !important;
136
+ color: white !important;
137
+ border: none !important;
138
+ padding: 8px 16px !important;
139
+ border-radius: 4px !important;
140
+ font-size: 0.9rem !important;
141
+ }
142
+
143
+ .download-button:hover {
144
+ background-color: #2980b9 !important;
145
+ }
146
+
147
+ /* Table Styling */
148
+ .dataframe-layout table {
149
+ width: 100%;
150
+ border-collapse: collapse;
151
+ margin-top: 15px;
152
+ }
153
+
154
+ .dataframe-layout th {
155
+ background-color: #34495e;
156
+ color: white;
157
+ padding: 10px;
158
+ text-align: left;
159
+ }
160
+
161
+ .dataframe-layout td {
162
+ padding: 8px 10px;
163
+ border-bottom: 1px solid #dddddd;
164
+ }
165
+
166
+ .dataframe-layout tr:nth-child(even) {
167
+ background-color: #85a285;
168
+ }
169
+
170
+ .dataframe-layout tr:nth-child(odd) {
171
+ background-color: #466c45;
172
+ }
173
+
174
+ /* EDA */
175
+ .subheader{
176
+ font-weight: bold;
177
+ font-size: 24px;
178
+ color: #3d3d3c;
179
+ margin-bottom: 10px;
180
+ }
181
+
182
+ /* Recomendation system */
183
+ #system .header-title {
184
+ color: white;
185
+ font-size: 2rem;
186
+ }
187
+
188
+ #system {
189
+ background-color: #3d3d3c;
190
+ }
191
+
192
+ .dropdown, .text-input{
193
+ height: 100%;
194
+ flex: 1 1 auto;
195
+ /* background-color: #dddddd; */
196
+ border: none;
197
+ }
198
+
199
+ .text-input label.gr-label,
200
+ .dropdown label.gr-label {
201
+ color: #3d3d3c !important;
202
+ }
203
+
204
+ /* .results-column h2{
205
+ color: black;
206
+ } */
207
+
208
+