locke1 commited on
Commit
b70aaba
·
verified ·
1 Parent(s): 99da3ae

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +116 -96
src/streamlit_app.py CHANGED
@@ -358,13 +358,23 @@ st.markdown("""
358
  border-color: rgba(255, 255, 255, 0.1);
359
  }
360
  }
361
- /* Model display checkboxes: force horizontal row on mobile */
362
  .st-key-narrow_model_display [data-testid="stHorizontalBlock"] {
363
  flex-wrap: nowrap !important;
364
  }
365
  .st-key-narrow_model_display [data-testid="column"] {
366
  min-width: 0 !important;
367
  }
 
 
 
 
 
 
 
 
 
 
368
  /* Mobile: stack image comparison columns vertically */
369
  @media (max-width: 768px) {
370
  .st-key-image_comparison [data-testid="stHorizontalBlock"] {
@@ -387,8 +397,17 @@ def _parse_success(value):
387
  return False
388
 
389
 
 
 
 
 
 
 
 
 
 
390
  @st.cache_data
391
- def load_data():
392
  """Load and clean data. Tries repo root (HF Space: /app/data/) then script dir."""
393
  csv_path = None
394
  for base in (_repo_root, _script_dir):
@@ -823,8 +842,11 @@ FAILURE_MODE_OPTIONS = [
823
  "Click Region Error",
824
  "Visual Confusion",
825
  "Spatial Reasoning Error",
826
- "Multiple targets",
827
- "Ambiguous instruction",
 
 
 
828
  ]
829
 
830
 
@@ -857,11 +879,7 @@ def _build_available_samples(df_filtered, selected_variant, failure_mode="All"):
857
  if failure_mode != "All":
858
  # Get one row per sample to check failure mode (use original variant)
859
  df_orig = df_rel[df_rel["variant"] == "original"].drop_duplicates(subset=["task_id", "step_index"])
860
- if failure_mode in ("Multiple targets", "Ambiguous instruction"):
861
- # These are sub-categories of "Invalid" stored in the description column
862
- match_mask = df_orig["description"].str.lower() == failure_mode.lower()
863
- else:
864
- match_mask = df_orig["interesting_cases"] == failure_mode
865
  match_pairs = set(zip(df_orig.loc[match_mask, "task_id"], df_orig.loc[match_mask, "step_index"]))
866
  filtered_pairs &= match_pairs
867
 
@@ -919,7 +937,7 @@ def _render_compact_header():
919
  html = f"""
920
  <div class="gui-viewer-compact-header">
921
  <h2 style="margin:0;color:var(--gui-viewer-heading);font-size:1.3rem;font-weight:700;">GUI-Perturbed Baseline Result Viewer</h2>
922
- <p style="margin:0.25rem 0 0.4rem;color:var(--gui-viewer-muted);font-size:0.85rem;line-height:1.4;">a baseline study of 7B GUI models using GUI-Perturbed data to stress test CUA models on GUI and understand what agents fail at and why</p>
923
  <div class="header-row">
924
  {fig_logo_html}
925
  {manifold_logo_html}
@@ -1110,7 +1128,7 @@ def main():
1110
 
1111
  # --- Load data ---
1112
  with st.spinner("Loading results..."):
1113
- df = load_data()
1114
  _t = _lap("load_data", _t)
1115
  if df.empty:
1116
  st.error("No data found")
@@ -1125,7 +1143,10 @@ def main():
1125
  # --- Compute state from session defaults (widgets rendered later read from previous rerun) ---
1126
  query_types = sorted(df['query_type'].unique().tolist())
1127
  use_reasoning_options = sorted(df['use_reasoning'].unique().tolist())
1128
- selected_query_type = st.session_state.get("query_type_filter", query_types[0] if query_types else None)
 
 
 
1129
  selected_use_reasoning = st.session_state.get("use_reasoning_filter", use_reasoning_options[0] if use_reasoning_options else None)
1130
  if selected_query_type not in query_types:
1131
  selected_query_type = query_types[0] if query_types else None
@@ -1155,8 +1176,8 @@ def main():
1155
  # to avoid 1-rerun lag vs the manually maintained selected_models dict
1156
  selected_models = [m for m in all_models if st.session_state.get(f"model_{m}", True)]
1157
 
1158
- if 'failure_mode_filter' not in st.session_state:
1159
- st.session_state.failure_mode_filter = "All"
1160
 
1161
  # Initialize navigation state
1162
  if 'current_sample_index' not in st.session_state:
@@ -1171,16 +1192,20 @@ def main():
1171
  st.session_state.sample_nav_input = st.session_state.current_sample_index + 1
1172
 
1173
  # Build available samples
 
 
 
1174
  available_samples, available_samples_all, full_list_index_by_sample = _build_available_samples(
1175
  df_filtered,
1176
  st.session_state.selected_variant,
1177
- st.session_state.failure_mode_filter,
1178
  )
1179
  _t = _lap("_build_available_samples", _t)
1180
 
1181
  if not available_samples:
1182
  st.error(
1183
- f"No samples found with both original and {st.session_state.selected_variant} perturbation"
 
1184
  )
1185
  return
1186
 
@@ -1189,7 +1214,7 @@ def main():
1189
  selected_query_type,
1190
  selected_use_reasoning,
1191
  st.session_state.selected_variant,
1192
- st.session_state.failure_mode_filter,
1193
  )
