File size: 9,711 Bytes
ab0a6c9
5d3d9da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f6d1946
a6ab183
 
 
6388752
 
b9435d1
5d3d9da
 
ed8fc2e
84de7fa
985f897
b2d5b74
 
8370475
b9435d1
6388752
5d3d9da
b9435d1
 
 
 
eadb379
5d3d9da
a312a60
9a4ad78
f6d1946
 
 
 
 
a312a60
5d3d9da
 
 
 
 
 
 
a312a60
b9435d1
eadb379
5d3d9da
 
 
 
 
 
 
 
 
 
 
a312a60
5d3d9da
 
 
 
 
 
 
 
 
 
 
 
eadb379
b9435d1
5d3d9da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eadb379
b9435d1
 
 
 
 
 
 
5d3d9da
 
 
b9435d1
 
5d3d9da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a41be0f
b9435d1
 
a41be0f
5d3d9da
 
 
 
 
 
a41be0f
5d3d9da
 
 
 
 
 
 
 
 
 
978bdd6
e55ebde
5d3d9da
 
e55ebde
 
 
343fdcb
a41be0f
 
 
 
 
 
 
 
 
343fdcb
a41be0f
 
 
 
 
 
343fdcb
a41be0f
 
 
 
 
 
 
343fdcb
a41be0f
343fdcb
a41be0f
 
 
 
08efbfa
f6d1946
343fdcb
 
 
 
 
 
 
 
 
a41be0f
 
 
 
3d06875
8caee6c
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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()