lollipop match 2/2
This commit is contained in:
parent
6529f9d49a
commit
57488b529f
@ -1,274 +1,181 @@
|
||||
# Places LLM “AI Filters” — investigation & draft plan
|
||||
# Places AI grid filters
|
||||
|
||||
This document captures the current codebase touchpoints and a proposed architecture for **customizable grid-level LLM filters**: user-defined prompts, **target fields** exposed as **MUI DataGrid** columns/filters, persisted **cache** on `places.meta`, definitions in **user secrets**, and **batch** execution with **SSE** progress—without bloating `GridSearchResults.tsx` further.
|
||||
This document describes the **AI grid filters** feature: user-defined prompts stored in **user secrets**, batch LLM evaluation over grid search results, results cached on **`places.meta`**, and **MUI DataGrid** columns driven by those definitions.
|
||||
|
||||
### Implementation order (agreed)
|
||||
|
||||
1. **Server first** — New HTTP handlers and kbot batch/single logic live in **`server/src/products/places/llm.ts`** (same module family as `handleGetPlaceInfo` / `handleGetLlmRegionInfo`). Register routes next to existing place LLM routes in **`routes.ts`** / `PlacesProduct` as today. Split into e.g. `llm-grid-filters.ts` only if the file grows unwieldy.
|
||||
2. **UI second** — New components for the **filter editor**: same interaction pattern as the **Context** editor (toolbar control → **collapsible panel** below the toolbar with textarea / fields, commit-on-blur where appropriate). **Do not** park this next to the view-mode buttons as a second “AI” row long term.
|
||||
3. **Placement** — **Collapse AI filters under “Grid Filters”**: the **Grid filters** control (`SlidersHorizontal` + label in `GridSearchResults.tsx`) expands a section that includes both **saved type excludes** (current behavior) and **AI filter definitions** (add/remove/enable, prompts, target field, run on selection / all). Optionally fold or relocate the standalone **Sparkles / Context** strip into this panel so grid-scoped AI lives in one place.
|
||||
4. **MUI last** — **`extraColumns`**, **`useAiGridColumns`**, and **`CompetitorsGridView`** wiring once API + editor exist.
|
||||
It replaces the older **per-search “potential customer” context** on place summaries; scoring and extraction are **per filter definition** under `meta.llm_grid_filters`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Goals
|
||||
## Overview
|
||||
|
||||
1. **Remove** campaign-specific **`potentialCustomer`** from the place LLM summary flow (prompt + client usage) in favor of **generic, user-defined** “AI filters.” : done
|
||||
2. **Extend** the existing **“Grid Filters”** UX (today: toggle + saved **type excludes** from secrets + MUI filters) with **“AI Filters”**: add/remove/enable filter definitions; run them over **current search** and/or **selected rows**.
|
||||
3. **Persist filter definitions** in **`user_secrets.settings`** (same pattern as `gridsearch_exclude_types`).
|
||||
4. **Persist per-place LLM outputs** in **`public.places.meta`** (JSONB), including **prompt identity**, **result**, **`computed_at`**, and **`source_user_id`** (to allow reuse / shared cache semantics).
|
||||
5. **Server**: a **generic batch** runner (many places × many filters) with **SSE** notifications to the client, aligned with existing grid search streaming.
|
||||
6. **Frontend**: extract **dynamic columns + row projection** into dedicated modules so `GridSearchResults.tsx` stays orchestration-only.
|
||||
1. **Definitions** live in `user_secrets.settings.places_ai_grid_filters` (array), edited in the **Grid filters** panel when the grid search is expanded.
|
||||
2. **Runs** call `POST /api/places/llm-filters/run` with `placeIds` and `filterIds`; the server evaluates each pair, merges into `places.meta.llm_grid_filters`, and emits **SSE** events on `GET /api/places/llm-filters/stream?runId=…`.
|
||||
3. **Columns** are built client-side (`useAiGridColumns`) from enabled definitions: `GridColDef.field === targetField`, cell value from `row.llm_grid_filters[filterId].value` (by **filter UUID**, not by `targetField` key in meta — see below).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph secrets["User secrets"]
|
||||
defs["places_ai_grid_filters[]"]
|
||||
end
|
||||
subgraph server["Server"]
|
||||
run["POST /llm-filters/run"]
|
||||
llm["grid-filter.md + LLM"]
|
||||
meta["places.meta.llm_grid_filters"]
|
||||
end
|
||||
subgraph client["Client"]
|
||||
sse["EventSource stream"]
|
||||
grid["DataGrid extraColumns"]
|
||||
end
|
||||
defs --> run
|
||||
run --> llm --> meta
|
||||
run --> sse
|
||||
sse --> grid
|
||||
meta --> grid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Current state (inventory)
|
||||
## Filter definitions (`places_ai_grid_filters`)
|
||||
|
||||
### 2.1 Grid “Grid Filters” toggle (`GridSearchResults.tsx`)
|
||||
Stored as JSON via existing **`/api/me/secrets`** (same pattern as `gridsearch_exclude_types`). TypeScript: `PlacesAiGridFilterDef` in `src/modules/places/client-gridsearch.ts`.
|
||||
|
||||
- State: `applySavedExcludesInGrid` toggles between:
|
||||
- **Off**: `filteredCompetitors` (server/client prefilter) + URL-persisted grid filters.
|
||||
- **On**: full `competitors` list + **seeded MUI column filters** on `types` via `seedExcludeTypeFilters={excludedTypes}` (`PlacesGridView` / `useGridColumns` custom operator `excludesAnyOf`).
|
||||
- **Saved excludes** are loaded/stored via **`/api/me/secrets`** as `gridsearch_exclude_types` (see `client-gridsearch.ts` and `PlacesLibrary.postPlacesGridSearchExpand` reading `getUserSecrets`).
|
||||
| Field | Description |
|
||||
|--------|-------------|
|
||||
| `id` | Stable UUID; **key** for `meta.llm_grid_filters[id]` and SSE payloads. |
|
||||
| `label` | Optional display name for the column header. If empty or the legacy default “New filter”, the UI falls back to **`targetField`**. |
|
||||
| `prompt` | User instructions combined with the server template `grid-filter.md`. |
|
||||
| `targetField` | Slug for **`GridColDef.field`** (must match `^[a-z][a-z0-9_]{0,63}$`). Must **not** collide with built-in columns (`types`, `title`, … — see `RESERVED_GRID_FIELDS` in `llm.ts`). |
|
||||
| `enabled` | Optional; default **on**. Disabled filters are skipped by the runner. |
|
||||
| `valueType` | Optional: **`auto`** (default), **`number`**, or **`string`**. Controls coercion of the LLM `value` for sorting/filtering and MUI column `type`. Included in **prompt hash** so changing it invalidates cached cells until you re-run. |
|
||||
|
||||
**Target UX:** Introduce a **`gridFiltersOpen`** (name TBD) collapsible **below** the main toolbar row—same structural pattern as **`aiContextOpen`** + violet panel (see ~`{aiContextOpen && (…textarea…)}`). **Grid filters** becomes the parent affordance: expanded panel holds **type-exclude / MUI hint copy** and the **AI filter editor** (not a duplicate top-level “AI” entry). Today the **Sparkles** control sits beside view modes; plan is to **merge** grid-related AI into this **Grid filters** section.
|
||||
|
||||
**Implication:** “AI Filters” **compose** with existing grid behavior: same DataGrid, extra **dynamic columns** whose `field` names match **target fields** stored under `meta`.
|
||||
|
||||
### 2.2 MUI grid columns (`useGridColumns.tsx`, `PlacesGridView.tsx`)
|
||||
|
||||
- Columns are **mostly static** (`thumbnail`, `title`, `types`, …).
|
||||
- **Community DataGrid** constraint: **one filter item** in the filter model in some modes; type excludes are encoded as a **single** synthetic filter (`types` + `excludesAnyOf`).
|
||||
- **Extension point:** `GridColDef[]` can be **concatenated** with dynamic defs built from enabled AI filters (string/number columns + `valueGetter` reading `row.meta?.llm_filters?.[key]` or a flattened path).
|
||||
|
||||
### 2.3 Place LLM today (`server/src/products/places/llm.ts`, `place-info.md`)
|
||||
|
||||
- `GET` handler resolves **`runId`** / manual **`context`**, builds `${grid_search_context}`, runs kbot completion, returns summary JSON for the place detail tab.
|
||||
- **Product direction:** campaign fit moves to **per-filter** outputs in **`meta.llm_grid_filters`**, not a dedicated **`potentialCustomer`** field on the summary response (removed from prompt/UI per §7).
|
||||
|
||||
### 2.4 `places.meta` (`server/src/products/places/db-places.ts`, `places.ts`)
|
||||
|
||||
- **`updatePlaceMeta(placeId, meta)`** merges/replaces JSONB as implemented today.
|
||||
- Many code paths **strip** heavy keys (`pages`, `pageErrors`, `bodyHtml`) when sending to clients; any new subtree should be **namespaced** (e.g. `meta.llm_grid_filters`) to avoid accidental huge payloads if logs/debug include raw pages.
|
||||
|
||||
### 2.5 SSE today (`PlacesProduct.handleGetPlacesGridSearchStream`)
|
||||
|
||||
- Route: **`/api/places/gridsearch/{id}/stream`** (`routes.ts`).
|
||||
- Uses Hono **`streamSSE`**, subscribes to **`EventBus`**: `job:progress`, `job:complete`, `job:failed`.
|
||||
- Events already include **`location`**, **`node`**, **`job_result`**, with **hydration** from Postgres for fresh `meta` / top-level fields.
|
||||
|
||||
**Implication:** batch AI filter runs can either:
|
||||
|
||||
- **A)** Emit new **`EventBus`** event types (e.g. `ai_filter_progress`) **on the same `jobId` stream** (client multiplexes), or
|
||||
- **B)** Add a **dedicated SSE route** (e.g. `/api/places/llm-filters/stream?jobId=…`) that only carries filter events.
|
||||
|
||||
Option **A** minimizes concurrent connections if the user already holds the grid search stream open; **B** is simpler to reason about if batch jobs are decoupled from the C++ pipeline. **Recommendation:** start with **B** or a **shared helper** that writes SSE frames, then **optionally merge** into the grid stream once event shapes stabilize.
|
||||
**UI:** `GridFiltersPanel.tsx` — add/remove filters, save definitions, run on selection or all rows (owner-only).
|
||||
|
||||
---
|
||||
|
||||
## 3. Proposed data model
|
||||
## Per-place cache (`meta.llm_grid_filters`)
|
||||
|
||||
### 3.1 User secrets — filter definitions
|
||||
|
||||
Store under e.g. **`settings.places_ai_grid_filters`** (name TBD):
|
||||
Merged with `updatePlaceMeta`. Shape per filter id:
|
||||
|
||||
```ts
|
||||
type AiGridFilterDef = {
|
||||
id: string; // stable UUID, used in meta keys
|
||||
label: string; // UI label
|
||||
prompt: string; // user text; combined with server base template
|
||||
targetField: string; // slug: must match GridColDef.field, e.g. "ai_fit_seo"
|
||||
enabled: boolean; // default on/off for new sessions
|
||||
// optional: valueType: 'string' | 'number' | 'enum'
|
||||
// optional: enumOptions for MUI singleSelect
|
||||
meta.llm_grid_filters[filterId] = {
|
||||
value: string | number; // normalized for the grid (see below)
|
||||
raw: { value, value_type?, detail? }; // parsed LLM JSON (value may match stored value after normalization)
|
||||
promptHash: string; // invalidates when template version, prompt, targetField, or valueType changes
|
||||
model: string; // resolved model id at run time
|
||||
computedAt: string; // ISO timestamp
|
||||
sourceUserId: string; // user who ran the job
|
||||
};
|
||||
```
|
||||
|
||||
- **Validation:** `targetField` — regex `^[a-z][a-z0-9_]{0,63}$`, no collision with built-in fields (`types`, `title`, …).
|
||||
- **API:** extend existing **`PATCH /api/me/secrets`** merge behavior (same as `gridsearch_exclude_types`).
|
||||
|
||||
### 3.2 Per–grid-search activation
|
||||
|
||||
Persist **which filters are active for this run** without a DB migration:
|
||||
|
||||
- **`grid_search_runs.settings`** JSON (already used for `is_public`, etc.) **or**
|
||||
- **localStorage** key `gridsearch_ai_filters_${jobId}` listing enabled `filter id`s.
|
||||
|
||||
**Tradeoff:** DB settings sync across devices; localStorage is faster to ship. Plan: **settings JSON** if multi-device consistency matters; else **localStorage** for v1.
|
||||
|
||||
### 3.3 `places.meta` cache subtree
|
||||
|
||||
Proposed shape (exact names flexible):
|
||||
|
||||
```ts
|
||||
meta.llm_grid_filters = {
|
||||
[filterId: string]: {
|
||||
value: string | number | null; // primary cell value for MUI + quick filter
|
||||
raw?: unknown; // optional full model JSON if needed
|
||||
promptHash: string; // hash of def.prompt + base template version
|
||||
model: string; // LLM model id
|
||||
computedAt: string; // ISO
|
||||
sourceUserId: string; // user who paid for the LLM call / first writer
|
||||
// optional: shareable: boolean
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Sharing / reuse:** If another user runs the **same** `(place_id, filterId, promptHash)` and a row already exists, the server may **skip** LLM and **copy** or **reference** the cached value (policy: always allow read if place is visible to user; **write** only if owner or explicit share). **`sourceUserId`** documents provenance for audits.
|
||||
**Skip / idempotency:** If `promptHash` for this filter already matches the stored entry, the server **skips** another LLM call for that `(placeId, filterId)` until the definition or template version changes.
|
||||
|
||||
---
|
||||
|
||||
## 4. Server API sketch
|
||||
## LLM contract
|
||||
|
||||
**Home for handlers:** implement in **`server/src/products/places/llm.ts`** (re-export or register from `PlacesProduct` like existing LLM routes). Keeps kbot tasks, Zod `format`, and prompt path helpers in one place.
|
||||
### Prompt template
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `POST /api/places/llm-filters/run` | Body: `{ placeIds: string[], filterIds: string[], jobId?: string }`. Enqueues work, returns `{ runId }`. |
|
||||
| `GET /api/places/llm-filters/stream?runId=` **or** reuse grid SSE | SSE: `progress` (placeId, filterId, status), `cell` (placeId, filterId, value), `complete`, `error`. |
|
||||
| `GET /api/places/{id}` | Existing; ensure `meta.llm_grid_filters` is included after merge (respect current stripping rules). |
|
||||
- Default path: `server/data/products/places/llm/grid-filter.md`.
|
||||
- Override: env **`LLM_GRID_FILTER_PROMPT_PATH`** (absolute or relative to server cwd as implemented by `resolvePromptPath`).
|
||||
- Substitutions include place fields from `buildPlaceSubstituteVars`, plus `filter_prompt`, `filter_label`, `target_field`, **`value_type_hint`** (from `valueType`: auto / number / string).
|
||||
|
||||
**Internals:**
|
||||
### JSON response (`AiGridFilterResultSchema`)
|
||||
|
||||
- Drive outputs through **kbot structured data** (see §4.1), not ad-hoc “JSON in prose” parsing only.
|
||||
- **Concurrency:** bounded pool (env `LLM_GRID_FILTER_CONCURRENCY`); **idempotent** writes to `meta` (compare `promptHash`).
|
||||
- **Auth:** same as place read; batch aborted if any `place_id` not accessible.
|
||||
| Field | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `value` | `string \| number` | Primary cell value (short; no PII per prompt). |
|
||||
| `value_type` | optional `"number" \| "string"` | **Guided** hint: use `"number"` for numeric scales (1–5) so the server coerces and DataGrid can filter numerically. |
|
||||
| `detail` | optional `string` | Longer rationale. |
|
||||
|
||||
### 4.1 Kbot structured output (`format`) — align with `kbot/src/zod_schema.ts`
|
||||
After parse, **`normalizeAiGridFilterValue`** applies **user `valueType`** and/or **`value_type`** and/or numeric-string heuristics so `meta` stores a **number** when appropriate (see `server/src/products/places/llm.ts`).
|
||||
|
||||
The kbot CLI and **`run()`** (`@polymech/kbot-d`, used from `server/src/products/places/llm.ts`) support a **`format`** option on **`IKBotTask`**, declared in **`packages/kbot/src/zod_schema.ts`** (`OptionsSchema` → generated `IKBotOptions`).
|
||||
### Template version
|
||||
|
||||
**What it does**
|
||||
|
||||
- **`format`** is transformed (CLI) from:
|
||||
- a path to a **JSON Schema** `.json` file,
|
||||
- a **JSON Schema** object string,
|
||||
- a **Zod** schema string, or
|
||||
- an in-memory **Zod** schema (`zodResponseFormat` branch in `zod_schema.ts`).
|
||||
|
||||
- Under the hood, OpenAI’s **`zodResponseFormat(zodSchema, name)`** (`openai/helpers/zod`) produces a **`response_format`** payload for **chat.completions**.
|
||||
|
||||
- **`run-completion.ts`** passes it through:
|
||||
|
||||
```ts
|
||||
await client.chat.completions.create({
|
||||
model: options.model,
|
||||
messages: params.messages,
|
||||
response_format: options.format as any,
|
||||
})
|
||||
```
|
||||
|
||||
So the **model is constrained by the API** to emit JSON matching the schema, not only by prompt text.
|
||||
|
||||
**Recommendation for AI grid filters**
|
||||
|
||||
1. **Fixed Zod schema** in pm-pics (or shared), e.g. `AiGridFilterResultSchema`:
|
||||
|
||||
- `value`: `string` (primary cell; short, filterable) or `z.union([z.string(), z.number()])` if you need numeric columns.
|
||||
- Optional: `detail`, `confidence`, `labels`, etc., as needed for tooltips or future UI.
|
||||
|
||||
2. Build the task roughly as:
|
||||
|
||||
- `import { zodResponseFormat } from 'openai/helpers/zod'`
|
||||
- `format: zodResponseFormat(AiGridFilterResultSchema, 'ai_grid_filter')` (name stable for provider logs).
|
||||
- `mode: 'completion'`, `prompt` = base markdown + substitutions + user **`filter_prompt`**.
|
||||
|
||||
3. **Server-side**: still **`schema.parse(JSON.parse(content))`** (or equivalent) after the completion returns, so bad provider output fails fast and never writes garbage to **`meta`**.
|
||||
|
||||
4. **promptHash** should include a hash of:
|
||||
- base template text,
|
||||
- **serialized Zod/JSON Schema** (or a fixed **schema version** constant),
|
||||
- user filter prompt,
|
||||
so cache invalidation tracks **schema** changes, not only wording.
|
||||
|
||||
**What to avoid**
|
||||
|
||||
- Relying on **`runLlmJsonCompletion`**-style **regex strip of ` ```json `** as the **only** contract; keep it as a fallback only if **`format`** is unsupported for a given router/model.
|
||||
- Letting users paste **arbitrary JSON Schema** into secrets **without validation** (injection / oversized schema). Prefer **one server-owned Zod schema** for v1; optional **preset enum variants** (e.g. “numeric score 1–5”) as separate fixed schemas later.
|
||||
|
||||
**Optional asset**
|
||||
|
||||
- Check in **`data/products/places/llm/grid-filter.schema.json`** mirroring the Zod shape if you want CLI parity with kbot’s **file path** `format` loading (`zod_schema.ts` lines 298–308).
|
||||
- Constant **`LLM_GRID_FILTER_TEMPLATE_VERSION`** (currently **`2`**) is part of **`promptHash`**. Bump it when the base template or normalization rules change in a way that should force re-computation.
|
||||
|
||||
---
|
||||
|
||||
## 5. Prompt strategy (`place-base-filter.md` or not)
|
||||
## Environment variables (grid filter LLM)
|
||||
|
||||
**Recommendation:**
|
||||
Resolution order for **provider**:
|
||||
|
||||
- **One server-owned base template** (e.g. `data/products/places/llm/grid-filter.md`) that:
|
||||
- Injects **place facts** (reuse `buildPlaceSubstituteVars` minus campaign context, or a slimmer variant).
|
||||
- Injects **`${filter_prompt}`** from the user definition.
|
||||
- States **privacy / no PII in output** (same spirit as `place-info.md`).
|
||||
- **Shape of the answer** is enforced by **`format` + Zod** (§4.1), not by asking for “raw JSON only” in the prompt alone.
|
||||
- **Per-filter `.md` files** are **not** required for v1; user prompt in secrets is enough. Add optional **advanced** “prompt file” path later if needed.
|
||||
1. `LLM_GRID_FILTER_PROVIDER`
|
||||
2. `LLM_PLACE_FILTERS_PROVIDER`
|
||||
3. `LLM_PLACE_PROVIDER`
|
||||
4. `LLM_REGION_PROVIDER`
|
||||
5. default `openrouter`
|
||||
|
||||
This avoids proliferating files while keeping **consistent** privacy rules and **machine-verifiable** outputs.
|
||||
Resolution order for **model**:
|
||||
|
||||
1. `LLM_GRID_FILTER_MODEL`
|
||||
2. `LLM_PLACE_FILTERS_MODEL`
|
||||
3. `LLM_PLACE_MODEL`
|
||||
4. `LLM_REGION_MODEL`
|
||||
5. default `openai/gpt-5.2`
|
||||
|
||||
Set e.g. `LLM_PLACE_FILTERS_PROVIDER` / `LLM_PLACE_FILTERS_MODEL` in `server/.env` when you want grid filters to use a dedicated model without changing place-info or region LLMs.
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontend (after server): Grid Filters panel + components
|
||||
## HTTP API
|
||||
|
||||
**Pattern:** Reuse the **Context** editor UX: a **toolbar button** toggles open state; when open, a **full-width panel** under the toolbar (bordered card, compact labels) holds forms. Apply the same for **AI filters** inside the **Grid filters** region—**not** a separate floating section next to view modes.
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `POST` | `/api/places/llm-filters/run` | Body: `{ placeIds: string[], filterIds: string[], lang?: string }`. Returns **`202`** with `{ data: { runId } }`. Max **80** place ids, **20** filter ids (see route validation). |
|
||||
| `GET` | `/api/places/llm-filters/stream?runId=` | **SSE** (`text/event-stream`). For browsers, pass **`?token=`** (auth) when using `EventSource`. Events include progress, per-cell updates, completion, errors. |
|
||||
|
||||
Suggested new files under `src/modules/places/gridsearch/`:
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|------------------|
|
||||
| `GridFiltersPanel.tsx` (or split) | Collapsible content for **Grid filters**: type-exclude explanation, **AI filter list** (add/remove/enable), prompts, target field, run actions. Consumed when `gridFiltersOpen` (or merged state with `applySavedExcludesInGrid`). |
|
||||
| `aiGridFiltersState.ts` | Load/save defs from secrets; enabled set per `jobId`; localStorage helpers if used. |
|
||||
| `useAiGridColumns.ts` | Build `GridColDef[]` from enabled defs + `valueGetter` from `meta.llm_grid_filters`. |
|
||||
| `PlacesGridView.tsx` (or wrapper) | Accept **optional** `extraColumns: GridColDef[]` merged after static columns. |
|
||||
|
||||
`GridSearchResults.tsx` stays layout-only: view modes, **`GridFiltersPanel`** slot, **`CompetitorsGridView`** props.
|
||||
|
||||
**Phase order:** build **Grid Filters** collapsible + editor **after** endpoints exist (mock with static data if needed); add **MUI dynamic columns** once `meta.llm_grid_filters` flows from the server.
|
||||
Handlers: `handlePostLlmFiltersRun`, `handleGetLlmFiltersStream` in `server/src/products/places/llm.ts`; route definitions in `server/src/products/places/routes.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Migration from `potentialCustomer`
|
||||
## Frontend
|
||||
|
||||
1. Remove **`potentialCustomer`** from `place-info.md` and **Zod/UI** types in `LocationDetail` summary tab. : done
|
||||
2. If product still needs a **single** “campaign fit” score, implement it as **one AI filter def** with a default prompt (stored in secrets or shipped as preset), not a special-case column in the LLM summary. : eventually yes, we might have default wizard driven filters
|
||||
| Area | Behavior |
|
||||
|------|----------|
|
||||
| `GridSearchResults.tsx` | Loads filter defs (`getPlacesAiGridFilters`), passes **`extraColumns={useAiGridColumns(aiFilterDefs)}`** to the grid for **owners**; wires `GridFiltersPanel`, SSE refresh hooks. |
|
||||
| `useAiGridColumns.tsx` | Builds `GridColDef[]`: `field: targetField`, `headerName` from label / targetField, **`type: 'number' \| 'string'`** when `valueType` is set, `valueGetter` reads `row.llm_grid_filters[def.id].value`. |
|
||||
| `PlacesGridView.tsx` | Merges **`extraColumns`** after base columns; filter model sync when toggling **“Apply saved type excludes as DataGrid filters”** without remounting the grid (preserves column visibility/order). |
|
||||
| `GridFiltersPanel.tsx` | Edit definitions, save to secrets, run batch. |
|
||||
|
||||
**Auth:** Only the search **owner** sees the AI filter editor and dynamic columns; viewers use the standard grid without those columns.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risks & open questions
|
||||
## Numeric scores (1–5, etc.)
|
||||
|
||||
- **GET URL length:** batch IDs may require **POST**-only APIs (already directionally true for run).
|
||||
- **MUI single-filter constraint:** adding many AI columns may interact badly with **community** grid limits; may need to **lift** filter encoding (documented in `useGridColumns`) when AI columns need filtering simultaneously with types.
|
||||
- **Meta size:** cap string length per cell; strip `raw` in list views if large.
|
||||
- **Cost / abuse:** rate limits per user; optional confirmation before “run on all.”
|
||||
- **Public / shared grid searches:** hide or disable **AI filter** columns and editor affordances for viewers when `is_public` and `!isOwner` (same spirit as hiding sensitive tools); only owner runs LLM fills that write `meta`.
|
||||
1. Prefer **`valueType: number`** on the filter (dropdown in the panel) so the column is typed as numeric in MUI.
|
||||
2. In the prompt, ask for a **JSON number** and optionally **`"value_type":"number"`** in the model output (see `grid-filter.md`).
|
||||
3. **Re-run** AI filters after changing `valueType` or normalization logic so old string cells are replaced.
|
||||
|
||||
---
|
||||
|
||||
## 9. Suggested implementation phases (matches § order at top)
|
||||
## Related features
|
||||
|
||||
1. **Server (`llm.ts` + routes):** Zod **`AiGridFilterResultSchema`** + **`zodResponseFormat`**; single-place single-filter handler (debug); **`POST …/run`** + **`GET …/stream`** (or EventBus bridge); **`updatePlaceMeta`** merge with **`promptHash`** checks.
|
||||
2. **Client — Grid Filters UI:** **`GridFiltersPanel`** (or equivalent); filter editor **like Context** (collapsible under **Grid filters**); wire secrets **`PATCH /api/me/secrets`** for definitions; run actions calling batch API + SSE.
|
||||
3. **Client — MUI:** **`useAiGridColumns`** + **`CompetitorsGridView`** `extraColumns`; row refresh on SSE / refetch.
|
||||
4. **Product cleanup:** `potentialCustomer` removal from place-info pipeline — **done** where noted in §1 / §7.
|
||||
- **Saved type excludes:** Same **Grid filters** panel; `gridsearch_exclude_types` + optional **seeded** `types` column filter when “Apply saved type excludes as DataGrid filters” is on (`seedExcludeTypeFilters` / `PlacesGridView`).
|
||||
- **Place tab LLM** (`place-info.md`): Separate flow; does not use `places_ai_grid_filters` definitions.
|
||||
|
||||
---
|
||||
|
||||
## 10. File reference cheat sheet
|
||||
## File reference
|
||||
|
||||
| Area | Files |
|
||||
|------|--------|
|
||||
| Grid toggle & toolbar | `src/modules/places/gridsearch/GridSearchResults.tsx` |
|
||||
| Column defs | `src/modules/places/useGridColumns.tsx`, `PlacesGridView.tsx` |
|
||||
| Secrets (exclude types) | `src/modules/places/client-gridsearch.ts`, `server/.../places/places.ts` |
|
||||
| Place LLM & **new grid-filter LLM** | `server/src/products/places/llm.ts` (handlers + kbot tasks), `server/data/products/places/llm/place-info.md`, `grid-filter.md` (TBD) |
|
||||
| Grid Filters UI / Context pattern | `src/modules/places/gridsearch/GridSearchResults.tsx` (toolbar; collapse under Grid filters per §6) |
|
||||
| SSE | `server/src/products/places/index.ts` (`handleGetPlacesGridSearchStream`), `server/src/products/places/routes.ts` |
|
||||
| Meta persistence | `server/src/products/places/db-places.ts` (`updatePlaceMeta`) |
|
||||
| Shared place type | `packages/ui/shared/src/competitors/schemas.ts` (`PlaceSchemaFull`) |
|
||||
| **Kbot `format` / options** | `polymech-mono/packages/kbot/src/zod_schema.ts` (`OptionsSchema`, `format` → `zodResponseFormat`) |
|
||||
| **Kbot completion → API** | `polymech-mono/packages/kbot/src/commands/run-completion.ts` (`response_format: options.format`) |
|
||||
| **Structured output example** | `polymech-mono/packages/kbot/src/examples/core/iterator-markdown-example.ts` (JSON Schema in `format`) |
|
||||
| Server logic & SSE | `server/src/products/places/llm.ts` |
|
||||
| Routes | `server/src/products/places/routes.ts`, `server/src/products/places/index.ts` |
|
||||
| Prompt | `server/data/products/places/llm/grid-filter.md` |
|
||||
| Client API & types | `src/modules/places/client-gridsearch.ts` |
|
||||
| Grid UI | `src/modules/places/gridsearch/GridSearchResults.tsx`, `GridFiltersPanel.tsx` |
|
||||
| Dynamic columns | `src/modules/places/gridsearch/useAiGridColumns.tsx` |
|
||||
| Grid shell | `src/modules/places/PlacesGridView.tsx`, `useGridColumns.tsx` |
|
||||
|
||||
---
|
||||
|
||||
*Living plan — implementation follows server (`llm.ts`) → Grid Filters panel + editor → MUI columns.*
|
||||
## Operational notes
|
||||
|
||||
- **Cost / abuse:** Batch size is capped; consider rate limits per deployment.
|
||||
- **Meta size:** Keep `value` / `detail` short; avoid large blobs in `raw` if list payloads grow.
|
||||
- **Public grid searches:** Non-owners do not get AI filter columns or the editor; LLM writes require an authenticated owner context for runs tied to their secrets.
|
||||
|
||||
---
|
||||
|
||||
*Implementation reference — behavior is defined by `server/src/products/places/llm.ts` and the routes above.*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user