""" AURA Chat — Hedge Fund Picks (Dark Mode + Enhanced UI) Single-file Gradio app with: - YouTube explainer video - Info container (what it does, accuracy, example prompts) - Dark theme (green, gray, black) - Two-column layout: inputs left, analysis/chat right - Interactive chat component """ import os import time import sys import asyncio import requests import atexit import traceback from typing import List import gradio as gr # ============================================================================= # EVENT LOOP FOR NON-WINDOWS # ============================================================================= if sys.platform != "win32": try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) except Exception: traceback.print_exc() # ============================================================================= # CONFIGURATION # ============================================================================= SCRAPER_API_URL = os.getenv("SCRAPER_API_URL", "https://deep-scraper-96.created.app/api/deep-scrape") SCRAPER_HEADERS = {"User-Agent": "Mozilla/5.0", "Content-Type": "application/json"} LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b:free") MAX_TOKENS = int(os.getenv("LLM_MAX_TOKENS", "3000")) SCRAPE_DELAY = float(os.getenv("SCRAPE_DELAY", "1.0")) OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1") PROMPT_TEMPLATE = f"""You are AURA, a concise, professional hedge-fund research assistant. Task: - List top 5 stock picks (or fewer if data limited), with short rationale and Investment Duration (entry/exit). - Include a summary (2-3 sentences) and Assumptions & Risks (2-3 bullet points). - Keep entries short, scannable, plain text, no JSON. Max tokens: {MAX_TOKENS}, Model: {LLM_MODEL}""" # ============================================================================= # SCRAPER # ============================================================================= def deep_scrape(query: str, retries: int = 3, timeout: int = 40) -> str: last_err = None for attempt in range(retries): try: resp = requests.post(SCRAPER_API_URL, headers=SCRAPER_HEADERS, json={"query": query}, timeout=timeout) resp.raise_for_status() data = resp.json() if isinstance(data, dict): return "\n".join([f"{k.upper()}:\n{v}" for k, v in data.items()]) return str(data) except Exception as e: last_err = e time.sleep(1) return f"ERROR: Scraper failed: {last_err}" def multi_scrape(queries: List[str], delay: float = SCRAPE_DELAY) -> str: aggregated = [] for q in queries: if not q.strip(): continue aggregated.append(f"\n=== QUERY: {q.strip()} ===\n") aggregated.append(deep_scrape(q.strip())) time.sleep(delay) return "\n".join(aggregated) # ============================================================================= # LLM # ============================================================================= try: from openai import OpenAI except Exception: OpenAI = None def run_llm_system_and_user(system_prompt: str, user_text: str) -> str: if OpenAI is None: return "ERROR: openai package not installed." if not OPENAI_API_KEY: return "ERROR: OPENAI_API_KEY not set." client = OpenAI(base_url=OPENAI_BASE_URL, api_key=OPENAI_API_KEY) try: completion = client.chat.completions.create( model=LLM_MODEL, messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_text}], max_tokens=MAX_TOKENS ) return completion.choices[0].message.content if hasattr(completion, "choices") else str(completion) except Exception as e: return f"ERROR: LLM call failed: {e}" finally: try: client.close() except: pass # ============================================================================= # ANALYSIS PIPELINE # ============================================================================= def analyze_and_seed_chat(prompts_text: str): if not prompts_text.strip(): return "Please enter at least one prompt.", [] queries = [line.strip() for line in prompts_text.splitlines() if line.strip()] scraped = multi_scrape(queries) if scraped.startswith("ERROR"): return scraped, [] user_payload = f"SCRAPED DATA:\n\n{scraped}\n\nFollow instructions and output analysis." analysis = run_llm_system_and_user(PROMPT_TEMPLATE, user_payload) if analysis.startswith("ERROR"): return analysis, [] return analysis, [ {"role": "user", "content": f"Analyze the data (prompts: {', '.join(queries)})"}, {"role": "assistant", "content": analysis} ] def continue_chat(chat_messages, user_message: str, analysis_text: str): if not user_message.strip(): return chat_messages or [] chat_messages = chat_messages or [] chat_messages.append({"role": "user", "content": user_message}) system_prompt = "You are AURA. Use previous analysis as reference and answer concisely." user_payload = f"REFERENCE ANALYSIS:\n\n{analysis_text}\n\nUSER QUESTION: {user_message}" assistant_reply = run_llm_system_and_user(system_prompt, user_payload) chat_messages.append({"role": "assistant", "content": assistant_reply}) return chat_messages # ============================================================================= # GRADIO UI # ============================================================================= def build_demo(): with gr.Blocks(title="AURA Chat — Hedge Fund Picks") as demo: # Dark theme CSS gr.HTML(""" """) gr.HTML('