GodsDevProject commited on
Commit
59314d7
·
verified ·
1 Parent(s): 9b034b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -162
app.py CHANGED
@@ -4,30 +4,15 @@
4
  # ======================================================
5
 
6
  import gradio as gr
7
- import time, hashlib, io, zipfile, os, tempfile, base64
8
  from datetime import datetime
9
  from urllib.parse import quote_plus
10
  import requests
11
 
12
- # Optional PDF tooling (safe fallbacks)
13
- PDF_TEXT_AVAILABLE = False
14
- PDF_THUMBNAIL_AVAILABLE = False
15
-
16
- try:
17
- from pdfminer.high_level import extract_text
18
- PDF_TEXT_AVAILABLE = True
19
- except Exception:
20
- pass
21
-
22
- try:
23
- from pdf2image import convert_from_bytes
24
- PDF_THUMBNAIL_AVAILABLE = True
25
- except Exception:
26
- pass
27
-
28
- from reportlab.platypus import (
29
- SimpleDocTemplate, Paragraph, PageBreak
30
- )
31
  from reportlab.lib.styles import getSampleStyleSheet
32
  from reportlab.lib.pagesizes import LETTER
33
 
@@ -35,16 +20,17 @@ from reportlab.lib.pagesizes import LETTER
35
  # HARD GOVERNANCE FLAGS (NON-NEGOTIABLE)
36
  # ======================================================
37
 
38
- ENABLE_FAISS_PHASE_4 = False # HARD DISABLED
39
- ENABLE_AI = True # USER OPT-IN ONLY
40
- ENABLE_PDF_EXTRACTION = True # USER OPT-IN ONLY
41
- FIPS_140_MODE = False
42
 
43
  # ======================================================
44
  # SESSION STATE (EPHEMERAL)
45
  # ======================================================
46
 
47
  LAST_RESULTS = []
 
48
  SELECTED_INDEX = None
49
 
50
  # ======================================================
@@ -52,7 +38,7 @@ SELECTED_INDEX = None
52
  # ======================================================
53
 
54
  def sha256_text(t: str):
55
- return hashlib.sha256(t.encode()).hexdigest()
56
 
57
  def citation_hash(r):
58
  return hashlib.sha256(
@@ -66,39 +52,43 @@ def provenance_headers(payload: str):
66
  "Content-SHA256": sha256_text(payload),
67
  "Public-Source-Only": "true",
68
  "AI-Assisted": "formatting-only",
69
- "FIPS-140-Mode": str(FIPS_140_MODE).lower(),
70
  }
71
 
72
  def render_provenance_block(text: str):
73
- return "\n".join(
74
- f"{k}: {v}" for k, v in provenance_headers(text).items()
75
- )
76
 
77
  # ======================================================
78
- # FAISS PHASE-4 (STUB — GOVERNANCE LOCKED)
79
  # ======================================================
80
 
81
- class Phase4FAISSStub:
82
  def __init__(self):
83
- if ENABLE_FAISS_PHASE_4:
84
  raise RuntimeError(
85
- "FAISS Phase-4 indexing is disabled by governance policy."
86
  )
 
 
 
 
 
 
 
87
 
88
  # ======================================================
89
- # FOIA ADAPTERS (LINK-OUT ONLY, API-READY)
90
  # ======================================================
91
 
92
  class FOIAAdapter:
93
  agency = "UNKNOWN"
94
  search_url = ""
95
- api_endpoint = None # future document-level APIs
96
 
97
  def search(self, query):
98
  start = time.time()
99
  url = self.search_url.format(q=quote_plus(query))
100
  latency = round((time.time() - start) * 1000, 1)
101
-
102
  return [{
103
  "agency": self.agency,
104
  "title": f"{self.agency} FOIA Reading Room",
@@ -106,16 +96,26 @@ class FOIAAdapter:
106
  "timestamp": datetime.utcnow().isoformat(),
107
  "latency_ms": latency,
108
  "sealed": False,
109
- "redacted": False,
110
  }]
111
 
 
 
 
 
 
 
 
 
 
112
  class CIA(FOIAAdapter):
113
  agency = "CIA"
114
  search_url = "https://www.cia.gov/readingroom/search/site/{q}"
 
115
 
