Spaces:
Running
Running
| 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"<style>{crypto_css}</style>") | |
| 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"<div class='kpi-line'>{kpi_text}</div>" | |
| 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( | |
| "<center><small style='color:#6e7681;'>Developed with Featherless.ai β’ Powered by OpenAI-compatible API</small></center>", | |
| elem_classes="footer", | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |