GabrielSalem commited on
Commit
4edc9b1
Β·
verified Β·
1 Parent(s): 9dad169

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +227 -79
app.py CHANGED
@@ -1,6 +1,15 @@
1
  """
2
- AURA Chat β€” Hedge Fund Picks
3
- Enhanced UI/UX with instructions, examples, YouTube video, and colored containers.
 
 
 
 
 
 
 
 
 
4
  """
5
 
6
  import os
@@ -10,10 +19,12 @@ import asyncio
10
  import requests
11
  import atexit
12
  import traceback
 
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,197 +32,334 @@ if sys.platform != "win32":
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
 
47
  for attempt in range(1, retries + 1):
48
  try:
49
- resp = requests.post(SCRAPER_API_URL, headers=SCRAPER_HEADERS, json=payload, timeout=timeout)
 
 
 
 
 
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
57
  if attempt < retries:
58
  time.sleep(1.0)
 
59
  return f"ERROR: Scraper failed: {last_err}"
60
 
 
61
  def multi_scrape(queries: List[str], delay: float = SCRAPE_DELAY) -> str:
 
62
  aggregated = []
63
  for q in queries:
64
  q = q.strip()
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:
98
  return f"ERROR: LLM call failed: {e}"
99
  finally:
 
100
  try:
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)))
 
 
 
 
1
  """
2
+ AURA Chat β€” Gradio Space
3
+ Single-file Gradio app that:
4
+ - Accepts newline-separated prompts (data queries) from the user.
5
+ - On "Analyze" scrapes those queries, sends the aggregated text to a locked LLM,
6
+ and returns a polished analysis with a ranked list of best stocks and an
7
+ "Investment Duration" (when to enter / when to exit) for each stock.
8
+ - Seeds a chat component with the generated analysis; user can then chat about it.
9
+
10
+ Notes:
11
+ - Model, max tokens, and delay between scrapes are fixed and cannot be changed via UI.
12
+ - Set OPENAI_API_KEY in environment (Space Secrets).
13
  """
14
 
15
  import os
 
19
  import requests
20
  import atexit
21
  import traceback
22
+ from datetime import datetime
23
  from typing import List
24
  import gradio as gr
25
 
26
+
27
+ # Defensive: ensure a fresh event loop early to avoid fd race on shutdown.
28
  if sys.platform != "win32":
29
  try:
30
  loop = asyncio.new_event_loop()
 
32
  except Exception:
33
  traceback.print_exc()
34
 
35
+
36
+ # =============================================================================
37
+ # CONFIGURATION (fixed)
38
+ # =============================================================================
39
  SCRAPER_API_URL = os.getenv("SCRAPER_API_URL", "https://deep-scraper-96.created.app/api/deep-scrape")
40
  SCRAPER_HEADERS = {
41
  "User-Agent": "Mozilla/5.0",
42
  "Content-Type": "application/json"
43
  }
44
+
45
+ # FIXED model & tokens (cannot be changed from UI)
46
  LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b:free")
47
  MAX_TOKENS = int(os.getenv("LLM_MAX_TOKENS", "3000"))
48
  SCRAPE_DELAY = float(os.getenv("SCRAPE_DELAY", "1.0"))
49
+
50
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
51
  OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1")
52
 
53
+
54
+ # =============================================================================
55
+ # PROMPT ENGINEERING (fixed)
56
+ # =============================================================================
57
  PROMPT_TEMPLATE = f"""You are AURA, a concise, professional hedge-fund research assistant.
58
+
59
  Task:
60
+ - Given scraped data below, produce a clear, readable analysis that:
61
+ 1) Lists the top 5 stock picks (or fewer if not enough data).
62
+ 2) For each stock provide: Ticker / Company name, short rationale (2-3 bullets),
63
+ and an explicit **Investment Duration** entry: a one-line "When to Invest"
64
+ and a one-line "When to Sell" instruction (these two lines are mandatory
65
+ for each stock).
66
+ 3) Keep each stock entry short and scannable. Use a bullet list or numbered list.
67
+ 4) At the top, provide a 2-3 sentence summary conclusion (market context +
68
+ highest conviction pick).
69
+ 5) Output in plain text, clean formatting, easy for humans to read. No JSON.
70
+ 6) After the list, include a concise "Assumptions & Risks" section (2-3 bullet points).
71
+
72
+ Important: Be decisive. If data is insufficient, state that clearly and provide
73
+ the best-available picks with lower confidence.
74
+
75
+ Max tokens for the LLM response: {MAX_TOKENS}
76
+ Model: {LLM_MODEL}"""
77
+
78
 
