File size: 7,217 Bytes
d6f2ce5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1bd8b25
d6f2ce5
 
 
 
 
 
 
 
 
1bd8b25
d6f2ce5
 
 
 
 
 
 
 
 
 
 
 
 
1bd8b25
d6f2ce5
 
 
 
 
 
 
 
1bd8b25
d6f2ce5
 
 
 
 
 
 
 
 
 
ddd1563
 
d6f2ce5
ddd1563
d6f2ce5
 
ddd1563
e686e66
d6f2ce5
ddd1563
e686e66
ddd1563
 
 
e686e66
 
1bd8b25
e686e66
 
1bd8b25
e686e66
 
ddd1563
e686e66
 
 
ddd1563
1bd8b25
ddd1563
 
 
 
 
 
1bd8b25
e686e66
d6f2ce5
1bd8b25
d6f2ce5
e686e66
d6f2ce5
 
 
ddd1563
26b8bd4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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())