GabrielSalem commited on
Commit
9dad169
·
verified ·
1 Parent(s): c24d711

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -168
app.py CHANGED
@@ -1,8 +1,6 @@
1
  """
2
- AURA Chat — Gradio Space + MCP Server Tab
3
- Single-file Gradio app that:
4
- - Chat / Analysis Tab: Enter prompts, analyze, and chat with the assistant.
5
- - MCP Server Tab: Call scraping and analysis functions directly with JSON output.
6
  """
7
 
8
  import os
@@ -15,7 +13,7 @@ import traceback
15
  from typing import List
16
  import gradio as gr
17
 
18
- # Defensive: fresh event loop early to avoid fd race on shutdown
19
  if sys.platform != "win32":
20
  try:
21
  loop = asyncio.new_event_loop()
@@ -23,43 +21,26 @@ if sys.platform != "win32":
23
  except Exception:
24
  traceback.print_exc()
25
 
26
- # =============================================================================
27
- # CONFIGURATION (fixed)
28
- # =============================================================================
29
  SCRAPER_API_URL = os.getenv("SCRAPER_API_URL", "https://deep-scraper-96.created.app/api/deep-scrape")
30
  SCRAPER_HEADERS = {
31
  "User-Agent": "Mozilla/5.0",
32
  "Content-Type": "application/json"
33
  }
34
-
35
  LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b:free")
36
  MAX_TOKENS = int(os.getenv("LLM_MAX_TOKENS", "3000"))
37
  SCRAPE_DELAY = float(os.getenv("SCRAPE_DELAY", "1.0"))
38
-
39
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
40
  OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1")
41
 
42
  PROMPT_TEMPLATE = f"""You are AURA, a concise, professional hedge-fund research assistant.
43
  Task:
44
- - Given scraped data below, produce a clear, readable analysis that:
45
- 1) Lists the top 5 stock picks (or fewer if not enough data).
46
- 2) For each stock provide: Ticker / Company name, short rationale (2-3 bullets),
47
- and an explicit **Investment Duration** entry: a one-line "When to Invest"
48
- and a one-line "When to Sell" instruction (these two lines are mandatory
49
- for each stock).
50
- 3) Keep each stock entry short and scannable. Use a bullet list or numbered list.
51
- 4) At the top, provide a 2-3 sentence summary conclusion (market context +
52
- highest conviction pick).
53
- 5) Output in plain text, clean formatting, easy for humans to read. No JSON.
54
- 6) After the list, include a concise "Assumptions & Risks" section (2-3 bullet points).
55
- Important: Be decisive. If data is insufficient, state that clearly and provide
56
- the best-available picks with lower confidence.
57
- Max tokens for the LLM response: {MAX_TOKENS}
58
- Model: {LLM_MODEL}"""
59
 
60
- # =============================================================================
61
- # SCRAPING HELPERS
62
- # =============================================================================
63
  def deep_scrape(query: str, retries: int = 3, timeout: int = 40) -> str:
64
  payload = {"query": query}
65
  last_err = None
@@ -69,8 +50,7 @@ def deep_scrape(query: str, retries: int = 3, timeout: int = 40) -> str:
69
  resp.raise_for_status()
70
  data = resp.json()
71
  if isinstance(data, dict):
72
- parts = [f"{k.upper()}:\n{v}\n" for k, v in data.items()]
73
- return "\n".join(parts)
74
  return str(data)
75
  except Exception as e:
76
  last_err = e
@@ -85,36 +65,33 @@ def multi_scrape(queries: List[str], delay: float = SCRAPE_DELAY) -> str:
85
  if not q:
86
  continue
87
  aggregated.append(f"\n=== QUERY: {q} ===\n")
88
- scraped = deep_scrape(q)
89
- aggregated.append(scraped)
90
  time.sleep(delay)
91
  return "\n".join(aggregated)
92
 
93
- # =============================================================================
94
- # LLM INTERACTION
95
- # =============================================================================
96
  try:
97
  from openai import OpenAI
98
  except Exception:
99
  OpenAI = None
100
 
101
- def run_llm_system_and_user(system_prompt: str, user_text: str, model: str = LLM_MODEL, max_tokens: int = MAX_TOKENS) -> str:
102
  if OpenAI is None:
103
- return "ERROR: openai package not installed or available."
104
  if not OPENAI_API_KEY:
105
- return "ERROR: OPENAI_API_KEY not set in environment."
106
  client = None