79
+ # =============================================================================
80
+ # SCRAPING HELPERS
81
+ # =============================================================================
82
  def deep_scrape(query: str, retries: int = 3, timeout: int = 40) -> str:
83
+ """Post a query to SCRAPER_API_URL and return a readable aggregation (or an error string)."""
84
  payload = {"query": query}
85
  last_err = None
86
+
87
  for attempt in range(1, retries + 1):
88
  try:
89
+ resp = requests.post(
90
+ SCRAPER_API_URL,
91
+ headers=SCRAPER_HEADERS,
92
+ json=payload,
93
+ timeout=timeout
94
+ )
95
  resp.raise_for_status()
96
  data = resp.json()
97
+
98
+ # Format into readable text
99
  if isinstance(data, dict):
100
+ parts = [f"{k.upper()}:\n{v}\n" for k, v in data.items()]
101
+ return "\n".join(parts)
102
+ else:
103
+ return str(data)
104
+
105
  except Exception as e:
106
  last_err = e
107
  if attempt < retries:
108
  time.sleep(1.0)
109
+
110
  return f"ERROR: Scraper failed: {last_err}"
111
 
112
+
113
  def multi_scrape(queries: List[str], delay: float = SCRAPE_DELAY) -> str:
114
+ """Scrape multiple queries and join results into one large string."""
115
  aggregated = []
116
  for q in queries:
117
  q = q.strip()
118
  if not q:
119
  continue
120
  aggregated.append(f"\n=== QUERY: {q} ===\n")
121
+ scraped = deep_scrape(q)
122
+ aggregated.append(scraped)
123
  time.sleep(delay)
124
  return "\n".join(aggregated)
125
 
126
+
127
+ # =============================================================================
128
+ # LLM INTERACTION
129
+ # =============================================================================
130
  try:
131
  from openai import OpenAI
132
  except Exception:
133
  OpenAI = None
134
 
135
+
136
+ def run_llm_system_and_user(
137
+ system_prompt: str,
138
+ user_text: str,
139
+ model: str = LLM_MODEL,
140
+ max_tokens: int = MAX_TOKENS
141
+ ) -> str:
142
+ """Create the OpenAI client lazily, call the chat completions endpoint, then close."""
143
  if OpenAI is None:
144
+ return "ERROR: openai package not installed or available. See requirements."
145
+
146
  if not OPENAI_API_KEY:
147
+ return "ERROR: OPENAI_API_KEY not set in environment. Please add it to Space Secrets."
148
+
149
  client = None
150
  try:
151
  client = OpenAI(base_url=OPENAI_BASE_URL, api_key=OPENAI_API_KEY)
152
  completion = client.chat.completions.create(
153
+ model=model,
154
+ messages=[
155
+ {"role": "system", "content": system_prompt},
156
+ {"role": "user", "content": user_text},
157
+ ],
158
+ max_tokens=max_tokens,
159
  )
160
+
161
+ # Extract content robustly
162
+ if hasattr(completion, "choices") and len(completion.choices) > 0:
163
  try:
164
  return completion.choices[0].message.content
165
+ except Exception:
166
  return str(completion.choices[0])
167
  return str(completion)
168
+
169
  except Exception as e:
170
  return f"ERROR: LLM call failed: {e}"
171
  finally:
172
+ # Try to close client transport
173
  try:
