| import os |
| import requests |
| import pandas as pd |
| import time |
| from dotenv import load_dotenv |
|
|
| load_dotenv() |
|
|
| API_KEY = os.getenv("RAPIDAPI_KEY") |
| API_HOST = os.getenv("RAPIDAPI_HOST") |
|
|
| HEADERS = { |
| "x-rapidapi-host": API_HOST if API_HOST else "", |
| "x-rapidapi-key": API_KEY if API_KEY else "" |
| } |
|
|
| |
| BASE_URL = f"https://{API_HOST}/v1/app-store-api/reviews" if API_HOST else None |
|
|
| |
| APPLE_APP_IDS = { |
| "Photo Editor & Candy Camera & Grid & ScrapBook": 364709193, |
| "Coloring book moana": 123456789, |
| "U Launcher Lite – FREE Live Cool Themes, Hide Apps": 987654321, |
| |
| } |
|
|
| def fetch_reviews(app_id, page=1, country="us", lang="en", retries=3): |
| """ |
| Fetch reviews from the API. If BASE_URL or API credentials are missing, |
| safely return None and print an explanatory message. |
| """ |
| if BASE_URL is None or not API_KEY or not API_HOST: |
| print("⚠️ Skipping network call: RAPIDAPI_HOST or RAPIDAPI_KEY is not set in environment.") |
| return None |
|
|
| params = { |
| "id": app_id, |
| "sort": "mostRecent", |
| "page": page, |
| "country": country, |
| "lang": lang |
| } |
|
|
| for attempt in range(1, retries+1): |
| try: |
| resp = requests.get(BASE_URL, headers=HEADERS, params=params, timeout=10) |
| resp.raise_for_status() |
| return resp.json() |
| except requests.exceptions.HTTPError as e: |
| status = resp.status_code if 'resp' in locals() else 'unknown' |
| print(f"HTTPError (status {status}) on attempt {attempt} for app_id {app_id}: {e}") |
| if status == 429: |
| wait = 2 ** attempt |
| print(f"Rate limited. Waiting {wait}s before retry...") |
| time.sleep(wait) |
| elif status == 403: |
| print("Forbidden. Skipping this app.") |
| return None |
| else: |
| time.sleep(1) |
| except Exception as e: |
| print(f"Error fetching app_id {app_id}:", e) |
| time.sleep(1) |
| return None |
|
|
| def merge_google_apple(google_df): |
| apple_data = [] |
| for app in google_df['App']: |
| app_id = APPLE_APP_IDS.get(app) |
| if not app_id: |
| print(f"No Apple App ID for {app}. Skipping.") |
| apple_data.append({"App": app, "AppleRating": None, "AppleReviews": 0}) |
| continue |
|
|
| reviews = fetch_reviews(app_id) |
| if reviews: |
| |
| try: |
| ratings = [r.get("score", 0) for r in reviews] |
| avg_rating = sum(ratings)/len(ratings) if ratings else None |
| apple_data.append({ |
| "App": app, |
| "AppleRating": avg_rating, |
| "AppleReviews": len(reviews) |
| }) |
| except Exception: |
| |
| apple_data.append({"App": app, "AppleRating": None, "AppleReviews": 0}) |
| else: |
| apple_data.append({"App": app, "AppleRating": None, "AppleReviews": 0}) |
|
|
| apple_df = pd.DataFrame(apple_data) |
| combined = pd.merge(google_df, apple_df, on="App", how="outer") |
| os.makedirs("data/cleaned_data", exist_ok=True) |
| combined.to_csv("data/cleaned_data/combined_apps.csv", index=False) |
| print("✅ Combined data saved to data/cleaned_data/combined_apps.csv") |
| return combined |
|
|
| def main(): |
| |
| google_data = {"App": list(APPLE_APP_IDS.keys())} |
| google_df = pd.DataFrame(google_data) |
|
|
| print("Merging with Apple App Store data...") |
| combined_df = merge_google_apple(google_df) |
| print(combined_df) |
|
|
| if __name__ == "__main__": |
| |
| main() |
|
|
| |
| |
| |
| import gradio as gr |
| import html |
|
|
| |
| def render_terminal_html(history): |
| """ |
| history: list of (role, message) tuples. role in ("user","assistant","system") |
| """ |
| lines = [] |
| for role, msg in history: |
| safe = html.escape(str(msg)).replace("\n", "<br>") |
| if role == "user": |
| lines.append(f'<div class="line user">> {safe}</div>') |
| elif role == "assistant": |
| lines.append(f'<div class="line assistant">{safe}</div>') |
| else: |
| lines.append(f'<div class="line system">{safe}</div>') |
| return "<div class='terminal'>\n" + "\n".join(lines) + "\n</div>" |
|
|
| |
| def load_last_sentences(n=10): |
| path = "data/cleaned_data/combined_apps.csv" |
| if os.path.exists(path): |
| try: |
| df = pd.read_csv(path) |
| rows = [] |
| for i, row in df.head(n).iterrows(): |
| rows.append(f"{i+1}. App='{row.get('App', '')}' | AppleRating={row.get('AppleRating', '')} | AppleReviews={row.get('AppleReviews', '')}") |
| return "<pre class='sentences'>" + "\n".join(rows) + "</pre>" |
| except Exception as e: |
| return f"<pre class='sentences'>Error reading CSV: {e}</pre>" |
| else: |
| return "<pre class='sentences'>No combined CSV found. Run the merge (type 'run' or '/run').</pre>" |
|
|
| |
| def handle_message(message, history): |
| if history is None: |
| history = [] |
|
|
| history.append(("user", message)) |
| normalized = message.strip().lower() |
| assistant_reply = "" |
| right_html = load_last_sentences() |
|
|
| if normalized in ["run", "/run", "merge", "/merge"]: |
| history.append(("system", "Executing main() — merging data. This may take a moment (uses your configured RAPIDAPI keys).")) |
| try: |
| |
| main() |
| assistant_reply = "✅ main() finished. Combined CSV saved to data/cleaned_data/combined_apps.csv" |
| right_html = load_last_sentences() |
| except Exception as e: |
| assistant_reply = f"❌ main() raised an exception: {e}" |
| right_html = f"<pre class='sentences'>Exception during run:\n{html.escape(str(e))}</pre>" |
| elif normalized in ["help", "/help", "commands"]: |
| assistant_reply = ("Available commands:\n" |
| "- run or /run : execute the merge (calls main())\n" |
| "- help : show this message\n" |
| "- any other text will be echoed back in terminal style.") |
| else: |
| assistant_reply = f"Echo: {message}" |
|
|
| history.append(("assistant", assistant_reply)) |
| chat_html = render_terminal_html(history) |
| return chat_html, history, right_html |
|
|
| |
| def reset_chat(): |
| history = [("system", "Terminal chat started. Type 'run' to execute data merge.")] |
| return render_terminal_html(history), history, load_last_sentences() |
|
|
| |
| TERMINAL_CSS = """ |
| <style> |
| .terminal { |
| background: #0b0f13; |
| color: #c8f7c5; |
| font-family: "Courier New", Courier, monospace; |
| padding: 12px; |
| height: 520px; |
| overflow: auto; |
| border-radius: 6px; |
| border: 1px solid #111; |
| } |
| .terminal .line { padding: 4px 0; white-space: pre-wrap; word-break: break-word; } |
| .terminal .user { color: #9be5ff; } |
| .terminal .assistant { color: #c8f7c5; } |
| .terminal .system { color: #9ea7b0; font-style: italic; } |
| .sentences { |
| background: #021013; |
| color: #bfe6c6; |
| font-family: "Courier New", monospace; |
| padding: 12px; |
| height: 520px; |
| overflow: auto; |
| border-radius: 6px; |
| border: 1px solid #111; |
| } |
| body { background: #0f1720; } |
| </style> |
| """ |
|
|
| |
| with gr.Blocks(title="Terminal-style Chat UI for main.py") as demo: |
| |
| gr.HTML(TERMINAL_CSS) |
| gr.Markdown("<h2 style='color: #c8f7c5; font-family: monospace;'>🔌 main.py — Terminal Chat UI (Gradio)</h2>") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| chat_html = gr.HTML(render_terminal_html([("system", "Terminal chat started. Type 'run' to execute data merge.")]), elem_id="chat_terminal") |
| user_input = gr.Textbox(label="Terminal input", placeholder="Type command or message (e.g. run, help) and press Enter", lines=1) |
| send_btn = gr.Button("Send") |
| reset_btn = gr.Button("Reset") |
| with gr.Column(scale=1.6): |
| gr.Markdown("**Output sentences / last merged rows**") |
| right_panel = gr.HTML(load_last_sentences(), elem_id="right_sentences") |
|
|
| history_state = gr.State([("system", "Terminal chat started. Type 'run' to execute data merge.")]) |
|
|
| def submit_and_update(message, history): |
| chat_html_str, new_history, right_html = handle_message(message, history) |
| return chat_html_str, new_history, right_html |
|
|
| send_btn.click(fn=submit_and_update, inputs=[user_input, history_state], outputs=[chat_html, history_state, right_panel]) |
| user_input.submit(fn=submit_and_update, inputs=[user_input, history_state], outputs=[chat_html, history_state, right_panel]) |
| reset_btn.click(fn=reset_chat, inputs=None, outputs=[chat_html, history_state, right_panel]) |
|
|
| gr.Markdown("<small style='color:#9ea7b0'>Notes: Type 'run' to call main(). If you want the app to actually fetch from RapidAPI, set RAPIDAPI_HOST and RAPIDAPI_KEY in your environment or a .env file.</small>") |
|
|
| |
| if __name__ == "__main__": |
| demo.launch() |
|
|