BrianIsaac commited on
Commit
c4cdf32
·
1 Parent(s): b9892ff

fix: improve light/dark theme support for UI and visualisations

Browse files

- Replace hardcoded light mode colours with CSS variables in theme.py
- Add dark mode variants for success/error/info messages
- Update Plotly charts to use adaptive template instead of hardcoded backgrounds
- Remove explicit colour specifications to allow theme inheritance
- Charts now properly adapt to HuggingFace Spaces light/dark mode toggle

backend/theme.py CHANGED
@@ -92,37 +92,34 @@ class FinancialTheme(Base):
92
  )
93
 
94
 
95
- # Custom CSS for additional styling
96
  FINANCIAL_CSS = """
97
  /* Global container styling */
98
  .gradio-container {
99
  font-family: 'Inter', 'Arial', sans-serif;
100
- background-color: #f8f9fa;
101
  max-width: 1400px;
102
  margin: auto;
103
  }
104
 
105
  /* Header styling */
106
  .markdown-text h1 {
107
- color: #242c34;
108
  font-weight: 600;
109
- border-bottom: 2px solid #288cfa;
110
  padding-bottom: 0.5rem;
111
  margin-bottom: 1rem;
112
  }
113
 
114
  .markdown-text h2 {
115
- color: #242c34;
116
  font-weight: 600;
117
  margin-top: 1.5rem;
118
  margin-bottom: 0.75rem;
119
  }
120
 
121
  .markdown-text h3 {
122
- color: #6b7280;
123
  font-weight: 500;
124
  margin-top: 1rem;
125
  margin-bottom: 0.5rem;
 
126
  }
127
 
128
  /* Metric cards */
@@ -131,26 +128,22 @@ FINANCIAL_CSS = """
131
  }
132
 
133
  .metric-card {
134
- background: white;
135
  padding: 1.5rem;
136
  border-radius: 8px;
137
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
138
- border: 1px solid #e5e7eb;
139
  }
140
 
141
  .metric-card .label {
142
  font-size: 0.875rem;
143
- color: #6b7280;
144
  font-weight: 500;
145
  text-transform: uppercase;
146
  letter-spacing: 0.05em;
147
  margin-bottom: 0.5rem;
 
148
  }
149
 
150
  .metric-card .value {
151
  font-size: 2rem;
152
  font-weight: 600;
153
- color: #242c34;
154
  }
155
 
156
  /* Status colours */
@@ -165,30 +158,23 @@ FINANCIAL_CSS = """
165
  }
166
 
167
  .neutral {
168
- color: #6b7280;
169
  font-weight: 500;
 
170
  }
171
 
172
  /* Data tables */
173
  .dataframe {
174
- border: 1px solid #e5e7eb !important;
175
  border-radius: 6px !important;
176
  overflow: hidden;
177
  }
178
 
179
  .dataframe th {
180
- background-color: #f9fafb !important;
181
- color: #242c34 !important;
182
  font-weight: 600 !important;
183
  text-transform: uppercase;
184
  font-size: 0.75rem;
185
  letter-spacing: 0.05em;
186
  }
187
 
188
- .dataframe td {
189
- border-color: #e5e7eb !important;
190
- }
191
-
192
  /* Button enhancements */
193
  .primary.lg {
194
  padding: 0.75rem 2rem;
@@ -198,28 +184,26 @@ FINANCIAL_CSS = """
198
 
199
  /* Progress bar */