1194
  filters_changed = st.session_state.get("_filter_signature") != current_filter_signature
1195
  if filters_changed:
@@ -1231,7 +1256,7 @@ def main():
1231
  _t = _lap("build rows by model", _t)
1232
 
1233
  # ==========================================
1234
- # RENDER: Images first (hero content)
1235
  # ==========================================
1236
  _render_images(
1237
  original_rows_by_model,
@@ -1243,19 +1268,44 @@ def main():
1243
  _t = _lap("render images", _t)
1244
 
1245
  # ==========================================
1246
- # RENDER: Model display checkboxes (under images)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1247
  # ==========================================
1248
  with st.container(key="narrow_model_display"):
1249
- st.markdown("**Model Display**")
1250
- model_display_cols = st.columns(len(all_models)) if all_models else []
1251
- for i, model in enumerate(all_models):
1252
- with model_display_cols[i]:
1253
- checked = st.checkbox(
1254
- _model_label(model),
1255
- value=st.session_state.selected_models.get(model, True),
1256
- key=f"model_{model}",
1257
- )
1258
- st.session_state.selected_models[model] = checked
1259
 
1260
  # Recompute selected_models from current checkbox widget state
1261
  selected_models = [m for m in all_models if st.session_state.get(f"model_{m}", True)]
@@ -1272,61 +1322,21 @@ def main():
1272
  _t = _lap("render model results", _t)
1273
 
1274
  # ==========================================
1275
- # RENDER: Navigation & Filters
1276
  # ==========================================
1277
  with st.container(key="narrow_controls"):
1278
  st.markdown("---")
1279
- st.markdown("#### Navigation & Filters")
1280
-
1281
- # Navigation callbacks (use -/+ on number input for prev/next)
1282
- def on_sample_change():
1283
- new_val = st.session_state.sample_nav_input
1284
- if new_val - 1 != st.session_state.current_sample_index:
1285
- st.session_state.current_sample_index = new_val - 1
1286
-
1287
- def on_search():
1288
- query = st.session_state.get("instruction_search", "").strip().lower()
1289
- if not query:
1290
- return
1291
- # Search from current position + 1, wrapping around
1292
- n = len(available_samples)
1293
- start = (st.session_state.current_sample_index + 1) % n
1294
- for offset in range(n):
1295
- idx = (start + offset) % n
1296
- instr = available_samples[idx].get("instruction", "").lower()
1297
- if query in instr:
1298
- st.session_state.current_sample_index = idx
1299
- st.session_state.sample_nav_input = idx + 1
1300
- return
1301
 
1302
- # Row 1: Sample number + search
1303
- nav_col, search_col = st.columns([1, 2])
1304
- with nav_col:
1305
- position_in_full_list = full_list_index_by_sample.get(
1306
- (current_sample['task_id'], current_sample['step_index'])
1307
- )
1308
- total_in_full_list = len(available_samples_all)
1309
- nav_label = f"Sample ({st.session_state.current_sample_index + 1} of {len(available_samples)}"
1310
- if position_in_full_list is not None and total_in_full_list != len(available_samples):
1311
- nav_label += f" | {position_in_full_list} of {total_in_full_list} total"
1312
- nav_label += ")"
1313
- st.number_input(
1314
- nav_label,
1315
- min_value=1,
1316
- max_value=len(available_samples),
1317
- key="sample_nav_input",
1318
- on_change=on_sample_change,
1319
- )
1320
- with search_col:
1321
- st.text_input(
1322
- "Search instructions",
1323
- key="instruction_search",
1324
- on_change=on_search,
1325
- placeholder="Type to search task instructions...",
1326
- )
1327
 
1328
- # Row 2: Filters
1329
- f = st.columns(4)
1330
  with f[0]:
1331
  new_variant = st.selectbox(
1332
  "Visual Variant",
@@ -1337,7 +1347,7 @@ def main():
1337
  help="Precision: viewport zoom. Style: visual randomization. Text Shrink: font size reduced.",
1338
  )
1339
  with f[1]:
1340
- selected_query_type = st.selectbox(
1341
  "Instruction Variant",
1342
  query_types,
1343
  key="query_type_filter",
@@ -1345,33 +1355,43 @@ def main():
1345
  help="Direct Instruction vs Relational Instruction",
1346
  )
1347
  with f[2]:
1348
- selected_use_reasoning = st.selectbox(
1349
  "Reasoning",
1350
  use_reasoning_options,
1351
  key="use_reasoning_filter",
1352
  format_func=lambda x: "Yes" if x else "No",
1353
  help="Whether chain-of-thought reasoning was used",
1354
  )
1355
- with f[3]:
1356
- new_failure_mode = st.selectbox(
1357
- "Failure Mode",
1358
- FAILURE_MODE_OPTIONS,
1359
- index=FAILURE_MODE_OPTIONS.index(st.session_state.failure_mode_filter),
1360
- key="failure_mode_select",
1361
- help="Filter samples by failure mode category",
1362
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1363
 
1364
  _t = _lap("control panel widgets", _t)
1365
 
1366
- # Handle variant or failure mode change
1367
- needs_rerun = False
1368
  if new_variant != st.session_state.selected_variant:
1369
  st.session_state.selected_variant = new_variant
1370
- needs_rerun = True
1371
- if new_failure_mode != st.session_state.failure_mode_filter:
1372
- st.session_state.failure_mode_filter = new_failure_mode
1373
- needs_rerun = True
1374
- if needs_rerun:
1375
  st.rerun()
1376
 
1377
  _lap("main() total", _t0)
 
358
  border-color: rgba(255, 255, 255, 0.1);
359
  }
360
  }
