Ákos Hadnagy commited on
Commit
2018d03
·
1 Parent(s): a8d2652

Hook it up to the data-source

Browse files
Files changed (5) hide show
  1. .gitignore +1 -0
  2. CLAUDE.md +0 -90
  3. app.py +356 -665
  4. data.py +86 -0
  5. styles.css +589 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__
CLAUDE.md DELETED
@@ -1,90 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- This is a **Test Results Dashboard** project (Tcid) that provides interactive visualization of AI model testing results. The project consists of two main applications:
8
-
9
- 1. **Gradio Dashboard** (`app.py`) - Python-based web dashboard using Gradio and Matplotlib
10
- 2. **HTML Dashboard** (`index.html`) - Standalone HTML dashboard with Chart.js visualization
11
-
12
- Both dashboards display test results for AI models including metrics like passed, failed, skipped, and error counts.
13
-
14
- ## Architecture
15
-
16
- ### Core Components
17
-
18
- - **app.py**: Main Gradio application with dark theme UI, sidebar navigation, and matplotlib pie charts
19
- - **model_stats.json**: JSON data file containing test results for different AI models
20
- - **index.html**: Self-contained HTML dashboard with device-specific performance comparison (NVIDIA vs AMD)
21
- - **requirements.txt**: Python dependencies (currently only matplotlib>=3.8)
22
-
23
- ### Data Structure
24
-
25
- Model statistics follow this format:
26
- ```json
27
- {
28
- "model_name": {
29
- "passed": int,
30
- "failed": int,
31
- "skipped": int,
32
- "error": int
33
- }
34
- }
35
- ```
36
-
37
- The HTML dashboard extends this with device-specific data for NVIDIA and AMD performance comparisons.
38
-
39
- ## Development Commands
40
-
41
- ### Environment Setup
42
- ```bash
43
- # Activate virtual environment
44
- source venv_tci/bin/activate
45
-
46
- # Install dependencies
47
- pip install -r requirements.txt
48
- ```
49
-
50
- ### Running the Applications
51
-
52
- **Gradio Dashboard:**
53
- ```bash
54
- python app.py
55
- ```
56
-
57
- **HTML Dashboard:**
58
- Open `index.html` directly in a web browser - no server required.
59
-
60
- ### Python Environment
61
- - Python 3.12.4
62
- - Virtual environment located at `venv_tci/`
63
- - Dependencies managed via `requirements.txt`
64
-
65
- ## Key Implementation Details
66
-
67
- ### Gradio Application (app.py)
68
- - Uses `MODELS` dictionary for hardcoded test data (lines 8-12)
69
- - `plot_model_stats()` function generates matplotlib pie charts with dark theme
70
- - Custom CSS for dark theme styling (lines 77-133)
71
- - Sidebar navigation with model selection buttons
72
- - Real-time chart updates on model selection
73
-
74
- ### Data Management
75
- - Model data is currently hardcoded in `app.py`
76
- - External JSON data file `model_stats.json` exists but is not integrated
77
- - HTML dashboard has embedded JavaScript data
78
-
79
- ### Styling
80
- - Dark theme with black backgrounds (#000000)
81
- - Custom color scheme: Green (passed), Red (failed), Orange (skipped), Purple (error)
82
- - Responsive design with sidebar layout
83
-
84
- ## Hugging Face Spaces Configuration
85
-
86
- This project is configured as a Hugging Face Space:
87
- - SDK: Gradio 5.38.0
88
- - App file: app.py
89
- - Space emoji: 👁
90
- - Color theme: indigo to pink gradient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,8 +1,12 @@
1
  import matplotlib.pyplot as plt
2
  import matplotlib
3
  import numpy as np
4
-
5
  import gradio as gr
 
 
 
 
6
 
7
  # Configure matplotlib to prevent memory warnings and set dark background
8
  matplotlib.rcParams['figure.max_open_warning'] = 0
@@ -11,129 +15,87 @@ matplotlib.rcParams['axes.facecolor'] = '#000000'
11
  matplotlib.rcParams['savefig.facecolor'] = '#000000'
12
  plt.ioff() # Turn off interactive mode to prevent figure accumulation
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- # Sample test results with test names
16
- MODELS = {
17
- "llama": {
18
- "amd": {
19
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore"],
20
- "failed": ["network_timeout"],
21
- "skipped": ["gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "distributed", "multi_gpu"],
22
- "error": []
23
- },
24
- "nvidia": {
25
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops"],
26
- "failed": ["network_timeout", "distributed"],
27
- "skipped": ["multi_gpu"],
28
- "error": []
29
- }
30
- },
31
- "gemma3": {
32
- "amd": {
33
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload", "config_watch", "env_vars", "secrets_mgmt", "tls_cert", "encryption", "compression", "serialization", "deserialization", "validation"],
34
- "failed": ["gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "distributed", "multi_gpu", "opencl_init", "driver_conflict", "memory_bandwidth", "compute_units", "rocm_version", "hip_compile", "kernel_launch", "buffer_transfer", "atomic_ops", "wavefront_sync"],
35
- "skipped": ["perf_test", "stress_test", "load_test", "endurance", "benchmark", "profiling", "memory_leak", "cpu_usage", "disk_io", "network_bw", "latency", "throughput"],
36
- "error": []
37
- },
38
- "nvidia": {
39
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload", "config_watch", "env_vars", "secrets_mgmt", "tls_cert", "encryption", "compression", "serialization", "deserialization", "validation", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops"],
40
- "failed": ["distributed", "multi_gpu", "cuda_version", "nvcc_compile", "stream_sync", "device_reset", "peer_access", "unified_memory", "texture_bind", "surface_write", "constant_mem", "shared_mem"],
41
- "skipped": ["perf_test", "stress_test", "load_test", "endurance", "benchmark", "profiling", "memory_leak", "cpu_usage", "disk_io", "network_bw"],
42
- "error": []
43
- }
44
- },
45
- "csm": {
46
- "amd": {
47
- "passed": [],
48
- "failed": [],
49
- "skipped": [],
50
- "error": ["system_crash"]
51
- },
52
- "nvidia": {
53
- "passed": [],
54
- "failed": [],
55
- "skipped": [],
56
- "error": ["system_crash"]
57
- }
58
- },
59
- "claude": {
60
- "amd": {
61
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break"],
62
- "failed": ["gpu_accel", "cuda_ops", "ml_inference", "distributed", "multi_gpu", "opencl_init", "driver_conflict"],
63
- "skipped": ["tensor_ops", "perf_test", "stress_test", "load_test", "endurance", "benchmark"],
64
- "error": ["memory_bandwidth"]
65
- },
66
- "nvidia": {
67
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops"],
68
- "failed": ["distributed", "multi_gpu", "cuda_version", "nvcc_compile"],
69
- "skipped": ["perf_test", "stress_test", "load_test", "endurance"],
70
- "error": []
71
- }
72
- },
73
- "mistral": {
74
- "amd": {
75
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring"],
76
- "failed": ["gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "distributed", "multi_gpu", "opencl_init", "driver_conflict", "memory_bandwidth", "compute_units", "rocm_version", "hip_compile", "kernel_launch"],
77
- "skipped": ["security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break"],
78
- "error": ["buffer_transfer", "atomic_ops"]
79
- },
80
- "nvidia": {
81
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "security_scan"],
82
- "failed": ["distributed", "multi_gpu", "cuda_version", "nvcc_compile", "stream_sync", "device_reset"],
83
- "skipped": ["password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter"],
84
- "error": ["peer_access"]
85
- }
86
- },
87
- "phi": {
88
- "amd": {
89
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection"],
90
- "failed": ["gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "distributed", "multi_gpu", "opencl_init", "driver_conflict", "memory_bandwidth"],
91
- "skipped": ["rate_limiter", "load_balance", "circuit_break", "retry_logic", "timeout_handle", "graceful_shutdown"],
92
- "error": []
93
- },
94
- "nvidia": {
95
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "rate_limiter"],
96
- "failed": ["distributed", "multi_gpu", "cuda_version"],
97
- "skipped": ["load_balance", "circuit_break", "retry_logic", "timeout_handle"],
98
- "error": []
99
- }
100
- },
101
- "qwen": {
102
- "amd": {
103
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety"],
104
- "failed": ["backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "distributed", "multi_gpu"],
105
- "skipped": ["retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload", "config_watch"],
106
- "error": ["env_vars", "secrets_mgmt", "tls_cert"]
107
- },
108
- "nvidia": {
109
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops"],
110
- "failed": ["log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "distributed", "multi_gpu", "cuda_version", "nvcc_compile"],
111
- "skipped": ["retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload"],
112
- "error": ["config_watch", "env_vars"]
113
- }
114
- },
115
- "deepseek": {
116
- "amd": {
117
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload", "config_watch", "env_vars", "secrets_mgmt", "tls_cert", "encryption", "compression"],
118
- "failed": ["gpu_accel", "cuda_ops", "ml_inference", "tensor_ops", "opencl_init", "driver_conflict", "memory_bandwidth", "compute_units"],
119
- "skipped": ["distributed", "multi_gpu", "serialization", "deserialization", "validation"],
120
- "error": []
121
- },
122
- "nvidia": {
123
- "passed": ["auth_login", "data_validation", "api_response", "file_upload", "cache_hit", "user_permissions", "db_query", "session_mgmt", "input_sanitize", "rate_limit", "error_handling", "memory_alloc", "thread_safety", "backup_restore", "config_load", "log_rotation", "health_check", "metrics", "alerts", "monitoring", "security_scan", "password_hash", "jwt_token", "oauth_flow", "csrf_protect", "xss_filter", "sql_injection", "rate_limiter", "load_balance", "circuit_break", "retry_logic", "timeout_handle", "graceful_shutdown", "hot_reload", "config_watch", "env_vars", "secrets_mgmt", "tls_cert", "encryption", "compression", "gpu_accel", "cuda_ops", "ml_inference", "tensor_ops"],
124
- "failed": ["distributed", "multi_gpu"],
125
- "skipped": ["serialization", "deserialization", "validation"],
126
- "error": []
127
- }
128
- }
129
- }
130
 
131
  def generate_underlined_line(text: str) -> str:
132
  return text + "\n" + "─" * len(text) + "\n"
133
 
134
  def plot_model_stats(model_name: str) -> tuple[plt.Figure, str, str]:
135
  """Draws a pie chart of model's passed, failed, skipped, and error stats."""
136
- model_stats = MODELS[model_name]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # Softer color palette - less pastel, more vibrant
139
  colors = {
@@ -143,9 +105,20 @@ def plot_model_stats(model_name: str) -> tuple[plt.Figure, str, str]:
143
  'error': '#8B0000' # Dark red
144
  }
145
 
146
- # Convert test lists to counts for chart display
147
- amd_stats = {k: len(v) for k, v in model_stats['amd'].items()}
148
- nvidia_stats = {k: len(v) for k, v in model_stats['nvidia'].items()}
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  # Filter out categories with 0 values for cleaner visualization
151
  amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
@@ -234,68 +207,88 @@ def plot_model_stats(model_name: str) -> tuple[plt.Figure, str, str]:
234
  plt.tight_layout()
235
  plt.subplots_adjust(top=0.85, wspace=0.4) # Added wspace for padding between charts
236
 
237
- # Generate separate failed tests info for AMD and NVIDIA with exclusive/common separation
238
- amd_failed = set(model_stats['amd']['failed'])
239
- nvidia_failed = set(model_stats['nvidia']['failed'])
240
-
241
- # Find exclusive and common failures
242
- amd_exclusive = amd_failed - nvidia_failed
243
- nvidia_exclusive = nvidia_failed - amd_failed
244
- common_failures = amd_failed & nvidia_failed
245
-
246
- # Build AMD info
247
- amd_failed_info = ""
248
- if not amd_exclusive and not common_failures:
249
- msg = "Error(s) detected" if model_stats["amd"]["error"] else "No failures"
250
- amd_failed_info += generate_underlined_line(msg)
251
- if amd_exclusive:
252
- amd_failed_info += generate_underlined_line("Failures on AMD (exclusive):")
253
- amd_failed_info += "\n".join(sorted(amd_exclusive))
254
- amd_failed_info += "\n\n" if common_failures else ""
255
- if common_failures:
256
- amd_failed_info += generate_underlined_line("Failures on AMD (common):")
257
- amd_failed_info += "\n".join(sorted(common_failures))
258
-
259
- # Build NVIDIA info
260
- nvidia_failed_info = ""
261
- if not nvidia_exclusive and not common_failures:
262
- msg = "Error(s) detected" if model_stats["nvidia"]["error"] else "No failures"
263
- nvidia_failed_info += generate_underlined_line(msg)
264
- if nvidia_exclusive:
265
- nvidia_failed_info += generate_underlined_line("Failures on NVIDIA (exclusive):")
266
- nvidia_failed_info += "\n".join(sorted(nvidia_exclusive))
267
- nvidia_failed_info += "\n\n" if common_failures else ""
268
- if common_failures:
269
- nvidia_failed_info += generate_underlined_line("Failures on NVIDIA (common):")
270
- nvidia_failed_info += "\n".join(sorted(common_failures))
271
 
272
  return fig, amd_failed_info, nvidia_failed_info
273
 
274
- def get_model_stats_summary(model_name: str) -> tuple:
275
- """Get summary stats for a model (total tests, success rate, status indicator)."""
276
- stats = MODELS[model_name]
277
- # Combine AMD and NVIDIA results
278
- total_passed = len(stats['amd']['passed']) + len(stats['nvidia']['passed'])
279
- total_failed = len(stats['amd']['failed']) + len(stats['nvidia']['failed'])
280
- total_skipped = len(stats['amd']['skipped']) + len(stats['nvidia']['skipped'])
281
- total_error = len(stats['amd']['error']) + len(stats['nvidia']['error'])
282
-
283
- total = total_passed + total_failed + total_skipped + total_error
284
- success_rate = (total_passed / total * 100) if total > 0 else 0
285
-
286
- # Determine status indicator color
287
- if success_rate >= 80:
288
- status_class = "success-high"
289
- elif success_rate >= 50:
290
- status_class = "success-medium"
291
- else:
292
- status_class = "success-low"
293
-
294
- return total, success_rate, status_class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  def create_summary_page() -> plt.Figure:
297
  """Create a summary page with model names and both AMD/NVIDIA test stats bars."""
298
- fig, ax = plt.subplots(figsize=(16, len(MODELS) * 2.5 + 2), facecolor='#000000')
 
 
 
 
 
 
 
 
 
 
299
  ax.set_facecolor('#000000')
300
 
301
  colors = {
@@ -307,11 +300,37 @@ def create_summary_page() -> plt.Figure:
307
 
308
  visible_model_count = 0
309
  max_y = 0
310
- for i, (model_name, model_data) in enumerate(MODELS.items()):
311
- # Process AMD and NVIDIA data
312
- amd_stats = {k: len(v) for k, v in model_data['amd'].items()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  amd_total = sum(amd_stats.values())
314
- nvidia_stats = {k: len(v) for k, v in model_data['nvidia'].items()}
315
  nvidia_total = sum(nvidia_stats.values())
316
 
317
  if amd_total == 0 and nvidia_total == 0:
@@ -391,471 +410,41 @@ def create_summary_page() -> plt.Figure:
391
 
392
  return fig
393
 
394
- # Custom CSS for dark theme
395
- dark_theme_css = """
396
- /* Global dark theme */
397
- .gradio-container {
398
- background-color: #000000 !important;
399
- color: white !important;
400
- height: 100vh !important;
401
- max-height: 100vh !important;
402
- overflow: hidden !important;
403
- }
404
-
405
- /* Remove borders from all components */
406
- .gr-box, .gr-form, .gr-panel {
407
- border: none !important;
408
- background-color: #000000 !important;
409
- }
410
-
411
- /* Sidebar styling */
412
- .sidebar {
413
- background: linear-gradient(145deg, #111111, #1a1a1a) !important;
414
- border: none !important;
415
- padding: 25px !important;
416
- box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.3) !important;
417
- margin: 0 !important;
418
- height: 100vh !important;
419
- position: fixed !important;
420
- left: 0 !important;
421
- top: 0 !important;
422
- width: 300px !important;
423
- box-sizing: border-box !important;
424
- overflow-y: auto !important;
425
- scrollbar-width: thin !important;
426
- scrollbar-color: #333333 #111111 !important;
427
- }
428
-
429
- /* Sidebar scrollbar styling */
430
- .sidebar::-webkit-scrollbar {
431
- width: 8px !important;
432
- background: #111111 !important;
433
- }
434
-
435
- .sidebar::-webkit-scrollbar-track {
436
- background: #111111 !important;
437
- }
438
-
439
- .sidebar::-webkit-scrollbar-thumb {
440
- background-color: #333333 !important;
441
- border-radius: 4px !important;
442
- }
443
-
444
- .sidebar::-webkit-scrollbar-thumb:hover {
445
- background-color: #555555 !important;
446
- }
447
-
448
- /* Enhanced model button styling */
449
- .model-button {
450
- background: linear-gradient(135deg, #2a2a2a, #1e1e1e) !important;
451
- color: white !important;
452
- border: 2px solid transparent !important;
453
- margin: 2px 0 !important;
454
- border-radius: 5px !important;
455
- padding: 8px 12px !important;
456
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
457
- position: relative !important;
458
- overflow: hidden !important;
459
- box-shadow:
460
- 0 4px 15px rgba(0, 0, 0, 0.2),
461
- inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
462
- font-weight: 600 !important;
463
- font-size: 16px !important;
464
- text-transform: uppercase !important;
465
- letter-spacing: 0.5px !important;
466
- font-family: monospace !important;
467
- }
468
-
469
- .model-button:hover {
470
- background: linear-gradient(135deg, #3a3a3a, #2e2e2e) !important;
471
- color: #74b9ff !important;
472
- }
473
-
474
- .model-button:active {
475
- background: linear-gradient(135deg, #2a2a2a, #1e1e1e) !important;
476
- color: #5a9bd4 !important;
477
- }
478
-
479
- /* Model stats badge */
480
- .model-stats {
481
- display: flex !important;
482
- justify-content: space-between !important;
483
- align-items: center !important;
484
- margin-top: 8px !important;
485
- font-size: 12px !important;
486
- opacity: 0.8 !important;
487
- }
488
-
489
- .stats-badge {
490
- background: rgba(116, 185, 255, 0.2) !important;
491
- padding: 4px 8px !important;
492
- border-radius: 10px !important;
493
- font-weight: 500 !important;
494
- font-size: 11px !important;
495
- color: #74b9ff !important;
496
- }
497
-
498
- .success-indicator {
499
- width: 8px !important;
500
- height: 8px !important;
501
- border-radius: 50% !important;
502
- display: inline-block !important;
503
- margin-right: 6px !important;
504
- }
505
-
506
- .success-high { background-color: #4CAF50 !important; }
507
- .success-medium { background-color: #FF9800 !important; }
508
- .success-low { background-color: #F44336 !important; }
509
-
510
- /* Summary button styling - distinct from model buttons */
511
- .summary-button {
512
- background: linear-gradient(135deg, #4a4a4a, #3e3e3e) !important;
513
- color: white !important;
514
- border: 2px solid #555555 !important;
515
- margin: 2px 0 15px 0 !important;
516
- border-radius: 5px !important;
517
- padding: 12px 12px !important;
518
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
519
- position: relative !important;
520
- overflow: hidden !important;
521
- box-shadow:
522
- 0 4px 15px rgba(0, 0, 0, 0.3),
523
- inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
524
- font-weight: 600 !important;
525
- font-size: 16px !important;
526
- text-transform: uppercase !important;
527
- letter-spacing: 0.5px !important;
528
- font-family: monospace !important;
529
- height: 60px !important;
530
- display: flex !important;
531
- flex-direction: column !important;
532
- justify-content: center !important;
533
- align-items: center !important;
534
- line-height: 1.2 !important;
535
- }
536
-
537
- .summary-button:hover {
538
- background: linear-gradient(135deg, #5a5a5a, #4e4e4e) !important;
539
- color: #74b9ff !important;
540
- border-color: #666666 !important;
541
- }
542
-
543
- .summary-button:active {
544
- background: linear-gradient(135deg, #4a4a4a, #3e3e3e) !important;
545
- color: #5a9bd4 !important;
546
- }
547
-
548
- /* Regular button styling for non-model buttons */
549
- .gr-button:not(.model-button):not(.summary-button) {
550
- background-color: #222222 !important;
551
- color: white !important;
552
- border: 1px solid #444444 !important;
553
- margin: 5px 0 !important;
554
- border-radius: 8px !important;
555
- transition: all 0.3s ease !important;
556
- }
557
-
558
- .gr-button:not(.model-button):not(.summary-button):hover {
559
- background-color: #333333 !important;
560
- border-color: #666666 !important;
561
- }
562
-
563
- /* Plot container with smooth transitions and controlled scrolling */
564
- .plot-container {
565
- background-color: #000000 !important;
566
- border: none !important;
567
- transition: opacity 0.6s ease-in-out !important;
568
- flex: 1 1 auto !important;
569
- min-height: 0 !important;
570
- overflow-y: auto !important;
571
- scrollbar-width: thin !important;
572
- scrollbar-color: #333333 #000000 !important;
573
- }
574
-
575
- /* Custom scrollbar for plot container */
576
- .plot-container::-webkit-scrollbar {
577
- width: 8px !important;
578
- background: #000000 !important;
579
- }
580
-
581
- .plot-container::-webkit-scrollbar-track {
582
- background: #000000 !important;
583
- }
584
-
585
- .plot-container::-webkit-scrollbar-thumb {
586
- background-color: #333333 !important;
587
- border-radius: 4px !important;
588
- }
589
-
590
- .plot-container::-webkit-scrollbar-thumb:hover {
591
- background-color: #555555 !important;
592
- }
593
-
594
- /* Gradio plot component styling */
595
- .gr-plot {
596
- background-color: #000000 !important;
597
- transition: opacity 0.6s ease-in-out !important;
598
- }
599
-
600
- .gr-plot .gradio-plot {
601
- background-color: #000000 !important;
602
- transition: opacity 0.6s ease-in-out !important;
603
- }
604
-
605
- .gr-plot img {
606
- transition: opacity 0.6s ease-in-out !important;
607
- }
608
-
609
- /* Target the plot wrapper */
610
- div[data-testid="plot"] {
611
- background-color: #000000 !important;
612
- }
613
-
614
- /* Target all possible plot containers */
615
- .plot-container img,
616
- .gr-plot img,
617
- .gradio-plot img {
618
- background-color: #000000 !important;
619
- }
620
-
621
- /* Ensure plot area background */
622
- .gr-plot > div,
623
- .plot-container > div {
624
- background-color: #000000 !important;
625
- }
626
-
627
- /* Prevent white flash during plot updates */
628
- .plot-container::before {
629
- content: "";
630
- position: absolute;
631
- top: 0;
632
- left: 0;
633
- right: 0;
634
- bottom: 0;
635
- background-color: #000000;
636
- z-index: -1;
637
- }
638
-
639
- /* Force all plot elements to have black background */
640
- .plot-container *,
641
- .gr-plot *,
642
- div[data-testid="plot"] * {
643
- background-color: #000000 !important;
644
- }
645
-
646
- /* Override any white backgrounds in matplotlib */
647
- .plot-container canvas,
648
- .gr-plot canvas {
649
- background-color: #000000 !important;
650
- }
651
-
652
- /* Text elements */
653
- h1, h2, h3, p, .markdown {
654
- color: white !important;
655
- }
656
-
657
- /* Sidebar header enhancement */
658
- .sidebar h1 {
659
- background: linear-gradient(45deg, #74b9ff, #a29bfe) !important;
660
- -webkit-background-clip: text !important;
661
- -webkit-text-fill-color: transparent !important;
662
- background-clip: text !important;
663
- text-align: center !important;
664
- margin-bottom: 15px !important;
665
- font-size: 28px !important;
666
- font-weight: 700 !important;
667
- font-family: monospace !important;
668
- }
669
-
670
- /* Sidebar description text */
671
- .sidebar p {
672
- text-align: center !important;
673
- margin-bottom: 20px !important;
674
- line-height: 1.5 !important;
675
- font-size: 14px !important;
676
- font-family: monospace !important;
677
- }
678
-
679
- .sidebar strong {
680
- color: #74b9ff !important;
681
- font-weight: 600 !important;
682
- font-family: monospace !important;
683
- }
684
-
685
- .sidebar em {
686
- color: #a29bfe !important;
687
- font-style: normal !important;
688
- opacity: 0.9 !important;
689
- font-family: monospace !important;
690
- }
691
-
692
- /* Remove all borders globally */
693
- * {
694
- border-color: transparent !important;
695
- }
696
-
697
- /* Main content area */
698
- .main-content {
699
- background-color: #000000 !important;
700
- padding: 20px 20px 40px 20px !important;
701
- margin-left: 300px !important;
702
- height: 100vh !important;
703
- overflow-y: auto !important;
704
- box-sizing: border-box !important;
705
- display: flex !important;
706
- flex-direction: column !important;
707
- }
708
-
709
- /* Custom scrollbar for main content */
710
- .main-content {
711
- scrollbar-width: thin !important;
712
- scrollbar-color: #333333 #000000 !important;
713
- }
714
-
715
- .main-content::-webkit-scrollbar {
716
- width: 8px !important;
717
- background: #000000 !important;
718
- }
719
-
720
- .main-content::-webkit-scrollbar-track {
721
- background: #000000 !important;
722
- }
723
-
724
- .main-content::-webkit-scrollbar-thumb {
725
- background-color: #333333 !important;
726
- border-radius: 4px !important;
727
- }
728
-
729
- .main-content::-webkit-scrollbar-thumb:hover {
730
- background-color: #555555 !important;
731
- }
732
-
733
- /* Failed tests display - seamless appearance with constrained height */
734
- .failed-tests textarea {
735
- background-color: #000000 !important;
736
- color: #FFFFFF !important;
737
- font-family: monospace !important;
738
- font-size: 14px !important;
739
- border: none !important;
740
- padding: 10px !important;
741
- outline: none !important;
742
- line-height: 1.4 !important;
743
- height: 180px !important;
744
- max-height: 180px !important;
745
- min-height: 180px !important;
746
- overflow-y: auto !important;
747
- resize: none !important;
748
- scrollbar-width: thin !important;
749
- scrollbar-color: #333333 #000000 !important;
750
- scroll-behavior: auto;
751
- transition: opacity 0.5s ease-in-out !important;
752
- }
753
-
754
- /* WebKit scrollbar styling for failed tests */
755
- .failed-tests textarea::-webkit-scrollbar {
756
- width: 8px !important;
757
- }
758
-
759
- .failed-tests textarea::-webkit-scrollbar-track {
760
- background: #000000 !important;
761
- }
762
-
763
- .failed-tests textarea::-webkit-scrollbar-thumb {
764
- background-color: #333333 !important;
765
- border-radius: 4px !important;
766
- }
767
-
768
- .failed-tests textarea::-webkit-scrollbar-thumb:hover {
769
- background-color: #555555 !important;
770
- }
771
-
772
- /* Prevent white flash in text boxes during updates */
773
- .failed-tests::before {
774
- content: "";
775
- position: absolute;
776
- top: 0;
777
- left: 0;
778
- right: 0;
779
- bottom: 0;
780
- background-color: #000000;
781
- z-index: -1;
782
- }
783
-
784
- .failed-tests {
785
- background-color: #000000 !important;
786
- height: 200px !important;
787
- max-height: 200px !important;
788
- min-height: 200px !important;
789
- position: relative;
790
- transition: opacity 0.5s ease-in-out !important;
791
- flex-shrink: 0 !important;
792
- }
793
-
794
- .failed-tests .gr-textbox {
795
- background-color: #000000 !important;
796
- border: none !important;
797
- height: 180px !important;
798
- max-height: 180px !important;
799
- min-height: 180px !important;
800
- transition: opacity 0.5s ease-in-out !important;
801
- }
802
-
803
- /* Force all textbox elements to have black background */
804
- .failed-tests *,
805
- .failed-tests .gr-textbox *,
806
- .failed-tests textarea * {
807
- background-color: #000000 !important;
808
- }
809
-
810
- /* Summary display styling */
811
- .summary-display textarea {
812
- background-color: #000000 !important;
813
- color: #FFFFFF !important;
814
- font-family: monospace !important;
815
- font-size: 24px !important;
816
- border: none !important;
817
- padding: 20px !important;
818
- outline: none !important;
819
- line-height: 2 !important;
820
- text-align: right !important;
821
- resize: none !important;
822
- }
823
-
824
- .summary-display {
825
- background-color: #000000 !important;
826
- }
827
-
828
-
829
-
830
- /* Detail view layout */
831
- .detail-view {
832
- display: flex !important;
833
- flex-direction: column !important;
834
- height: 100% !important;
835
- min-height: 0 !important;
836
- }
837
-
838
- /* JavaScript to reset scroll position */
839
- .scroll-reset {
840
- animation: resetScroll 0.1s ease;
841
- }
842
-
843
- @keyframes resetScroll {
844
- 0% { scroll-behavior: auto; }
845
- 100% { scroll-behavior: auto; }
846
- }
847
-
848
-
849
- """
850
 
851
  # Create the Gradio interface with sidebar and dark theme
852
- with gr.Blocks(title="Model Test Results Dashboard", css=dark_theme_css) as demo:
853
 
854
  with gr.Row():
855
- # Sidebar for model selection
856
  with gr.Column(scale=1, elem_classes=["sidebar"]):
857
  gr.Markdown("# 🤖 TCID")
858
- gr.Markdown("**Transformer CI Dashboard**\n\n*Analyze transformers CI results across AMD and NVIDIA devices*\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
859
 
860
  # Summary button at the top
861
  summary_button = gr.Button(
@@ -865,16 +454,18 @@ with gr.Blocks(title="Model Test Results Dashboard", css=dark_theme_css) as demo
865
  elem_classes=["summary-button"]
866
  )
867
 
868
- # Model selection buttons in sidebar
869
- model_buttons = []
870
- for model_name in MODELS.keys():
871
- btn = gr.Button(
872
- f"{model_name.lower()}",
873
- variant="secondary",
874
- size="lg",
875
- elem_classes=["model-button"]
876
- )
877
- model_buttons.append(btn)
 
 
878
 
879
  # Main content area
880
  with gr.Column(scale=4, elem_classes=["main-content"]):
@@ -901,7 +492,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=dark_theme_css) as demo
901
  with gr.Row():
902
  with gr.Column(scale=1):
903
  amd_failed_tests_output = gr.Textbox(
904
- value="Failures on AMD (exclusive):\n─────────────────────────────\nnetwork_timeout\n\nFailures on AMD (common):\n────────────────────────\ndistributed",
905
  lines=8,
906
  max_lines=8,
907
  interactive=False,
@@ -910,7 +501,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=dark_theme_css) as demo
910
  )
911
  with gr.Column(scale=1):
912
  nvidia_failed_tests_output = gr.Textbox(
913
- value="Failures on NVIDIA (exclusive):\n─────────────────────────────────\nmulti_gpu\n\nFailures on NVIDIA (common):\n────────────────────────────\ndistributed",
914
  lines=8,
915
  max_lines=8,
916
  interactive=False,
@@ -918,27 +509,127 @@ with gr.Blocks(title="Model Test Results Dashboard", css=dark_theme_css) as demo
918
  elem_classes=["failed-tests"]
919
  )
920
 
921
- # Set up click handlers for each button
922
- for i, (model_name, button) in enumerate(zip(MODELS.keys(), model_buttons)):
923
- button.click(
924
- fn=lambda name=model_name: plot_model_stats(name),
925
- outputs=[plot_output, amd_failed_tests_output, nvidia_failed_tests_output]
926
- ).then(
927
- fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
928
- outputs=[summary_display, detail_view]
929
- ).then(
930
- fn=None,
931
- js="() => { setTimeout(() => { document.querySelectorAll('textarea').forEach(t => { if (t.closest('.failed-tests')) { t.scrollTop = 0; setTimeout(() => { t.style.scrollBehavior = 'smooth'; t.scrollTo({ top: 0, behavior: 'smooth' }); t.style.scrollBehavior = 'auto'; }, 50); } }); }, 300); }"
932
- )
933
 
934
  # Summary button click handler
 
 
 
 
935
  summary_button.click(
936
- fn=lambda: create_summary_page(),
937
- outputs=[summary_display]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
938
  ).then(
939
  fn=lambda: [gr.update(visible=True), gr.update(visible=False)],
940
  outputs=[summary_display, detail_view]
941
  )
942
 
 
 
 
 
 
 
943
  if __name__ == "__main__":
944
  demo.launch()
 
1
  import matplotlib.pyplot as plt
2
  import matplotlib
3
  import numpy as np
4
+ import pandas as pd
5
  import gradio as gr
6
+ import threading
7
+ import time
8
+ from datetime import datetime
9
+ from data import get_data
10
 
11
  # Configure matplotlib to prevent memory warnings and set dark background
12
  matplotlib.rcParams['figure.max_open_warning'] = 0
 
15
  matplotlib.rcParams['savefig.facecolor'] = '#000000'
16
  plt.ioff() # Turn off interactive mode to prevent figure accumulation
17
 
18
+ # Global variables for data
19
+ df = pd.DataFrame()
20
+ available_models = []
21
+ last_update_time = None
22
+
23
+ def load_data():
24
+ """Load data from the data source."""
25
+ global df, available_models, last_update_time
26
+ try:
27
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Loading data...")
28
+ new_df = get_data()
29
+ new_models = new_df.index.tolist()
30
+
31
+ # Update global variables
32
+ df = new_df
33
+ available_models = new_models
34
+ last_update_time = datetime.now()
35
+
36
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Data loaded successfully: {len(available_models)} models")
37
+ print(f"Models: {available_models[:5]}{'...' if len(available_models) > 5 else ''}")
38
+
39
+ return True
40
+ except Exception as e:
41
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Error loading data: {e}")
42
+ return False
43
+
44
+ def schedule_data_reload():
45
+ """Schedule the next data reload."""
46
+ def reload_data():
47
+ load_data()
48
+ # Schedule the next reload in 15 minutes (900 seconds)
49
+ timer = threading.Timer(900.0, reload_data)
50
+ timer.daemon = True # Dies when main thread dies
51
+ timer.start()
52
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Next data reload scheduled in 15 minutes")
53
+
54
+ # Start the first reload timer
55
+ timer = threading.Timer(900.0, reload_data)
56
+ timer.daemon = True
57
+ timer.start()
58
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Data auto-reload scheduled every 15 minutes")
59
 
60
+ # Load data once at startup
61
+ if not load_data():
62
+ print("WARNING: Failed to load data! Adding fallback models.")
63
+ available_models = ["auto", "bert", "clip", "llama", "t5"] # Fallback models for testing
64
+
65
+ # Start the auto-reload scheduler
66
+ schedule_data_reload()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  def generate_underlined_line(text: str) -> str:
69
  return text + "\n" + "─" * len(text) + "\n"
70
 
71
  def plot_model_stats(model_name: str) -> tuple[plt.Figure, str, str]:
72
  """Draws a pie chart of model's passed, failed, skipped, and error stats."""
73
+ if df.empty or model_name not in df.index:
74
+ # Handle case where model data is not available
75
+ fig, ax = plt.subplots(figsize=(10, 8), facecolor='#000000')
76
+ ax.set_facecolor('#000000')
77
+ ax.text(0.5, 0.5, f'No data available for {model_name}',
78
+ horizontalalignment='center', verticalalignment='center',
79
+ transform=ax.transAxes, fontsize=16, color='#888888',
80
+ fontfamily='monospace', weight='normal')
81
+ ax.set_xlim(0, 1)
82
+ ax.set_ylim(0, 1)
83
+ ax.axis('off')
84
+ return fig, "No data available", "No data available"
85
+
86
+ row = df.loc[model_name]
87
+
88
+ # Handle missing values and get counts directly from dataframe
89
+ success_amd = int(row.get('success_amd', 0)) if pd.notna(row.get('success_amd', 0)) else 0
90
+ success_nvidia = int(row.get('success_nvidia', 0)) if pd.notna(row.get('success_nvidia', 0)) else 0
91
+ failed_multi_amd = int(row.get('failed_multi_no_amd', 0)) if pd.notna(row.get('failed_multi_no_amd', 0)) else 0
92
+ failed_multi_nvidia = int(row.get('failed_multi_no_nvidia', 0)) if pd.notna(row.get('failed_multi_no_nvidia', 0)) else 0
93
+ failed_single_amd = int(row.get('failed_single_no_amd', 0)) if pd.notna(row.get('failed_single_no_amd', 0)) else 0
94
+ failed_single_nvidia = int(row.get('failed_single_no_nvidia', 0)) if pd.notna(row.get('failed_single_no_nvidia', 0)) else 0
95
+
96
+ # Calculate total failures
97
+ total_failed_amd = failed_multi_amd + failed_single_amd
98
+ total_failed_nvidia = failed_multi_nvidia + failed_single_nvidia
99
 
100
  # Softer color palette - less pastel, more vibrant
101
  colors = {
 
105
  'error': '#8B0000' # Dark red
106
  }
107
 
108
+ # Create stats dictionaries directly from dataframe values
109
+ amd_stats = {
110
+ 'passed': success_amd,
111
+ 'failed': total_failed_amd,
112
+ 'skipped': 0, # Not available in this dataset
113
+ 'error': 0 # Not available in this dataset
114
+ }
115
+
116
+ nvidia_stats = {
117
+ 'passed': success_nvidia,
118
+ 'failed': total_failed_nvidia,
119
+ 'skipped': 0, # Not available in this dataset
120
+ 'error': 0 # Not available in this dataset
121
+ }
122
 
123
  # Filter out categories with 0 values for cleaner visualization
124
  amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
 
207
  plt.tight_layout()
208
  plt.subplots_adjust(top=0.85, wspace=0.4) # Added wspace for padding between charts
209
 
210
+ # Generate failure info directly from dataframe
211
+ failures_amd = row.get('failures_amd', {})
212
+ failures_nvidia = row.get('failures_nvidia', {})
213
+
214
+ amd_failed_info = extract_failure_info(failures_amd, 'AMD', failed_multi_amd, failed_single_amd)
215
+ nvidia_failed_info = extract_failure_info(failures_nvidia, 'NVIDIA', failed_multi_nvidia, failed_single_nvidia)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
  return fig, amd_failed_info, nvidia_failed_info
218
 
219
+ def extract_failure_info(failures_obj, device: str, multi_count: int, single_count: int) -> str:
220
+ """Extract failure information from failures object."""
221
+ if (not failures_obj or pd.isna(failures_obj)) and multi_count == 0 and single_count == 0:
222
+ return f"No failures on {device}"
223
+
224
+ info_lines = []
225
+
226
+ # Add counts summary
227
+ if multi_count > 0 or single_count > 0:
228
+ info_lines.append(generate_underlined_line(f"Failure Summary for {device}:"))
229
+ if multi_count > 0:
230
+ info_lines.append(f"Multi GPU failures: {multi_count}")
231
+ if single_count > 0:
232
+ info_lines.append(f"Single GPU failures: {single_count}")
233
+ info_lines.append("")
234
+
235
+ # Try to extract detailed failure information
236
+ try:
237
+ if isinstance(failures_obj, dict):
238
+ # Check for multi and single failure categories
239
+ if 'multi' in failures_obj and failures_obj['multi']:
240
+ info_lines.append(generate_underlined_line(f"Multi GPU failure details:"))
241
+ if isinstance(failures_obj['multi'], list):
242
+ # Handle list of failures (could be strings or dicts)
243
+ for i, failure in enumerate(failures_obj['multi'][:10]): # Limit to first 10
244
+ if isinstance(failure, dict):
245
+ # Extract meaningful info from dict (e.g., test name, line, etc.)
246
+ failure_str = failure.get('line', failure.get('test', failure.get('name', str(failure))))
247
+ info_lines.append(f" {i+1}. {failure_str}")
248
+ else:
249
+ info_lines.append(f" {i+1}. {str(failure)}")
250
+ if len(failures_obj['multi']) > 10:
251
+ info_lines.append(f"... and {len(failures_obj['multi']) - 10} more")
252
+ else:
253
+ info_lines.append(str(failures_obj['multi']))
254
+ info_lines.append("")
255
+
256
+ if 'single' in failures_obj and failures_obj['single']:
257
+ info_lines.append(generate_underlined_line(f"Single GPU failure details:"))
258
+ if isinstance(failures_obj['single'], list):
259
+ # Handle list of failures (could be strings or dicts)
260
+ for i, failure in enumerate(failures_obj['single'][:10]): # Limit to first 10
261
+ if isinstance(failure, dict):
262
+ # Extract meaningful info from dict (e.g., test name, line, etc.)
263
+ failure_str = failure.get('line', failure.get('test', failure.get('name', str(failure))))
264
+ info_lines.append(f" {i+1}. {failure_str}")
265
+ else:
266
+ info_lines.append(f" {i+1}. {str(failure)}")
267
+ if len(failures_obj['single']) > 10:
268
+ info_lines.append(f"... and {len(failures_obj['single']) - 10} more")
269
+ else:
270
+ info_lines.append(str(failures_obj['single']))
271
+
272
+ return "\n".join(info_lines) if info_lines else f"No detailed failure info for {device}"
273
+
274
+ except Exception as e:
275
+ if multi_count > 0 or single_count > 0:
276
+ return f"Failures detected on {device} (Multi: {multi_count}, Single: {single_count})\nDetails unavailable: {str(e)}"
277
+ return f"Error processing failure info for {device}: {str(e)}"
278
 
279
  def create_summary_page() -> plt.Figure:
280
  """Create a summary page with model names and both AMD/NVIDIA test stats bars."""
281
+ if df.empty:
282
+ fig, ax = plt.subplots(figsize=(16, 8), facecolor='#000000')
283
+ ax.set_facecolor('#000000')
284
+ ax.text(0.5, 0.5, 'No data available',
285
+ horizontalalignment='center', verticalalignment='center',
286
+ transform=ax.transAxes, fontsize=20, color='#888888',
287
+ fontfamily='monospace', weight='normal')
288
+ ax.axis('off')
289
+ return fig
290
+
291
+ fig, ax = plt.subplots(figsize=(16, len(available_models) * 2.5 + 2), facecolor='#000000')
292
  ax.set_facecolor('#000000')
293
 
294
  colors = {
 
300
 
301
  visible_model_count = 0
302
  max_y = 0
303
+
304
+ for i, model_name in enumerate(available_models):
305
+ if model_name not in df.index:
306
+ continue
307
+
308
+ row = df.loc[model_name]
309
+
310
+ # Get values directly from dataframe
311
+ success_amd = int(row.get('success_amd', 0)) if pd.notna(row.get('success_amd', 0)) else 0
312
+ success_nvidia = int(row.get('success_nvidia', 0)) if pd.notna(row.get('success_nvidia', 0)) else 0
313
+ failed_multi_amd = int(row.get('failed_multi_no_amd', 0)) if pd.notna(row.get('failed_multi_no_amd', 0)) else 0
314
+ failed_multi_nvidia = int(row.get('failed_multi_no_nvidia', 0)) if pd.notna(row.get('failed_multi_no_nvidia', 0)) else 0
315
+ failed_single_amd = int(row.get('failed_single_no_amd', 0)) if pd.notna(row.get('failed_single_no_amd', 0)) else 0
316
+ failed_single_nvidia = int(row.get('failed_single_no_nvidia', 0)) if pd.notna(row.get('failed_single_no_nvidia', 0)) else 0
317
+
318
+ # Calculate stats
319
+ amd_stats = {
320
+ 'passed': success_amd,
321
+ 'failed': failed_multi_amd + failed_single_amd,
322
+ 'skipped': 0,
323
+ 'error': 0
324
+ }
325
+
326
+ nvidia_stats = {
327
+ 'passed': success_nvidia,
328
+ 'failed': failed_multi_nvidia + failed_single_nvidia,
329
+ 'skipped': 0,
330
+ 'error': 0
331
+ }
332
+
333
  amd_total = sum(amd_stats.values())
 
334
  nvidia_total = sum(nvidia_stats.values())
335
 
336
  if amd_total == 0 and nvidia_total == 0:
 
410
 
411
  return fig
412
 
413
+ # Load CSS from external file
414
+ def load_css():
415
+ try:
416
+ with open("styles.css", "r") as f:
417
+ return f.read()
418
+ except FileNotFoundError:
419
+ print("Warning: styles.css not found, using minimal default styles")
420
+ return "body { background: #000; color: #fff; }"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
  # Create the Gradio interface with sidebar and dark theme
423
+ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
424
 
425
  with gr.Row():
426
+ # Sidebar for model selection
427
  with gr.Column(scale=1, elem_classes=["sidebar"]):
428
  gr.Markdown("# 🤖 TCID")
429
+ gr.Markdown("**Transformer CI Dashboard**\n\n*Result overview by model and hardware*\n")
430
+
431
+ # Data status indicator
432
+ if last_update_time:
433
+ status_text = f"📊 **Updated:** {last_update_time.strftime('%H:%M')}\n\n*Auto-refresh: 15min*"
434
+ else:
435
+ status_text = f"📊 **Loading...**\n\n*Auto-refresh: 15min*"
436
+ status_display = gr.Markdown(status_text)
437
+
438
+ # Manual refresh button
439
+ refresh_button = gr.Button(
440
+ "🔄 refresh data",
441
+ variant="secondary",
442
+ size="sm",
443
+ elem_classes=["refresh-button"]
444
+ )
445
+
446
+ # CI job links
447
+ ci_links_display = gr.Markdown("🔗 **CI Jobs:** *Loading...*")
448
 
449
  # Summary button at the top
450
  summary_button = gr.Button(
 
454
  elem_classes=["summary-button"]
455
  )
456
 
457
+ # Back to simple buttons that work
458
+ # Model selector dropdown - much better for long lists
459
+ gr.Markdown(f"**Select Model ({len(available_models)}):**")
460
+
461
+ model_choices = [model.lower() for model in available_models] if available_models else ["auto", "bert", "clip", "llama"]
462
+ model_dropdown = gr.Dropdown(
463
+ choices=model_choices,
464
+ value=model_choices[0] if model_choices else "auto",
465
+ label="Choose Model",
466
+ interactive=True,
467
+ allow_custom_value=False
468
+ )
469
 
470
  # Main content area
471
  with gr.Column(scale=4, elem_classes=["main-content"]):
 
492
  with gr.Row():
493
  with gr.Column(scale=1):
494
  amd_failed_tests_output = gr.Textbox(
495
+ value="",
496
  lines=8,
497
  max_lines=8,
498
  interactive=False,
 
501
  )
502
  with gr.Column(scale=1):
503
  nvidia_failed_tests_output = gr.Textbox(
504
+ value="",
505
  lines=8,
506
  max_lines=8,
507
  interactive=False,
 
509
  elem_classes=["failed-tests"]
510
  )
511
 
512
+ # Set up change handler for dropdown
513
+ model_dropdown.change(
514
+ fn=lambda selected_model: plot_model_stats(selected_model),
515
+ inputs=[model_dropdown],
516
+ outputs=[plot_output, amd_failed_tests_output, nvidia_failed_tests_output]
517
+ ).then(
518
+ fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
519
+ outputs=[summary_display, detail_view]
520
+ )
 
 
 
521
 
522
  # Summary button click handler
523
+ def show_summary_and_update_links():
524
+ """Show summary page and update CI links."""
525
+ return create_summary_page(), get_ci_links()
526
+
527
  summary_button.click(
528
+ fn=show_summary_and_update_links,
529
+ outputs=[summary_display, ci_links_display]
530
+ ).then(
531
+ fn=lambda: [gr.update(visible=True), gr.update(visible=False)],
532
+ outputs=[summary_display, detail_view]
533
+ )
534
+
535
+ # Function to get current status text
536
+ def get_status_text():
537
+ """Get current status text with last update time."""
538
+ if last_update_time:
539
+ return f"📊 **Updated:** {last_update_time.strftime('%H:%M')}\n\n*Auto-refresh: 15min*"
540
+ else:
541
+ return f"📊 **Loading...**\n\n*Auto-refresh: 15min*"
542
+
543
+ # Function to get CI job links
544
+ def get_ci_links():
545
+ """Get CI job links from the most recent data."""
546
+ try:
547
+ # Check if df exists and is not empty
548
+ if 'df' not in globals() or df is None or df.empty:
549
+ return "🔗 **CI Jobs:** *Loading...*"
550
+
551
+ # Get links from any available model (they should be the same for all models in a run)
552
+ amd_multi_link = None
553
+ amd_single_link = None
554
+ nvidia_multi_link = None
555
+ nvidia_single_link = None
556
+
557
+ for model_name in df.index:
558
+ row = df.loc[model_name]
559
+
560
+ # Extract AMD links
561
+ if pd.notna(row.get('job_link_amd')) and (not amd_multi_link or not amd_single_link):
562
+ amd_link_raw = row.get('job_link_amd')
563
+ if isinstance(amd_link_raw, dict):
564
+ if 'multi' in amd_link_raw and not amd_multi_link:
565
+ amd_multi_link = amd_link_raw['multi']
566
+ if 'single' in amd_link_raw and not amd_single_link:
567
+ amd_single_link = amd_link_raw['single']
568
+
569
+ # Extract NVIDIA links
570
+ if pd.notna(row.get('job_link_nvidia')) and (not nvidia_multi_link or not nvidia_single_link):
571
+ nvidia_link_raw = row.get('job_link_nvidia')
572
+ if isinstance(nvidia_link_raw, dict):
573
+ if 'multi' in nvidia_link_raw and not nvidia_multi_link:
574
+ nvidia_multi_link = nvidia_link_raw['multi']
575
+ if 'single' in nvidia_link_raw and not nvidia_single_link:
576
+ nvidia_single_link = nvidia_link_raw['single']
577
+
578
+ # Break if we have all links
579
+ if amd_multi_link and amd_single_link and nvidia_multi_link and nvidia_single_link:
580
+ break
581
+
582
+ links_md = "🔗 **CI Jobs:**\n\n"
583
+
584
+ # AMD links
585
+ if amd_multi_link or amd_single_link:
586
+ links_md += "**AMD:**\n"
587
+ if amd_multi_link:
588
+ links_md += f"• [Multi GPU]({amd_multi_link})\n"
589
+ if amd_single_link:
590
+ links_md += f"• [Single GPU]({amd_single_link})\n"
591
+ links_md += "\n"
592
+
593
+ # NVIDIA links
594
+ if nvidia_multi_link or nvidia_single_link:
595
+ links_md += "**NVIDIA:**\n"
596
+ if nvidia_multi_link:
597
+ links_md += f"• [Multi GPU]({nvidia_multi_link})\n"
598
+ if nvidia_single_link:
599
+ links_md += f"• [Single GPU]({nvidia_single_link})\n"
600
+
601
+ if not (amd_multi_link or amd_single_link or nvidia_multi_link or nvidia_single_link):
602
+ links_md += "*No links available*"
603
+
604
+ return links_md
605
+ except Exception as e:
606
+ print(f"Error getting CI links: {e}")
607
+ return "🔗 **CI Jobs:** *Error loading links*"
608
+
609
+ # Refresh button click handler
610
+ def refresh_data_and_status():
611
+ """Manual data refresh triggered by user."""
612
+ success = load_data()
613
+ if success:
614
+ # Return updated summary page, status, and CI links
615
+ return create_summary_page(), get_status_text(), get_ci_links()
616
+ else:
617
+ # Return current summary page, status, and CI links if reload failed
618
+ return create_summary_page(), get_status_text(), get_ci_links()
619
+
620
+ refresh_button.click(
621
+ fn=refresh_data_and_status,
622
+ outputs=[summary_display, status_display, ci_links_display]
623
  ).then(
624
  fn=lambda: [gr.update(visible=True), gr.update(visible=False)],
625
  outputs=[summary_display, detail_view]
626
  )
627
 
628
+ # Auto-update CI links when the interface loads
629
+ demo.load(
630
+ fn=get_ci_links,
631
+ outputs=[ci_links_display]
632
+ )
633
+
634
  if __name__ == "__main__":
635
  demo.launch()
data.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi, HfFileSystem, login
2
+ import pandas as pd
3
+
4
+ fs = HfFileSystem()
5
+
6
+ IMPORTANT_MODELS = [
7
+ "auto",
8
+ "bert", # old but dominant (encoder only)
9
+ "gpt2", # old (decoder)
10
+ "t5", # old (encoder-decoder)
11
+ "modernbert", # (encoder only)
12
+ "vit", # old (vision) - fixed comma
13
+ "clip", # old but dominant (vision)
14
+ "detr", # objection detection, segmentation (vision)
15
+ "table-transformer", # objection detection (visioin) - maybe just detr?
16
+ "got_ocr2", # ocr (vision)
17
+ "whisper", # old but dominant (audio)
18
+ "wav2vec2", # old (audio)
19
+ "llama", # new and dominant (meta)
20
+ "gemma3", # new (google)
21
+ "qwen2", # new (Alibaba)
22
+ "mistral3", # new (Mistral) - added missing comma
23
+ "qwen2_5_vl", # new (vision)
24
+ "llava", # many models from it (vision)
25
+ "smolvlm", # new (video)
26
+ "internvl", # new (video)
27
+ "gemma3n", # new (omnimodal models)
28
+ "qwen2_5_omni", # new (omnimodal models)
29
+ ]
30
+
31
+
32
+ def get_data():
33
+ files_amd = fs.glob(
34
+ "hf://datasets/optimum-amd/transformers_daily_ci/**/runs/**/ci_results_run_models_gpu/model_results.json"
35
+ )
36
+ files_amd.sort(reverse=True)
37
+
38
+ df_amd = pd.read_json(f"hf://{files_amd[0]}", orient="index")
39
+ df_amd.index.name = "model_name"
40
+ df_amd["failed_multi_no_amd"] = df_amd["failures"].apply(
41
+ lambda x: len(x["multi"]) if "multi" in x else 0
42
+ )
43
+ df_amd["failed_single_no_amd"] = df_amd["failures"].apply(
44
+ lambda x: len(x["single"]) if "single" in x else 0
45
+ )
46
+
47
+ files_nvidia = fs.glob(
48
+ "hf://datasets/hf-internal-testing/transformers_daily_ci/**/ci_results_run_models_gpu/model_results.json"
49
+ )
50
+ files_nvidia.sort(reverse=True)
51
+
52
+ df_nvidia = pd.read_json(
53
+ f"https://huggingface.co/datasets/hf-internal-testing/transformers_daily_ci/raw/main/{files_nvidia[0].lstrip('datasets/hf-internal-testing/transformers_daily_ci/')}",
54
+ orient="index",
55
+ )
56
+ df_nvidia.index.name = "model_name"
57
+ df_nvidia["failed_multi_no_nvidia"] = df_nvidia["failures"].apply(
58
+ lambda x: len(x["multi"]) if "multi" in x else 0
59
+ )
60
+ df_nvidia["failed_single_no_nvidia"] = df_nvidia["failures"].apply(
61
+ lambda x: len(x["single"]) if "single" in x else 0
62
+ )
63
+ df_nvidia
64
+
65
+ joined = df_amd.join(df_nvidia, rsuffix="_nvidia", lsuffix="_amd", how="outer")
66
+ joined = joined[
67
+ [
68
+ "success_amd",
69
+ "success_nvidia",
70
+ "failed_multi_no_amd",
71
+ "failed_multi_no_nvidia",
72
+ "failed_single_no_amd",
73
+ "failed_single_no_nvidia",
74
+ "failures_amd",
75
+ "failures_nvidia",
76
+ "job_link_amd",
77
+ "job_link_nvidia",
78
+ ]
79
+ ]
80
+
81
+ joined.index = joined.index.str.replace("^models_", "", regex=True)
82
+
83
+ important_models_lower = [model.lower() for model in IMPORTANT_MODELS]
84
+ filtered_joined = joined[joined.index.str.lower().isin(important_models_lower)]
85
+
86
+ return filtered_joined
styles.css ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global dark theme */
2
+ .gradio-container {
3
+ background-color: #000000 !important;
4
+ color: white !important;
5
+ height: 100vh !important;
6
+ max-height: 100vh !important;
7
+ overflow: hidden !important;
8
+ }
9
+
10
+ /* Remove borders from all components */
11
+ .gr-box, .gr-form, .gr-panel {
12
+ border: none !important;
13
+ background-color: #000000 !important;
14
+ }
15
+
16
+ /* Simplified sidebar styling */
17
+ .sidebar {
18
+ background: linear-gradient(145deg, #111111, #1a1a1a) !important;
19
+ border: none !important;
20
+ padding: 15px !important;
21
+ margin: 0 !important;
22
+ height: 100vh !important;
23
+ position: fixed !important;
24
+ left: 0 !important;
25
+ top: 0 !important;
26
+ width: 300px !important;
27
+ box-sizing: border-box !important;
28
+ overflow-y: auto !important;
29
+ overflow-x: hidden !important;
30
+ }
31
+
32
+ /* Target the actual Gradio column containing sidebar */
33
+ div[data-testid="column"]:has(.sidebar) {
34
+ height: 100vh !important;
35
+ overflow-y: auto !important;
36
+ overflow-x: hidden !important;
37
+ }
38
+
39
+ /* Scrollbar styling for sidebar */
40
+ .sidebar::-webkit-scrollbar {
41
+ width: 8px !important;
42
+ background: #111111 !important;
43
+ }
44
+
45
+ .sidebar::-webkit-scrollbar-track {
46
+ background: #111111 !important;
47
+ }
48
+
49
+ .sidebar::-webkit-scrollbar-thumb {
50
+ background-color: #333333 !important;
51
+ border-radius: 4px !important;
52
+ }
53
+
54
+ .sidebar::-webkit-scrollbar-thumb:hover {
55
+ background-color: #555555 !important;
56
+ }
57
+
58
+ /* Ensure all sidebar content fits within width */
59
+ .sidebar * {
60
+ max-width: 100% !important;
61
+ word-wrap: break-word !important;
62
+ overflow-wrap: break-word !important;
63
+ }
64
+
65
+ /* Specific control for markdown content */
66
+ .sidebar .markdown,
67
+ .sidebar h1,
68
+ .sidebar h2,
69
+ .sidebar h3,
70
+ .sidebar p {
71
+ max-width: 100% !important;
72
+ word-wrap: break-word !important;
73
+ overflow: hidden !important;
74
+ }
75
+
76
+ /* Sidebar scrollbar styling */
77
+ .sidebar::-webkit-scrollbar {
78
+ width: 8px !important;
79
+ background: #111111 !important;
80
+ }
81
+
82
+ .sidebar::-webkit-scrollbar-track {
83
+ background: #111111 !important;
84
+ }
85
+
86
+ .sidebar::-webkit-scrollbar-thumb {
87
+ background-color: #333333 !important;
88
+ border-radius: 4px !important;
89
+ }
90
+
91
+ .sidebar::-webkit-scrollbar-thumb:hover {
92
+ background-color: #555555 !important;
93
+ }
94
+
95
+ /* Force button containers to single column - DISABLED */
96
+ /*
97
+ .sidebar .gr-button,
98
+ .sidebar button {
99
+ display: block !important;
100
+ width: 100% !important;
101
+ max-width: 100% !important;
102
+ margin: 2px 0 !important;
103
+ flex: none !important;
104
+ }
105
+ */
106
+
107
+ /* Model button styling - DISABLED */
108
+ /*
109
+ .model-button {
110
+ background: linear-gradient(135deg, #2a2a2a, #1e1e1e) !important;
111
+ color: white !important;
112
+ border: 1px solid #555 !important;
113
+ margin: 3px 0 !important;
114
+ border-radius: 6px !important;
115
+ padding: 8px 12px !important;
116
+ font-weight: 600 !important;
117
+ font-size: 14px !important;
118
+ text-transform: uppercase !important;
119
+ letter-spacing: 0.3px !important;
120
+ font-family: monospace !important;
121
+ width: 100% !important;
122
+ max-width: 100% !important;
123
+ box-sizing: border-box !important;
124
+ white-space: nowrap !important;
125
+ text-overflow: ellipsis !important;
126
+ display: block !important;
127
+ cursor: pointer !important;
128
+ transition: all 0.3s ease !important;
129
+ }
130
+
131
+ .model-button:hover {
132
+ background: linear-gradient(135deg, #3a3a3a, #2e2e2e) !important;
133
+ border-color: #74b9ff !important;
134
+ color: #74b9ff !important;
135
+ transform: translateY(-1px) !important;
136
+ box-shadow: 0 2px 8px rgba(116, 185, 255, 0.2) !important;
137
+ }
138
+ */
139
+
140
+ /*
141
+ .model-button:active {
142
+ background: linear-gradient(135deg, #2a2a2a, #1e1e1e) !important;
143
+ color: #5a9bd4 !important;
144
+ }
145
+ */
146
+
147
+ /* Model stats badge */
148
+ .model-stats {
149
+ display: flex !important;
150
+ justify-content: space-between !important;
151
+ align-items: center !important;
152
+ margin-top: 8px !important;
153
+ font-size: 12px !important;
154
+ opacity: 0.8 !important;
155
+ }
156
+
157
+ .stats-badge {
158
+ background: rgba(116, 185, 255, 0.2) !important;
159
+ padding: 4px 8px !important;
160
+ border-radius: 10px !important;
161
+ font-weight: 500 !important;
162
+ font-size: 11px !important;
163
+ color: #74b9ff !important;
164
+ }
165
+
166
+ .success-indicator {
167
+ width: 8px !important;
168
+ height: 8px !important;
169
+ border-radius: 50% !important;
170
+ display: inline-block !important;
171
+ margin-right: 6px !important;
172
+ }
173
+
174
+ .success-high { background-color: #4CAF50 !important; }
175
+ .success-medium { background-color: #FF9800 !important; }
176
+ .success-low { background-color: #F44336 !important; }
177
+
178
+ /* Refresh button styling */
179
+ .refresh-button {
180
+ background: linear-gradient(135deg, #2d5aa0, #1e3f73) !important;
181
+ color: white !important;
182
+ border: 1px solid #3a6bc7 !important;
183
+ margin: 0 0 10px 0 !important;
184
+ border-radius: 5px !important;
185
+ padding: 6px 8px !important;
186
+ transition: all 0.3s ease !important;
187
+ font-weight: 500 !important;
188
+ font-size: 11px !important;
189
+ text-transform: lowercase !important;
190
+ letter-spacing: 0.1px !important;
191
+ font-family: monospace !important;
192
+ width: 100% !important;
193
+ max-width: 100% !important;
194
+ min-width: 0 !important;
195
+ box-sizing: border-box !important;
196
+ white-space: nowrap !important;
197
+ overflow: hidden !important;
198
+ text-overflow: ellipsis !important;
199
+ }
200
+
201
+ .refresh-button:hover {
202
+ background: linear-gradient(135deg, #3a6bc7, #2d5aa0) !important;
203
+ border-color: #4a7bd9 !important;
204
+ }
205
+
206
+ /* Summary button styling - distinct from model buttons */
207
+ .summary-button {
208
+ background: linear-gradient(135deg, #4a4a4a, #3e3e3e) !important;
209
+ color: white !important;
210
+ border: 2px solid #555555 !important;
211
+ margin: 0 0 15px 0 !important;
212
+ border-radius: 5px !important;
213
+ padding: 12px 10px !important;
214
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
215
+ position: relative !important;
216
+ overflow: hidden !important;
217
+ box-shadow:
218
+ 0 4px 15px rgba(0, 0, 0, 0.3),
219
+ inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
220
+ font-weight: 600 !important;
221
+ font-size: 14px !important;
222
+ text-transform: uppercase !important;
223
+ letter-spacing: 0.3px !important;
224
+ font-family: monospace !important;
225
+ height: 60px !important;
226
+ display: flex !important;
227
+ flex-direction: column !important;
228
+ justify-content: center !important;
229
+ align-items: center !important;
230
+ line-height: 1.2 !important;
231
+ width: 100% !important;
232
+ max-width: 100% !important;
233
+ min-width: 0 !important;
234
+ box-sizing: border-box !important;
235
+ }
236
+
237
+ /* Simplified Gradio layout control */
238
+ .sidebar .gr-column,
239
+ .sidebar .gradio-column {
240
+ width: 100% !important;
241
+ }
242
+
243
+ /* Simplified Gradio targeting */
244
+ div[data-testid="column"]:has(.sidebar) {
245
+ width: 300px !important;
246
+ min-width: 300px !important;
247
+ }
248
+
249
+ /* Button container with fixed height - DISABLED */
250
+ /*
251
+ .button-container {
252
+ height: 50vh !important;
253
+ max-height: 50vh !important;
254
+ overflow-y: auto !important;
255
+ overflow-x: hidden !important;
256
+ scrollbar-width: thin !important;
257
+ scrollbar-color: #333333 #111111 !important;
258
+ width: 100% !important;
259
+ max-width: 100% !important;
260
+ box-sizing: border-box !important;
261
+ padding: 5px 0 !important;
262
+ margin-top: 10px !important;
263
+ }
264
+ */
265
+
266
+ /* Removed simple scroll CSS - was hiding buttons */
267
+
268
+ .summary-button:hover {
269
+ background: linear-gradient(135deg, #5a5a5a, #4e4e4e) !important;
270
+ color: #74b9ff !important;
271
+ border-color: #666666 !important;
272
+ }
273
+
274
+ .summary-button:active {
275
+ background: linear-gradient(135deg, #4a4a4a, #3e3e3e) !important;
276
+ color: #5a9bd4 !important;
277
+ }
278
+
279
+ /* Regular button styling for non-model buttons */
280
+ .gr-button:not(.model-button):not(.summary-button) {
281
+ background-color: #222222 !important;
282
+ color: white !important;
283
+ border: 1px solid #444444 !important;
284
+ margin: 5px 0 !important;
285
+ border-radius: 8px !important;
286
+ transition: all 0.3s ease !important;
287
+ }
288
+
289
+ .gr-button:not(.model-button):not(.summary-button):hover {
290
+ background-color: #333333 !important;
291
+ border-color: #666666 !important;
292
+ }
293
+
294
+ /* Plot container with smooth transitions and controlled scrolling */
295
+ .plot-container {
296
+ background-color: #000000 !important;
297
+ border: none !important;
298
+ transition: opacity 0.6s ease-in-out !important;
299
+ flex: 1 1 auto !important;
300
+ min-height: 0 !important;
301
+ overflow-y: auto !important;
302
+ scrollbar-width: thin !important;
303
+ scrollbar-color: #333333 #000000 !important;
304
+ }
305
+
306
+ /* Custom scrollbar for plot container */
307
+ .plot-container::-webkit-scrollbar {
308
+ width: 8px !important;
309
+ background: #000000 !important;
310
+ }
311
+
312
+ .plot-container::-webkit-scrollbar-track {
313
+ background: #000000 !important;
314
+ }
315
+
316
+ .plot-container::-webkit-scrollbar-thumb {
317
+ background-color: #333333 !important;
318
+ border-radius: 4px !important;
319
+ }
320
+
321
+ .plot-container::-webkit-scrollbar-thumb:hover {
322
+ background-color: #555555 !important;
323
+ }
324
+
325
+ /* Gradio plot component styling */
326
+ .gr-plot {
327
+ background-color: #000000 !important;
328
+ transition: opacity 0.6s ease-in-out !important;
329
+ }
330
+
331
+ .gr-plot .gradio-plot {
332
+ background-color: #000000 !important;
333
+ transition: opacity 0.6s ease-in-out !important;
334
+ }
335
+
336
+ .gr-plot img {
337
+ transition: opacity 0.6s ease-in-out !important;
338
+ }
339
+
340
+ /* Target the plot wrapper */
341
+ div[data-testid="plot"] {
342
+ background-color: #000000 !important;
343
+ }
344
+
345
+ /* Target all possible plot containers */
346
+ .plot-container img,
347
+ .gr-plot img,
348
+ .gradio-plot img {
349
+ background-color: #000000 !important;
350
+ }
351
+
352
+ /* Ensure plot area background */
353
+ .gr-plot > div,
354
+ .plot-container > div {
355
+ background-color: #000000 !important;
356
+ }
357
+
358
+ /* Prevent white flash during plot updates */
359
+ .plot-container::before {
360
+ content: "";
361
+ position: absolute;
362
+ top: 0;
363
+ left: 0;
364
+ right: 0;
365
+ bottom: 0;
366
+ background-color: #000000;
367
+ z-index: -1;
368
+ }
369
+
370
+ /* Force all plot elements to have black background */
371
+ .plot-container *,
372
+ .gr-plot *,
373
+ div[data-testid="plot"] * {
374
+ background-color: #000000 !important;
375
+ }
376
+
377
+ /* Override any white backgrounds in matplotlib */
378
+ .plot-container canvas,
379
+ .gr-plot canvas {
380
+ background-color: #000000 !important;
381
+ }
382
+
383
+ /* Text elements */
384
+ h1, h2, h3, p, .markdown {
385
+ color: white !important;
386
+ }
387
+
388
+ /* Sidebar header enhancement */
389
+ .sidebar h1 {
390
+ background: linear-gradient(45deg, #74b9ff, #a29bfe) !important;
391
+ -webkit-background-clip: text !important;
392
+ -webkit-text-fill-color: transparent !important;
393
+ background-clip: text !important;
394
+ text-align: center !important;
395
+ margin-bottom: 15px !important;
396
+ font-size: 28px !important;
397
+ font-weight: 700 !important;
398
+ font-family: monospace !important;
399
+ }
400
+
401
+ /* Sidebar description text */
402
+ .sidebar p {
403
+ text-align: center !important;
404
+ margin-bottom: 20px !important;
405
+ line-height: 1.5 !important;
406
+ font-size: 14px !important;
407
+ font-family: monospace !important;
408
+ }
409
+
410
+ /* CI Links styling */
411
+ .sidebar a {
412
+ color: #74b9ff !important;
413
+ text-decoration: none !important;
414
+ font-weight: 500 !important;
415
+ font-family: monospace !important;
416
+ transition: color 0.3s ease !important;
417
+ }
418
+
419
+ .sidebar a:hover {
420
+ color: #a29bfe !important;
421
+ text-decoration: underline !important;
422
+ }
423
+
424
+ .sidebar strong {
425
+ color: #74b9ff !important;
426
+ font-weight: 600 !important;
427
+ font-family: monospace !important;
428
+ }
429
+
430
+ .sidebar em {
431
+ color: #a29bfe !important;
432
+ font-style: normal !important;
433
+ opacity: 0.9 !important;
434
+ font-family: monospace !important;
435
+ }
436
+
437
+ /* Remove all borders globally */
438
+ * {
439
+ border-color: transparent !important;
440
+ }
441
+
442
+ /* Main content area */
443
+ .main-content {
444
+ background-color: #000000 !important;
445
+ padding: 20px 20px 40px 20px !important;
446
+ margin-left: 300px !important;
447
+ height: 100vh !important;
448
+ overflow-y: auto !important;
449
+ box-sizing: border-box !important;
450
+ display: flex !important;
451
+ flex-direction: column !important;
452
+ }
453
+
454
+ /* Custom scrollbar for main content */
455
+ .main-content {
456
+ scrollbar-width: thin !important;
457
+ scrollbar-color: #333333 #000000 !important;
458
+ }
459
+
460
+ .main-content::-webkit-scrollbar {
461
+ width: 8px !important;
462
+ background: #000000 !important;
463
+ }
464
+
465
+ .main-content::-webkit-scrollbar-track {
466
+ background: #000000 !important;
467
+ }
468
+
469
+ .main-content::-webkit-scrollbar-thumb {
470
+ background-color: #333333 !important;
471
+ border-radius: 4px !important;
472
+ }
473
+
474
+ .main-content::-webkit-scrollbar-thumb:hover {
475
+ background-color: #555555 !important;
476
+ }
477
+
478
+ /* Failed tests display - seamless appearance with constrained height */
479
+ .failed-tests textarea {
480
+ background-color: #000000 !important;
481
+ color: #FFFFFF !important;
482
+ font-family: monospace !important;
483
+ font-size: 14px !important;
484
+ border: none !important;
485
+ padding: 10px !important;
486
+ outline: none !important;
487
+ line-height: 1.4 !important;
488
+ height: 180px !important;
489
+ max-height: 180px !important;
490
+ min-height: 180px !important;
491
+ overflow-y: auto !important;
492
+ resize: none !important;
493
+ scrollbar-width: thin !important;
494
+ scrollbar-color: #333333 #000000 !important;
495
+ scroll-behavior: auto;
496
+ transition: opacity 0.5s ease-in-out !important;
497
+ }
498
+
499
+ /* WebKit scrollbar styling for failed tests */
500
+ .failed-tests textarea::-webkit-scrollbar {
501
+ width: 8px !important;
502
+ }
503
+
504
+ .failed-tests textarea::-webkit-scrollbar-track {
505
+ background: #000000 !important;
506
+ }
507
+
508
+ .failed-tests textarea::-webkit-scrollbar-thumb {
509
+ background-color: #333333 !important;
510
+ border-radius: 4px !important;
511
+ }
512
+
513
+ .failed-tests textarea::-webkit-scrollbar-thumb:hover {
514
+ background-color: #555555 !important;
515
+ }
516
+
517
+ /* Prevent white flash in text boxes during updates */
518
+ .failed-tests::before {
519
+ content: "";
520
+ position: absolute;
521
+ top: 0;
522
+ left: 0;
523
+ right: 0;
524
+ bottom: 0;
525
+ background-color: #000000;
526
+ z-index: -1;
527
+ }
528
+
529
+ .failed-tests {
530
+ background-color: #000000 !important;
531
+ height: 200px !important;
532
+ max-height: 200px !important;
533
+ min-height: 200px !important;
534
+ position: relative;
535
+ transition: opacity 0.5s ease-in-out !important;
536
+ flex-shrink: 0 !important;
537
+ }
538
+
539
+ .failed-tests .gr-textbox {
540
+ background-color: #000000 !important;
541
+ border: none !important;
542
+ height: 180px !important;
543
+ max-height: 180px !important;
544
+ min-height: 180px !important;
545
+ transition: opacity 0.5s ease-in-out !important;
546
+ }
547
+
548
+ /* Force all textbox elements to have black background */
549
+ .failed-tests *,
550
+ .failed-tests .gr-textbox *,
551
+ .failed-tests textarea * {
552
+ background-color: #000000 !important;
553
+ }
554
+
555
+ /* Summary display styling */
556
+ .summary-display textarea {
557
+ background-color: #000000 !important;
558
+ color: #FFFFFF !important;
559
+ font-family: monospace !important;
560
+ font-size: 24px !important;
561
+ border: none !important;
562
+ padding: 20px !important;
563
+ outline: none !important;
564
+ line-height: 2 !important;
565
+ text-align: right !important;
566
+ resize: none !important;
567
+ }
568
+
569
+ .summary-display {
570
+ background-color: #000000 !important;
571
+ }
572
+
573
+ /* Detail view layout */
574
+ .detail-view {
575
+ display: flex !important;
576
+ flex-direction: column !important;
577
+ height: 100% !important;
578
+ min-height: 0 !important;
579
+ }
580
+
581
+ /* JavaScript to reset scroll position */
582
+ .scroll-reset {
583
+ animation: resetScroll 0.1s ease;
584
+ }
585
+
586
+ @keyframes resetScroll {
587
+ 0% { scroll-behavior: auto; }
588
+ 100% { scroll-behavior: auto; }
589
+ }