import gradio as gr import requests import pandas as pd import plotly.graph_objects as go from huggingface_hub import InferenceClient import os # ----------------------------------------------------------------------------- # 1. CONFIGURATION & STATE # ----------------------------------------------------------------------------- # We keep a global variable to store the latest market text for the AI to read MARKET_CONTEXT = "Market data is currently loading..." def get_client(): # Tries to get the HF_TOKEN from the Space secrets, otherwise falls back to free tier token = os.getenv("HF_TOKEN") return InferenceClient(model="Qwen/Qwen2.5-VL-3B-Instruct", token=token) # ----------------------------------------------------------------------------- # 2. DATA FETCHING & PROCESSING # ----------------------------------------------------------------------------- def fetch_crypto_data(): """Fetches data from CoinGecko and returns a dict of metrics + sparklines + context string""" url = "https://api.coingecko.com/api/v3/coins/markets" params = { "vs_currency": "usd", "ids": "bitcoin,ethereum,solana,binancecoin", "order": "market_cap_desc", "per_page": 4, "page": 1, "sparkline": "true", "price_change_percentage": "24h,7d" } global MARKET_CONTEXT try: response = requests.get(url, params=params, timeout=10) data = response.json() # Build the context string for AI context_parts = [] processed_data = [] for coin in data: symbol = coin['symbol'].upper() price = coin['current_price'] chg_24 = coin.get('price_change_percentage_24h_in_currency', 0) chg_7d = coin.get('price_change_percentage_7d_in_currency', 0) mcap = coin['market_cap'] history = coin.get('sparkline_in_7d', {}).get('price', []) # Add to AI context context_parts.append(f"{symbol}: ${price}, 24h: {chg_24:.1f}%, 7d: {chg_7d:.1f}%") processed_data.append({ "name": coin['name'], "symbol": symbol, "price": price, "chg_24": chg_24, "chg_7d": chg_7d, "mcap": mcap, "history": history }) MARKET_CONTEXT = " | ".join(context_parts) return processed_data except Exception as e: MARKET_CONTEXT = "Error fetching market data." print(f"API Error: {e}") return [] # ----------------------------------------------------------------------------- # 3. UI GENERATION (HTML + PLOTS) # ----------------------------------------------------------------------------- def create_sparkline(history, chg_24): """Creates a minimalist Plotly sparkline""" color = "#00ff00" if chg_24 >= 0 else "#ff4444" fig = go.Figure() fig.add_trace(go.Scatter( y=history, mode='lines', fill='tozeroy', line=dict(color=color, width=2), fillcolor=f"rgba{tuple(int(color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) + (0.1,)}" )) fig.update_layout( template="plotly_dark", paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(l=0, r=0, t=0, b=0), xaxis=dict(visible=False, fixedrange=True), yaxis=dict(visible=False, fixedrange=True), showlegend=False, height=80, ) return fig def generate_dashboard(): """Main function called to refresh the dashboard UI""" data = fetch_crypto_data() outputs = [] if not data: # If API fails, return empty placeholders return [gr.update(), gr.update()] * 4 for coin in data: # 1. Create HTML Block for Metrics color_24 = "#4caf50" if coin['chg_24'] >= 0 else "#ff5252" arrow = "▲" if coin['chg_24'] >= 0 else "▼" html_content = f"""

{coin['name']} {coin['symbol']}

${coin['price']:,.2f}
{arrow} {coin['chg_24']:.2f}% (24h) 7d: {coin['chg_7d']:.2f}%
MCap: ${coin['mcap']/1e9:.1f}B
""" outputs.append(html_content) # 2. Create Plot fig = create_sparkline(coin['history'], coin['chg_24']) outputs.append(fig) return outputs # ----------------------------------------------------------------------------- # 4. AI CHAT FUNCTION # ----------------------------------------------------------------------------- def chat_response(message, history): client = get_client() system_prompt = f"""You are a helpful Crypto Dashboard Assistant using the Qwen model. REAL-TIME MARKET CONTEXT: {MARKET_CONTEXT} INSTRUCTIONS: 1. If the user asks for the price, trend, or stats of a coin in the list, USE THE CONTEXT PROVIDED. 2. If the user asks general questions ("What is DeFi?", "Tell a joke"), answer normally. 3. Keep answers concise and professional. """ messages = [{"role": "system", "content": system_prompt}] # Add history for human, assistant in history: messages.append({"role": "user", "content": human}) messages.append({"role": "assistant", "content": assistant}) messages.append({"role": "user", "content": message}) # Stream response stream = client.chat_completion(messages, max_tokens=500, stream=True) partial_message = "" for chunk in stream: if chunk.choices[0].delta.content: partial_message += chunk.choices[0].delta.content yield partial_message # ----------------------------------------------------------------------------- # 5. GRADIO APP LAYOUT # ----------------------------------------------------------------------------- custom_css = """ body { background-color: #0b0f19; } .contain { max-width: 1200px; margin: auto; } #dashboard-header { text-align: center; color: white; margin-bottom: 20px; } .plot-container { border: none !important; } footer { visibility: hidden; } """ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")) as demo: gr.HTML("

⚡ CryptoDash AI

") # --- DASHBOARD ROW --- with gr.Row(): # We need 4 Columns, each containing HTML (metrics) and Plot (sparkline) # Coin 1 with gr.Column(): c1_html = gr.HTML() c1_plot = gr.Plot(label="Trend", show_label=False, container=False) # Coin 2 with gr.Column(): c2_html = gr.HTML() c2_plot = gr.Plot(label="Trend", show_label=False, container=False) # Coin 3 with gr.Column(): c3_html = gr.HTML() c3_plot = gr.Plot(label="Trend", show_label=False, container=False) # Coin 4 with gr.Column(): c4_html = gr.HTML() c4_plot = gr.Plot(label="Trend", show_label=False, container=False) # Refresh Button btn_refresh = gr.Button("🔄 Refresh Market Data", variant="secondary") # --- CHAT ROW --- gr.HTML("


") gr.Markdown("### 🤖 Ask Qwen about the market") chat_interface = gr.ChatInterface( fn=chat_response, examples=["What is the price of Bitcoin?", "Is Solana up or down today?", "Explain market cap"], theme="soft" ) # --- WIRING --- # When app loads, fetch data and populate the 8 outputs (4 html + 4 plots) demo.load( fn=generate_dashboard, inputs=None, outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot] ) # When refresh clicked, do the same btn_refresh.click( fn=generate_dashboard, inputs=None, outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot] ) if __name__ == "__main__": demo.launch()