cjc0013 commited on
Commit
5dab21e
·
verified ·
1 Parent(s): 4a1f0db

Improve text contrast and horizontal table scrolling in Space UI

Browse files
Files changed (1) hide show
  1. public_space_app.py +136 -9
public_space_app.py CHANGED
@@ -463,6 +463,67 @@ def _space_css() -> str:
463
  color: #4b4b4b;
464
  margin-bottom: 12px;
465
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  """
467
 
468
 
@@ -1091,6 +1152,38 @@ def _graph_table(edges: pd.DataFrame) -> pd.DataFrame:
1091
  ]
1092
 
1093
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
  def _filter_events(events: pd.DataFrame, member_query: str, event_type: str, score_label: str, text_query: str) -> pd.DataFrame:
1095
  filtered = events.copy()
1096
  if member_query.strip():
@@ -1441,7 +1534,15 @@ def build_app(copy_path: str | Path):
1441
  review_status=review_status,
1442
  max_edges=int(top_n),
1443
  )
1444
- return summary, _render_graph(filtered_nodes, filtered_edges), _graph_table(filtered_edges)
 
 
 
 
 
 
 
 
1445
 
1446
  def _reset_graph(member_query: str):
1447
  default_family = str(graph_defaults.get("relationship_family", "sector"))
@@ -1474,11 +1575,21 @@ def build_app(copy_path: str | Path):
1474
  gr.update(value=int(default_top_n)),
1475
  summary,
1476
  _render_graph(filtered_nodes, filtered_edges),
1477
- _graph_table(filtered_edges),
 
 
 
 
1478
  )
1479
 
1480
  def _update_events(member_query: str, event_type: str, score_label: str, text_query: str):
1481
- return _filter_events(events, member_query, event_type, score_label, text_query)
 
 
 
 
 
 
1482
 
1483
  with gr.Blocks(title=copy_payload.get("title", "Congress Public Records Slice"), css=_space_css()) as app:
1484
  gr.HTML(_hero_html(manifest))
@@ -1565,7 +1676,7 @@ def build_app(copy_path: str | Path):
1565
  graph_summary_md = gr.Markdown()
1566
  graph_html = gr.HTML()
1567
  with gr.Accordion("Current relationships in this map", open=False):
1568
- graph_df = gr.Dataframe(interactive=False)
1569
 
1570
  for control in (graph_family, graph_only_strong, graph_top_n):
1571
  control.change(
@@ -1600,20 +1711,36 @@ def build_app(copy_path: str | Path):
1600
  event_type = gr.Dropdown(label="Event type", choices=event_type_choices, value="all")
1601
  score_label = gr.Dropdown(label="Score label", choices=score_label_choices, value="all")
1602
  text_query = gr.Textbox(label="Issuer or sector search")
1603
- explore_df = gr.Dataframe(value=events.head(100), interactive=False)
1604
  for control in (member_query, event_type, score_label, text_query):
1605
  control.change(_update_events, [member_query, event_type, score_label, text_query], explore_df)
1606
 
1607
  with gr.Accordion("Inspect one released event row", open=False):
1608
  event_id = gr.Dropdown(label="Event id", choices=event_id_choices, value=event_id_choices[0] if event_id_choices else None)
1609
  event_detail_md = gr.Markdown()
1610
- event_detail_df = gr.Dataframe(interactive=False)
1611
- event_id.change(_event_detail, [gr.State(events), gr.State(provenance), event_id], [event_detail_md, event_detail_df])
1612
- app.load(_event_detail, [gr.State(events), gr.State(provenance), event_id], [event_detail_md, event_detail_df])
 
 
 
 
 
 
 
 
 
 
1613
 
1614
  with gr.Accordion("Integrity-checked source records and audit summary", open=False):
1615
  gr.Markdown(_consistency_summary_markdown(data["consistency"]))
1616
- gr.Dataframe(value=data["artifact_index"].head(200), interactive=False)
 
 
 
 
 
 
1617
 
1618
  with gr.Accordion("Methodology, limits, and downloads", open=False):
1619
  gr.Markdown(copy_payload.get("landing_markdown", ""))
 
463
  color: #4b4b4b;
464
  margin-bottom: 12px;
465
  }
466
+ .gradio-container .prose,
467
+ .gradio-container .prose p,
468
+ .gradio-container .prose li,
469
+ .gradio-container .prose strong,
470
+ .gradio-container .prose h1,
471
+ .gradio-container .prose h2,
472
+ .gradio-container .prose h3,
473
+ .gradio-container .prose h4,
474
+ .gradio-container .prose code {
475
+ color: var(--body-text-color) !important;
476
+ }
477
+ .table-shell {
478
+ background: var(--block-background-fill);
479
+ border: 1px solid var(--border-color-primary);
480
+ border-radius: 18px;
481
+ overflow: hidden;
482
+ margin-top: 10px;
483
+ }
484
+ .table-scroll {
485
+ overflow-x: auto;
486
+ overflow-y: auto;
487
+ max-height: 520px;
488
+ }
489
+ .public-table {
490
+ border-collapse: collapse;
491
+ width: max-content;
492
+ min-width: 100%;
493
+ font-size: 0.92rem;
494
+ }
495
+ .public-table thead th {
496
+ position: sticky;
497
+ top: 0;
498
+ z-index: 1;
499
+ background: var(--block-title-background-fill, var(--block-background-fill));
500
+ color: var(--body-text-color);
501
+ text-align: left;
502
+ padding: 10px 12px;
503
+ border-bottom: 1px solid var(--border-color-primary);
504
+ white-space: nowrap;
505
+ }
506
+ .public-table tbody td {
507
+ padding: 10px 12px;
508
+ border-bottom: 1px solid var(--border-color-primary);
509
+ color: var(--body-text-color);
510
+ white-space: nowrap;
511
+ max-width: none;
512
+ }
513
+ .public-table tbody tr:nth-child(even) td {
514
+ background: color-mix(in srgb, var(--block-background-fill) 88%, var(--body-background-fill) 12%);
515
+ }
516
+ .public-table a {
517
+ color: #c67f00 !important;
518
+ text-decoration: underline;
519
+ }
520
+ .table-note {
521
+ padding: 10px 12px;
522
+ font-size: 0.88rem;
523
+ color: var(--body-text-color-subdued);
524
+ border-top: 1px solid var(--border-color-primary);
525
+ background: var(--body-background-fill);
526
+ }
527
  """