361
+ /* Model display checkboxes: compact row */
362
  .st-key-narrow_model_display [data-testid="stHorizontalBlock"] {
363
  flex-wrap: nowrap !important;
364
  }
365
  .st-key-narrow_model_display [data-testid="column"] {
366
  min-width: 0 !important;
367
  }
368
+ /* Mobile: allow wrapping so checkboxes don't overflow */
369
+ @media (max-width: 768px) {
370
+ .st-key-narrow_model_display [data-testid="stHorizontalBlock"] {
371
+ flex-wrap: wrap !important;
372
+ }
373
+ }
374
+ /* Failure mode pills: no word breaking */
375
+ .st-key-failure_mode_pills button {
376
+ white-space: nowrap !important;
377
+ }
378
  /* Mobile: stack image comparison columns vertically */
379
  @media (max-width: 768px) {
380
  .st-key-image_comparison [data-testid="stHorizontalBlock"] {
 
397
  return False
398
 
399
 
400
+ def _csv_mtime():
401
+ """Return CSV modification time so cache invalidates when file changes."""
402
+ for base in (_repo_root, _script_dir):
403
+ candidate = base / "data" / "baseline_results_full_new.csv"
404
+ if candidate.exists():
405
+ return candidate.stat().st_mtime
406
+ return None
407
+
408
+
409
  @st.cache_data
410
+ def load_data(_mtime=None):
411
  """Load and clean data. Tries repo root (HF Space: /app/data/) then script dir."""
412
  csv_path = None
413
  for base in (_repo_root, _script_dir):
 
842
  "Click Region Error",
843
  "Visual Confusion",
844
  "Spatial Reasoning Error",
845
+ "Text Matching Bias",
846
+ "Instruction Misinterpretation",
847
+ "Goal Hallucination",
848
+ "Location Hallucination",
849
+ "Reasoning Drift",
850
  ]
851
 
852
 
 
879
  if failure_mode != "All":
880
  # Get one row per sample to check failure mode (use original variant)
881
  df_orig = df_rel[df_rel["variant"] == "original"].drop_duplicates(subset=["task_id", "step_index"])
882
+ match_mask = df_orig["interesting_cases"] == failure_mode
 
 
 
 
883
  match_pairs = set(zip(df_orig.loc[match_mask, "task_id"], df_orig.loc[match_mask, "step_index"]))
884
  filtered_pairs &= match_pairs
885
 
 
937
  html = f"""
938
  <div class="gui-viewer-compact-header">
939
  <h2 style="margin:0;color:var(--gui-viewer-heading);font-size:1.3rem;font-weight:700;">GUI-Perturbed Baseline Result Viewer</h2>
940
+ <p style="margin:0.25rem 0 0.4rem;color:var(--gui-viewer-muted);font-size:0.85rem;line-height:1.4;">Explore how 7B GUI grounding models perform on original vs. perturbed screenshots from GUI-Perturbed</p>
941
  <div class="header-row">
942
  {fig_logo_html}
943
  {manifold_logo_html}
 
1128
 
1129
  # --- Load data ---
1130
  with st.spinner("Loading results..."):
1131
+ df = load_data(_mtime=_csv_mtime())
1132
  _t = _lap("load_data", _t)
1133
  if df.empty:
1134
  st.error("No data found")
 
1143
  # --- Compute state from session defaults (widgets rendered later read from previous rerun) ---
1144
  query_types = sorted(df['query_type'].unique().tolist())
1145
  use_reasoning_options = sorted(df['use_reasoning'].unique().tolist())
1146
+ _default_query_type = "relational_query" if "relational_query" in query_types else (query_types[0] if query_types else None)
1147
+ if "query_type_filter" not in st.session_state and _default_query_type is not None:
1148
+ st.session_state.query_type_filter = _default_query_type
1149
+ selected_query_type = st.session_state.get("query_type_filter", _default_query_type)
1150
  selected_use_reasoning = st.session_state.get("use_reasoning_filter", use_reasoning_options[0] if use_reasoning_options else None)
1151
  if selected_query_type not in query_types:
1152
  selected_query_type = query_types[0] if query_types else None
 
1176
  # to avoid 1-rerun lag vs the manually maintained selected_models dict
1177
  selected_models = [m for m in all_models if st.session_state.get(f"model_{m}", True)]
1178
 
1179
+ if 'failure_mode_pills' not in st.session_state:
1180
+ st.session_state.failure_mode_pills = "Spatial Reasoning Error"
1181
 
1182
  # Initialize navigation state
1183
  if 'current_sample_index' not in st.session_state:
 
1192
  st.session_state.sample_nav_input = st.session_state.current_sample_index + 1
1193
 
1194
  # Build available samples
1195
+ _active_failure_mode = st.session_state.get("failure_mode_pills", "All")
1196
+ if _active_failure_mode is None:
1197
+ _active_failure_mode = "All"
1198
  available_samples, available_samples_all, full_list_index_by_sample = _build_available_samples(
1199
  df_filtered,
1200
  st.session_state.selected_variant,
1201
+ _active_failure_mode,
1202
  )
1203
  _t = _lap("_build_available_samples", _t)
1204
 
1205
  if not available_samples:
1206
  st.error(
1207
+ f"No samples found with both original and {st.session_state.selected_variant} perturbation "
1208
+ f"for failure mode \"{_active_failure_mode}\""
1209
  )
1210
  return
1211
 
 
1214
  selected_query_type,
1215
  selected_use_reasoning,
1216
  st.session_state.selected_variant,
1217
+ _active_failure_mode,
1218
  )
1219
  filters_changed = st.session_state.get("_filter_signature") != current_filter_signature
1220
  if filters_changed:
 
1256
  _t = _lap("build rows by model", _t)
1257
 
1258
  # ==========================================
1259
+ # RENDER: Images (hero content)
1260
  # ==========================================
1261
  _render_images(
1262
  original_rows_by_model,
 
1268
  _t = _lap("render images", _t)
1269
 
1270
  # ==========================================
1271
+ # RENDER: Sample navigation (right under images)
1272
+ # ==========================================
1273
+ def on_sample_change():
1274
+ new_val = st.session_state.sample_nav_input
1275
+ if new_val - 1 != st.session_state.current_sample_index:
1276
+ st.session_state.current_sample_index = new_val - 1
1277
+
1278
+ with st.container(key="narrow_sample_nav"):
1279
+ position_in_full_list = full_list_index_by_sample.get(
1280
+ (current_sample['task_id'], current_sample['step_index'])
1281
+ )
1282
+ total_in_full_list = len(available_samples_all)
1283
+ nav_label = f"Sample ({st.session_state.current_sample_index + 1} of {len(available_samples)}"
1284
+ if position_in_full_list is not None and total_in_full_list != len(available_samples):
1285
+ nav_label += f" | {position_in_full_list} of {total_in_full_list} total"
1286
+ nav_label += ")"
1287
+ st.number_input(
1288
+ nav_label,
1289
+ min_value=1,
1290
+ max_value=len(available_samples),
1291
+ key="sample_nav_input",
1292
+ on_change=on_sample_change,
1293
+ )
1294
+
1295
+ # ==========================================
1296
+ # RENDER: Model display checkboxes (collapsible, default collapsed)
1297
  # ==========================================
1298
  with st.container(key="narrow_model_display"):
1299
+ with st.expander("Model Display", expanded=False):
1300
+ model_display_cols = st.columns(len(all_models)) if all_models else []
1301
+ for i, model in enumerate(all_models):
1302
+ with model_display_cols[i]:
1303
+ checked = st.checkbox(
1304
+ _model_label(model),
1305
+ value=st.session_state.selected_models.get(model, True),
1306
+ key=f"model_{model}",
1307
+ )
1308
+ st.session_state.selected_models[model] = checked
1309
 
1310
  # Recompute selected_models from current checkbox widget state
1311
  selected_models = [m for m in all_models if st.session_state.get(f"model_{m}", True)]
 
1322
  _t = _lap("render model results", _t)
1323
 
1324
  # ==========================================
1325
+ # RENDER: Filters & Search
1326
  # ==========================================
1327
  with st.container(key="narrow_controls"):
1328
  st.markdown("---")
1329
+ st.markdown("#### Filters & Search")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1330
 
1331
+ # Failure mode pills (key pre-initialized in session state)
1332
+ st.pills(
1333
+ "Failure Mode",
1334
+ FAILURE_MODE_OPTIONS,
1335
+ key="failure_mode_pills",
1336
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1337
 
1338
+ # Filter dropdowns
1339
+ f = st.columns(3)
1340
  with f[0]:
1341
  new_variant = st.selectbox(
1342
  "Visual Variant",
 
1347
  help="Precision: viewport zoom. Style: visual randomization. Text Shrink: font size reduced.",
1348
  )
1349
  with f[1]:
1350
+ st.selectbox(
1351
  "Instruction Variant",
1352
  query_types,
1353
  key="query_type_filter",
 
1355
  help="Direct Instruction vs Relational Instruction",
1356
  )
1357
  with f[2]:
1358
+ st.selectbox(
1359
  "Reasoning",
1360
  use_reasoning_options,
1361
  key="use_reasoning_filter",
1362
  format_func=lambda x: "Yes" if x else "No",
1363
  help="Whether chain-of-thought reasoning was used",
1364
  )
1365
+
1366
+ # Search instructions
1367
+ def on_search():
1368
+ query = st.session_state.get("instruction_search", "").strip().lower()
1369
+ if not query:
1370
+ return
1371
+ n = len(available_samples)
1372
+ if n == 0:
1373
+ return
1374
+ start = (st.session_state.current_sample_index + 1) % n
1375
+ for offset in range(n):
1376
+ idx = (start + offset) % n
1377
+ instr = available_samples[idx].get("instruction", "").lower()
1378
+ if query in instr:
1379
+ st.session_state.current_sample_index = idx
1380
+ st.session_state.sample_nav_input = idx + 1
1381
+ return
1382
+
1383
+ st.text_input(
1384
+ "Search instructions",
1385
+ key="instruction_search",
1386
+ on_change=on_search,
1387
+ placeholder="Type to search task instructions...",
1388
+ )
1389
 
1390
  _t = _lap("control panel widgets", _t)
1391
 
1392
+ # Handle variant change
 
1393
  if new_variant != st.session_state.selected_variant:
1394
  st.session_state.selected_variant = new_variant
 
 
 
 
 
1395
  st.rerun()
1396
 
1397
  _lap("main() total", _t0)