udi-yac 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,14 +24,16 @@ Each is described once in [`src/app/envVars.ts`](src/app/envVars.ts); this table
24
24
 
25
25
  <!-- BEGIN generated: env vars (scripts/gen-chat-env-docs.mjs) -->
26
26
 
27
- | Variable | Default | Description |
28
- | -------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
29
- | `VITE_UDI_API_BASE_URL` | `http://localhost:8007` | UDIAgent FastAPI server URL. May also be a same-origin path (e.g. `/api/yac`) when the chat sits behind the host app’s reverse proxy. |
30
- | `VITE_UDI_DATA_PACKAGE` | `/data/hubmap/datapackage.json` | Path or URL to a `datapackage.json`. Defaults to the bundled HuBMAP snapshot synced from `sample-data/`. Ignored when `VITE_UDI_REMOTE_PACKAGE` is set. |
31
- | `VITE_UDI_REMOTE_PACKAGE` | — | Name of a server-side data package (configured on the agent via `UDI_QUERY_BACKENDS`). When set, no CSVs load in the browser: metadata comes from `/v1/yac/metadata` and queries go to `/v1/yac/query`. Takes precedence over `VITE_UDI_DATA_PACKAGE`. |
32
- | `VITE_UDI_REQUIRE_API_KEY` | `true` | Prompt the user for an OpenAI key in the UI. Defaults to `true`, but `.env.example` ships `false` because local dev runs against an agent that has its own `OPENAI_API_KEY`. |
33
- | `VITE_UDI_MODEL` | — | LLM model override. Sent **only when the user supplies their own OpenAI key** — the agent honors a requested model only alongside an `X-OpenAI-Key`, so whoever pays for the tokens picks the model. Otherwise the agent’s `GPT_MODEL_NAME` applies. |
34
- | `VITE_BASE` | `/` | Public base path for the built SPA (read in `vite.config.ts`, build-time only). The library build always uses `./`. |
27
+ | Variable | Default | Description |
28
+ | -------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
29
+ | `VITE_UDI_API_BASE_URL` | `http://localhost:8007` | UDIAgent FastAPI server URL. May also be a same-origin path (e.g. `/api/yac`) when the chat sits behind the host app’s reverse proxy. |
30
+ | `VITE_UDI_DATA_PACKAGE` | `/data/hubmap/datapackage.json` | Path or URL to a `datapackage.json`. Defaults to the bundled HuBMAP snapshot synced from `sample-data/`. Ignored when `VITE_UDI_REMOTE_PACKAGE` is set. |
31
+ | `VITE_UDI_REMOTE_PACKAGE` | — | Name of a server-side data package (configured on the agent via `UDI_QUERY_BACKENDS`). When set, no CSVs load in the browser: metadata comes from `/v1/yac/metadata` and queries go to `/v1/yac/query`. Takes precedence over `VITE_UDI_DATA_PACKAGE`. |
32
+ | `VITE_UDI_REQUIRE_API_KEY` | `true` | Prompt the user for an OpenAI key in the UI. Defaults to `true`, but `.env.example` ships `false` because local dev runs against an agent that has its own `OPENAI_API_KEY`. |
33
+ | `VITE_UDI_MODEL` | — | LLM model override. Sent **only when the user supplies their own OpenAI key** — the agent honors a requested model only alongside an `X-OpenAI-Key`, so whoever pays for the tokens picks the model. Otherwise the agent’s `GPT_MODEL_NAME` applies. |
34
+ | `VITE_UDI_READ_ONLY` | `false` | Start in read-only mode: the chat pane and the dashboard’s top bar (counts, grid settings, session import/export, downloads) are hidden, as is every editing control on the cards, while brushing, filters and the table toggle stay. `true` leaves an Explore Data button over the dashboard that opens the chat; `locked` removes it. With the chat hidden there is nothing to build a dashboard from, so pair this with `VITE_UDI_INITIAL_SESSION`. |
35
+ | `VITE_UDI_INITIAL_SESSION` | — | Path or URL to a session export — the JSON written by the dashboard’s `Session → Export session` action. Fetched at startup and used to seed the dashboard and the conversation behind it. Anything under `public/` is served from the site root, so `public/demo-session.json` is `/demo-session.json`. A malformed file fails loudly through the ErrorBoundary; a missing one only logs, and the dashboard starts empty. |
36
+ | `VITE_BASE` | `/` | Public base path for the built SPA (read in `vite.config.ts`, build-time only). The library build always uses `./`. |
35
37
 
