File size: 3,055 Bytes
0f4b74d
b75db41
 
 
0f4b74d
b75db41
 
b6a7950
 
 
 
 
 
 
 
 
 
89cfad1
b75db41
89cfad1
b6a7950
 
 
 
 
 
 
 
 
 
89cfad1
 
b6a7950
b75db41
 
b6a7950
b75db41
b6a7950
 
b75db41
 
89cfad1
 
 
 
b6a7950
89cfad1
 
b75db41
89cfad1
b75db41
 
89cfad1
0f4b74d
b75db41
 
 
 
 
89cfad1
b6a7950
89cfad1
 
 
b75db41
 
 
 
 
 
 
89cfad1
b75db41
89cfad1
 
 
 
 
 
 
 
b75db41
89cfad1
 
 
b75db41
 
89cfad1
 
b75db41
 
89cfad1
 
 
 
 
 
 
 
 
b75db41
89cfad1
b75db41
89cfad1
 
 
 
 
 
 
0f4b74d
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import asyncio
import gradio as gr
from typing import List, Dict

from ingest.cia_reading_room import CIAAdapter
from ingest.fbi_vault import FBIAdapter

from ingest.cia_extended import CIAExtendedAdapter
from ingest.nsa_extended import NSAExtendedAdapter
from ingest.nro_extended import NROExtendedAdapter
from ingest.dod_special_extended import (
    AATIPExtendedAdapter,
    SAPExtendedAdapter,
    TENCAPExtendedAdapter,
    SpecialActivitiesExtendedAdapter
)

import saved_searches

BASE_ADAPTERS = [CIAAdapter(), FBIAdapter()]
EXTENDED_ADAPTERS = [
    CIAExtendedAdapter(),
    NSAExtendedAdapter(),
    NROExtendedAdapter(),
    AATIPExtendedAdapter(),
    SAPExtendedAdapter(),
    TENCAPExtendedAdapter(),
    SpecialActivitiesExtendedAdapter()
]

async def federated_search_async(query: str, extended: bool):
    adapters = BASE_ADAPTERS + (EXTENDED_ADAPTERS if extended else [])
    tasks = [a.search(query) for a in adapters]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    flat = []
    for r in results:
        if isinstance(r, list):
            flat.extend(r)
    return flat

def badge(r):
    h = r.get("health", {})
    latency = f"{h.get('latency_ms','?')}ms"
    status = "🟢" if h.get("ok") else "🔴"
    if r.get("extended"):
        return f"🟣 EXT · {status} {latency}"
    return "🟢 LIVE" if r.get("live") else "⚪ STUB"

def render(r):
    return f"""
### {r['title']}
**{badge(r)} · {r['source']}**

{r['snippet']}

🔗 {r['url']}
"""

def run_search(query, hide_stubs, extended):
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(federated_search_async(query, extended))

    saved_searches.save(query, extended)

    if hide_stubs:
        results = [r for r in results if r.get("live")]

    if not results:
        return "No results found."

    return "\n\n---\n\n".join(render(r) for r in results)

def show_saved():
    rows = saved_searches.list_saved()
    if not rows:
        return "No saved searches yet."
    return "\n".join(
        f"- **{r['query']}** (extended={r['extended']})"
        for r in rows
    )

with gr.Blocks(css="""
.gradio-container { max-width: 1100px }
""") as demo:
    gr.Markdown("# 📜 FOIA Federated Search")
    gr.Markdown(
        "Transparent federated search across public FOIA reading rooms.\n\n"
        "**Badges**: ⚪ Stub · 🟢 Live · 🟣 Extended · 🔴 Unhealthy"
    )

    with gr.Row():
        query = gr.Textbox(label="Search term", scale=3)
        hide_stubs = gr.Checkbox(label="Hide stub sources", value=False)
        extended = gr.Checkbox(
            label="Enable Extended Features",
            value=False,
            info="Advanced public sources. Disabled by default."
        )

    output = gr.Markdown()
    saved = gr.Markdown()

    with gr.Row():
        gr.Button("Search").click(
            run_search,
            inputs=[query, hide_stubs, extended],
            outputs=output
        )
        gr.Button("Saved Searches").click(show_saved, outputs=saved)

demo.launch()