cryptodashboard / app.py
CryptoCreeper
Update app.py
7f95055 verified
Raw
History Blame
9.11 kB
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()