CryptoCreeper commited on
Commit
7f95055
·
verified ·
1 Parent(s): 25315a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -91
app.py CHANGED
@@ -3,24 +3,24 @@ import requests
3
  import pandas as pd
4
  import plotly.graph_objects as go
5
  from huggingface_hub import InferenceClient
6
- import os
7
 
8
  # -----------------------------------------------------------------------------
9
  # 1. CONFIGURATION & STATE
10
  # -----------------------------------------------------------------------------
11
- # We keep a global variable to store the latest market text for the AI to read
12
- MARKET_CONTEXT = "Market data is currently loading..."
13
 
14
- def get_client():
15
- # Tries to get the HF_TOKEN from the Space secrets, otherwise falls back to free tier
16
- token = os.getenv("HF_TOKEN")
17
- return InferenceClient(model="Qwen/Qwen2.5-VL-3B-Instruct", token=token)
18
 
19
  # -----------------------------------------------------------------------------
20
- # 2. DATA FETCHING & PROCESSING
21
  # -----------------------------------------------------------------------------
22
  def fetch_crypto_data():
23
- """Fetches data from CoinGecko and returns a dict of metrics + sparklines + context string"""
 
 
 
24
  url = "https://api.coingecko.com/api/v3/coins/markets"
25
  params = {
26
  "vs_currency": "usd",
@@ -36,6 +36,10 @@ def fetch_crypto_data():
36
 
37
  try:
38
  response = requests.get(url, params=params, timeout=10)
 
 
 
 
39
  data = response.json()
40
 
41
  # Build the context string for AI
@@ -45,13 +49,13 @@ def fetch_crypto_data():
45
  for coin in data:
46
  symbol = coin['symbol'].upper()
47
  price = coin['current_price']
48
- chg_24 = coin.get('price_change_percentage_24h_in_currency', 0)
49
- chg_7d = coin.get('price_change_percentage_7d_in_currency', 0)
50
- mcap = coin['market_cap']
51
  history = coin.get('sparkline_in_7d', {}).get('price', [])
52
 
53
  # Add to AI context
54
- context_parts.append(f"{symbol}: ${price}, 24h: {chg_24:.1f}%, 7d: {chg_7d:.1f}%")
55
 
56
  processed_data.append({
57
  "name": coin['name'],
@@ -67,16 +71,16 @@ def fetch_crypto_data():
67
  return processed_data
68
 
69
  except Exception as e:
70
- MARKET_CONTEXT = "Error fetching market data."
71
  print(f"API Error: {e}")
72
- return []
73
 
74
  # -----------------------------------------------------------------------------
75
- # 3. UI GENERATION (HTML + PLOTS)
76
  # -----------------------------------------------------------------------------
77
  def create_sparkline(history, chg_24):
78
  """Creates a minimalist Plotly sparkline"""
79
- color = "#00ff00" if chg_24 >= 0 else "#ff4444"
 
80
 
81
  fig = go.Figure()
82
  fig.add_trace(go.Scatter(
@@ -91,7 +95,7 @@ def create_sparkline(history, chg_24):
91
  template="plotly_dark",
92
  paper_bgcolor='rgba(0,0,0,0)',
93
  plot_bgcolor='rgba(0,0,0,0)',
94
- margin=dict(l=0, r=0, t=0, b=0),
95
  xaxis=dict(visible=False, fixedrange=True),
96
  yaxis=dict(visible=False, fixedrange=True),
97
  showlegend=False,
@@ -99,138 +103,145 @@ def create_sparkline(history, chg_24):
99
  )
100
  return fig
101
 
102
- def generate_dashboard():
103
- """Main function called to refresh the dashboard UI"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  data = fetch_crypto_data()
105
- outputs = []
106
 
 
107
  if not data:
108
- # If API fails, return empty placeholders
109
- return [gr.update(), gr.update()] * 4
110
 
 
111
  for coin in data:
112
- # 1. Create HTML Block for Metrics
113
- color_24 = "#4caf50" if coin['chg_24'] >= 0 else "#ff5252"
114
- arrow = "▲" if coin['chg_24'] >= 0 else "▼"
115
-
116
- html_content = f"""
117
- <div style="background-color: #1f2937; padding: 15px; border-radius: 10px; border: 1px solid #374151;">
118
- <div style="display: flex; justify-content: space-between; align-items: center;">
119
- <h3 style="margin: 0; color: white;">{coin['name']} <span style="font-size: 0.8em; color: #9ca3af;">{coin['symbol']}</span></h3>
120
- <span style="font-size: 1.2em; font-weight: bold; color: white;">${coin['price']:,.2f}</span>
121
- </div>
122
- <div style="display: flex; justify-content: space-between; margin-top: 10px;">
123
- <span style="color: {color_24}; font-weight: bold;">{arrow} {coin['chg_24']:.2f}% (24h)</span>
124
- <span style="color: #9ca3af;">7d: {coin['chg_7d']:.2f}%</span>
125
- </div>
126
- <div style="font-size: 0.8em; color: #6b7280; margin-top: 5px;">MCap: ${coin['mcap']/1e9:.1f}B</div>
127
- </div>
128
- """
129
- outputs.append(html_content)
130
-
131
- # 2. Create Plot
132
- fig = create_sparkline(coin['history'], coin['chg_24'])
133
- outputs.append(fig)
134
 
135
  return outputs
136
 
137
  # -----------------------------------------------------------------------------
138
- # 4. AI CHAT FUNCTION
139
  # -----------------------------------------------------------------------------
140
- def chat_response(message, history):
141
- client = get_client()
142
-
143
- system_prompt = f"""You are a helpful Crypto Dashboard Assistant using the Qwen model.
144
 
145
- REAL-TIME MARKET CONTEXT:
146
  {MARKET_CONTEXT}
147
 
148
  INSTRUCTIONS:
149
- 1. If the user asks for the price, trend, or stats of a coin in the list, USE THE CONTEXT PROVIDED.
150
- 2. If the user asks general questions ("What is DeFi?", "Tell a joke"), answer normally.
151
- 3. Keep answers concise and professional.
152
  """
153
 
154
  messages = [{"role": "system", "content": system_prompt}]
155
 
156
- # Add history
157
  for human, assistant in history:
158
  messages.append({"role": "user", "content": human})
159
  messages.append({"role": "assistant", "content": assistant})
160
 
161
  messages.append({"role": "user", "content": message})
162
 
163
- # Stream response
164
- stream = client.chat_completion(messages, max_tokens=500, stream=True)
165
-
166
- partial_message = ""
167
- for chunk in stream:
168
- if chunk.choices[0].delta.content:
169
- partial_message += chunk.choices[0].delta.content
170
- yield partial_message
 
171
 
172
  # -----------------------------------------------------------------------------
173
  # 5. GRADIO APP LAYOUT
174
  # -----------------------------------------------------------------------------
175
  custom_css = """
176
- body { background-color: #0b0f19; }
177
- .contain { max-width: 1200px; margin: auto; }
178
- #dashboard-header { text-align: center; color: white; margin-bottom: 20px; }
179
- .plot-container { border: none !important; }
180
  footer { visibility: hidden; }
181
  """
182
 
 
183
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")) as demo:
184
 
185
- gr.HTML("<h1 id='dashboard-header'>⚡ CryptoDash AI</h1>")
186
 
187
  # --- DASHBOARD ROW ---
 
188
  with gr.Row():
189
- # We need 4 Columns, each containing HTML (metrics) and Plot (sparkline)
190
- # Coin 1
191
  with gr.Column():
192
  c1_html = gr.HTML()
193
- c1_plot = gr.Plot(label="Trend", show_label=False, container=False)
194
 
195
- # Coin 2
196
  with gr.Column():
197
  c2_html = gr.HTML()
198
- c2_plot = gr.Plot(label="Trend", show_label=False, container=False)
199
 
200
- # Coin 3
201
  with gr.Column():
202
  c3_html = gr.HTML()
203
- c3_plot = gr.Plot(label="Trend", show_label=False, container=False)
204
 
205
- # Coin 4
206
  with gr.Column():
207
  c4_html = gr.HTML()
208
- c4_plot = gr.Plot(label="Trend", show_label=False, container=False)
209
 
210
- # Refresh Button
211
  btn_refresh = gr.Button("🔄 Refresh Market Data", variant="secondary")
212
 
213
- # --- CHAT ROW ---
214
- gr.HTML("<br><hr style='border-color: #374151;'><br>")
215
- gr.Markdown("### 🤖 Ask Qwen about the market")
216
-
217
- chat_interface = gr.ChatInterface(
218
- fn=chat_response,
219
- examples=["What is the price of Bitcoin?", "Is Solana up or down today?", "Explain market cap"],
220
- theme="soft"
221
  )
222
 
223
- # --- WIRING ---
224
- # When app loads, fetch data and populate the 8 outputs (4 html + 4 plots)
225
  demo.load(
226
- fn=generate_dashboard,
227
  inputs=None,
228
  outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot]
229
  )
230
 
231
- # When refresh clicked, do the same
232
  btn_refresh.click(
233
- fn=generate_dashboard,
234
  inputs=None,
235
  outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot]
236
  )
 
3
  import pandas as pd
4
  import plotly.graph_objects as go
5
  from huggingface_hub import InferenceClient
 
6
 
7
  # -----------------------------------------------------------------------------
8
  # 1. CONFIGURATION & STATE
9
  # -----------------------------------------------------------------------------
10
+ # Global variable to store the latest market text for the AI
11
+ MARKET_CONTEXT = "Market data is loading..."
12
 
13
+ # Initialize the public model (no token required)
14
+ client = InferenceClient(model="Qwen/Qwen2.5-VL-3B-Instruct")
 
 
15
 
16
  # -----------------------------------------------------------------------------
17
+ # 2. DATA FETCHING (CoinGecko)
18
  # -----------------------------------------------------------------------------
19
  def fetch_crypto_data():
20
+ """
21
+ Fetches data from CoinGecko.
22
+ Returns: A list of dicts for the UI, and updates the global AI context.
23
+ """
24
  url = "https://api.coingecko.com/api/v3/coins/markets"
25
  params = {
26
  "vs_currency": "usd",
 
36
 
37
  try:
38
  response = requests.get(url, params=params, timeout=10)
39
+
40
+ if response.status_code != 200:
41
+ return None
42
+
43
  data = response.json()
44
 
45
  # Build the context string for AI
 
49
  for coin in data:
50
  symbol = coin['symbol'].upper()
51
  price = coin['current_price']
52
+ chg_24 = coin.get('price_change_percentage_24h_in_currency', 0) or 0
53
+ chg_7d = coin.get('price_change_percentage_7d_in_currency', 0) or 0
54
+ mcap = coin['market_cap'] or 0
55
  history = coin.get('sparkline_in_7d', {}).get('price', [])
56
 
57
  # Add to AI context
58
+ context_parts.append(f"[{symbol}: ${price}, 24h:{chg_24:.1f}%, 7d:{chg_7d:.1f}%]")
59
 
60
  processed_data.append({
61
  "name": coin['name'],
 
71
  return processed_data
72
 
73
  except Exception as e:
 
74
  print(f"API Error: {e}")
75
+ return None
76
 
77
  # -----------------------------------------------------------------------------
78
+ # 3. UI HELPERS (Plots & HTML)
79
  # -----------------------------------------------------------------------------
80
  def create_sparkline(history, chg_24):
81
  """Creates a minimalist Plotly sparkline"""
82
+ # Green if positive, Red if negative
83
+ color = "#10B981" if chg_24 >= 0 else "#EF4444"
84
 
85
  fig = go.Figure()
86
  fig.add_trace(go.Scatter(
 
95
  template="plotly_dark",
96
  paper_bgcolor='rgba(0,0,0,0)',
97
  plot_bgcolor='rgba(0,0,0,0)',
98
+ margin=dict(l=0, r=0, t=10, b=0),
99
  xaxis=dict(visible=False, fixedrange=True),
100
  yaxis=dict(visible=False, fixedrange=True),
101
  showlegend=False,
 
103
  )
104
  return fig
105
 
106
+ def create_card_html(coin):
107
+ """Generates the HTML card for a single coin"""
108
+ if not coin:
109
+ return "<div style='color:white;'>Error Loading</div>"
110
+
111
+ color_24 = "#10B981" if coin['chg_24'] >= 0 else "#EF4444"
112
+ arrow = "▲" if coin['chg_24'] >= 0 else "▼"
113
+
114
+ html = f"""
115
+ <div style="background-color: #1F2937; padding: 20px; border-radius: 12px; border: 1px solid #374151; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
116
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
117
+ <div style="display: flex; align-items: baseline; gap: 8px;">
118
+ <h2 style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #F3F4F6;">{coin['name']}</h2>
119
+ <span style="font-size: 0.875rem; color: #9CA3AF;">{coin['symbol']}</span>
120
+ </div>
121
+ <span style="font-size: 1.25rem; font-weight: 700; color: #F3F4F6;">${coin['price']:,.2f}</span>
122
+ </div>
123
+ <div style="display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem;">
124
+ <div style="color: {color_24}; font-weight: 600;">
125
+ {arrow} {coin['chg_24']:.2f}% (24h)
126
+ </div>
127
+ <div style="color: #9CA3AF;">
128
+ MCap: ${coin['mcap']/1e9:.1f}B
129
+ </div>
130
+ </div>
131
+ </div>
132
+ """
133
+ return html
134
+
135
+ def refresh_dashboard():
136
+ """Main function called to update the dashboard"""
137
  data = fetch_crypto_data()
 
138
 
139
+ # If API fails (rate limit), return dummy updates
140
  if not data:
141
+ return [gr.update()] * 8 # 4 HTML blocks + 4 Plots
 
142
 
143
+ outputs = []
144
  for coin in data:
145
+ outputs.append(create_card_html(coin))
146
+ outputs.append(create_sparkline(coin['history'], coin['chg_24']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  return outputs
149
 
150
  # -----------------------------------------------------------------------------
151
+ # 4. AI CHAT LOGIC
152
  # -----------------------------------------------------------------------------
153
+ def chat_logic(message, history):
154
+ system_prompt = f"""You are a professional Crypto Dashboard Assistant.
 
 
155
 
156
+ LIVE MARKET DATA (Use this if asked about prices):
157
  {MARKET_CONTEXT}
158
 
159
  INSTRUCTIONS:
160
+ 1. If the user asks for the price, trend, or stats of a coin, USE THE LIVE DATA above.
161
+ 2. If the user asks general questions ("What is mining?", "Who is Satoshi?"), answer normally.
162
+ 3. Keep answers concise, helpful, and professional.
163
  """
164
 
165
  messages = [{"role": "system", "content": system_prompt}]
166
 
 
167
  for human, assistant in history:
168
  messages.append({"role": "user", "content": human})
169
  messages.append({"role": "assistant", "content": assistant})
170
 
171
  messages.append({"role": "user", "content": message})
172
 
173
+ try:
174
+ stream = client.chat_completion(messages, max_tokens=512, stream=True, temperature=0.7)
175
+ partial_message = ""
176
+ for chunk in stream:
177
+ if chunk.choices[0].delta.content:
178
+ partial_message += chunk.choices[0].delta.content
179
+ yield partial_message
180
+ except Exception as e:
181
+ yield f"⚠️ AI Error: {str(e)}"
182
 
183
  # -----------------------------------------------------------------------------
184
  # 5. GRADIO APP LAYOUT
185
  # -----------------------------------------------------------------------------
186
  custom_css = """
187
+ body { background-color: #111827; }
188
+ .contain { max-width: 1400px; margin: auto; padding-top: 20px; }
189
+ h1 { color: #F3F4F6; text-align: center; margin-bottom: 30px; }
190
+ .plot-container { border: none !important; background: transparent !important; }
191
  footer { visibility: hidden; }
192
  """
193
 
194
+ # We define the theme on the Blocks object, but remove it from ChatInterface
195
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")) as demo:
196
 
197
+ gr.HTML("<h1>⚡ CryptoDash AI</h1>")
198
 
199
  # --- DASHBOARD ROW ---
200
+ # We initialize with empty plots/html, they get filled by the load() event
201
  with gr.Row():
202
+ # Column 1: BTC
 
203
  with gr.Column():
204
  c1_html = gr.HTML()
205
+ c1_plot = gr.Plot(show_label=False, container=False)
206
 
207
+ # Column 2: ETH
208
  with gr.Column():
209
  c2_html = gr.HTML()
210
+ c2_plot = gr.Plot(show_label=False, container=False)
211
 
212
+ # Column 3: SOL
213
  with gr.Column():
214
  c3_html = gr.HTML()
215
+ c3_plot = gr.Plot(show_label=False, container=False)
216
 
217
+ # Column 4: BNB
218
  with gr.Column():
219
  c4_html = gr.HTML()
220
+ c4_plot = gr.Plot(show_label=False, container=False)
221
 
 
222
  btn_refresh = gr.Button("🔄 Refresh Market Data", variant="secondary")
223
 
224
+ # --- CHAT SECTION ---
225
+ gr.HTML("<br><div style='height: 1px; background-color: #374151; margin: 20px 0;'></div>")
226
+ gr.Markdown("### 🤖 Qwen Crypto Assistant")
227
+
228
+ # NOTE: Removed 'theme' argument here to fix the error
229
+ chat = gr.ChatInterface(
230
+ fn=chat_logic,
231
+ examples=["What is the price of Bitcoin?", "Is the market up today?", "Explain market cap"],
232
  )
233
 
234
+ # --- EVENT WIRING ---
235
+ # Load data immediately when app starts
236
  demo.load(
237
+ fn=refresh_dashboard,
238
  inputs=None,
239
  outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot]
240
  )
241
 
242
+ # Refresh button click
243
  btn_refresh.click(
244
+ fn=refresh_dashboard,
245
  inputs=None,
246
  outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot]
247
  )