Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import ast | |
| from sentence_transformers import SentenceTransformer | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| # --- 1. Load Data --- | |
| print("β³ Loading Data...") | |
| try: | |
| df = pd.read_pickle("apps_clustered.pkl") | |
| print("β Data Loaded") | |
| except Exception as e: | |
| print(f"β Error loading data: {e}") | |
| df = pd.DataFrame() | |
| # --- Column Setup --- | |
| def get_col_name(candidates, df_cols): | |
| return next((col for col in candidates if col in df_cols), None) | |
| image_col = get_col_name(['artworkUrl512', 'artworkUrl100', 'artworkUrl60', 'image_url', 'icon'], df.columns) | |
| desc_col = get_col_name(['description', 'longDescription', 'shortDescription', 'summary'], df.columns) | |
| lang_col = get_col_name(['languageCodesISO2A', 'primaryLanguage', 'languages', 'language'], df.columns) | |
| rating_col = 'averageUserRating' | |
| print("β³ Loading AI Model...") | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| print("β Ready!") | |
| # --- 2. The Logic --- | |
| def search_apps(user_query): | |
| if not user_query: | |
| return "" | |
| try: | |
| # Search | |
| user_vector = model.encode(user_query).reshape(1, -1) | |
| app_embeddings = np.vstack(df["text_embedding"].values) | |
| scores = cosine_similarity(user_vector, app_embeddings).flatten() | |
| df_results = df.copy() | |
| df_results["similarity_score"] = scores | |
| top_results = df_results.sort_values(by="similarity_score", ascending=False).head(3) | |
| # --- HTML GENERATION --- | |
| html_content = """ | |
| <div style="margin-bottom: 20px; text-align: center;"> | |
| <h3 style="color: #374151; margin: 0;">Here is what our recommendation system found for you:</h3> | |
| </div> | |
| """ | |
| html_content += '<div style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">' | |
| for _, row in top_results.iterrows(): | |
| # Image Logic | |
| img_url = row.get(image_col, "") | |
| if pd.isna(img_url) or not isinstance(img_url, str): | |
| img_tag = '<div style="width: 100%; height: 180px; background: #eee; border-radius: 15px; display: flex; align-items: center; justify-content: center;">No Image</div>' | |
| else: | |
| img_tag = f'<img src="{img_url}" style="width: 100%; height: auto; border-radius: 15px; margin-bottom: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1);">' | |
| # Rating Logic | |
| score = row.get(rating_col, 0) | |
| rating_text = "No ratings yet" if (pd.isna(score) or score == 0) else f"{float(score):.1f} β" | |
| # Language Logic | |
| lang_val = row.get(lang_col, "N/A") | |
| if pd.isna(lang_val): lang_val = "N/A" | |
| if isinstance(lang_val, str) and lang_val.startswith('[') and lang_val.endswith(']'): | |
| try: lang_val = ast.literal_eval(lang_val) | |
| except: pass | |
| if isinstance(lang_val, list): | |
| lang_text = f"{lang_val[0]} and more" if len(lang_val) > 1 else (str(lang_val[0]) if len(lang_val) == 1 else "N/A") | |
| else: | |
| lang_text = str(lang_val) | |
| # Description Logic | |
| desc = str(row.get(desc_col, "No description")) | |
| if len(desc) > 120: desc = desc[:120] + "..." | |
| # Link Logic | |
| url = row.get('trackViewUrl', '#') | |
| if pd.isna(url) and 'trackId' in row: url = f"https://apps.apple.com/app/id{row['trackId']}" | |
| # Card HTML | |
| card_html = f""" | |
| <div style="width: 280px; padding: 20px; background-color: white; border: 1px solid #e5e7eb; border-radius: 20px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); display: flex; flex-direction: column; align-items: flex-start; font-family: sans-serif;"> | |
| {img_tag} | |
| <h3 style="margin: 0 0 5px 0; color: #1f2937; font-size: 18px;">{row['trackName']}</h3> | |
| <p style="margin: 0 0 10px 0; color: #6b7280; font-size: 14px; font-weight: 500;">{row['primaryGenreName']}</p> | |
| <div style="display: flex; gap: 10px; margin-bottom: 10px; font-size: 13px; color: #4b5563;"> | |
| <span style="background: #f3f4f6; padding: 4px 8px; border-radius: 6px;">{rating_text}</span> | |
| <span style="background: #f3f4f6; padding: 4px 8px; border-radius: 6px;">π£οΈ {lang_text}</span> | |
| </div> | |
| <p style="font-size: 13px; color: #6b7280; margin-bottom: 15px; line-height: 1.4;">{desc}</p> | |
| <a href="{url}" target="_blank" style="margin-top: auto; width: 100%; text-align: center; background-color: #2563eb; color: white; padding: 10px 0; border-radius: 10px; text-decoration: none; font-weight: bold; transition: background 0.2s;">Get App βοΈ</a> | |
| </div> | |
| """ | |
| html_content += card_html | |
| html_content += '</div>' | |
| return html_content | |
| except Exception as e: | |
| return f"<div style='color:red;'>Error: {str(e)}</div>" | |
| # --- 3. The UI (Rearranged Layout) --- | |
| with gr.Blocks() as demo: | |
| # Title stays at the absolute top (Header) | |
| gr.Markdown("# π± AI App Recommender") | |
| # TABS are now at the top of the content | |
| with gr.Tabs(): | |
| # --- TAB 1: SEARCH --- | |
| with gr.Tab("π Find Apps"): | |
| # Description is now INSIDE the tab, above the search bar | |
| gr.Markdown("Describe what you need, and the AI will find the best apps for you.") | |
| with gr.Column(): | |
| with gr.Row(): | |
| txt_input = gr.Textbox(show_label=False, placeholder="Type here... (e.g., 'I need a puzzle game for kids')", scale=4) | |
| btn_submit = gr.Button("π Find Apps", variant="primary", scale=1) | |
| gr.Examples(examples=[["I want a timer for my kitchen"], ["A game to learn math"]], inputs=txt_input) | |
| output_html = gr.HTML(label="Recommended Apps") | |
| # --- TAB 2: VIDEO (Smaller & Centered) --- | |
| with gr.Tab("π₯ Video Presentation"): | |
| gr.Markdown("### Watch our project presentation below:") | |
| # Smaller video (600px) and centered | |
| gr.HTML(""" | |
| <div style="display: flex; justify-content: center; align-items: center;"> | |
| <video width="600" controls style="border-radius: 10px; box-shadow: 0 4px 10px rgba(0,0,0,0.1);"> | |
| <source src="https://huggingface.co/spaces/Liori25/ReccomendationSystemForApps/resolve/main/PresentationVideo.mp4" type="video/mp4"> | |
| Your browser does not support the video tag. | |
| </video> | |
| </div> | |
| """) | |
| # --- FOOTER --- | |
| gr.Markdown("""<div style="text-align: center; margin-top: 40px; color: #9ca3af; font-size: 14px;">app developed by Karin M & Lior F</div>""") | |
| # Logic connection | |
| btn_submit.click(fn=search_apps, inputs=txt_input, outputs=output_html) | |
| txt_input.submit(fn=search_apps, inputs=txt_input, outputs=output_html) | |
| # --- Launch --- | |
| demo.launch(theme=gr.themes.Soft()) |