import gradio as gr from application.metrics_table import show_metrics_table from application.portfolio_analyzer import PortfolioAnalyzer from application.portfolio_comparer import PortfolioComparer from infrastructure.llm_client import llm_service from core.news_digest import fetch_crypto_news, summarize_news from presentation.components.crypto_dashboard import ( build_crypto_dashboard, ) # Plotly dashboard + KPI-line from presentation.components.comparison_table import show_comparison_table from presentation.components.visual_comparison import ( build_comparison_chart, build_volatility_chart, preload_pairs, ) # Interactive pair comparison # === CSS loader === def load_css(path: str) -> str: with open(path, "r", encoding="utf-8") as f: return f.read() # === Styles === base_css = load_css("presentation/styles/themes/base.css") crypto_css = load_css("presentation/styles/themes/crypto_dashboard.css") # === Model setup === MODEL_NAME = "meta-llama/Meta-Llama-3.1-8B-Instruct" analyzer = PortfolioAnalyzer(llm_service, MODEL_NAME) comparer = PortfolioComparer(llm_service, MODEL_NAME) # === Main Interface === with gr.Blocks(css=base_css) as demo: gr.Markdown("## Investment Portfolio Analyzer", elem_classes="page-title") gr.Markdown( "Professional AI-driven analytics for investment and crypto markets.", elem_classes="subtitle", ) with gr.Tabs(elem_id="main_tabs"): # --- Analysis --- with gr.TabItem("Portfolio Insights"): portfolio_input = gr.Textbox( label="Portfolio ID or Link", placeholder="Enter portfolio ID (uuid)", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb", ) analyze_btn = gr.Button("Run Analysis", variant="primary") analyze_out = gr.HTML(value="", elem_id="analysis_output") analyze_btn.click( fn=analyzer.run, inputs=portfolio_input, outputs=analyze_out, show_progress="minimal", ) # --- Comparison Table --- with gr.TabItem("Comparison Table"): with gr.Row(): pid_a = gr.Textbox( label="Portfolio A", value="3852a354-e66e-4bc5-97e9-55124e31e687", scale=1, ) pid_b = gr.Textbox( label="Portfolio B", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb", scale=1, ) compare_btn = gr.Button("Load Comparison", variant="primary") comp_table = gr.Dataframe( label="Comparative Metrics", wrap=True, elem_id="comparison_table", ) comp_comment = gr.HTML(elem_id="llm_comment_box") compare_btn.click( fn=show_comparison_table, inputs=[pid_a, pid_b], outputs=[comp_table, comp_comment], show_progress="minimal", ) # --- Metrics Table --- # with gr.TabItem("Metrics Table"): # metrics_in = gr.Textbox( # label="Portfolio ID", # value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb", # ) # metrics_btn = gr.Button("Load Metrics", variant="primary") # metrics_out = gr.Dataframe(label="Portfolio Metrics", wrap=True) # metrics_btn.click(fn=show_metrics_table, inputs=metrics_in, outputs=metrics_out) # --- Assistant (temporarily disabled; duplicate removed) --- # with gr.TabItem("Assistant"): # chat_in = gr.Textbox(label="Ask about investments or analysis") # chat_btn = gr.Button("Send Question", variant="primary") # chat_out = gr.Textbox(label="AI Response", lines=8, interactive=False) # chat_btn.click( # fn=chatbot.run, # inputs=chat_in, # outputs=chat_out, # show_progress="minimal", # ) # --- Crypto News Digest --- with gr.TabItem("Crypto News Digest"): gr.Markdown("### 🗞️ Latest Crypto News (via NewsData.io)") headlines_box = gr.Markdown("Fetching latest headlines...", elem_id="news_headlines") ai_summary_box = gr.Textbox( label="AI Market Summary", lines=10, interactive=False, ) refresh_btn = gr.Button("🔄 Refresh News", variant="primary") def run_news(): headlines, ok = fetch_crypto_news() if not ok: return headlines, "⚠️ Summary will appear once fresh headlines are available." summary = summarize_news(headlines) return headlines, summary refresh_btn.click( fn=run_news, inputs=None, outputs=[headlines_box, ai_summary_box], show_progress="minimal", ) demo.load( fn=run_news, inputs=None, outputs=[headlines_box, ai_summary_box], ) # --- Visual Comparison (Interactive Plotly Edition) --- with gr.TabItem("Visual Comparison"): gr.Markdown("### 📊 Market Pair Comparison — Interactive Plotly Edition") available_pairs = [ ("bitcoin", "ethereum"), ("ethereum", "bnb"), ("solana", "avalanche-2"), ("litecoin", "bitcoin-cash"), ("dogecoin", "shiba-inu"), ] def _format_pair(pair: tuple[str, str]) -> str: def _label(asset: str) -> str: return asset.replace("-", " ").title() a, b = pair return f"{_label(a)} vs {_label(b)}" pair_map = {_format_pair(pair): pair for pair in available_pairs} default_label = _format_pair(available_pairs[0]) with gr.Row(): pair_selector = gr.Dropdown( label="Select Pair for Comparison", choices=list(pair_map.keys()), value=default_label, interactive=True, scale=3, ) normalize_toggle = gr.Checkbox( label="Normalized Mode (%)", value=False, interactive=True, scale=1, ) price_plot = gr.Plot(label="Price Comparison") vol_plot = gr.Plot(label="Volatility Comparison") def update_visuals(selected_pair: str, normalized: bool): pair = pair_map.get(selected_pair, available_pairs[0]) return ( build_comparison_chart(pair, normalized=normalized), build_volatility_chart(pair), ) pair_selector.change( fn=update_visuals, inputs=[pair_selector, normalize_toggle], outputs=[price_plot, vol_plot], ) normalize_toggle.change( fn=update_visuals, inputs=[pair_selector, normalize_toggle], outputs=[price_plot, vol_plot], ) def init_visuals(): preload_pairs(available_pairs) return update_visuals(default_label, False) demo.load(fn=init_visuals, inputs=None, outputs=[price_plot, vol_plot]) # --- Crypto Intelligence Dashboard (Plotly Edition + KPI line, auto-load) --- with gr.TabItem("Crypto Intelligence Dashboard"): gr.HTML(f"") with gr.Row(elem_id="kpi_row"): kpi_line = gr.HTML(elem_id="kpi_line") with gr.Row(elem_id="controls_row"): top_slider = gr.Slider( label="Top N coins", minimum=20, maximum=100, step=10, value=50, scale=100, ) with gr.Row(equal_height=True, elem_id="main_charts_row"): with gr.Column(scale=70): treemap_plot = gr.Plot(label="Market Composition") with gr.Column(scale=30): ai_box = gr.Textbox(label="AI Market Summary", lines=18, elem_id="ai_summary_sidebar") with gr.Row(equal_height=True, elem_id="bottom_charts_row"): movers_plot = gr.Plot(label="Top Movers", scale=50) scatter_plot = gr.Plot(label="Market Cap vs Volume", scale=50) def run_dash(n): fig_treemap, fig_bar, fig_bubble, ai_comment, kpi_text = build_crypto_dashboard(n) return fig_treemap, fig_bar, fig_bubble, ai_comment, f"
{kpi_text}
" top_slider.change( fn=run_dash, inputs=top_slider, outputs=[treemap_plot, movers_plot, scatter_plot, ai_box, kpi_line], show_progress="minimal", ) def init_crypto(): return run_dash(50) demo.load( fn=init_crypto, inputs=None, outputs=[treemap_plot, movers_plot, scatter_plot, ai_box, kpi_line], ) gr.Markdown( "
Developed with Featherless.ai • Powered by OpenAI-compatible API
", elem_classes="footer", ) if __name__ == "__main__": demo.launch()