528
 
529
 
 
1152
  ]
1153
 
1154
 
1155
+ def _format_table_cell(value: Any) -> str:
1156
+ text = "" if value is None else str(value)
1157
+ if not text:
1158
+ return ""
1159
+ escaped = html.escape(text)
1160
+ if text.startswith("http://") or text.startswith("https://"):
1161
+ label = escaped if len(text) <= 90 else html.escape(text[:87] + "...")
1162
+ return f'<a href="{escaped}" target="_blank" rel="noopener noreferrer">{label}</a>'
1163
+ display = escaped if len(text) <= 120 else html.escape(text[:117] + "...")
1164
+ return f'<span title="{escaped}">{display}</span>'
1165
+
1166
+
1167
+ def _table_html(frame: pd.DataFrame, *, empty_message: str, note: str = "", max_rows: int | None = None) -> str:
1168
+ if frame is None or frame.empty:
1169
+ return f'<div class="panel-note">{html.escape(empty_message)}</div>'
1170
+ preview = frame.head(int(max_rows)) if max_rows is not None else frame
1171
+ headers = "".join(f"<th>{html.escape(str(col))}</th>" for col in preview.columns)
1172
+ body_rows: list[str] = []
1173
+ for row in preview.fillna("").astype(str).to_dict("records"):
1174
+ body_cells = "".join(f"<td>{_format_table_cell(value)}</td>" for value in row.values())
1175
+ body_rows.append(f"<tr>{body_cells}</tr>")
1176
+ note_html = f'<div class="table-note">{html.escape(note)}</div>' if note else ""
1177
+ return (
1178
+ '<div class="table-shell">'
1179
+ '<div class="table-scroll">'
1180
+ f'<table class="public-table"><thead><tr>{headers}</tr></thead><tbody>{"".join(body_rows)}</tbody></table>'
1181
+ '</div>'
1182
+ f"{note_html}"
1183
+ '</div>'
1184
+ )
1185
+
1186
+
1187
  def _filter_events(events: pd.DataFrame, member_query: str, event_type: str, score_label: str, text_query: str) -> pd.DataFrame:
1188
  filtered = events.copy()
1189
  if member_query.strip():
 