174
  if client is not None:
175
  try:
176
  client.close()
177
+ except Exception:
178
+ try:
179
+ asyncio.get_event_loop().run_until_complete(client.aclose())
180
+ except Exception:
181
+ pass
182
+ except Exception:
183
+ pass
184
 
185
+
186
+ # =============================================================================
187
+ # MAIN PIPELINE
188
+ # =============================================================================
189
  def analyze_and_seed_chat(prompts_text: str):
190
+ """Called when user clicks Analyze. Returns: (analysis_text, initial_chat_messages_list)"""
191
  if not prompts_text.strip():
192
+ return "Please enter at least one prompt (query) describing what data to gather.", []
193
+
194
  queries = [line.strip() for line in prompts_text.splitlines() if line.strip()]
195
+ scraped = multi_scrape(queries, delay=SCRAPE_DELAY)
196
+
197
  if scraped.startswith("ERROR"):
198
  return scraped, []
199
+
200
+ # Compose user payload for LLM
201
+ user_payload = f"SCRAPED DATA:\n\n{scraped}\n\nPlease follow the system instructions and output the analysis."
202
  analysis = run_llm_system_and_user(PROMPT_TEMPLATE, user_payload)
203
+
204
  if analysis.startswith("ERROR"):
205
  return analysis, []
206
+
207
+ # Seed chat with user request and assistant analysis
208
  initial_chat = [
209
+ {"role": "user", "content": f"Analyze the data I provided (prompts: {', '.join(queries)})"},
210
  {"role": "assistant", "content": analysis}
211
  ]
212
  return analysis, initial_chat
213
 
214
+
215
  def continue_chat(chat_messages, user_message: str, analysis_text: str):
216
+ """Handle chat follow-ups. Returns updated list of message dicts."""
217
+ if chat_messages is None:
218
+ chat_messages = []
219
+ if not user_message or not user_message.strip():
220
+ return chat_messages
221
+
222
+ # Append user's new message
223
  chat_messages.append({"role": "user", "content": user_message})
224
+
225
+ # Build LLM input using analysis as reference context
226
+ followup_system = (
227
+ "You are AURA, a helpful analyst. The conversation context includes a recently "
228
+ "generated analysis from scraped data. Use that analysis as ground truth context; "
229
+ "answer follow-up questions, explain rationale, and provide clarifications. "
230
+ "Be concise and actionable."
231
+ )
232
+ user_payload = f"REFERENCE ANALYSIS:\n\n{analysis_text}\n\nUSER QUESTION: {user_message}\n\nRespond concisely and reference lines from the analysis where appropriate."
233
+
234
  assistant_reply = run_llm_system_and_user(followup_system, user_payload)
235
+ if assistant_reply.startswith("ERROR"):
236
+ assistant_reply = assistant_reply
237
+
238
+ # Append assistant reply
239
  chat_messages.append({"role": "assistant", "content": assistant_reply})
240
  return chat_messages
241
 
242
+
243
+ # =============================================================================
244
+ # GRADIO UI
245
+ # =============================================================================
246
  def build_demo():
247
  with gr.Blocks(title="AURA Chat β€” Hedge Fund Picks") as demo:
248
+ # Custom CSS
249
  gr.HTML("""
250
  <style>
251
+ .gradio-container { max-width: 1100px; margin: 18px auto; }
252
+ .header { text-align: left; margin-bottom: 6px; }
253
+ .muted { color: #7d8590; font-size: 14px; }
254
+ .analysis-box { background: #ffffff; border-radius: 8px; padding: 12px; box-shadow: 0 4px 14px rgba(0,0,0,0.06); }
255
  </style>
256
  """)
 
257
 
258
+ gr.Markdown("# AURA Chat β€” Hedge Fund Picks")
259
+ gr.Markdown(
260
+ "**Enter one or more data prompts (one per line)** β€” e.g. SEC insider transactions october 2025 company XYZ.\n\n"
261
+ "Only input prompts; model, tokens and timing are fixed. Press **Analyze** to fetch & generate the picks. "
262
+ "After analysis you can chat with the assistant about the results."
263
+ )
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  with gr.Row():
266
  with gr.Column(scale=1):
