5.2 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-research-agent-search | 02 | search |
|
|
|
|
|
|
Phase 07 Plan 02: Natural Language Search Summary
Natural language inventory search via Tier 1 LLM (Gemma 4): user query sanitized, translated to structured filter params (catalog_status + name_contains), applied in-memory against ListDevices(200), returned as InventoryItem JSON; dashboard gains a debounced NL search input with volt accent styling and fallback to substring match on LLM parse failure.
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | SearchHandler — NL query to NetBox filter | 9db7707 |
search.go, search_test.go, router.go, main.go |
| 2 | Frontend NL search bar + api.ts wiring | 7db093c |
api.ts, FilterBar.tsx, DashboardPage.tsx |
What Was Built
Backend (Task 1)
internal/api/handlers/search.go provides SearchHandler with SearchDevices(w, r):
- Validates
qparam — returns 400 if empty - Sanitizes query: strips non-printable chars, truncates to 200 runes (T-07-05)
- Calls
tier1.TextCompletewith an extraction prompt requesting{"catalog_status","name_contains","tag"}JSON - Calls
extractJSONto strip markdown fences before parse (LLM sometimes wraps in code blocks) - On parse failure: logs warning, falls back to
NameContains = rawQuery(never 500) tagfield logged as not implemented and ignored (MVP)- Fetches
ListDevices(ctx, 200), applies in-memory filter, encodes result
Router wired: GET /api/search with nil-guard 503 fallback. main.go constructs handlers.NewSearchHandler(nbClient, tier1) and passes to NewRouter.
TDD: 4 tests written before implementation — TestSearch_EmptyQ, TestSearch_LLMParseFallback, TestSearch_CatalogStatusFilter, TestSearch_NameContainsAndStatus — all pass.
Frontend (Task 2)
web/src/lib/api.ts: fetchSearch(q) added — calls GET /api/search?q=<encoded>.
FilterBar.tsx restructured to two rows:
- Row 1: existing local text search + status select + item count + view toggle (unchanged)
- Row 2: NL input with
Sparklesicon (volt/60 tint),border-volt/20→focus:border-volt,Loader2spinner whennlSearchLoading - 400ms debounce via
useEffect + setTimeoutpattern; localnlInputValuestate, propagates viaonNlQueryChange
DashboardPage.tsx:
nlQuerystate +useQuery({ queryKey: ['search', nlQuery], enabled: nlQuery.trim().length > 2 })displayItems = nlQuery > 2 ? searchResults : filtered— local filter fully preserved when NL empty- Loading/empty state messages adapt to NL vs local mode
Threat Mitigations Applied
| Threat | Mitigation |
|---|---|
| T-07-05 | sanitizeQuery: non-printable chars stripped, truncated to 200 runes |
| T-07-06 | Only catalog_status, name_contains, tag extracted; unknown keys ignored; fallback on parse failure |
Deviations from Plan
Auto-added: extractJSON helper
Found during: Task 1 implementation
Issue: LLMs commonly wrap JSON responses in markdown code fences (```json ... ```), which breaks json.Unmarshal directly
Fix: Added extractJSON(s string) string that finds first { and last } to extract the JSON object before parsing
Files modified: internal/api/handlers/search.go
Rule: Rule 2 — missing critical functionality (robustness of LLM output parsing)
No other deviations — plan executed as specified.
Known Stubs
None — all data paths are wired end-to-end.
Threat Flags
None — no new network endpoints or trust boundaries beyond what the plan specified.
Self-Check: PASSED
- internal/api/handlers/search.go: exists
- internal/api/handlers/search_test.go: exists, 4 tests pass
- internal/api/router.go: GET /api/search wired
- web/src/lib/api.ts: fetchSearch exported
- web/src/pages/DashboardPage.tsx: nlQuery state + useQuery present
- web/src/components/inventory/FilterBar.tsx: NL input with debounce present
- go build ./...: clean
- npm run build: clean (built in 3.14s)
- Commits
9db7707and7db093c: verified in git log