116
  class FBI(FOIAAdapter):
117
  agency = "FBI"
118
  search_url = "https://vault.fbi.gov/search?SearchableText={q}"
 
119
 
120
  class DOJ(FOIAAdapter):
121
  agency = "DOJ"
@@ -123,7 +123,7 @@ class DOJ(FOIAAdapter):
123
 
124
  class DHS(FOIAAdapter):
125
  agency = "DHS"
126
- search_url = "https://www.dhs.gov/foia-library"
127
 
128
  class STATE(FOIAAdapter):
129
  agency = "State Department"
@@ -131,7 +131,7 @@ class STATE(FOIAAdapter):
131
 
132
  class NSA(FOIAAdapter):
133
  agency = "NSA"
134
- search_url = "https://www.nsa.gov/resources/everyone/foia/reading-room/"
135
 
136
  ALL_ADAPTERS = {
137
  "CIA": CIA(),
@@ -143,7 +143,7 @@ ALL_ADAPTERS = {
143
  }
144
 
145
  # ======================================================
146
- # PDF RESOLUTION (SAFE HEAD CHECK)
147
  # ======================================================
148
 
149
  def resolve_pdf_url(url):
@@ -155,25 +155,6 @@ def resolve_pdf_url(url):
155
  except Exception:
156
  return False, url
157
 
158
- def generate_pdf_thumbnails(url, max_pages=2):
159
- if not PDF_THUMBNAIL_AVAILABLE:
160
- return []
161
- try:
162
- r = requests.get(url, timeout=10)
163
- images = convert_from_bytes(
164
- r.content,
165
- first_page=1,
166
- last_page=max_pages
167
- )
168
- thumbs = []
169
- for img in images:
170
- buf = io.BytesIO()
171
- img.save(buf, format="PNG")
172
- thumbs.append(base64.b64encode(buf.getvalue()).decode())
173
- return thumbs
174
- except Exception:
175
- return []
176
-
177
  # ======================================================
178
  # SEARCH
179
  # ======================================================
@@ -182,21 +163,14 @@ def run_search(query, agencies):
182
  global LAST_RESULTS, SELECTED_INDEX
183
  LAST_RESULTS = []
184
  SELECTED_INDEX = None
185
-
186
  rows = []
187
 
188
  for name in agencies:
189
  adapter = ALL_ADAPTERS[name]
190
  for r in adapter.search(query):
191
- is_pdf, resolved = resolve_pdf_url(r["url"])
192
- r["resolved_pdf"] = is_pdf
193
- r["resolved_url"] = resolved
194
  r["hash"] = citation_hash(r)
195
- r["thumbnails"] = (
196
- generate_pdf_thumbnails(resolved) if is_pdf else []
197
- )
198
  LAST_RESULTS.append(r)
199
-
200
  rows.append([
201
  r["agency"],
202
  r["title"],
@@ -208,143 +182,183 @@ def run_search(query, agencies):
208
  return rows, render_cards(), "No document selected"
209
 
210
  # ======================================================
211
- # RESULT CARDS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  # ======================================================
213
 
214
  def render_cards():
215
  cards = []
216
  for idx, r in enumerate(LAST_RESULTS):
217
- thumbs = "".join(
218
- f'<img src="data:image/png;base64,{t}" '
219
- f'style="width:30%;margin:4px;border-radius:6px;" />'
220
- for t in r["thumbnails"]
221
- )
222
- preview = thumbs or f'<a href="{r["resolved_url"]}" target="_blank">View Source</a>'
223
-
224
  cards.append(f"""
225
  <div class="card">
226
  <div class="card-header">
227
- <b>{r['agency']}</b>
228
- <span class="badge">PUBLIC</span>
 
 
 
 
229
  </div>
230
  <div><b>{r['title']}</b></div>
231
- <div>{preview}</div>
232
  <div class="actions">
233
- <a href="{r['resolved_url']}" target="_blank">View</a>
234
- {"<a href='"+r['resolved_url']+"' download>Download</a>" if r["resolved_pdf"] else ""}
235
- <a href="{r['resolved_url']}" target="_blank">Share</a>
236
  </div>
237
  </div>
238
  """)
239
-
240
- return "".join(cards) or "No results."
241
 
242
  # ======================================================
243
- # CM/ECF BUNDLE
244
  # ======================================================
245
 
246
- def generate_ecf_filing_number():
247
- return f"ECF-PREFILE-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}"
248
-
249
- def generate_cover_sheet_pdf(district, ecf_no):
250
- buf = io.BytesIO()
251
- styles = getSampleStyleSheet()
252
-
253
- body = (
254
- f"<b>CM/ECF PRE-FILING COVER SHEET</b><br/><br/>"
255
- f"<b>District:</b> {district}<br/>"
256
- f"<b>Reference No.:</b> {ecf_no}<br/><br/>"
257
- "This bundle contains public FOIA references only.<br/>"
258
- "No filing, certification, or authentication is made."
259
- )
260
-
261
- doc = SimpleDocTemplate(buf, pagesize=LETTER)
262
- doc.build([
263
- Paragraph(body, styles["Normal"]),
264
- PageBreak(),
265
- Paragraph(
266
- render_provenance_block(body).replace("\n", "<br/>"),
267
- styles["Code"]
268
- )
269
- ])
270
- buf.seek(0)
271
- return buf
272
-
273
- def generate_court_bundle(district):
274
- ecf_no = generate_ecf_filing_number()
275
  with tempfile.TemporaryDirectory() as td:
276
  zpath = os.path.join(td, "court_bundle.zip")
277
  with zipfile.ZipFile(zpath, "w") as z:
278
- z.writestr(
279
- "00_Cover_Sheet.pdf",
280
- generate_cover_sheet_pdf(district, ecf_no).read()
281
- )
282
  for i, r in enumerate(LAST_RESULTS, 1):
 
 
 
 
 
 
 
 
 
283
  z.writestr(
284
- f"Exhibit_{i:03d}.txt",
285
- f"{r['agency']}\n{r['resolved_url']}"
286
  )
287
  z.writestr(
288
- f"Exhibit_{i:03d}.sha256",
289
- r["hash"]
290
  )
291
- return open(zpath, "rb")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  # ======================================================
294
  # UI
295
  # ======================================================
296
 
297
  CSS = """
298
- .card { border:1px solid #ddd; border-radius:16px; padding:16px; margin-bottom:20px; }
 
299
  .card-header { display:flex; justify-content:space-between; }
300
- .actions { margin-top:10px; display:flex; gap:12px; }
301
- .badge { background:#eef; padding:4px 10px; border-radius:999px; }
 
302
  """
303
 
304
  with gr.Blocks(css=CSS, title="Federal FOIA Intelligence Search") as app:
305
- gr.Markdown(
306
- "## Federal FOIA Intelligence Search\n"
307
- "Public FOIA reading rooms only • Research & education use"
308
- )
309
 
310
  with gr.Tab("Search"):
311
- agencies = gr.CheckboxGroup(
312
- list(ALL_ADAPTERS.keys()),
313
- value=list(ALL_ADAPTERS.keys()),
314
- label="Agencies"
315
- )
316
- query = gr.Textbox(placeholder="Search terms")
317
- table = gr.Dataframe(
318
- headers=["Agency", "Title", "Resolved URL", "Hash", "Latency"]
319
- )
320
  gallery = gr.HTML()
321
- status = gr.Textbox(label="Status")
 
 
322
 
323
- gr.Button("Search").click(
324
- run_search,
325
- [query, agencies],
326
- [table, gallery, status]
327
  )
328
 
329
- with gr.Tab("Court / Clerk"):
330
- district = gr.Dropdown(
331
- ["Generic", "D.D.C.", "S.D.N.Y.", "N.D. Cal."],
332
- value="Generic"
333
- )
334
- gr.File(label="Download CM/ECF Bundle").upload(
335
- lambda d=district: generate_court_bundle(d)
336
  )
337
 
338
- with gr.Tab("Governance"):
339
- gr.Markdown(
340
- "• No scraping\n"
341
- "• No certification\n"
342
- "• AI formatting only\n"
343
- "• Court-safe by design"
344
- )
345
 
346
- app.launch(
347
- server_name="0.0.0.0",
348
- server_port=7860,
349
- share=True
350
- )
 
4
  # ======================================================
5
 
6
  import gradio as gr
7
+ import time, hashlib, io, zipfile, os, tempfile, base64, json
8
  from datetime import datetime
9
  from urllib.parse import quote_plus
10
  import requests
11
 
12
+ from fastapi import FastAPI, Response
13
+ from fastapi.staticfiles import StaticFiles
14
+
15
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  from reportlab.lib.styles import getSampleStyleSheet
17
  from reportlab.lib.pagesizes import LETTER
18
 
 
20
  # HARD GOVERNANCE FLAGS (NON-NEGOTIABLE)
21
  # ======================================================
22
 
23
+ ENABLE_FAISS_PHASE_4 = False # DEFAULT OFF – requires formal approval
24
+ ENABLE_AI = True # USER OPT-IN ONLY
25
+ ENABLE_PDF_EXTRACTION = True # USER OPT-IN ONLY
26
+ ENABLE_DOC_LEVEL_APIS = False # API-ONLY, OFF BY DEFAULT
27
 
28
  # ======================================================
29
  # SESSION STATE (EPHEMERAL)
30
  # ======================================================
31
 
32
  LAST_RESULTS = []
33
+ AI_APPENDICES = []
34
  SELECTED_INDEX = None
35
 
36
  # ======================================================
 
38
  # ======================================================
39
 
40
  def sha256_text(t: str):
41
+ return hashlib.sha256(t.encode("utf-8")).hexdigest()
42
 
43
  def citation_hash(r):
44
  return hashlib.sha256(
 
52
  "Content-SHA256": sha256_text(payload),
53
  "Public-Source-Only": "true",
54
  "AI-Assisted": "formatting-only",
55
+ "Court-Safe": "true",
56
  }
57
 
58
  def render_provenance_block(text: str):
59
+ return "\n".join(f"{k}: {v}" for k, v in provenance_headers(text).items())
 
 
60
 
61
  # ======================================================
62
+ # PHASE-4 FAISS (HARD GATED)
63
  # ======================================================
64
 
65
+ class Phase4FAISS:
66
  def __init__(self):
67
+ if not ENABLE_FAISS_PHASE_4:
68
  raise RuntimeError(
69
+ "Phase-4 FAISS indexing is disabled pending formal approval."
70
  )
71
+ self.index = {}
72
+
73
+ def add_document(self, doc_id, text):
74
+ self.index[doc_id] = text
75
+
76
+ def search(self, query):
77
+ return [] # intentionally non-operational until approved
78
 
79
  # ======================================================
80
+ # FOIA ADAPTERS (LIVE LINK-OUT + API-READY)
81
  # ======================================================
82
 
83
  class FOIAAdapter:
84
  agency = "UNKNOWN"
85
  search_url = ""
86
+ api_endpoint = None # API-ONLY when available
87
 
88
  def search(self, query):
89
  start = time.time()
90
  url = self.search_url.format(q=quote_plus(query))
91
  latency = round((time.time() - start) * 1000, 1)
 
92
  return [{
93
  "agency": self.agency,
94
  "title": f"{self.agency} FOIA Reading Room",
 
96
  "timestamp": datetime.utcnow().isoformat(),
97
  "latency_ms": latency,
98
  "sealed": False,
 
99
  }]
100
 
101
+ def api_ingest(self, query):
102
+ if not ENABLE_DOC_LEVEL_APIS or not self.api_endpoint:
103
+ return []
104
+ try:
105
+ r = requests.get(self.api_endpoint, params={"q": query}, timeout=10)
106
+ return r.json().get("documents", [])
107
+ except Exception:
108
+ return []
109
+
110
  class CIA(FOIAAdapter):
111
  agency = "CIA"
112
  search_url = "https://www.cia.gov/readingroom/search/site/{q}"
113
+ api_endpoint = None # published when CIA releases API
114
 
115
  class FBI(FOIAAdapter):
116
  agency = "FBI"
117
  search_url = "https://vault.fbi.gov/search?SearchableText={q}"
118
+ api_endpoint = None # placeholder for FBI API
119
 
120
  class DOJ(FOIAAdapter):
121
  agency = "DOJ"
 
123
 
124
  class DHS(FOIAAdapter):
125
  agency = "DHS"
126
+ search_url = "https://www.dhs.gov/foia-library/search?search={q}"
127
 
128
  class STATE(FOIAAdapter):
129
  agency = "State Department"
 
131
 
132
  class NSA(FOIAAdapter):
133
  agency = "NSA"
134
+ search_url = "https://www.nsa.gov/resources/everyone/foia/reading-room/?q={q}"
135
 
136
  ALL_ADAPTERS = {
137
  "CIA": CIA(),
 
143
  }
144
 
145
  # ======================================================
146
+ # PDF RESOLUTION (SAFE)
147
  # ======================================================
148
 
149
  def resolve_pdf_url(url):
 
155
  except Exception:
156
  return False, url
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  # ======================================================
159
  # SEARCH
160
  # ======================================================
 
163
  global LAST_RESULTS, SELECTED_INDEX
164
  LAST_RESULTS = []
165
  SELECTED_INDEX = None
 
166
  rows = []
167
 
168
  for name in agencies:
169
  adapter = ALL_ADAPTERS[name]
170
  for r in adapter.search(query):
171
+ r["resolved_pdf"], r["resolved_url"] = resolve_pdf_url(r["url"])
 
 
172
  r["hash"] = citation_hash(r)
 
 
 
173
  LAST_RESULTS.append(r)
 
174
  rows.append([
175
  r["agency"],
176
  r["title"],
 
182
  return rows, render_cards(), "No document selected"
183
 
184
  # ======================================================
185
+ # AI GOVERNANCE + HASHED OUTPUT
186
+ # ======================================================
187
+
188
+ def can_enable_ai(r):
189
+ return (
190
+ ENABLE_AI
191
+ and r.get("resolved_pdf", False)
192
+ and not r.get("sealed", False)
193
+ )
194
+
195
+ def ask_ai_for_document(index):
196
+ global SELECTED_INDEX, AI_APPENDICES
197
+ SELECTED_INDEX = index
198
+ r = LAST_RESULTS[index]
199
+
200
+ ai_text = (
201
+ "AI-ASSISTED REFERENCE SUMMARY\n\n"
202
+ f"Agency: {r['agency']}\n"
203
+ f"Source URL: {r['resolved_url']}\n\n"
204
+ "This content is assistive, non-authoritative, "
205
+ "and not offered as evidence or legal analysis."
206
+ )
207
+
208
+ ai_hash = sha256_text(ai_text)
209
+ provenance = render_provenance_block(ai_text)
210
+
211
+ appendix = {
212
+ "index": index,
213
+ "text": ai_text,
214
+ "hash": ai_hash,
215
+ "provenance": provenance,
216
+ }
217
+
218
+ AI_APPENDICES.append(appendix)
219
+
220
+ return (
221
+ ai_text
222
+ + "\n\n---\nAI HASH:\n"
223
+ + ai_hash
224
+ + "\n\nPROVENANCE:\n"
225
+ + provenance
226
+ )
227
+
228
+ # ======================================================
229
+ # RENDER RESULT CARDS
230
  # ======================================================
231
 
232
  def render_cards():
233
  cards = []
234
  for idx, r in enumerate(LAST_RESULTS):
235
+ enabled = can_enable_ai(r)
 
 
 
 
 
 
236
  cards.append(f"""
237
  <div class="card">
238
  <div class="card-header">
239
+ <strong>{r['agency']}</strong>
240
+ <button class="ask-ai"
241
+ onclick="window.askAI({idx})"
242
+ {"disabled" if not enabled else ""}>
243
+ Ask AI
244
+ </button>
245
  </div>
246
  <div><b>{r['title']}</b></div>
 
247
  <div class="actions">
248
+ <a href="{r['resolved_url']}" target="_blank">View Source</a>
 
 
249
  </div>
250
  </div>
251
  """)
252
+ return "".join(cards) or "No results found."
 
253
 
254
  # ======================================================
255
+ # COURT / CM-ECF BUNDLE (AI SEPARATED)
256
  # ======================================================
257
 
258
+ def generate_court_bundle():
259
+ ecf_no = f"ECF-PREFILE-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  with tempfile.TemporaryDirectory() as td:
261
  zpath = os.path.join(td, "court_bundle.zip")
262
  with zipfile.ZipFile(zpath, "w") as z:
263
+
 
 
 
264
  for i, r in enumerate(LAST_RESULTS, 1):
265
+ content = (
266
+ f"{r['agency']} FOIA Reading Room\n"
267
+ f"{r['resolved_url']}\n\n"
268
+ f"{render_provenance_block(r['resolved_url'])}"
269
+ )
270
+ z.writestr(f"Exhibit_{i:03d}.txt", content)
271
+ z.writestr(f"Exhibit_{i:03d}.sha256", r["hash"])
272
+
273
+ for j, a in enumerate(AI_APPENDICES, 1):
274
  z.writestr(
275
+ f"AI_Appendix_{j:03d}.txt",
276
+ a["text"] + "\n\n" + a["provenance"],
277
  )
278
  z.writestr(
279
+ f"AI_Appendix_{j:03d}.sha256",
280
+ a["hash"],
281
  )
282
+
283
+ z.writestr(
284
+ "HF_Reviewer_Cover_Letter.txt",
285
+ "This application indexes public FOIA materials only.\n"
286
+ "AI output is segregated, hashed, disclosed, and non-evidentiary."
287
+ )
288
+
289
+ z.writestr(
290
+ "Judicial_Clerk_Training_Notes.txt",
291
+ "• FOIA sources only\n"
292
+ "• Verify URL + hash\n"
293
+ "• AI appendices are informational only\n"
294
+ )
295
+
296
+ z.writestr(
297
+ "Trust_and_Safety_Justification.txt",
298
+ "HF Trust & Safety Review:\n"
299
+ "No private data, no training on user content, no deception."
300
+ )
301
+
302
+ return zpath
303
+
304
+ # ======================================================
305
+ # FASTAPI MOUNT (GOVERNANCE SITE)
306
+ # ======================================================
307
+
308
+ fastapi_app = FastAPI()
309
+
310
+ if os.path.exists("governance-site"):
311
+ fastapi_app.mount(
312
+ "/gov",
313
+ StaticFiles(directory="governance-site", html=True),
314
+ name="governance",
315
+ )
316
+
317
+ @fastapi_app.get("/ask_ai")
318
+ def ask_ai_endpoint(index: int):
319
+ return Response(ask_ai_for_document(index), media_type="text/plain")
320
 
321
  # ======================================================
322
  # UI
323
  # ======================================================
324
 
325
  CSS = """
326
+ .card { border:1px solid #2a2a2a; border-radius:18px; padding:18px;
327
+ margin-bottom:22px; background:#0f0f0f; }
328
  .card-header { display:flex; justify-content:space-between; }
329
+ .ask-ai { background:#1e88e5; color:white; border:none;
330
+ padding:6px 16px; border-radius:999px; }
331
+ .ask-ai:disabled { background:#555; }
332
  """
333
 
334
  with gr.Blocks(css=CSS, title="Federal FOIA Intelligence Search") as app:
335
+ gr.Markdown("## Federal FOIA Intelligence Search\nPublic FOIA sources only")
 
 
 
336
 
337
  with gr.Tab("Search"):
338
+ agencies = gr.CheckboxGroup(list(ALL_ADAPTERS.keys()),
339
+ value=list(ALL_ADAPTERS.keys()))
340
+ query = gr.Textbox()
341
+ table = gr.Dataframe(headers=["Agency","Title","URL","Hash","Latency"])
 
 
 
 
 
342
  gallery = gr.HTML()
343
+ status = gr.Textbox(lines=10)
344
+ gr.Button("Search").click(run_search, [query, agencies],
345
+ [table, gallery, status])
346
 
347
+ with gr.Tab("Court / CM-ECF"):
348
+ gr.File(label="Download Court Bundle").upload(
349
+ lambda: generate_court_bundle()
 
350
  )
351
 
352
+ with gr.Tab("Trust & Governance"):
353
+ gr.HTML(
354
+ "<iframe src='/gov/index.html' "
355
+ "style='width:100%;height:700px;border:1px solid #ccc'></iframe>"
 
 
 
356
  )
357
 
358
+ app = gr.mount_gradio_app(fastapi_app, app, path="/")
 
 
 
 
 
 
359
 
360
+ app.js = """
361
+ window.askAI = function(idx) {
362
+ fetch('/ask_ai?index=' + idx)
363
+ }
364
+ """