267
+ prompts = gr.Textbox(
268
+ lines=6,
269
+ label="Data Prompts (one per line)",
270
+ placeholder="SEC insider transactions october 2025\n13F filings Q3 2025\ncompany: ACME corp insider buys"
271
+ )
272
  analyze_btn = gr.Button("Analyze", variant="primary")
273
  error_box = gr.Markdown("", visible=False)
274
+ gr.Markdown(f"**Fixed settings:** Model = {LLM_MODEL} β€’ Max tokens = {MAX_TOKENS} β€’ Scrape delay = {SCRAPE_DELAY}s")
275
+ gr.Markdown("**Important:** Add your OPENAI_API_KEY to Space Secrets before running.")
276
 
277
  with gr.Column(scale=1):
278
+ analysis_out = gr.Textbox(
279
+ label="Generated Analysis (Top picks with Investment Duration)",
280
+ lines=18,
281
+ interactive=False
282
+ )
283
  gr.Markdown("**Chat with AURA about this analysis**")
284
+ chatbot = gr.Chatbot(label="AURA Chat", height=420)
285
+ user_input = gr.Textbox(
286
+ placeholder="Ask a follow-up question about the analysis...",
287
+ label="Your question"
288
+ )
289
  send_btn = gr.Button("Send")
290
+
291
+ # States
292
  analysis_state = gr.State("")
293
  chat_state = gr.State([])
294
+
295
+ # Handler functions
296
  def on_analyze(prompts_text):
297
  analysis_text, initial_chat = analyze_and_seed_chat(prompts_text)
298
  if analysis_text.startswith("ERROR"):
299
  return "", f"**Error:** {analysis_text}", "", []
300
  return analysis_text, "", analysis_text, initial_chat
301
+
302
  def on_send(chat_state_list, user_msg, analysis_text):
303
+ if not user_msg or not user_msg.strip():
304
+ return chat_state_list or [], ""
305
  updated_history = continue_chat(chat_state_list or [], user_msg, analysis_text)
306
  return updated_history, ""
307
+
308
+ def render_chat(chat_messages):
309
+ return chat_messages or []
310
+
311
+ # Wire handlers
312
+ analyze_btn.click(
313
+ fn=on_analyze,
314
+ inputs=[prompts],
315
+ outputs=[analysis_out, error_box, analysis_state, chat_state]
316
+ )
317
+ send_btn.click(
318
+ fn=on_send,
319
+ inputs=[chat_state, user_input, analysis_state],
320
+ outputs=[chat_state, user_input]
321
+ )
322
+ user_input.submit(
323
+ fn=on_send,
324
+ inputs=[chat_state, user_input, analysis_state],
325
+ outputs=[chat_state, user_input]
326
+ )
327
+ chat_state.change(
328
+ fn=render_chat,
329
+ inputs=[chat_state],
330
+ outputs=[chatbot]
331
+ )
332
+
333
  return demo
334
 
335
+
336
+ # =============================================================================
337
+ # CLEAN SHUTDOWN
338
+ # =============================================================================
339
  def _cleanup_on_exit():
340
  try:
341
  loop = asyncio.get_event_loop()
342
  if loop and not loop.is_closed():
343
+ try:
344
+ loop.stop()
345
+ except Exception:
346
+ pass
347
+ try:
348
+ loop.close()
349
+ except Exception:
350
+ pass
351
+ except Exception:
352
+ pass
353
 
354
  atexit.register(_cleanup_on_exit)
355
 
356
+
357
+ # =============================================================================
358
+ # RUN
359
+ # =============================================================================
360
  if __name__ == "__main__":
361
  demo = build_demo()
362
+ demo.launch(
363
+ server_name="0.0.0.0",
364
+ server_port=int(os.environ.get("PORT", 7860))
365
+ )