import { useMemo, useState } from "react"; type Definition = { source: string; title: string; url: string; definition: string; }; type BulkTermMeta = { definitions_count: number; taxonomy_count: number; }; type BulkTermResult = { term: string; results: Definition[]; taxonomy?: TaxonomyMatch[]; meta: BulkTermMeta; error?: string | null; }; type BulkDefinitionResponse = { terms: string[]; results: Record; request_id?: string | null; }; type TaxonomyMatch = { category: string; class_name: string; class_code: string; type_description?: string | null; type_code?: string | null; annex?: string | null; full_name: string; }; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL?.toString() || "http://localhost:8000"; const MAX_TERMS = 5; export default function App() { const [termInput, setTermInput] = useState(""); const [resultsByTerm, setResultsByTerm] = useState< Record >({}); const [orderedTerms, setOrderedTerms] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const parsedTerms = useMemo(() => { const terms: string[] = []; const seen = new Set(); for (const line of termInput.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || seen.has(trimmed)) continue; terms.push(trimmed); seen.add(trimmed); } return terms; }, [termInput]); const hasTooManyTerms = parsedTerms.length > MAX_TERMS; const canSearch = parsedTerms.length > 0 && !hasTooManyTerms && !loading; const apiUrl = useMemo(() => { const url = new URL("/api/definitions/bulk", API_BASE_URL); return url.toString(); }, [API_BASE_URL]); const summary = useMemo(() => { let definitions = 0; let taxonomy = 0; let failed = 0; for (const term of orderedTerms) { const item = resultsByTerm[term]; if (!item) continue; if (item.error) failed += 1; definitions += item.meta?.definitions_count ?? item.results?.length ?? 0; taxonomy += item.meta?.taxonomy_count ?? item.taxonomy?.length ?? 0; } return { definitions, taxonomy, failed }; }, [orderedTerms, resultsByTerm]); const handleSearch = async (event: React.FormEvent) => { event.preventDefault(); if (!canSearch) return; setLoading(true); setError(null); try { const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ terms: parsedTerms }), }); if (!response.ok) { let message = "Failed to fetch definitions."; try { const payload = (await response.json()) as { detail?: string }; if (payload?.detail) { message = payload.detail; } } catch { // ignore JSON parsing errors } throw new Error(message); } const data = (await response.json()) as BulkDefinitionResponse; setOrderedTerms(data.terms ?? parsedTerms); setResultsByTerm(data.results ?? {}); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); setOrderedTerms([]); setResultsByTerm({}); } finally { setLoading(false); } }; return (

TermSearch

Oil & Gas term definitions

Search multiple glossary sources from a single interface.

{parsedTerms.length}/{MAX_TERMS} terms