1534
  review_status=review_status,
1535
  max_edges=int(top_n),
1536
  )
1537
+ return (
1538
+ summary,
1539
+ _render_graph(filtered_nodes, filtered_edges),
1540
+ _table_html(
1541
+ _graph_table(filtered_edges),
1542
+ empty_message="No relationships match the current graph filters.",
1543
+ note="Scroll sideways if you want to inspect every column in the current graph view.",
1544
+ ),
1545
+ )
1546
 
1547
  def _reset_graph(member_query: str):
1548
  default_family = str(graph_defaults.get("relationship_family", "sector"))
 
1575
  gr.update(value=int(default_top_n)),
1576
  summary,
1577
  _render_graph(filtered_nodes, filtered_edges),
1578
+ _table_html(
1579
+ _graph_table(filtered_edges),
1580
+ empty_message="No relationships match the current graph filters.",
1581
+ note="Scroll sideways if you want to inspect every column in the current graph view.",
1582
+ ),
1583
  )
1584
 
1585
  def _update_events(member_query: str, event_type: str, score_label: str, text_query: str):
1586
+ filtered = _filter_events(events, member_query, event_type, score_label, text_query)
1587
+ display = filtered.head(150)
1588
+ return _table_html(
1589
+ display,
1590
+ empty_message="No released event rows match the current filters.",
1591
+ note=f"Showing {len(display)} of {len(filtered)} matching released event rows." if len(filtered) > len(display) else f"Showing {len(display)} released event rows.",
1592
+ )
1593
 
1594
  with gr.Blocks(title=copy_payload.get("title", "Congress Public Records Slice"), css=_space_css()) as app:
1595
  gr.HTML(_hero_html(manifest))
 
1676
  graph_summary_md = gr.Markdown()
1677
  graph_html = gr.HTML()
1678
  with gr.Accordion("Current relationships in this map", open=False):
1679
+ graph_df = gr.HTML()
1680
 
1681
  for control in (graph_family, graph_only_strong, graph_top_n):
1682
  control.change(
 
1711
  event_type = gr.Dropdown(label="Event type", choices=event_type_choices, value="all")
1712
  score_label = gr.Dropdown(label="Score label", choices=score_label_choices, value="all")
1713
  text_query = gr.Textbox(label="Issuer or sector search")
1714
+ explore_df = gr.HTML(value=_table_html(events.head(100), empty_message="No released event rows are available."))
1715
  for control in (member_query, event_type, score_label, text_query):
1716
  control.change(_update_events, [member_query, event_type, score_label, text_query], explore_df)
1717
 
1718
  with gr.Accordion("Inspect one released event row", open=False):
1719
  event_id = gr.Dropdown(label="Event id", choices=event_id_choices, value=event_id_choices[0] if event_id_choices else None)
1720
  event_detail_md = gr.Markdown()
1721
+ event_detail_df = gr.HTML()
1722
+
1723
+ def _event_detail_view(events_state: pd.DataFrame, prov_state: pd.DataFrame, event_id_value: str):
1724
+ detail_md, prov_rows = _event_detail(events_state, prov_state, event_id_value)
1725
+ table_html = _table_html(
1726
+ prov_rows,
1727
+ empty_message="No provenance rows are attached to this released event row.",
1728
+ note="Scroll sideways to inspect all provenance columns and URLs.",
1729
+ )
1730
+ return detail_md, table_html
1731
+
1732
+ event_id.change(_event_detail_view, [gr.State(events), gr.State(provenance), event_id], [event_detail_md, event_detail_df])
1733
+ app.load(_event_detail_view, [gr.State(events), gr.State(provenance), event_id], [event_detail_md, event_detail_df])
1734
 
1735
  with gr.Accordion("Integrity-checked source records and audit summary", open=False):
1736
  gr.Markdown(_consistency_summary_markdown(data["consistency"]))
1737
+ gr.HTML(
1738
+ _table_html(
1739
+ data["artifact_index"].head(200),
1740
+ empty_message="No source artifact rows are available in the audit index.",
1741
+ note="Scroll sideways to inspect long URLs and SHA-256 values.",
1742
+ )
1743
+ )
1744
 
1745
  with gr.Accordion("Methodology, limits, and downloads", open=False):
1746
  gr.Markdown(copy_payload.get("landing_markdown", ""))