Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from huggingface_hub import InferenceClient | |
| # ----------------------------------------------------------------------------- | |
| # 1. CONFIGURATION & STATE | |
| # ----------------------------------------------------------------------------- | |
| # Global variable to store the latest market text for the AI | |
| MARKET_CONTEXT = "Market data is loading..." | |
| # Initialize the public model (no token required) | |
| client = InferenceClient(model="Qwen/Qwen2.5-VL-3B-Instruct") | |
| # ----------------------------------------------------------------------------- | |
| # 2. DATA FETCHING (CoinGecko) | |
| # ----------------------------------------------------------------------------- | |
| def fetch_crypto_data(): | |
| """ | |
| Fetches data from CoinGecko. | |
| Returns: A list of dicts for the UI, and updates the global AI context. | |
| """ | |
| 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) | |
| if response.status_code != 200: | |
| return None | |
| 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) or 0 | |
| chg_7d = coin.get('price_change_percentage_7d_in_currency', 0) or 0 | |
| mcap = coin['market_cap'] or 0 | |
| 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: | |
| print(f"API Error: {e}") | |
| return None | |
| # ----------------------------------------------------------------------------- | |
| # 3. UI HELPERS (Plots & HTML) | |
| # ----------------------------------------------------------------------------- | |
| def create_sparkline(history, chg_24): | |
| """Creates a minimalist Plotly sparkline""" | |
| # Green if positive, Red if negative | |
| color = "#10B981" if chg_24 >= 0 else "#EF4444" | |
| 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=10, b=0), | |
| xaxis=dict(visible=False, fixedrange=True), | |
| yaxis=dict(visible=False, fixedrange=True), | |
| showlegend=False, | |
| height=80, | |
| ) | |
| return fig | |
| def create_card_html(coin): | |
| """Generates the HTML card for a single coin""" | |
| if not coin: | |
| return "<div style='color:white;'>Error Loading</div>" | |
| color_24 = "#10B981" if coin['chg_24'] >= 0 else "#EF4444" | |
| arrow = "▲" if coin['chg_24'] >= 0 else "▼" | |
| html = f""" | |
| <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);"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> | |
| <div style="display: flex; align-items: baseline; gap: 8px;"> | |
| <h2 style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #F3F4F6;">{coin['name']}</h2> | |
| <span style="font-size: 0.875rem; color: #9CA3AF;">{coin['symbol']}</span> | |
| </div> | |
| <span style="font-size: 1.25rem; font-weight: 700; color: #F3F4F6;">${coin['price']:,.2f}</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem;"> | |
| <div style="color: {color_24}; font-weight: 600;"> | |
| {arrow} {coin['chg_24']:.2f}% (24h) | |
| </div> | |
| <div style="color: #9CA3AF;"> | |
| MCap: ${coin['mcap']/1e9:.1f}B | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| def refresh_dashboard(): | |
| """Main function called to update the dashboard""" | |
| data = fetch_crypto_data() | |
| # If API fails (rate limit), return dummy updates | |
| if not data: | |
| return [gr.update()] * 8 # 4 HTML blocks + 4 Plots | |
| outputs = [] | |
| for coin in data: | |
| outputs.append(create_card_html(coin)) | |
| outputs.append(create_sparkline(coin['history'], coin['chg_24'])) | |
| return outputs | |
| # ----------------------------------------------------------------------------- | |
| # 4. AI CHAT LOGIC | |
| # ----------------------------------------------------------------------------- | |
| def chat_logic(message, history): | |
| system_prompt = f"""You are a professional Crypto Dashboard Assistant. | |
| LIVE MARKET DATA (Use this if asked about prices): | |
| {MARKET_CONTEXT} | |
| INSTRUCTIONS: | |
| 1. If the user asks for the price, trend, or stats of a coin, USE THE LIVE DATA above. | |
| 2. If the user asks general questions ("What is mining?", "Who is Satoshi?"), answer normally. | |
| 3. Keep answers concise, helpful, and professional. | |
| """ | |
| messages = [{"role": "system", "content": system_prompt}] | |
| for human, assistant in history: | |
| messages.append({"role": "user", "content": human}) | |
| messages.append({"role": "assistant", "content": assistant}) | |
| messages.append({"role": "user", "content": message}) | |
| try: | |
| stream = client.chat_completion(messages, max_tokens=512, stream=True, temperature=0.7) | |
| partial_message = "" | |
| for chunk in stream: | |
| if chunk.choices[0].delta.content: | |
| partial_message += chunk.choices[0].delta.content | |
| yield partial_message | |
| except Exception as e: | |
| yield f"⚠️ AI Error: {str(e)}" | |
| # ----------------------------------------------------------------------------- | |
| # 5. GRADIO APP LAYOUT | |
| # ----------------------------------------------------------------------------- | |
| custom_css = """ | |
| body { background-color: #111827; } | |
| .contain { max-width: 1400px; margin: auto; padding-top: 20px; } | |
| h1 { color: #F3F4F6; text-align: center; margin-bottom: 30px; } | |
| .plot-container { border: none !important; background: transparent !important; } | |
| footer { visibility: hidden; } | |
| """ | |
| # We define the theme on the Blocks object, but remove it from ChatInterface | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")) as demo: | |
| gr.HTML("<h1>⚡ CryptoDash AI</h1>") | |
| # --- DASHBOARD ROW --- | |
| # We initialize with empty plots/html, they get filled by the load() event | |
| with gr.Row(): | |
| # Column 1: BTC | |
| with gr.Column(): | |
| c1_html = gr.HTML() | |
| c1_plot = gr.Plot(show_label=False, container=False) | |
| # Column 2: ETH | |
| with gr.Column(): | |
| c2_html = gr.HTML() | |
| c2_plot = gr.Plot(show_label=False, container=False) | |
| # Column 3: SOL | |
| with gr.Column(): | |
| c3_html = gr.HTML() | |
| c3_plot = gr.Plot(show_label=False, container=False) | |
| # Column 4: BNB | |
| with gr.Column(): | |
| c4_html = gr.HTML() | |
| c4_plot = gr.Plot(show_label=False, container=False) | |
| btn_refresh = gr.Button("🔄 Refresh Market Data", variant="secondary") | |
| # --- CHAT SECTION --- | |
| gr.HTML("<br><div style='height: 1px; background-color: #374151; margin: 20px 0;'></div>") | |
| gr.Markdown("### 🤖 Qwen Crypto Assistant") | |
| # NOTE: Removed 'theme' argument here to fix the error | |
| chat = gr.ChatInterface( | |
| fn=chat_logic, | |
| examples=["What is the price of Bitcoin?", "Is the market up today?", "Explain market cap"], | |
| ) | |
| # --- EVENT WIRING --- | |
| # Load data immediately when app starts | |
| demo.load( | |
| fn=refresh_dashboard, | |
| inputs=None, | |
| outputs=[c1_html, c1_plot, c2_html, c2_plot, c3_html, c3_plot, c4_html, c4_plot] | |
| ) | |
| # Refresh button click | |
| btn_refresh.click( | |
| fn=refresh_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() |