200
  .progress-bar {
201
- background: linear-gradient(90deg, #288cfa, #1a7de8) !important;
202
  }
203
 
204
  /* Accordion styling for ChatMessage metadata */
205
  .message-metadata {
206
- border-left: 3px solid #288cfa;
207
  padding-left: 1rem;
208
  margin: 0.5rem 0;
209
- background: #f8f9fa;
210
  border-radius: 4px;
211
  padding: 0.75rem 1rem;
212
  }
213
 
214
  .message-metadata .title {
215
  font-weight: 600;
216
- color: #242c34;
217
  margin-bottom: 0.25rem;
218
  }
219
 
220
  .message-metadata .log {
221
  font-size: 0.875rem;
222
- color: #6b7280;
223
  }
224
 
225
  /* Tool call status indicators */
@@ -237,8 +221,8 @@ FINANCIAL_CSS = """
237
 
238
  /* Loading spinner */
239
  .loading {
240
- border: 3px solid #e5e7eb;
241
- border-top-color: #288cfa;
242
  animation: spin 1s linear infinite;
243
  }
244
 
@@ -259,21 +243,14 @@ FINANCIAL_CSS = """
259
 
260
  /* Chart containers */
261
  .plotly-chart {
262
- border: 1px solid #e5e7eb;
263
  border-radius: 8px;
264
  padding: 1rem;
265
- background: white;
266
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
267
  }
268
 
269
  /* Tab styling */
270
- .tab-nav {
271
- border-bottom: 2px solid #e5e7eb;
272
- }
273
-
274
  .tab-nav button.selected {
275
- border-bottom: 2px solid #288cfa;
276
- color: #288cfa;
277
  font-weight: 600;
278
  }
279
 
@@ -286,6 +263,11 @@ FINANCIAL_CSS = """
286
  color: #991b1b;
287
  }
288
 
 
 
 
 
 
289
  /* Success messages */
290
  .success-message {
291
  background: #d1fae5;
@@ -295,6 +277,11 @@ FINANCIAL_CSS = """
295
  color: #065f46;
296
  }
297
 
 
 
 
 
 
298
  /* Info messages */
299
  .info-message {
300
  background: #dbeafe;
@@ -303,6 +290,11 @@ FINANCIAL_CSS = """
303
  border-radius: 4px;
304
  color: #1e40af;
305
  }
 
 
 
 
 
306
  """
307
 
308
 
 
92
  )
93
 
94
 
95
+ # Custom CSS for additional styling (theme-adaptive)
96
  FINANCIAL_CSS = """
97
  /* Global container styling */
98
  .gradio-container {
99
  font-family: 'Inter', 'Arial', sans-serif;
 
100
  max-width: 1400px;
101
  margin: auto;
102
  }
103
 
104
  /* Header styling */
105
  .markdown-text h1 {
 
106
  font-weight: 600;
107
+ border-bottom: 2px solid var(--color-accent);
108
  padding-bottom: 0.5rem;
109
  margin-bottom: 1rem;
110
  }
111
 
112
  .markdown-text h2 {
 
113
  font-weight: 600;
114
  margin-top: 1.5rem;
115
  margin-bottom: 0.75rem;
116
  }
117
 
118
  .markdown-text h3 {
 
119
  font-weight: 500;
120
  margin-top: 1rem;
121
  margin-bottom: 0.5rem;
122
+ opacity: 0.8;
123
  }
124
 
125
  /* Metric cards */
 
128
  }
129
 
130
  .metric-card {
 
131
  padding: 1.5rem;
132
  border-radius: 8px;
 
 
133
  }
134
 
135
  .metric-card .label {
136
  font-size: 0.875rem;
 
137
  font-weight: 500;
138
  text-transform: uppercase;
139
  letter-spacing: 0.05em;
140
  margin-bottom: 0.5rem;
141
+ opacity: 0.7;
142
  }
143
 
144
  .metric-card .value {
145
  font-size: 2rem;
146
  font-weight: 600;
 
147
  }
148
 
149
  /* Status colours */
 
158
  }
159
 
160
  .neutral {
 
161
  font-weight: 500;
162
+ opacity: 0.7;
163
  }
164
 
165
  /* Data tables */
166
  .dataframe {
 
167
  border-radius: 6px !important;
168
  overflow: hidden;
169
  }
170
 
171
  .dataframe th {
 
 
172
  font-weight: 600 !important;
173
  text-transform: uppercase;
174
  font-size: 0.75rem;
175
  letter-spacing: 0.05em;
176
  }
177
 
 
 
 
 
178
  /* Button enhancements */
179
  .primary.lg {
180
  padding: 0.75rem 2rem;
 
184
 
185
  /* Progress bar */
186
  .progress-bar {
187
+ background: linear-gradient(90deg, var(--color-accent), var(--color-accent-soft)) !important;
188
  }
189
 
190
  /* Accordion styling for ChatMessage metadata */
191
  .message-metadata {
192
+ border-left: 3px solid var(--color-accent);
193
  padding-left: 1rem;
194
  margin: 0.5rem 0;
 
195
  border-radius: 4px;
196
  padding: 0.75rem 1rem;
197
  }
198
 
199
  .message-metadata .title {
200
  font-weight: 600;
 
201
  margin-bottom: 0.25rem;
202
  }
203
 
204
  .message-metadata .log {
205
  font-size: 0.875rem;
206
+ opacity: 0.7;
207
  }
208
 
209
  /* Tool call status indicators */
 
221
 
222
  /* Loading spinner */
223
  .loading {
224
+ border: 3px solid var(--border-color-primary);
225
+ border-top-color: var(--color-accent);
226
  animation: spin 1s linear infinite;
227
  }
228
 
 
243
 
244
  /* Chart containers */
245
  .plotly-chart {
 
246
  border-radius: 8px;
247
  padding: 1rem;
 
 
248
  }
249
 
250
  /* Tab styling */
 
 
 
 
251
  .tab-nav button.selected {
252
+ border-bottom: 2px solid var(--color-accent);
253
+ color: var(--color-accent);
254
  font-weight: 600;
255
  }
256
 
 
263
  color: #991b1b;
264
  }
265
 
266
+ .dark .error-message {
267
+ background: #450a0a;
268
+ color: #fca5a5;
269
+ }
270
+
271
  /* Success messages */
272
  .success-message {
273
  background: #d1fae5;
 
277
  color: #065f46;
278
  }
279
 
280
+ .dark .success-message {
281
+ background: #064e3b;
282
+ color: #6ee7b7;
283
+ }
284
+
285
  /* Info messages */
286
  .info-message {
287
  background: #dbeafe;
 
290
  border-radius: 4px;
291
  color: #1e40af;
292
  }
293
+
294
+ .dark .info-message {
295
+ background: #1e3a8a;
296
+ color: #93c5fd;
297
+ }
298
  """