107
  try:
108
  client = OpenAI(base_url=OPENAI_BASE_URL, api_key=OPENAI_API_KEY)
109
  completion = client.chat.completions.create(
110
- model=model,
111
  messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_text}],
112
- max_tokens=max_tokens,
113
  )
114
- if hasattr(completion, "choices") and len(completion.choices) > 0:
115
  try:
116
  return completion.choices[0].message.content
117
- except Exception:
118
  return str(completion.choices[0])
119
  return str(completion)
120
  except Exception as e:
@@ -124,164 +101,117 @@ def run_llm_system_and_user(system_prompt: str, user_text: str, model: str = LLM
124
  if client is not None:
125
  try:
126
  client.close()
127
- except Exception:
128
- try:
129
- asyncio.get_event_loop().run_until_complete(client.aclose())
130
- except Exception:
131
- pass
132
- except Exception:
133
- pass
134
 
135
- # =============================================================================
136
- # MAIN PIPELINE
137
- # =============================================================================
138
  def analyze_and_seed_chat(prompts_text: str):
139
  if not prompts_text.strip():
140
- return "Please enter at least one prompt (query).", []
141
  queries = [line.strip() for line in prompts_text.splitlines() if line.strip()]
142
- scraped = multi_scrape(queries, delay=SCRAPE_DELAY)
143
  if scraped.startswith("ERROR"):
144
  return scraped, []
145
- user_payload = f"SCRAPED DATA:\n\n{scraped}\n\nPlease follow the system instructions and output the analysis."
146
  analysis = run_llm_system_and_user(PROMPT_TEMPLATE, user_payload)
147
  if analysis.startswith("ERROR"):
148
  return analysis, []
149
  initial_chat = [
150
- {"role": "user", "content": f"Analyze the data I provided (prompts: {', '.join(queries)})"},
151
  {"role": "assistant", "content": analysis}
152
  ]
153
  return analysis, initial_chat
154
 
155
  def continue_chat(chat_messages, user_message: str, analysis_text: str):
156
- if chat_messages is None:
157
- chat_messages = []
158
- if not user_message.strip():
159
- return chat_messages
160
  chat_messages.append({"role": "user", "content": user_message})
161
- followup_system = (
162
- "You are AURA, a helpful analyst. Use the previous analysis as context; answer follow-ups concisely."
163
- )
164
  user_payload = f"REFERENCE ANALYSIS:\n\n{analysis_text}\n\nUSER QUESTION: {user_message}"
165
  assistant_reply = run_llm_system_and_user(followup_system, user_payload)
166
  chat_messages.append({"role": "assistant", "content": assistant_reply})
167
  return chat_messages
168
 
169
- def convert_to_gradio_chat_format(chat_messages):
170
- return chat_messages or []
171
-
172
- # =============================================================================
173
- # MCP SERVER HELPERS
174
- # =============================================================================
175
- def mcp_scrape(query: str):
176
- return {"query": query, "result": deep_scrape(query)}
177
-
178
- def mcp_multi_scrape(queries_text: str):
179
- queries = [line.strip() for line in queries_text.splitlines() if line.strip()]
180
- return {"queries": queries, "result": multi_scrape(queries)}
181
-
182
- def mcp_analyze(prompts_text: str):
183
- analysis, seed_chat = analyze_and_seed_chat(prompts_text)
184
- return {"prompts": prompts_text, "analysis": analysis, "seed_chat": seed_chat}
185
-
186
- # =============================================================================
187
- # GRADIO UI
188
- # =============================================================================
189
- def build_demo_with_mcp():
190
- with gr.Blocks(title="AURA Chat Hedge Fund Picks + MCP Server") as demo:
191
- with gr.Tabs():
192
- # Chat / Analysis Tab
193
- with gr.TabItem("Chat / Analysis"):
194
- with gr.Row():
195
- with gr.Column(scale=1):
196
- prompts = gr.Textbox(lines=6, label="Data Prompts")
197
- analyze_btn = gr.Button("Analyze", variant="primary")
198
- error_box = gr.Markdown("", visible=False)
199
- with gr.Column(scale=1):
200
- analysis_out = gr.Textbox(label="Generated Analysis", lines=18, interactive=False)
201
- gr.Markdown("**Chat with AURA**")
202
- chatbot = gr.Chatbot(height=420)
203
- user_input = gr.Textbox(placeholder="Ask follow-up...", label="Your question")
204
- send_btn = gr.Button("Send")
205
-
206
- analysis_state = gr.State("")
207
- chat_state = gr.State([])
208
-
209
- analyze_btn.click(
210
- fn=lambda txt: on_analyze(txt),
211
- inputs=[prompts],
212
- outputs=[analysis_out, error_box, analysis_state, chat_state]
213
- )
214
- send_btn.click(
215
- fn=lambda chat_list, msg, analysis_txt: on_send(chat_list, msg, analysis_txt),
216
- inputs=[chat_state, user_input, analysis_state],
217
- outputs=[chat_state, user_input]
218
- )
219
- user_input.submit(
220
- fn=lambda chat_list, msg, analysis_txt: on_send(chat_list, msg, analysis_txt),
221
- inputs=[chat_state, user_input, analysis_state],
222
- outputs=[chat_state, user_input]
223
- )
224
- chat_state.change(
225
- fn=convert_to_gradio_chat_format,
226
- inputs=[chat_state],
227
- outputs=[chatbot]
228
- )
229
-
230
- # MCP Server Tab
231
- with gr.TabItem("MCP Server"):
232
- gr.Markdown("**Call scraping and analysis functions directly:**")
233
- with gr.Row():
234
- with gr.Column(scale=1):
235
- single_query = gr.Textbox(label="Single Scrape Query")
236
- scrape_btn = gr.Button("Scrape Query")
237
- scrape_out = gr.JSON()
238
- multi_queries = gr.Textbox(lines=6, label="Multi Scrape Queries")
239
- multi_scrape_btn = gr.Button("Multi Scrape")
240
- multi_scrape_out = gr.JSON()
241
- with gr.Column(scale=1):
242
- analysis_prompts = gr.Textbox(lines=6, label="Analysis Prompts")
243
- analyze_mcp_btn = gr.Button("Run Full Analysis")
244
- analyze_mcp_out = gr.JSON()
245
-
246
- scrape_btn.click(fn=mcp_scrape, inputs=[single_query], outputs=[scrape_out])
247
- multi_scrape_btn.click(fn=mcp_multi_scrape, inputs=[multi_queries], outputs=[multi_scrape_out])
248
- analyze_mcp_btn.click(fn=mcp_analyze, inputs=[analysis_prompts], outputs=[analyze_mcp_out])
249
 
250
  return demo
251
 
252
- # =============================================================================
253
- # Handlers for Chat Tab
254
- # =============================================================================
255
- def on_analyze(prompts_text):
256
- analysis_text, initial_chat = analyze_and_seed_chat(prompts_text)
257
- if analysis_text.startswith("ERROR"):
258
- return "", f"**Error:** {analysis_text}", "", []
259
- return analysis_text, "", analysis_text, initial_chat
260
-
261
- def on_send(chat_state_list, user_msg, analysis_text):
262
- if not user_msg.strip():
263
- return chat_state_list or [], ""
264
- updated_history = continue_chat(chat_state_list or [], user_msg, analysis_text)
265
- return updated_history, ""
266
-
267
- # =============================================================================
268
- # CLEAN SHUTDOWN
269
- # =============================================================================
270
  def _cleanup_on_exit():
271
  try:
272
  loop = asyncio.get_event_loop()
273
  if loop and not loop.is_closed():
274
  try: loop.stop()
275
- except Exception: pass
276
  try: loop.close()
277
- except Exception: pass
278
- except Exception: pass
279
 
280
  atexit.register(_cleanup_on_exit)
281
 
282
- # =============================================================================
283
- # RUN
284
- # =============================================================================
285
  if __name__ == "__main__":
286
- demo = build_demo_with_mcp()
287
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
 
1
  """
2
+ AURA Chat — Hedge Fund Picks
3
+ Enhanced UI/UX with instructions, examples, YouTube video, and colored containers.
 
 
4
  """
5
 
6
  import os
 
13
  from typing import List
14
  import gradio as gr
15
 
16
+ # ===================== Defensive event loop =====================
17
  if sys.platform != "win32":
18
  try:
19
  loop = asyncio.new_event_loop()
 
21
  except Exception:
22
  traceback.print_exc()
23
 
24
+ # ===================== CONFIGURATION =====================
 
 
25
  SCRAPER_API_URL = os.getenv("SCRAPER_API_URL", "https://deep-scraper-96.created.app/api/deep-scrape")
26
  SCRAPER_HEADERS = {
27
  "User-Agent": "Mozilla/5.0",
28
  "Content-Type": "application/json"
29
  }
 
30
  LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b:free")
31
  MAX_TOKENS = int(os.getenv("LLM_MAX_TOKENS", "3000"))
32
  SCRAPE_DELAY = float(os.getenv("SCRAPE_DELAY", "1.0"))
 
33
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
34
  OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1")
35
 
36
  PROMPT_TEMPLATE = f"""You are AURA, a concise, professional hedge-fund research assistant.
37
  Task:
38
+ - Given scraped data below, produce a clear analysis listing top stocks (with Investment Duration)
39
+ - Output must be human-readable text, 2-3 sentence summary, 5 top stocks max, and Assumptions & Risks
40
+ Max tokens: {MAX_TOKENS}, Model: {LLM_MODEL}
41
+ """
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ # ===================== SCRAPING HELPERS =====================
 
 
44
  def deep_scrape(query: str, retries: int = 3, timeout: int = 40) -> str:
45
  payload = {"query": query}
46
  last_err = None
 
50
  resp.raise_for_status()
51
  data = resp.json()
52
  if isinstance(data, dict):
53
+ return "\n".join([f"{k.upper()}:\n{v}\n" for k, v in data.items()])
 
54
  return str(data)
55
  except Exception as e:
56
  last_err = e
 
65
  if not q:
66
  continue
67
  aggregated.append(f"\n=== QUERY: {q} ===\n")
68
+ aggregated.append(deep_scrape(q))
 
69
  time.sleep(delay)
70
  return "\n".join(aggregated)
71
 
72
+ # ===================== LLM INTERACTION =====================
 
 
73
  try:
74
  from openai import OpenAI
75
  except Exception:
76
  OpenAI = None
77
 
78
+ def run_llm_system_and_user(system_prompt: str, user_text: str) -> str:
79
  if OpenAI is None:
80
+ return "ERROR: openai package not installed."
81
  if not OPENAI_API_KEY:
82
+ return "ERROR: OPENAI_API_KEY not set."
83
  client = None
84
  try:
85
  client = OpenAI(base_url=OPENAI_BASE_URL, api_key=OPENAI_API_KEY)
86
  completion = client.chat.completions.create(
87
+ model=LLM_MODEL,
88
  messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_text}],