36
38
  <!-- END generated: env vars -->
37
39
 
@@ -68,8 +70,8 @@ landed print the unknown token instead, so an old enough `udi-yac` shows
68
70
 
69
71
  The project builds as both a **library** and a **standalone app**:
70
72
 
71
- - **Library** (`pnpm build`): Exports the `UDIChat` component and `UDIChatConfig` type. Consumers provide React and render `<UDIChat>` with configuration props.
72
- - **Standalone** (`pnpm build:app`): Builds `App.tsx` as a full SPA with dev defaults.
73
+ - **Library** (`pnpm build:lib`): Exports the `UDIChat` / `UDIDashboard` components and the `UDIChatConfig` type. Consumers provide React and render `<UDIChat>` with configuration props.
74
+ - **Standalone** (`pnpm build`): Builds `App.tsx` as a full SPA with dev defaults.
73
75
 
74
76
  ### Library Usage
75
77
 
@@ -112,25 +114,117 @@ Embedding in another app? Two things worth knowing up front:
112
114
 
113
115
  ### Config Props
114
116
 
115
- | Prop | Type | Description |
116
- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
117
- | `apiBaseUrl` | `string` | Base URL for the UDIAgent API. Absolute (`https://agent.example.org`) or a same-origin path (`/api/yac`). |
118
- | `remotePackage` | `string?` | Server-side data package name. Schema/domains come from `GET /v1/yac/metadata` and queries from `POST /v1/yac/query`; no CSVs load in the browser and cross-filtering becomes commit-on-mouse-up. Takes precedence over both data-package props. |
119
- | `dataPackagePath` | `string?` | URL/path to `datapackage_udi.json`. Ignored when `dataPackage` is provided. |
120
- | `dataPackage` | `DataPackage?` | Provide a data package object directly instead of fetching from a URL. Takes precedence over `dataPackagePath`. |
121
- | `dataFieldDomains` | `DataFieldDomain[]?` | Pre-computed field domains. Skips CSV loading for domain computation when provided with `dataPackage`. |
122
- | `fetchOptions` | `RequestInit?` | Custom fetch options (headers, credentials, etc.) forwarded to all data-loading fetch calls. |
123
- | `authToken` | `string?` | JWT bearer token for API auth. Omit it when a backend proxy attaches the header instead; no `Authorization` header is then sent. |
124
- | `requireApiKey` | `boolean?` | Show API key input before chatting |
125
- | `model` | `string?` | LLM model name override |
126
- | `downloadActions` | `DownloadAction[]?` | Extra items appended to the Download Data dropdown. See [Custom download actions](#custom-download-actions). |
127
- | `entityIcons` | `EntityIconMap?` | Icon overrides for entity count chips. See [Custom entity icons](#custom-entity-icons). |
128
- | `mascot` | `ReactNode \| null?` | Replace or hide the welcome mascot. See [Custom mascot](#custom-mascot). |
129
- | `splashMessages` | `readonly string[]?` | Override or hide the randomised prompt above the mascot. See [Custom splash messages](#custom-splash-messages). |
130
- | `onEvent` | `TrackerFn?` | Analytics callback invoked on key user actions. See [Analytics events](#analytics-events). |
131
- | `palette` | `UDIPalette?` | Default color palette for every chart and table. See [Custom color palette](#custom-color-palette). |
132
- | `className` | `string?` | CSS class for the root element |
133
- | `style` | `CSSProperties?` | Inline styles for the root element |
117
+ | Prop | Type | Description |
118
+ | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
119
+ | `apiBaseUrl` | `string` | Base URL for the UDIAgent API. Absolute (`https://agent.example.org`) or a same-origin path (`/api/yac`). |
120
+ | `remotePackage` | `string?` | Server-side data package name. Schema/domains come from `GET /v1/yac/metadata` and queries from `POST /v1/yac/query`; no CSVs load in the browser and cross-filtering becomes commit-on-mouse-up. Takes precedence over both data-package props. |
121
+ | `dataPackagePath` | `string?` | URL/path to `datapackage_udi.json`. Ignored when `dataPackage` is provided. |
122
+ | `dataPackage` | `DataPackage?` | Provide a data package object directly instead of fetching from a URL. Takes precedence over `dataPackagePath`. |
123
+ | `dataFieldDomains` | `DataFieldDomain[]?` | Pre-computed field domains. Skips CSV loading for domain computation when provided with `dataPackage`. |
124
+ | `fetchOptions` | `RequestInit?` | Custom fetch options (headers, credentials, etc.) forwarded to all data-loading fetch calls. |
125
+ | `authToken` | `string?` | JWT bearer token for API auth. Omit it when a backend proxy attaches the header instead; no `Authorization` header is then sent. |
126
+ | `requireApiKey` | `boolean?` | Show API key input before chatting |
127
+ | `model` | `string?` | LLM model name override |
128
+ | `downloadActions` | `DownloadAction[]?` | Extra items appended to the Download Data dropdown. See [Custom download actions](#custom-download-actions). |
129
+ | `downloadButtonLabel` | `string?` | Override the Download dropdown's trigger label. Defaults to `Download Data`. |
130
+ | `entityIcons` | `EntityIconMap?` | Icon overrides for entity count chips. See [Custom entity icons](#custom-entity-icons). |
131
+ | `mascot` | `ReactNode \| null?` | Replace or hide the welcome mascot. See [Custom mascot](#custom-mascot). |
132
+ | `splashMessages` | `readonly string[]?` | Override or hide the randomised prompt above the mascot. See [Custom splash messages](#custom-splash-messages). |
133
+ | `onEvent` | `TrackerFn?` | Analytics callback invoked on key user actions. See [Analytics events](#analytics-events). |
134
+ | `palette` | `UDIPalette?` | Default color palette for every chart and table. See [Custom color palette](#custom-color-palette). |
135
+ | `readOnly` | `boolean \| 'locked'?` | Start with the chat collapsed to a rail and every editing control hidden. `'locked'` removes the way back out. See [Read-only dashboard embed](#read-only-dashboard-embed). |
136
+ | `initialSession` | `unknown?` | A session export to seed the dashboard and conversation with. See [Read-only dashboard embed](#read-only-dashboard-embed). |
137
+ | `filters` | `UDIFilter[]?` | Filters set by the embedding page. See [Filters from the host page](#filters-from-the-host-page). |
138
+ | `onFiltersChange` | `(filters) => void` | Called with every filter in place whenever one changes. See [Filters from the host page](#filters-from-the-host-page). |
139
+ | `className` | `string?` | CSS class for the root element |
140
+ | `style` | `CSSProperties?` | Inline styles for the root element |
141
+
142
+ ### Read-only dashboard embed
143
+
144
+ To drop UDI into a host app as a **dashboard** rather than a chat, use `UDIDashboard` (or
145
+ `UDIChat` with `readOnly`). The chat pane, the dashboard's top bar and every editing control
146
+ disappear, leaving the dashboard the full width and height.
147
+
148
+ ```tsx
149
+ import { UDIDashboard } from 'udi-yac';
150
+ import 'udi-yac/style.css';
151
+
152
+ <div className="h-screen">
153
+ <UDIDashboard
154
+ apiBaseUrl="/api/yac"
155
+ dataPackagePath="./data/hubmap/datapackage.json"
156
+ initialSession={savedSession}
157
+ />
158
+ </div>;
159
+ ```
160
+
161
+ **Read-only is the starting mode, not a lock.** A floating "Explore Data" button at the dashboard's
162
+ bottom right leaves read-only, at which point the full chat and every editing control appear. The
163
+ chat opens fresh: the conversation `initialSession` carried is hidden, though the model still has
164
+ it as context for the charts on the dashboard. If your users must not be able to chat at all,
165
+ pass `readOnly="locked"` to `UDIChat` instead: the button is dropped, leaving no way out.
166
+
167
+ | Removed in read-only | Kept in read-only |
168
+ | -------------------------------------------------------------------------- | --------------------------------------- |
169
+ | The chat pane and the data overview | Brushing and cross-filtering |
170
+ | Card drag, resize, rename, close, field tweak | The filter chips, including clearing |
171
+ | The top bar: counts, grid settings, session import / export, Download Data | The chart / table toggle and all-fields |
172
+ | The Filters section, while nothing is filtered | The info tooltips |
173
+
174
+ Layout changes are not written to `localStorage` while read-only, since there are none to make.
175
+
176
+ #### Seeding the dashboard
177
+
178
+ With the chat hidden there is nothing to build a dashboard from, so a read-only embed almost
179
+ always wants `initialSession`. It takes the JSON that the dashboard's **Session → Export session**
180
+ action writes — build a dashboard in the app, export it, and ship that file with your host:
181
+
182
+ ```tsx
183
+ import savedSession from './my-dashboard.json';
184
+
185
+ <UDIDashboard
186
+ apiBaseUrl="/api/yac"
187
+ dataPackagePath="/data/datapackage.json"
188
+ initialSession={savedSession}
189
+ />;
190
+ ```
191
+
192
+ The prop is typed `unknown` so `JSON.parse(text)` can go straight in; it is validated against the
193
+ `SessionExport` shape and a malformed payload throws a descriptive error rather than leaving a
194
+ silently empty dashboard. It is applied once, after the data package finishes loading. `readOnly`
195
+ and `initialSession` are independent — seeding a normal, chat-enabled session works too.
196
+
197
+ To try the same thing in the standalone app without writing any code, point the two env vars at
198
+ an exported session — `VITE_UDI_READ_ONLY=locked` and `VITE_UDI_INITIAL_SESSION=/demo-session.json`
199
+ in `.env.local`, with the file dropped in `public/` (served from the site root). The app fetches it
200
+ at startup and hands it to `initialSession`.
201
+
202
+ ### Filters from the host page
203
+
204
+ A host page can set filters and follow the ones in place, so controls outside the chat can drive it
205
+ and read the results it is narrowing to:
206
+
207
+ ```tsx
208
+ const [filters, setFilters] = useState<UDIFilter[]>([
209
+ { id: 'cohort', dataSourceKey: 'donors', type: 'point', selection: { sex: ['Female'] } },
210
+ ]);
211
+
212
+ <UDIDashboard {...config} filters={filters} onFiltersChange={(all) => console.log(all)} />;
213
+ ```
214
+
215
+ - **`filters`** entries are keyed by `id`. Each shows as a chip in the Filters bar and filters every
216
+ chart, like a filter asked for in the chat; the user can adjust, clear or remove it there. An
217
+ entry is applied again only when that entry changes, so re-rendering with the same array never
218
+ undoes the user's edits. Dropping an entry deletes its filter. Filters are applied once the data
219
+ package has loaded, and survive a conversation reset.
220
+ - **`onFiltersChange`** receives every filter in place — chat, chart and host filters, each tagged
221
+ with its `origin` (`'chat' | 'chart' | 'host'`). A filter whose values are empty (`[]`) has been
222
+ cleared: its chip stays, reading "All", and it drops nothing. Removed filters are left out.
223
+ Entries whose `origin` is `'chat'` or `'chart'` are ignored when passed back in `filters`, so the
224
+ callback's output can be fed straight back.
225
+
226
+ A `point` filter's `selection` maps each field to the values to keep; an `interval` filter's maps
227
+ each field to `[min, max]`.
134
228
 
135
229
  ### Data Source Configuration
136
230
 
@@ -257,7 +351,18 @@ See [`src/data/hubmapRemote.ts`](src/data/hubmapRemote.ts) for the canonical inl
257
351
 
258
352
  - **Interval filters**: range sliders for numeric fields
259
353
  - **Point filters**: checkbox selection for categorical fields
260
- - **Filter toolbar**: active filter chips with clear buttons
354
+ - **Filters bar**: a chip per filter; each opens a popover with that filter's controls, **Clear
355
+ all** (keep the filter, filter nothing — the chip reads "All") and **Remove filter** (drop the
356
+ chip and collapse the filter's chat item; expanding that item brings it back cleared). **Reset**
357
+ clears every filter.
358
+ - **Multi-select on charts**: hold a key over a chart and click bars, their axis labels, or its legend entries:
359
+ - **Shift** picks a range: every category from the first click to the last, in axis or legend order.
360
+ - **Ctrl** (**⌘** on macOS) toggles each click on its own.
361
+
362
+ Nothing is applied (or queried) until the key is released or the pointer leaves the chart, when the picks become one filter.
363
+ Picks are limited to the bars drawn; to add one the chart's own filter hides, clear the
364
+ filter first.
365
+
261
366
  - **Cross-entity filtering**: filters propagate across related entities via foreign keys
262
367
  - **Null value filtering** toggle
263
368
 
@@ -444,6 +549,7 @@ Event names are stable, snake_case strings. **Properties carry metadata only —
444
549
  | `api_key_set` | User submits a key through the `ApiKeyInput` | `inResponseToQuota: boolean` — true if a budget-exceeded rebuff had triggered the prompt (distinguishes first-time entry from quota-recovery entry) |
445
550
  | `api_key_cleared` | User clears their stored key via the header icon | _(none)_ |
446
551
  | `conversation_reset` | User clicks the Reset button (clears messages, pinned viz, filters, memory bank) | `conversationLength: number` — message count at the moment of reset |
552
+ | `read_only_exited` | User leaves read-only mode with the Explore Data button | _(none)_ |
447
553
  | `visualization_pinned` | A new visualization is auto-pinned from an assistant response | `hasTitle: boolean`<br>`toolCallIndex: number` — position within the assistant message's tool_calls |
448
554
  | `visualization_closed` | User clicks the × on a pinned visualization card | `hasTitle: boolean` |
449
555
  | `download_raw_data` | User clicks "Download Raw Data" in the Download Data dropdown | `sources: number` — count of sources contributing rows<br>`rowsTotal: number` |
@@ -462,7 +568,7 @@ Every row in the table above also includes `sessionId: string`; it's omitted fro
462
568
 
463
569
  **Properties you will _not_ see.** By design, the following never cross the tracker boundary: raw message text, tool_call `arguments`, OpenAI API keys, data rows, filter values. Only counts, booleans, tool-call names, ids, and short slug strings are emitted.
464
570
 
465
- ### Debug Mode (type `!/admin` in chat)
571
+ ### Debug Mode (type `//admin` in chat)
466
572
 
467
573
  - System prompts toggle (show/hide system messages)
468
574
  - Conversation sidebar drawer (load saved session JSON files)
@@ -518,7 +624,10 @@ src/
518
624
  DashboardPanel.tsx # Dashboard layout (counts, filters, viz grid)
519
625
  DashboardCard.tsx # Single pinned viz (chart, toolbar, tweak, spec)
520
626
  DataCounts.tsx # Per-entity row counts (total + filtered)
521
- FilterToolbar.tsx # Active filter chips
627
+ FilterToolbar.tsx # Filter chips, each a popover of its controls
628
+ FilterControls.tsx # Filter widget by type + collapsible chat item
629
+ IntervalFilterComponent.tsx # Range slider
630
+ PointFilterComponent.tsx # Checkbox list
522
631
  DownloadButton.tsx # CSV/manifest download dropdown
523
632
  VizTweakComponent.tsx # Field encoding swap dropdowns
524
633
  VizTweakComponent.types.ts # TweakableParam, LayerLike, MappingLike
@@ -546,9 +655,7 @@ src/
546
655
  components/
547
656
  ToolCallRenderer.tsx # Dispatches tool calls to renderers
548
657
  VisualizationCard.tsx # UDIVis preview (chat) / pinned badge
549
- FilterComponent.tsx # Filter dispatcher (interval/point)
550
- IntervalFilterComponent.tsx
551
- PointFilterComponent.tsx
658
+ FilterComponent.tsx # A FilterData tool call's chat item
552
659
  FreeTextExplain.tsx # Markdown explanation with structured text
553
660
  RebuffNotice.tsx # Rejection with suggestion buttons
554
661
  ClarifyVariable.tsx # Field disambiguation UI