299
 
300
 
backend/visualizations/plotly_charts.py CHANGED
@@ -17,6 +17,15 @@ from typing import Dict, List, Any, Optional
17
  from decimal import Decimal
18
 
19
 
 
 
 
 
 
 
 
 
 
20
  def create_portfolio_allocation_chart(holdings: List[Dict[str, Any]]) -> go.Figure:
21
  """Create interactive portfolio allocation pie chart.
22
 
@@ -43,7 +52,7 @@ def create_portfolio_allocation_chart(holdings: List[Dict[str, Any]]) -> go.Figu
43
  textposition='inside',
44
  textinfo='percent+label',
45
  marker=dict(
46
- line=dict(color='#000000', width=2),
47
  colors=px.colors.qualitative.Set3
48
  )
49
  ))
@@ -59,9 +68,8 @@ def create_portfolio_allocation_chart(holdings: List[Dict[str, Any]]) -> go.Figu
59
  uniformtext_minsize=12,
60
  uniformtext_mode='hide',
61
  autosize=True,
62
- paper_bgcolor='white',
63
- plot_bgcolor='#f8f9fa',
64
- font={'family': 'Inter, sans-serif', 'color': '#242c34'}
65
  )
66
 
67
  return fig
@@ -180,9 +188,8 @@ def create_risk_metrics_dashboard(
180
  fig.update_layout(
181
  autosize=True,
182
  height=600,
183
- paper_bgcolor='white',
184
- plot_bgcolor='#f8f9fa',
185
- font={'family': 'Inter, sans-serif', 'color': '#242c34'},
186
  margin=dict(t=80, b=50, l=50, r=50)
187
  )
188
 
@@ -268,9 +275,8 @@ def create_performance_chart(
268
  xanchor="right",
269
  x=1
270
  ),
271
- paper_bgcolor='white',
272
- plot_bgcolor='#f8f9fa',
273
- font={'family': 'Inter, sans-serif', 'color': '#242c34'}
274
  )
275
 
276
  return fig
@@ -348,8 +354,8 @@ def create_correlation_heatmap(
348
  autosize=True,
349
  width=700,
350
  height=700,
351
- paper_bgcolor='white',
352
- font={'family': 'Inter, sans-serif', 'color': '#242c34'}
353
  )
354
 
355
  return fig
@@ -427,9 +433,8 @@ def create_optimization_comparison(
427
  showlegend=False,
428
  autosize=True,
429
  height=400,
430
- paper_bgcolor='white',
431
- plot_bgcolor='#f8f9fa',
432
- font={'family': 'Inter, sans-serif', 'color': '#242c34'}
433
  )
434
 
435
  fig.update_xaxes(tickangle=-45, row=1, col=1)
@@ -452,38 +457,29 @@ def apply_financial_theme(fig: go.Figure) -> go.Figure:
452
  fig.update_layout(
453
  font=dict(
454
  family="Inter, Arial, sans-serif",
455
- size=12,
456
- color="#242c34"
457
  ),
458
  title_font=dict(
459
  family="Inter, Arial, sans-serif",
460
- size=18,
461
- color="#242c34"
462
  ),
463
- paper_bgcolor='white',
464
- plot_bgcolor='#f8f9fa',
465
  xaxis=dict(
466
  showgrid=True,
467
- gridcolor='#dee2e6',
468
  gridwidth=1,
469
  zeroline=False
470
  ),
471
  yaxis=dict(
472
  showgrid=True,
473
- gridcolor='#dee2e6',
474
  gridwidth=1,
475
  zeroline=False
476
  ),
477
  legend=dict(
478
- bgcolor="rgba(255,255,255,0.9)",
479
- bordercolor="#dee2e6",
480
  borderwidth=1
481
  ),
482
  hoverlabel=dict(
483
- bgcolor="white",
484
  font_size=12,
485
- font_family="Inter, Arial, sans-serif",
486
- bordercolor="#dee2e6"
487
  )
488
  )
489
 
 
17
  from decimal import Decimal
18
 
19
 
20
+ def get_plotly_template() -> str:
21
+ """Get Plotly template that supports light/dark mode.
22
+
23
+ Returns:
24
+ Template name for Plotly charts that auto-adapts to theme
25
+ """
26
+ return "plotly" # Uses plotly default which adapts to container background
27
+
28
+
29
  def create_portfolio_allocation_chart(holdings: List[Dict[str, Any]]) -> go.Figure:
30
  """Create interactive portfolio allocation pie chart.