89
+ max_tokens=MAX_TOKENS,
90
  )
91
+ if hasattr(completion, "choices") and completion.choices:
92
  try:
93
  return completion.choices[0].message.content
94
+ except:
95
  return str(completion.choices[0])
96
  return str(completion)
97
  except Exception as e:
 
101
  if client is not None:
102
  try:
103
  client.close()
104
+ except:
105
+ try: asyncio.get_event_loop().run_until_complete(client.aclose())
106
+ except: pass
107
+ except: pass
 
 
 
108
 
109
+ # ===================== MAIN PIPELINE =====================
 
 
110
  def analyze_and_seed_chat(prompts_text: str):
111
  if not prompts_text.strip():
112
+ return "Please enter at least one prompt.", []
113
  queries = [line.strip() for line in prompts_text.splitlines() if line.strip()]
114
+ scraped = multi_scrape(queries)
115
  if scraped.startswith("ERROR"):
116
  return scraped, []
117
+ user_payload = f"SCRAPED DATA:\n\n{scraped}\n\nGenerate analysis."
118
  analysis = run_llm_system_and_user(PROMPT_TEMPLATE, user_payload)
119
  if analysis.startswith("ERROR"):
120
  return analysis, []
121
  initial_chat = [
122
+ {"role": "user", "content": f"Analyze prompts: {', '.join(queries)}"},
123
  {"role": "assistant", "content": analysis}
124
  ]
