Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
47fd710
1
Parent(s):
4e50d9d
fix: resolve UI issues with landing page gap and missing loading overlay
Browse files- Implement three-state page system (input → loading → results) with yield-based transitions
- Fix landing page gap by using align-items: flex-start on portfolio row
- Reduce preview-card min-height from 400px to 300px for better column alignment
- Add animated loading page component with rotating educational messages
- Configure show_progress="full" for prominent built-in progress indicator
- WebSocket deprecation warning suppression already in place (waiting for uvicorn update)
app.py
CHANGED
|
@@ -613,12 +613,44 @@ def create_interface() -> gr.Blocks:
|
|
| 613 |
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 614 |
border-radius: 12px;
|
| 615 |
padding: 1.5rem;
|
| 616 |
-
min-height:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
display: flex;
|
| 618 |
align-items: center;
|
| 619 |
justify-content: center;
|
| 620 |
}
|
| 621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
/* Example buttons styling */
|
| 623 |
.gr-examples button {
|
| 624 |
background: linear-gradient(135deg, rgba(4, 140, 252, 0.1), rgba(17, 199, 170, 0.1));
|
|
@@ -696,7 +728,7 @@ def create_interface() -> gr.Blocks:
|
|
| 696 |
# Input Page with side-by-side layout
|
| 697 |
with gr.Group(visible=True) as input_page:
|
| 698 |
# Side-by-side input and preview
|
| 699 |
-
with gr.Row():
|
| 700 |
# Left column: Input
|
| 701 |
with gr.Column(scale=1):
|
| 702 |
portfolio_input = gr.Textbox(
|
|
@@ -773,6 +805,31 @@ def create_interface() -> gr.Blocks:
|
|
| 773 |
"""
|
| 774 |
)
|
| 775 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
# Results Page (unified dashboard)
|
| 777 |
with gr.Group(visible=False) as results_page:
|
| 778 |
with gr.Row():
|
|
@@ -811,14 +868,32 @@ def create_interface() -> gr.Blocks:
|
|
| 811 |
}
|
| 812 |
|
| 813 |
async def handle_analysis(portfolio_text, progress=gr.Progress()):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
page, analysis, alloc, risk, perf, corr, opt = await run_analysis_with_ui_update(
|
| 815 |
portfolio_text, progress
|
| 816 |
)
|
| 817 |
|
|
|
|
| 818 |
if page == "results":
|
| 819 |
-
|
| 820 |
input_page: gr.update(visible=False),
|
|
|
|
| 821 |
results_page: gr.update(visible=True),
|
|
|
|
| 822 |
analysis_output: analysis,
|
| 823 |
allocation_plot: alloc,
|
| 824 |
risk_plot: risk,
|
|
@@ -827,9 +902,11 @@ def create_interface() -> gr.Blocks:
|
|
| 827 |
optimization_plot: opt
|
| 828 |
}
|
| 829 |
else:
|
| 830 |
-
|
| 831 |
input_page: gr.update(visible=True),
|
|
|
|
| 832 |
results_page: gr.update(visible=False),
|
|
|
|
| 833 |
analysis_output: analysis,
|
| 834 |
allocation_plot: None,
|
| 835 |
risk_plot: None,
|
|
@@ -850,14 +927,17 @@ def create_interface() -> gr.Blocks:
|
|
| 850 |
inputs=[portfolio_input],
|
| 851 |
outputs=[
|
| 852 |
input_page,
|
|
|
|
| 853 |
results_page,
|
|
|
|
| 854 |
analysis_output,
|
| 855 |
allocation_plot,
|
| 856 |
risk_plot,
|
| 857 |
performance_plot,
|
| 858 |
correlation_plot,
|
| 859 |
optimization_plot
|
| 860 |
-
]
|
|
|
|
| 861 |
)
|
| 862 |
|
| 863 |
new_analysis_btn.click(
|
|
|
|
| 613 |
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 614 |
border-radius: 12px;
|
| 615 |
padding: 1.5rem;
|
| 616 |
+
min-height: 300px;
|
| 617 |
+
display: flex;
|
| 618 |
+
align-items: center;
|
| 619 |
+
justify-content: center;
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
/* Portfolio row - natural heights without stretching */
|
| 623 |
+
.portfolio-row {
|
| 624 |
+
align-items: flex-start !important;
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
/* Loading page styling */
|
| 628 |
+
.loading-header {
|
| 629 |
+
animation: fadeIn 0.5s ease-in;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.loading-message {
|
| 633 |
+
text-align: center;
|
| 634 |
+
font-size: 1.1rem;
|
| 635 |
+
color: #048CFC;
|
| 636 |
+
padding: 2rem;
|
| 637 |
+
min-height: 100px;
|
| 638 |
display: flex;
|
| 639 |
align-items: center;
|
| 640 |
justify-content: center;
|
| 641 |
}
|
| 642 |
|
| 643 |
+
@keyframes fadeIn {
|
| 644 |
+
from {
|
| 645 |
+
opacity: 0;
|
| 646 |
+
transform: translateY(-10px);
|
| 647 |
+
}
|
| 648 |
+
to {
|
| 649 |
+
opacity: 1;
|
| 650 |
+
transform: translateY(0);
|
| 651 |
+
}
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
/* Example buttons styling */
|
| 655 |
.gr-examples button {
|
| 656 |
background: linear-gradient(135deg, rgba(4, 140, 252, 0.1), rgba(17, 199, 170, 0.1));
|
|
|
|
| 728 |
# Input Page with side-by-side layout
|
| 729 |
with gr.Group(visible=True) as input_page:
|
| 730 |
# Side-by-side input and preview
|
| 731 |
+
with gr.Row(equal_height=False, elem_classes="portfolio-row"):
|
| 732 |
# Left column: Input
|
| 733 |
with gr.Column(scale=1):
|
| 734 |
portfolio_input = gr.Textbox(
|
|
|
|
| 805 |
"""
|
| 806 |
)
|
| 807 |
|
| 808 |
+
# Loading Page
|
| 809 |
+
with gr.Group(visible=False) as loading_page:
|
| 810 |
+
with gr.Row():
|
| 811 |
+
with gr.Column():
|
| 812 |
+
gr.HTML(
|
| 813 |
+
"""
|
| 814 |
+
<div style='text-align: center; padding: 4rem 2rem;'>
|
| 815 |
+
<div style='font-size: 3rem; margin-bottom: 1rem;'>⚡</div>
|
| 816 |
+
<h2 style='color: #048CFC; margin-bottom: 1rem;'>Analysing Your Portfolio</h2>
|
| 817 |
+
</div>
|
| 818 |
+
""",
|
| 819 |
+
elem_classes="loading-header"
|
| 820 |
+
)
|
| 821 |
+
loading_message = gr.Markdown(
|
| 822 |
+
value="Initialising analysis...",
|
| 823 |
+
elem_classes="loading-message",
|
| 824 |
+
)
|
| 825 |
+
gr.HTML(
|
| 826 |
+
"""
|
| 827 |
+
<div style='text-align: center; padding: 2rem; color: #999; font-size: 14px;'>
|
| 828 |
+
<p>This may take a few seconds as we process real-time market data</p>
|
| 829 |
+
</div>
|
| 830 |
+
"""
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
# Results Page (unified dashboard)
|
| 834 |
with gr.Group(visible=False) as results_page:
|
| 835 |
with gr.Row():
|
|
|
|
| 868 |
}
|
| 869 |
|
| 870 |
async def handle_analysis(portfolio_text, progress=gr.Progress()):
|
| 871 |
+
# Show loading page immediately
|
| 872 |
+
yield {
|
| 873 |
+
input_page: gr.update(visible=False),
|
| 874 |
+
loading_page: gr.update(visible=True),
|
| 875 |
+
results_page: gr.update(visible=False),
|
| 876 |
+
loading_message: random.choice(LOADING_MESSAGES),
|
| 877 |
+
analysis_output: "",
|
| 878 |
+
allocation_plot: None,
|
| 879 |
+
risk_plot: None,
|
| 880 |
+
performance_plot: None,
|
| 881 |
+
correlation_plot: None,
|
| 882 |
+
optimization_plot: None
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
# Run analysis with progress updates
|
| 886 |
page, analysis, alloc, risk, perf, corr, opt = await run_analysis_with_ui_update(
|
| 887 |
portfolio_text, progress
|
| 888 |
)
|
| 889 |
|
| 890 |
+
# Show results or return to input
|
| 891 |
if page == "results":
|
| 892 |
+
yield {
|
| 893 |
input_page: gr.update(visible=False),
|
| 894 |
+
loading_page: gr.update(visible=False),
|
| 895 |
results_page: gr.update(visible=True),
|
| 896 |
+
loading_message: "Analysis complete!",
|
| 897 |
analysis_output: analysis,
|
| 898 |
allocation_plot: alloc,
|
| 899 |
risk_plot: risk,
|
|
|
|
| 902 |
optimization_plot: opt
|
| 903 |
}
|
| 904 |
else:
|
| 905 |
+
yield {
|
| 906 |
input_page: gr.update(visible=True),
|
| 907 |
+
loading_page: gr.update(visible=False),
|
| 908 |
results_page: gr.update(visible=False),
|
| 909 |
+
loading_message: "",
|
| 910 |
analysis_output: analysis,
|
| 911 |
allocation_plot: None,
|
| 912 |
risk_plot: None,
|
|
|
|
| 927 |
inputs=[portfolio_input],
|
| 928 |
outputs=[
|
| 929 |
input_page,
|
| 930 |
+
loading_page,
|
| 931 |
results_page,
|
| 932 |
+
loading_message,
|
| 933 |
analysis_output,
|
| 934 |
allocation_plot,
|
| 935 |
risk_plot,
|
| 936 |
performance_plot,
|
| 937 |
correlation_plot,
|
| 938 |
optimization_plot
|
| 939 |
+
],
|
| 940 |
+
show_progress="full"
|
| 941 |
)
|
| 942 |
|
| 943 |
new_analysis_btn.click(
|