31
 
 
52
  textposition='inside',
53
  textinfo='percent+label',
54
  marker=dict(
55
+ line=dict(width=2),
56
  colors=px.colors.qualitative.Set3
57
  )
58
  ))
 
68
  uniformtext_minsize=12,
69
  uniformtext_mode='hide',
70
  autosize=True,
71
+ template=get_plotly_template(),
72
+ font={'family': 'Inter, sans-serif'}
 
73
  )
74
 
75
  return fig
 
188
  fig.update_layout(
189
  autosize=True,
190
  height=600,
191
+ template=get_plotly_template(),
192
+ font={'family': 'Inter, sans-serif'},
 
193
  margin=dict(t=80, b=50, l=50, r=50)
194
  )
195
 
 
275
  xanchor="right",
276
  x=1
277
  ),
278
+ template=get_plotly_template(),
279
+ font={'family': 'Inter, sans-serif'}
 
280
  )
281
 
282
  return fig
 
354
  autosize=True,
355
  width=700,
356
  height=700,
357
+ template=get_plotly_template(),
358
+ font={'family': 'Inter, sans-serif'}
359
  )
360
 
361
  return fig
 
433
  showlegend=False,
434
  autosize=True,
435
  height=400,
436
+ template=get_plotly_template(),
437
+ font={'family': 'Inter, sans-serif'}
 
438
  )
439
 
440
  fig.update_xaxes(tickangle=-45, row=1, col=1)
 
457
  fig.update_layout(
458
  font=dict(
459
  family="Inter, Arial, sans-serif",
460
+ size=12
 
461
  ),
462
  title_font=dict(
463
  family="Inter, Arial, sans-serif",
464
+ size=18
 
465
  ),
466
+ template=get_plotly_template(),
 
467
  xaxis=dict(
468
  showgrid=True,
 
469
  gridwidth=1,
470
  zeroline=False
471
  ),
472
  yaxis=dict(
473
  showgrid=True,
 
474
  gridwidth=1,
475
  zeroline=False
476
  ),
477
  legend=dict(
 
 
478
  borderwidth=1
479
  ),
480
  hoverlabel=dict(
 
481
  font_size=12,
482
+ font_family="Inter, Arial, sans-serif"
 
483
  )
484
  )
485