125
  return analysis, initial_chat
126
 
127
  def continue_chat(chat_messages, user_message: str, analysis_text: str):
128
+ if chat_messages is None: chat_messages = []
129
+ if not user_message.strip(): return chat_messages
 
 
130
  chat_messages.append({"role": "user", "content": user_message})
131
+ followup_system = "You are AURA. Use previous analysis as context; answer follow-ups concisely."
 
 
132
  user_payload = f"REFERENCE ANALYSIS:\n\n{analysis_text}\n\nUSER QUESTION: {user_message}"
133
  assistant_reply = run_llm_system_and_user(followup_system, user_payload)
134
  chat_messages.append({"role": "assistant", "content": assistant_reply})
135
  return chat_messages
136
 
137
+ # ===================== GRADIO UI =====================
138
+ def build_demo():
139
+ with gr.Blocks(title="AURA Chat — Hedge Fund Picks") as demo:
140
+ gr.HTML("""
141
+ <style>
142
+ .header {text-align:center; color:#2C3E50; font-size:32px; font-weight:bold; margin-bottom:10px;}
143
+ .container {background:#f9f9f9; border-radius:10px; padding:15px; margin-bottom:15px; box-shadow:0 4px 10px rgba(0,0,0,0.1);}
144
+ .instructions {color:#34495E; font-size:16px; line-height:1.6;}
145
+ .example {background:#EAF2F8; padding:8px; border-radius:5px; margin-top:5px; font-family:monospace;}
146
+ </style>
147
+ """)
148
+ gr.HTML('<div class="header">AURA Chat Hedge Fund Picks</div>')
149
+
150
+ # Explanatory YouTube video
151
+ gr.Video("https://youtu.be/56zpjyHd3d4", type="youtube", label="How it works")
152
+
153
+ # Instructions container
154
+ gr.HTML("""
155
+ <div class="container instructions">
156
+ <b>What this does:</b> Fetches latest public data on insider trading and top stock market insights based on your prompts.
157
+ It outputs top stock picks with <b>Investment Duration</b> guidance (when to buy and sell).<br><br>
158
+ <b>How to use:</b> Enter one or more prompts below, press <b>Analyze</b>, then chat with AURA about the results.<br><br>
159
+ <b>Example prompts you can copy:</b>
160
+ <div class="example">SEC insider transactions October 2025\n13F filings Q3 2025\nCompany: ACME corp insider buys</div>
161
+ <br>
162
+ The output will help you know which stocks are best to invest in and when to monitor alerts.
163
+ </div>
164
+ """)
165
+
166
+ # Main interface
167
+ with gr.Row():
168
+ with gr.Column(scale=1):
169
+ prompts = gr.Textbox(lines=6, label="Enter Prompts", placeholder="SEC insider transactions October 2025\n13F filings Q3 2025\nCompany: ACME corp insider buys")
170
+ analyze_btn = gr.Button("Analyze", variant="primary")
171
+ error_box = gr.Markdown("", visible=False)
172
+
173
+ with gr.Column(scale=1):
174
+ analysis_out = gr.Textbox(label="Generated Analysis", lines=18, interactive=False)
175
+ gr.Markdown("**Chat with AURA about this analysis**")
176
+ chatbot = gr.Chatbot(height=420)
177
+ user_input = gr.Textbox(placeholder="Ask a follow-up question...", label="Your question")
178
+ send_btn = gr.Button("Send")
179
+
180
+ analysis_state = gr.State("")
181
+ chat_state = gr.State([])
182
+
183
+ def on_analyze(prompts_text):
184
+ analysis_text, initial_chat = analyze_and_seed_chat(prompts_text)
185
+ if analysis_text.startswith("ERROR"):
186
+ return "", f"**Error:** {analysis_text}", "", []
187
+ return analysis_text, "", analysis_text, initial_chat
188
+
189
+ def on_send(chat_state_list, user_msg, analysis_text):
190
+ if not user_msg.strip(): return chat_state_list or [], ""
191
+ updated_history = continue_chat(chat_state_list or [], user_msg, analysis_text)
192
+ return updated_history, ""
193
+
194
+ analyze_btn.click(fn=on_analyze, inputs=[prompts], outputs=[analysis_out, error_box, analysis_state, chat_state])
195
+ send_btn.click(fn=on_send, inputs=[chat_state, user_input, analysis_state], outputs=[chat_state, user_input])
196
+ user_input.submit(fn=on_send, inputs=[chat_state, user_input, analysis_state], outputs=[chat_state, user_input])
197
+ chat_state.change(fn=lambda msgs: msgs or [], inputs=[chat_state], outputs=[chatbot])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  return demo
200
 
201
+ # ===================== CLEAN SHUTDOWN =====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  def _cleanup_on_exit():
203
  try:
204
  loop = asyncio.get_event_loop()
205
  if loop and not loop.is_closed():
206
  try: loop.stop()
207
+ except: pass
208
  try: loop.close()
209
+ except: pass
210
+ except: pass
211
 
212
  atexit.register(_cleanup_on_exit)
213
 
214
+ # ===================== RUN =====================
 
 
215
  if __name__ == "__main__":
216
+ demo = build_demo()
217
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))