acmc commited on
Commit
b32600d
·
verified ·
1 Parent(s): 20fab28

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +525 -92
streamlit_app.py CHANGED
@@ -13,6 +13,8 @@ from io import StringIO
13
  import zipfile
14
  import tempfile
15
  import shutil
 
 
16
 
17
  # Set page config
18
  st.set_page_config(
@@ -62,16 +64,185 @@ class AttentionResultsExplorer:
62
  if not self.cache_dir.exists():
63
  self.cache_dir.mkdir(parents=True, exist_ok=True)
64
 
65
- # Download and cache data if needed
66
- if not self._cache_exists() or not use_cache:
67
- self._download_repository()
68
-
69
- self.languages = self._get_available_languages()
70
  self.relation_types = None
71
 
72
- def _cache_exists(self):
73
- """Check if cached data exists"""
74
- return (self.cache_dir / "results_en").exists()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  def _download_repository(self):
77
  """Download repository data from GitHub"""
@@ -105,6 +276,19 @@ class AttentionResultsExplorer:
105
  st.error("Please check the repository URL and your internet connection.")
106
  raise
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def _download_directory(self, dir_name, path=""):
109
  """Recursively download a directory from GitHub"""
110
  url = f"https://api.github.com/repos/{self.github_repo}/contents/{path}{dir_name}"
@@ -129,9 +313,10 @@ class AttentionResultsExplorer:
129
  def _download_file(self, file_info, local_dir):
130
  """Download a single file from GitHub"""
131
  try:
132
- # Download file content
133
- response = requests.get(file_info['download_url'])
134
- response.raise_for_status()
 
135
 
136
  # Save to local cache
137
  local_file = local_dir / file_info['name']
@@ -139,38 +324,61 @@ class AttentionResultsExplorer:
139
  # Handle different file types
140
  if file_info['name'].endswith(('.csv', '.json')):
141
  with open(local_file, 'w', encoding='utf-8') as f:
142
- f.write(response.text)
143
  else: # Binary files like PDFs
144
  with open(local_file, 'wb') as f:
145
- f.write(response.content)
146
 
147
  except Exception as e:
148
  st.warning(f"Could not download file {file_info['name']}: {str(e)}")
149
 
150
  def _get_available_languages(self):
151
  """Get all available language directories"""
152
- if not self.base_path.exists():
153
- return []
154
- result_dirs = [d.name for d in self.base_path.iterdir()
155
- if d.is_dir() and d.name.startswith("results_")]
156
- languages = [d.replace("results_", "") for d in result_dirs]
157
- return sorted(languages)
158
 
159
  def _get_experimental_configs(self, language):
160
- """Get all experimental configurations for a language"""
 
 
 
 
 
 
 
 
 
 
 
 
161
  lang_dir = self.base_path / f"results_{language}"
162
- if not lang_dir.exists():
163
- return []
164
- configs = [d.name for d in lang_dir.iterdir() if d.is_dir()]
165
- return sorted(configs)
 
 
 
 
166
 
167
  def _get_models(self, language, config):
168
- """Get all models for a language and configuration"""
 
 
 
 
 
 
 
 
 
 
 
 
169
  config_dir = self.base_path / f"results_{language}" / config
170
- if not config_dir.exists():
171
- return []
172
- models = [d.name for d in config_dir.iterdir() if d.is_dir()]
173
- return sorted(models)
174
 
175
  def _parse_config_name(self, config_name):
176
  """Parse configuration name into readable format"""
@@ -184,6 +392,9 @@ class AttentionResultsExplorer:
184
 
185
  def _load_metadata(self, language, config, model):
186
  """Load metadata for a specific combination"""
 
 
 
187
  metadata_path = self.base_path / f"results_{language}" / config / model / "metadata" / "metadata.json"
188
  if metadata_path.exists():
189
  with open(metadata_path, 'r') as f:
@@ -192,6 +403,9 @@ class AttentionResultsExplorer:
192
 
193
  def _load_uas_scores(self, language, config, model):
194
  """Load UAS scores data"""
 
 
 
195
  uas_dir = self.base_path / f"results_{language}" / config / model / "uas_scores"
196
  if not uas_dir.exists():
197
  return {}
@@ -200,28 +414,33 @@ class AttentionResultsExplorer:
200
  csv_files = list(uas_dir.glob("uas_*.csv"))
201
 
202
  if csv_files:
203
- progress_bar = st.progress(0)
204
- status_text = st.empty()
205
-
206
- for i, csv_file in enumerate(csv_files):
207
- relation = csv_file.stem.replace("uas_", "")
208
- status_text.text(f"Loading UAS data: {relation}")
209
 
210
- try:
211
- df = pd.read_csv(csv_file, index_col=0)
212
- uas_data[relation] = df
213
- except Exception as e:
214
- st.warning(f"Could not load {csv_file.name}: {e}")
 
 
 
 
 
 
 
215
 
216
- progress_bar.progress((i + 1) / len(csv_files))
217
-
218
- progress_bar.empty()
219
- status_text.empty()
220
 
221
  return uas_data
222
 
223
  def _load_head_matching(self, language, config, model):
224
  """Load head matching data"""
 
 
 
225
  heads_dir = self.base_path / f"results_{language}" / config / model / "number_of_heads_matching"
226
  if not heads_dir.exists():
227
  return {}
@@ -230,28 +449,33 @@ class AttentionResultsExplorer:
230
  csv_files = list(heads_dir.glob("heads_matching_*.csv"))
231
 
232
  if csv_files:
233
- progress_bar = st.progress(0)
234
- status_text = st.empty()
235
-
236
- for i, csv_file in enumerate(csv_files):
237
- relation = csv_file.stem.replace("heads_matching_", "").replace(f"_{model}", "")
238
- status_text.text(f"Loading head matching data: {relation}")
239
 
240
- try:
241
- df = pd.read_csv(csv_file, index_col=0)
242
- heads_data[relation] = df
243
- except Exception as e:
244
- st.warning(f"Could not load {csv_file.name}: {e}")
 
 
 
 
 
 
 
245
 
246
- progress_bar.progress((i + 1) / len(csv_files))
247
-
248
- progress_bar.empty()
249
- status_text.empty()
250
 
251
  return heads_data
252
 
253
  def _load_variability(self, language, config, model):
254
  """Load variability data"""
 
 
 
255
  var_path = self.base_path / f"results_{language}" / config / model / "variability" / "variability_list.csv"
256
  if var_path.exists():
257
  try:
@@ -262,11 +486,100 @@ class AttentionResultsExplorer:
262
 
263
  def _get_available_figures(self, language, config, model):
264
  """Get all available figure files"""
 
 
 
265
  figures_dir = self.base_path / f"results_{language}" / config / model / "figures"
266
  if not figures_dir.exists():
267
  return []
268
  return list(figures_dir.glob("*.pdf"))
269
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  def main():
271
  # Title
272
  st.markdown('<div class="main-header">🔍 Attention Analysis Results Explorer</div>', unsafe_allow_html=True)
@@ -281,66 +594,150 @@ def main():
281
  use_cache = st.sidebar.checkbox("Use cached data", value=True,
282
  help="Use previously downloaded data if available")
283
 
284
- if st.sidebar.button("🔄 Refresh Data", help="Download fresh data from GitHub"):
285
  # Clear cache and re-download
286
  cache_dir = Path(tempfile.gettempdir()) / "attention_results_cache"
287
  if cache_dir.exists():
288
  shutil.rmtree(cache_dir)
 
289
  st.rerun()
290
 
291
  # Show cache status
292
  cache_dir = Path(tempfile.gettempdir()) / "attention_results_cache"
293
  if cache_dir.exists():
294
- st.sidebar.success("✅ Data cached locally")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  else:
296
- st.sidebar.info("📥 Will download data from GitHub")
297
 
298
  st.sidebar.markdown("---")
299
 
300
  # Initialize explorer with error handling
301
  try:
302
- explorer = AttentionResultsExplorer(use_cache=use_cache)
 
303
  except Exception as e:
304
  st.error(f"❌ Failed to initialize data explorer: {str(e)}")
305
  st.error("Please check your internet connection and try again.")
 
 
 
 
 
 
 
 
306
  return
307
 
308
  # Check if any languages are available
309
- if not explorer.languages:
310
  st.error("❌ No result data found. Please check the GitHub repository.")
 
 
 
311
  return
312
 
 
 
 
313
  # Language selection
314
  selected_language = st.sidebar.selectbox(
315
  "Select Language",
316
- options=explorer.languages,
317
  help="Choose the language dataset to explore"
318
  )
319
 
320
- # Get configurations for selected language
321
- configs = explorer._get_experimental_configs(selected_language)
322
- if not configs:
323
- st.error(f"No configurations found for language: {selected_language}")
324
- return
325
 
326
- # Configuration selection
327
- selected_config = st.sidebar.selectbox(
328
- "Select Experimental Configuration",
329
- options=configs,
330
- help="Choose the experimental configuration"
331
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
- # Parse and display configuration details
334
- config_details = explorer._parse_config_name(selected_config)
335
- st.sidebar.markdown("**Configuration Details:**")
336
- for key, value in config_details.items():
337
- st.sidebar.markdown(f"- **{key}**: {value}")
 
 
 
 
 
 
 
 
 
 
 
338
 
339
  # Get models for selected language and config
 
 
 
 
 
340
  models = explorer._get_models(selected_language, selected_config)
341
  if not models:
342
- st.error(f"No models found for {selected_language}/{selected_config}")
343
- return
 
344
 
345
  # Model selection
346
  selected_model = st.sidebar.selectbox(
@@ -362,9 +759,35 @@ def main():
362
  with tab1:
363
  st.markdown('<div class="section-header">Experiment Overview</div>', unsafe_allow_html=True)
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  # Load metadata
366
  metadata = explorer._load_metadata(selected_language, selected_config, selected_model)
367
  if metadata:
 
368
  col1, col2, col3, col4 = st.columns(4)
369
  with col1:
370
  st.metric("Total Samples", metadata.get('total_number', 'N/A'))
@@ -377,21 +800,25 @@ def main():
377
  metadata.get('total_number', 1)) * 100 if metadata.get('total_number') else 0
378
  st.metric("Success Rate", f"{success_rate:.1f}%")
379
 
380
- st.markdown("**Random Seed:**", metadata.get('random_seed', 'N/A'))
 
381
 
382
  if metadata.get('errored_phrases'):
383
- st.markdown("**Errored Phrase IDs:**")
384
- st.write(metadata['errored_phrases'])
385
  else:
386
  st.warning("No metadata available for this configuration.")
387
 
388
  # Quick stats about available data
389
- st.markdown('<div class="section-header">Available Data</div>', unsafe_allow_html=True)
 
390
 
391
- uas_data = explorer._load_uas_scores(selected_language, selected_config, selected_model)
392
- heads_data = explorer._load_head_matching(selected_language, selected_config, selected_model)
393
- variability_data = explorer._load_variability(selected_language, selected_config, selected_model)
394
- figures = explorer._get_available_figures(selected_language, selected_config, selected_model)
 
 
395
 
396
  col1, col2, col3, col4 = st.columns(4)
397
  with col1:
@@ -402,6 +829,12 @@ def main():
402
  st.metric("Variability Data", "✓" if variability_data is not None else "✗")
403
  with col4:
404
  st.metric("Figure Files", len(figures))
 
 
 
 
 
 
405
 
406
  # Tab 2: UAS Scores
407
  with tab2:
 
13
  import zipfile
14
  import tempfile
15
  import shutil
16
+ import time
17
+ from datetime import datetime, timezone
18
 
19
  # Set page config
20
  st.set_page_config(
 
64
  if not self.cache_dir.exists():
65
  self.cache_dir.mkdir(parents=True, exist_ok=True)
66
 
67
+ # Get available languages from GitHub without downloading
68
+ self.available_languages = self._get_available_languages_from_github()
 
 
 
69
  self.relation_types = None
70
 
71
+ def _get_available_languages_from_github(self):
72
+ """Get available languages from GitHub API without downloading"""
73
+ api_url = f"https://api.github.com/repos/{self.github_repo}/contents"
74
+
75
+ response = self._make_github_request(api_url, "available languages")
76
+ if response is None:
77
+ # Rate limit hit or other error, fallback to local cache
78
+ return self._get_available_languages_local()
79
+
80
+ try:
81
+ contents = response.json()
82
+ result_dirs = [item['name'] for item in contents
83
+ if item['type'] == 'dir' and item['name'].startswith('results_')]
84
+
85
+ languages = [d.replace("results_", "") for d in result_dirs]
86
+ return sorted(languages)
87
+
88
+ except Exception as e:
89
+ st.warning(f"Could not parse language list from GitHub: {str(e)}")
90
+ # Fallback to local cache if available
91
+ return self._get_available_languages_local()
92
+
93
+ def _get_available_languages_local(self):
94
+ """Get available languages from local cache"""
95
+ if not self.base_path.exists():
96
+ return []
97
+ result_dirs = [d.name for d in self.base_path.iterdir()
98
+ if d.is_dir() and d.name.startswith("results_")]
99
+ languages = [d.replace("results_", "") for d in result_dirs]
100
+ return sorted(languages)
101
+
102
+ def _ensure_specific_data_downloaded(self, language, config, model):
103
+ """Download specific files for a language/config/model combination if not cached"""
104
+ base_path = f"results_{language}/{config}/{model}"
105
+ local_path = self.base_path / f"results_{language}" / config / model
106
+
107
+ # Check if we already have this specific combination cached
108
+ if local_path.exists() and self.use_cache:
109
+ # Quick check if essential files exist
110
+ metadata_path = local_path / "metadata" / "metadata.json"
111
+ if metadata_path.exists():
112
+ return # Already have the data
113
+
114
+ with st.spinner(f"📥 Downloading data for {language.upper()}/{config}/{model}..."):
115
+ try:
116
+ self._download_specific_model_data(language, config, model)
117
+ st.success(f"✅ Downloaded {language.upper()}/{model} data!")
118
+ except Exception as e:
119
+ st.error(f"❌ Failed to download specific data: {str(e)}")
120
+ raise
121
+
122
+ def _download_specific_model_data(self, language, config, model):
123
+ """Download only the specific model data needed"""
124
+ base_remote_path = f"results_{language}/{config}/{model}"
125
+
126
+ # List of essential directories to download for a model
127
+ essential_dirs = ["metadata", "uas_scores", "number_of_heads_matching", "variability", "figures"]
128
+
129
+ for dir_name in essential_dirs:
130
+ remote_path = f"{base_remote_path}/{dir_name}"
131
+ try:
132
+ self._download_directory_targeted(dir_name, remote_path, language, config, model)
133
+ except Exception as e:
134
+ st.warning(f"Could not download {dir_name} for {model}: {str(e)}")
135
+
136
+ def _download_directory_targeted(self, dir_name, remote_path, language, config, model):
137
+ """Download a specific directory for a model"""
138
+ api_url = f"https://api.github.com/repos/{self.github_repo}/contents/{remote_path}"
139
+
140
+ response = self._make_github_request(api_url, f"directory {dir_name}", silent_404=True)
141
+ if response is None:
142
+ return # Rate limit, 404, or other error
143
+
144
+ try:
145
+ contents = response.json()
146
+
147
+ # Create local directory
148
+ local_dir = self.base_path / f"results_{language}" / config / model / dir_name
149
+ local_dir.mkdir(parents=True, exist_ok=True)
150
+
151
+ # Download all files in this directory
152
+ for item in contents:
153
+ if item['type'] == 'file':
154
+ self._download_file(item, local_dir)
155
+
156
+ except Exception as e:
157
+ st.warning(f"Could not download directory {dir_name}: {str(e)}")
158
+
159
+ def _get_available_configs_from_github(self, language):
160
+ """Get available configurations for a language from GitHub"""
161
+ api_url = f"https://api.github.com/repos/{self.github_repo}/contents/results_{language}"
162
+
163
+ response = self._make_github_request(api_url, f"configurations for {language}")
164
+ if response is None:
165
+ return []
166
+
167
+ try:
168
+ contents = response.json()
169
+ configs = [item['name'] for item in contents if item['type'] == 'dir']
170
+ return sorted(configs)
171
+
172
+ except Exception as e:
173
+ st.warning(f"Could not parse configurations for {language}: {str(e)}")
174
+ return []
175
+
176
+ def _discover_config_parameters(self, language=None):
177
+ """Dynamically discover configuration parameters from available configs
178
+
179
+ For performance optimization, we only inspect the first language since
180
+ configurations are consistent across all languages and models.
181
+ """
182
+ try:
183
+ # Use first available language if none specified (optimization)
184
+ if language is None:
185
+ if not self.available_languages:
186
+ return {}
187
+ language = self.available_languages[0]
188
+
189
+ available_configs = self._get_experimental_configs(language)
190
+ if not available_configs:
191
+ return {}
192
+
193
+ # Parse all configurations to extract unique parameters
194
+ all_params = set()
195
+ param_values = {}
196
+
197
+ for config in available_configs:
198
+ params = self._parse_config_params(config)
199
+ for param, value in params.items():
200
+ all_params.add(param)
201
+ if param not in param_values:
202
+ param_values[param] = set()
203
+ param_values[param].add(value)
204
+
205
+ # Convert sets to sorted lists for consistent UI
206
+ return {param: sorted(list(values)) for param, values in param_values.items()}
207
+
208
+ except Exception as e:
209
+ st.warning(f"Could not discover configuration parameters: {str(e)}")
210
+ return {}
211
+
212
+ def _build_config_from_params(self, param_dict):
213
+ """Build configuration string from parameter dictionary"""
214
+ config_parts = []
215
+ for param, value in sorted(param_dict.items()):
216
+ config_parts.append(f"{param}_{value}")
217
+ return "+".join(config_parts)
218
+
219
+ def _find_best_matching_config(self, language, target_params):
220
+ """Find the configuration that best matches the target parameters"""
221
+ available_configs = self._get_experimental_configs(language)
222
+
223
+ best_match = None
224
+ best_score = -1
225
+
226
+ for config in available_configs:
227
+ config_params = self._parse_config_params(config)
228
+
229
+ # Calculate match score
230
+ score = 0
231
+ total_params = len(target_params)
232
+
233
+ for param, target_value in target_params.items():
234
+ if param in config_params and config_params[param] == target_value:
235
+ score += 1
236
+
237
+ # Prefer configs with exact parameter count
238
+ if len(config_params) == total_params:
239
+ score += 0.5
240
+
241
+ if score > best_score:
242
+ best_score = score
243
+ best_match = config
244
+
245
+ return best_match, best_score == len(target_params)
246
 
247
  def _download_repository(self):
248
  """Download repository data from GitHub"""
 
276
  st.error("Please check the repository URL and your internet connection.")
277
  raise
278
 
279
+ def _parse_config_params(self, config_name):
280
+ """Parse configuration parameters into a dictionary"""
281
+ parts = config_name.split('+')
282
+ params = {}
283
+ for part in parts:
284
+ if '_' in part:
285
+ key_parts = part.split('_')
286
+ if len(key_parts) >= 2:
287
+ key = '_'.join(key_parts[:-1])
288
+ value = key_parts[-1]
289
+ params[key] = value == 'True'
290
+ return params
291
+
292
  def _download_directory(self, dir_name, path=""):
293
  """Recursively download a directory from GitHub"""
294
  url = f"https://api.github.com/repos/{self.github_repo}/contents/{path}{dir_name}"
 
313
  def _download_file(self, file_info, local_dir):
314
  """Download a single file from GitHub"""
315
  try:
316
+ # Use the rate limit handling for file downloads too
317
+ file_response = self._make_github_request(file_info['download_url'], f"file {file_info['name']}")
318
+ if file_response is None:
319
+ return # Rate limit or other error
320
 
321
  # Save to local cache
322
  local_file = local_dir / file_info['name']
 
324
  # Handle different file types
325
  if file_info['name'].endswith(('.csv', '.json')):
326
  with open(local_file, 'w', encoding='utf-8') as f:
327
+ f.write(file_response.text)
328
  else: # Binary files like PDFs
329
  with open(local_file, 'wb') as f:
330
+ f.write(file_response.content)
331
 
332
  except Exception as e:
333
  st.warning(f"Could not download file {file_info['name']}: {str(e)}")
334
 
335
  def _get_available_languages(self):
336
  """Get all available language directories"""
337
+ return self.available_languages
 
 
 
 
 
338
 
339
  def _get_experimental_configs(self, language):
340
+ """Get all experimental configurations for a language from GitHub API"""
341
+ api_url = f"https://api.github.com/repos/{self.github_repo}/contents/results_{language}"
342
+ response = self._make_github_request(api_url, f"experimental configs for {language}")
343
+
344
+ if response is not None:
345
+ try:
346
+ contents = response.json()
347
+ configs = [item['name'] for item in contents if item['type'] == 'dir']
348
+ return sorted(configs)
349
+ except Exception as e:
350
+ st.warning(f"Could not parse experimental configs for {language}: {str(e)}")
351
+
352
+ # Fallback to local cache if available
353
  lang_dir = self.base_path / f"results_{language}"
354
+ if lang_dir.exists():
355
+ configs = [d.name for d in lang_dir.iterdir() if d.is_dir()]
356
+ return sorted(configs)
357
+ return []
358
+
359
+ def _find_matching_config(self, language, target_params):
360
+ """Find the first matching configuration from target parameters"""
361
+ return self._find_best_matching_config(language, target_params)
362
 
363
  def _get_models(self, language, config):
364
+ """Get all models for a language and configuration from GitHub API"""
365
+ api_url = f"https://api.github.com/repos/{self.github_repo}/contents/results_{language}/{config}"
366
+ response = self._make_github_request(api_url, f"models for {language}/{config}")
367
+
368
+ if response is not None:
369
+ try:
370
+ contents = response.json()
371
+ models = [item['name'] for item in contents if item['type'] == 'dir']
372
+ return sorted(models)
373
+ except Exception as e:
374
+ st.warning(f"Could not parse models for {language}/{config}: {str(e)}")
375
+
376
+ # Fallback to local cache if available
377
  config_dir = self.base_path / f"results_{language}" / config
378
+ if config_dir.exists():
379
+ models = [d.name for d in config_dir.iterdir() if d.is_dir()]
380
+ return sorted(models)
381
+ return []
382
 
383
  def _parse_config_name(self, config_name):
384
  """Parse configuration name into readable format"""
 
392
 
393
  def _load_metadata(self, language, config, model):
394
  """Load metadata for a specific combination"""
395
+ # Ensure we have the specific data downloaded
396
+ self._ensure_specific_data_downloaded(language, config, model)
397
+
398
  metadata_path = self.base_path / f"results_{language}" / config / model / "metadata" / "metadata.json"
399
  if metadata_path.exists():
400
  with open(metadata_path, 'r') as f:
 
403
 
404
  def _load_uas_scores(self, language, config, model):
405
  """Load UAS scores data"""
406
+ # Ensure we have the specific data downloaded
407
+ self._ensure_specific_data_downloaded(language, config, model)
408
+
409
  uas_dir = self.base_path / f"results_{language}" / config / model / "uas_scores"
410
  if not uas_dir.exists():
411
  return {}
 
414
  csv_files = list(uas_dir.glob("uas_*.csv"))
415
 
416
  if csv_files:
417
+ with st.spinner("Loading UAS scores data..."):
418
+ progress_bar = st.progress(0)
419
+ status_text = st.empty()
 
 
 
420
 
421
+ for i, csv_file in enumerate(csv_files):
422
+ relation = csv_file.stem.replace("uas_", "")
423
+ status_text.text(f"Loading UAS data: {relation}")
424
+
425
+ try:
426
+ df = pd.read_csv(csv_file, index_col=0)
427
+ uas_data[relation] = df
428
+ except Exception as e:
429
+ st.warning(f"Could not load {csv_file.name}: {e}")
430
+
431
+ progress_bar.progress((i + 1) / len(csv_files))
432
+ time.sleep(0.01) # Small delay for smoother progress
433
 
434
+ progress_bar.empty()
435
+ status_text.empty()
 
 
436
 
437
  return uas_data
438
 
439
  def _load_head_matching(self, language, config, model):
440
  """Load head matching data"""
441
+ # Ensure we have the specific data downloaded
442
+ self._ensure_specific_data_downloaded(language, config, model)
443
+
444
  heads_dir = self.base_path / f"results_{language}" / config / model / "number_of_heads_matching"
445
  if not heads_dir.exists():
446
  return {}
 
449
  csv_files = list(heads_dir.glob("heads_matching_*.csv"))
450
 
451
  if csv_files:
452
+ with st.spinner("Loading head matching data..."):
453
+ progress_bar = st.progress(0)
454
+ status_text = st.empty()
 
 
 
455
 
456
+ for i, csv_file in enumerate(csv_files):
457
+ relation = csv_file.stem.replace("heads_matching_", "").replace(f"_{model}", "")
458
+ status_text.text(f"Loading head matching data: {relation}")
459
+
460
+ try:
461
+ df = pd.read_csv(csv_file, index_col=0)
462
+ heads_data[relation] = df
463
+ except Exception as e:
464
+ st.warning(f"Could not load {csv_file.name}: {e}")
465
+
466
+ progress_bar.progress((i + 1) / len(csv_files))
467
+ time.sleep(0.01) # Small delay for smoother progress
468
 
469
+ progress_bar.empty()
470
+ status_text.empty()
 
 
471
 
472
  return heads_data
473
 
474
  def _load_variability(self, language, config, model):
475
  """Load variability data"""
476
+ # Ensure we have the specific data downloaded
477
+ self._ensure_specific_data_downloaded(language, config, model)
478
+
479
  var_path = self.base_path / f"results_{language}" / config / model / "variability" / "variability_list.csv"
480
  if var_path.exists():
481
  try:
 
486
 
487
  def _get_available_figures(self, language, config, model):
488
  """Get all available figure files"""
489
+ # Ensure we have the specific data downloaded
490
+ self._ensure_specific_data_downloaded(language, config, model)
491
+
492
  figures_dir = self.base_path / f"results_{language}" / config / model / "figures"
493
  if not figures_dir.exists():
494
  return []
495
  return list(figures_dir.glob("*.pdf"))
496
+
497
+ def _handle_rate_limit_error(self, response):
498
+ """Handle GitHub API rate limit errors with detailed user feedback"""
499
+ if response.status_code in (403, 429):
500
+ # Check if it's a rate limit error
501
+ if 'rate limit' in response.text.lower() or 'api rate limit' in response.text.lower():
502
+ # Extract rate limit information from headers
503
+ remaining = response.headers.get('x-ratelimit-remaining', 'unknown')
504
+ reset_timestamp = response.headers.get('x-ratelimit-reset')
505
+ limit = response.headers.get('x-ratelimit-limit', 'unknown')
506
+
507
+ # Calculate reset time
508
+ reset_time_str = "unknown"
509
+ if reset_timestamp:
510
+ try:
511
+ reset_time = datetime.fromtimestamp(int(reset_timestamp), tz=timezone.utc)
512
+ reset_time_str = reset_time.strftime("%Y-%m-%d %H:%M:%S UTC")
513
+
514
+ # Calculate time until reset
515
+ now = datetime.now(timezone.utc)
516
+ time_until_reset = reset_time - now
517
+ minutes_until_reset = int(time_until_reset.total_seconds() / 60)
518
+
519
+ if minutes_until_reset > 0:
520
+ reset_time_str += f" (in {minutes_until_reset} minutes)"
521
+ except (ValueError, TypeError):
522
+ pass
523
+
524
+ # Display comprehensive rate limit information
525
+ st.error("🚫 **GitHub API Rate Limit Exceeded**")
526
+
527
+ with st.expander("📊 Rate Limit Details", expanded=True):
528
+ col1, col2 = st.columns(2)
529
+
530
+ with col1:
531
+ st.metric("Requests Remaining", remaining)
532
+ st.metric("Rate Limit", limit)
533
+
534
+ with col2:
535
+ st.metric("Reset Time", reset_time_str)
536
+ if reset_timestamp:
537
+ try:
538
+ reset_time = datetime.fromtimestamp(int(reset_timestamp), tz=timezone.utc)
539
+ now = datetime.now(timezone.utc)
540
+ time_until_reset = reset_time - now
541
+ if time_until_reset.total_seconds() > 0:
542
+ st.metric("Time Until Reset", f"{int(time_until_reset.total_seconds() / 60)} minutes")
543
+ except (ValueError, TypeError):
544
+ pass
545
+
546
+ return True # Indicates rate limit error was handled
547
+
548
+ return False # Not a rate limit error
549
+
550
+ def _make_github_request(self, url, description="GitHub API request", silent_404=False):
551
+ """Make a GitHub API request with rate limit handling"""
552
+ try:
553
+ # Add GitHub token if available
554
+ headers = {}
555
+ github_token = os.environ.get('GITHUB_TOKEN')
556
+ if github_token:
557
+ headers['Authorization'] = f'token {github_token}'
558
+
559
+ response = requests.get(url, headers=headers)
560
+
561
+ # Check for rate limit before raising for status
562
+ if self._handle_rate_limit_error(response):
563
+ return None # Rate limit handled, return None
564
+
565
+ # Handle 404 errors silently if requested (for optional directories)
566
+ if response.status_code == 404 and silent_404:
567
+ return None
568
+
569
+ response.raise_for_status()
570
+ return response
571
+
572
+ except requests.exceptions.RequestException as e:
573
+ if hasattr(e, 'response') and e.response is not None:
574
+ # Handle 404 silently if requested
575
+ if e.response.status_code == 404 and silent_404:
576
+ return None
577
+
578
+ if not self._handle_rate_limit_error(e.response):
579
+ st.warning(f"Request failed for {description}: {str(e)}")
580
+ else:
581
+ st.warning(f"Network error for {description}: {str(e)}")
582
+ return None
583
  def main():
584
  # Title
585
  st.markdown('<div class="main-header">🔍 Attention Analysis Results Explorer</div>', unsafe_allow_html=True)
 
594
  use_cache = st.sidebar.checkbox("Use cached data", value=True,
595
  help="Use previously downloaded data if available")
596
 
597
+ if st.sidebar.button("🔄 Clear Cache", help="Clear all cached data"):
598
  # Clear cache and re-download
599
  cache_dir = Path(tempfile.gettempdir()) / "attention_results_cache"
600
  if cache_dir.exists():
601
  shutil.rmtree(cache_dir)
602
+ st.sidebar.success("✅ Cache cleared!")
603
  st.rerun()
604
 
605
  # Show cache status
606
  cache_dir = Path(tempfile.gettempdir()) / "attention_results_cache"
607
  if cache_dir.exists():
608
+ # Get more detailed cache information
609
+ cached_items = []
610
+ for lang_dir in cache_dir.iterdir():
611
+ if lang_dir.is_dir() and lang_dir.name.startswith("results_"):
612
+ lang = lang_dir.name.replace("results_", "")
613
+ configs = [d.name for d in lang_dir.iterdir() if d.is_dir()]
614
+ if configs:
615
+ models_count = 0
616
+ for config_dir in lang_dir.iterdir():
617
+ if config_dir.is_dir():
618
+ models = [d.name for d in config_dir.iterdir() if d.is_dir()]
619
+ models_count += len(models)
620
+ cached_items.append(f"{lang} ({len(configs)} configs, {models_count} models)")
621
+
622
+ if cached_items:
623
+ st.sidebar.success("✅ **Cached Data:**")
624
+ for item in cached_items[:3]: # Show first 3
625
+ st.sidebar.text(f"• {item}")
626
+ if len(cached_items) > 3:
627
+ st.sidebar.text(f"... and {len(cached_items) - 3} more")
628
+ else:
629
+ st.sidebar.info("📥 Cache exists but empty")
630
  else:
631
+ st.sidebar.info("📥 No cached data")
632
 
633
  st.sidebar.markdown("---")
634
 
635
  # Initialize explorer with error handling
636
  try:
637
+ with st.spinner("🔄 Initializing attention analysis explorer..."):
638
+ explorer = AttentionResultsExplorer(use_cache=use_cache)
639
  except Exception as e:
640
  st.error(f"❌ Failed to initialize data explorer: {str(e)}")
641
  st.error("Please check your internet connection and try again.")
642
+
643
+ # Show some debugging information
644
+ with st.expander("🔍 Debugging Information"):
645
+ st.code(f"Error details: {str(e)}")
646
+ st.markdown("**Possible solutions:**")
647
+ st.markdown("- Check your internet connection")
648
+ st.markdown("- Try clearing the cache")
649
+ st.markdown("- Wait a moment and refresh the page")
650
  return
651
 
652
  # Check if any languages are available
653
+ if not explorer.available_languages:
654
  st.error("❌ No result data found. Please check the GitHub repository.")
655
+ st.markdown("**Expected repository structure:**")
656
+ st.markdown("- Repository should contain `results_*` directories")
657
+ st.markdown("- Each directory should contain experimental configurations")
658
  return
659
 
660
+ # Show success message
661
+ st.sidebar.success(f"✅ Found {len(explorer.available_languages)} languages: {', '.join(explorer.available_languages)}")
662
+
663
  # Language selection
664
  selected_language = st.sidebar.selectbox(
665
  "Select Language",
666
+ options=explorer.available_languages,
667
  help="Choose the language dataset to explore"
668
  )
669
 
670
+ st.sidebar.markdown("---")
 
 
 
 
671
 
672
+ # Configuration selection with dynamic discovery
673
+ st.sidebar.markdown("### ⚙️ Experimental Configuration")
674
+
675
+ # Discover available configuration parameters (optimized to use first language only)
676
+ with st.spinner("🔍 Discovering configuration options..."):
677
+ config_parameters = explorer._discover_config_parameters()
678
+
679
+ if not config_parameters:
680
+ st.sidebar.error("❌ Could not discover configuration parameters")
681
+ st.stop()
682
+
683
+ # Show discovered parameters
684
+ st.sidebar.success(f"✅ Found {len(config_parameters)} configuration parameters")
685
+ st.sidebar.info("💡 Configuration options are consistent across all languages - using optimized discovery")
686
+
687
+ # Create UI elements for each discovered parameter
688
+ selected_params = {}
689
+
690
+ for param_name, possible_values in config_parameters.items():
691
+ # Clean up parameter name for display
692
+ display_name = param_name.replace('_', ' ').title()
693
+
694
+ if len(possible_values) == 2 and set(possible_values) == {True, False}:
695
+ # Boolean parameter - use checkbox
696
+ default_value = False # Default to False for boolean params
697
+ selected_params[param_name] = st.sidebar.checkbox(
698
+ display_name,
699
+ value=default_value,
700
+ help=f"Parameter: {param_name}"
701
+ )
702
+ else:
703
+ # Multi-value parameter - use selectbox
704
+ selected_params[param_name] = st.sidebar.selectbox(
705
+ display_name,
706
+ options=possible_values,
707
+ help=f"Parameter: {param_name}"
708
+ )
709
+
710
+ # Find the best matching configuration
711
+ selected_config, config_exists = explorer._find_matching_config(selected_language, selected_params)
712
 
713
+ # Show current configuration
714
+ st.sidebar.markdown("**Selected Parameters:**")
715
+ for param, value in selected_params.items():
716
+ emoji = "✅" if value else "❌" if isinstance(value, bool) else "🔹"
717
+ st.sidebar.text(f"{emoji} {param}: {value}")
718
+
719
+ st.sidebar.markdown("**Matched Configuration:**")
720
+ st.sidebar.code(selected_config if selected_config else "No match found", language="text")
721
+
722
+ # Show configuration status
723
+ if config_exists:
724
+ st.sidebar.success("✅ Exact configuration match found!")
725
+ else:
726
+ st.sidebar.warning("⚠️ Using best available match")
727
+
728
+ st.sidebar.markdown("---")
729
 
730
  # Get models for selected language and config
731
+ if not selected_config:
732
+ st.error("❌ No valid configuration found")
733
+ st.info("Please try different parameter combinations.")
734
+ st.stop()
735
+
736
  models = explorer._get_models(selected_language, selected_config)
737
  if not models:
738
+ st.warning(f"No models found for {selected_language}/{selected_config}")
739
+ st.info("This configuration may not exist for the selected language. Try adjusting the configuration parameters above.")
740
+ st.stop()
741
 
742
  # Model selection
743
  selected_model = st.sidebar.selectbox(
 
759
  with tab1:
760
  st.markdown('<div class="section-header">Experiment Overview</div>', unsafe_allow_html=True)
761
 
762
+ # Show current configuration in a friendly format
763
+ st.markdown("### 🔧 Current Configuration")
764
+ config_params = explorer._parse_config_params(selected_config)
765
+
766
+ col1, col2 = st.columns(2)
767
+ with col1:
768
+ st.markdown("**Configuration Parameters:**")
769
+ for param, value in config_params.items():
770
+ emoji = "✅" if value else "❌" if isinstance(value, bool) else "🔹"
771
+ readable_param = param.replace('_', ' ').title()
772
+ st.markdown(f"{emoji} **{readable_param}**: {value}")
773
+
774
+ with col2:
775
+ st.markdown("**Selected Parameters vs Actual:**")
776
+ for param in selected_params:
777
+ selected_val = selected_params[param]
778
+ actual_val = config_params.get(param, "N/A")
779
+ match_emoji = "✅" if selected_val == actual_val else "⚠️"
780
+ st.markdown(f"{match_emoji} **{param}**: {selected_val} → {actual_val}")
781
+
782
+ st.markdown("**Raw Configuration String:**")
783
+ st.code(selected_config, language="text")
784
+
785
+ st.markdown("---")
786
+
787
  # Load metadata
788
  metadata = explorer._load_metadata(selected_language, selected_config, selected_model)
789
  if metadata:
790
+ st.markdown("### 📊 Experiment Statistics")
791
  col1, col2, col3, col4 = st.columns(4)
792
  with col1:
793
  st.metric("Total Samples", metadata.get('total_number', 'N/A'))
 
800
  metadata.get('total_number', 1)) * 100 if metadata.get('total_number') else 0
801
  st.metric("Success Rate", f"{success_rate:.1f}%")
802
 
803
+ if metadata.get('random_seed'):
804
+ st.markdown(f"**Random Seed:** {metadata.get('random_seed')}")
805
 
806
  if metadata.get('errored_phrases'):
807
+ with st.expander("🔍 View Errored Phrase IDs"):
808
+ st.write(metadata['errored_phrases'])
809
  else:
810
  st.warning("No metadata available for this configuration.")
811
 
812
  # Quick stats about available data
813
+ st.markdown("---")
814
+ st.markdown('<div class="section-header">Available Data Summary</div>', unsafe_allow_html=True)
815
 
816
+ # Show loading message since we're now loading on-demand
817
+ with st.spinner("Loading data summary..."):
818
+ uas_data = explorer._load_uas_scores(selected_language, selected_config, selected_model)
819
+ heads_data = explorer._load_head_matching(selected_language, selected_config, selected_model)
820
+ variability_data = explorer._load_variability(selected_language, selected_config, selected_model)
821
+ figures = explorer._get_available_figures(selected_language, selected_config, selected_model)
822
 
823
  col1, col2, col3, col4 = st.columns(4)
824
  with col1:
 
829
  st.metric("Variability Data", "✓" if variability_data is not None else "✗")
830
  with col4:
831
  st.metric("Figure Files", len(figures))
832
+
833
+ # Show what was just downloaded
834
+ if uas_data or heads_data or variability_data is not None or figures:
835
+ st.success(f"✅ Successfully loaded data for {selected_language.upper()}/{selected_model}")
836
+ else:
837
+ st.warning("⚠️ No data files found for this configuration")
838
 
839
  # Tab 2: UAS Scores
840
  with tab2: