A minimal, framework-free search page served by the llm service itself, in the same editorial style as the chat page, so the retrieval endpoints (#570) are usable from the dashboard and not only via curl. - src/llm/web/search.html: query box + collection/kind/docket/year filters (exactly the values the API validates), results with kind, linked label, date, docket/comment id, title and snippet, a "similar" toggle per result carrying a bib item_key (renders /similar/{key} in place), limit/offset pagination, and the search state mirrored into the query string so a search is shareable and survives a reload. Loading/error states surface the API's own 400/404 `detail`. All API data becomes text nodes — nothing is assigned to innerHTML — and the page carries no external script/style URL, so it renders behind oauth2-proxy with no outbound CDN access (fonts fall back to the local serif stack). An "llm:" tag filter is left as an HTML comment until the tagging chain (#575) makes chunks carry those tags. - api.py: `GET /ui/search` (HTMLResponse, like `/` serves chat.html), registered with and without the trailing slash so the headless probe's readiness check gets a 200 instead of a 307 redirect. - chat header → "Search", search header → "Chat". - tests/llm/test_api.py::TestSearchPage: the route serves the form and every allowed filter value, the trailing-slash form works, the results/similar/pagination/error code is present, the page has no external script/style URL and no innerHTML assignment, and both nav links resolve to routes that return 200. Headless probe, once the image is rolled (do not run before the roll — it probes the live service): python dev/scripts/nb_fe_smoke.py \ --url http://llm:8000/ui/search --network gateway nb_fe_smoke's --url mode probes `<base>/` and `<base>/?file=<notebook>` and fails on any console or page error; with the base above, both are the search page (the unused ?file= query is ignored by the route), which is why the route also answers the trailing-slash form.
16 KiB
16 KiB