@lightdash/cli 3.124.0 → 3.126.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.
@@ -208,6 +208,14 @@ order, `sortBy` describes result ordering, `totalColumnCount` exposes truncation
208
208
  plain text. `result` is the object `useLightdash` returns (keep a reference
209
209
  to it even when destructuring), never a rebuilt `{ data, columns }`. Hide the control when `available` is false; trigger from a user
210
210
  action. Full contract in `/app/references/ai-analysis.md`.
211
+ - `shared-ai-commentary`: `useSharedAiCommentary({ selectionKey, prompt,
212
+ sources, focus? })` saves a generated answer in shared app state keyed to
213
+ the current selection: the first viewer generates, later viewers of the
214
+ same selection read the saved text, anyone may edit it (`saveEdit`), and
215
+ the edited version is what others see. Build `selectionKey` with
216
+ `commentarySelectionKey(...)` from bounded controls (filters, tabs), never
217
+ row data. Needs both AI and saved app state available. See "Saved
218
+ commentary shared between viewers" in `/app/references/ai-analysis.md`.
211
219
  - `space-charts`: `useSpaceCharts('<space slug or uuid>')` returns
212
220
  `{ available, space, charts, loading, error, refresh }` — the saved charts
213
221
  the viewer can see in that space, resolved at runtime with the viewer's own
@@ -234,6 +234,76 @@ function RevenueTakeaway() {
234
234
  - Trigger `ask` from a user action, or at most once per loaded view. Never on a timer, never per row. Prompts are rate limited per viewer.
235
235
  - Prompts are capped at 2000 characters; write them as instructions to an analyst, and name the audience and the length you want.
236
236
 
237
+ ## Saved commentary shared between viewers (`useSharedAiCommentary`)
238
+
239
+ > Use this when the user wants generated commentary to be **saved** — "save the summary for the team", "everyone should see the same commentary", "let people edit the AI text", "keep the narrative with this view". It needs BOTH AI analysis and saved app state available (see the prompt's availability lines); without either, fall back to `useAiPrompt` (no saving) or plain `useSharedAppState` (no generation).
240
+
241
+ `useAiPrompt` answers one viewer once; `useSharedAiCommentary` keys the answer to the current selection and stores it in shared app state, so the first viewer generates it, later viewers of the same selection read the saved text, and anyone may edit it. The edited text is what later viewers see.
242
+
243
+ ```tsx
244
+ import {
245
+ commentarySelectionKey,
246
+ useLightdash,
247
+ useSharedAiCommentary,
248
+ } from '@lightdash/query-sdk';
249
+
250
+ function RegionCommentary({ region, period }: { region: string; period: string }) {
251
+ const revenue = useLightdash(revenueQuery);
252
+ const c = useSharedAiCommentary({
253
+ // Same selection => same saved commentary, for every viewer.
254
+ selectionKey: commentarySelectionKey('region', region, 'period', period),
255
+ prompt: `In three sentences, what should a sales lead know about ${region} revenue for ${period}?`,
256
+ sources: [{ result: revenue, label: 'Revenue' }],
257
+ });
258
+ const [draft, setDraft] = useState<string | null>(null);
259
+
260
+ if (c.meta.loading || revenue.loading) return null;
261
+ return (
262
+ <section>
263
+ {c.commentary ? (
264
+ draft === null ? (
265
+ <>
266
+ <p>{c.commentary.text}</p>
267
+ <footer className="text-xs text-muted-foreground">
268
+ {c.commentary.edited
269
+ ? `Edited by ${c.meta.updatedBy?.name ?? 'a viewer'}`
270
+ : 'AI-generated'}
271
+ </footer>
272
+ <button onClick={() => setDraft(c.commentary!.text)}>Edit</button>
273
+ {c.canGenerate && (
274
+ <button disabled={c.generating} onClick={() => c.generate()}>
275
+ Regenerate
276
+ </button>
277
+ )}
278
+ </>
279
+ ) : (
280
+ <>
281
+ <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />
282
+ <button onClick={() => { c.saveEdit(draft); setDraft(null); }}>Save</button>
283
+ <button onClick={() => setDraft(null)}>Cancel</button>
284
+ </>
285
+ )
286
+ ) : (
287
+ c.canGenerate && (
288
+ <button disabled={c.generating} onClick={() => c.generate()}>
289
+ {c.generating ? 'Generating…' : 'Generate commentary'}
290
+ </button>
291
+ )
292
+ )}
293
+ </section>
294
+ );
295
+ }
296
+ ```
297
+
298
+ Rules:
299
+
300
+ - **`selectionKey` names the selection, not the data**: build it with `commentarySelectionKey(...)` from the filter values, segment ids or tab names that define what the viewer is looking at. Never include row values or timestamps. One key per distinct selection; shared app state allows 200 keys per app, so key on controls with bounded choices (a region picker, a period), not free-text search terms.
301
+ - `prompt`, `sources` and `focus` work exactly as in `useAiPrompt`; the generated text is saved with the prompt and a `promptId` for provenance, `edited: false`.
302
+ - Generation replaces any saved text (the Regenerate case) — trigger it from a click, never on load. Reading saved commentary is free and works even when `canGenerate` is false, so always render saved text regardless.
303
+ - Label the text "AI-generated" until `commentary.edited` is true; then show who edited it from `meta.updatedBy`.
304
+ - The saved text is visible to **every** viewer of the app, including viewers whose data permissions would show them different rows. That is the point of the feature — it behaves like a comment — but do not build it over data some viewers must not see described in words.
305
+ - Storage errors surface on `meta.error`; generation errors on `error`. Handle both like any other state.
306
+
237
307
  ## What the app must not do
238
308
 
239
309
  - Do not send rows or field values anywhere. The hooks expose results; Lightdash already has the data.
@@ -781,7 +781,7 @@ For any external HTTP API call, read `/app/references/external-apis.md` and use
781
781
 
782
782
  ### AI analysis — executive summary and anomalies
783
783
 
784
- When the user asks for AI analysis, an executive summary, "what changed and why", anomaly callouts, or any AI-generated insight about the data on the page, read `/app/references/ai-analysis.md` and build its **Default build** with the shipped components in `src/components/insights/` (`InsightsSummary`, `InsightMarker` / `insightCellProps`, `InvestigateMenuItem`, `InvestigationCard`): summary block at the top, markers on every query-bound chart, Investigate in every point menu, the investigation card under the chart. A short ask gets the whole set; only a narrower ask ("just a summary") gets a subset. Never rewrite those components. Lightdash runs the analysis over the queries the app already loaded and pushes the result in; the app renders it and never sends rows or keys. The components render nothing when the host has no analysis to offer. For an author-written question over loaded results (a takeaway under a chart, "explain this row"), use `useAiPrompt` from the same reference. When the user wants the summaries written a certain way (language, currency, domain vocabulary), pass it via the `instructions` prop on `InsightsSummary` — see "Author instructions" in the same reference. When the user wants the analysis to run as soon as the app loads, set the `analyseOnLoad` prop on `InsightsSummary` — see "Analyse on load" in the same reference; never set it unasked.
784
+ When the user asks for AI analysis, an executive summary, "what changed and why", anomaly callouts, or any AI-generated insight about the data on the page, read `/app/references/ai-analysis.md` and build its **Default build** with the shipped components in `src/components/insights/` (`InsightsSummary`, `InsightMarker` / `insightCellProps`, `InvestigateMenuItem`, `InvestigationCard`): summary block at the top, markers on every query-bound chart, Investigate in every point menu, the investigation card under the chart. A short ask gets the whole set; only a narrower ask ("just a summary") gets a subset. Never rewrite those components. Lightdash runs the analysis over the queries the app already loaded and pushes the result in; the app renders it and never sends rows or keys. The components render nothing when the host has no analysis to offer. For an author-written question over loaded results (a takeaway under a chart, "explain this row"), use `useAiPrompt` from the same reference. When the user wants the summaries written a certain way (language, currency, domain vocabulary), pass it via the `instructions` prop on `InsightsSummary` — see "Author instructions" in the same reference. When the user wants the analysis to run as soon as the app loads, set the `analyseOnLoad` prop on `InsightsSummary` — see "Analyse on load" in the same reference; never set it unasked. When the user wants generated commentary **saved and shared** — "save the summary for the team", "everyone sees the same commentary", "let people edit the AI text" — and the prompt says saved app state is available, use `useSharedAiCommentary` from the same reference ("Saved commentary shared between viewers") instead of `useAiPrompt`; it keys the text to the current selection so later viewers read and edit the saved version instead of regenerating.
785
785
 
786
786
  ## Visual Design
787
787
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "3.124.0",
3
+ "version": "3.126.0",
4
4
  "description": "Lightdash CLI tool",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,8 +22,8 @@
22
22
  "@actions/core": "1.11.1",
23
23
  "@aws-sdk/credential-providers": "3.995.0",
24
24
  "@casl/ability": "6.8.0",
25
- "@lightdash/common": "3.124.0",
26
- "@lightdash/warehouses": "3.124.0",
25
+ "@lightdash/common": "3.126.0",
26
+ "@lightdash/warehouses": "3.126.0",
27
27
  "@sidvind/better-ajv-errors": "7.0.0",
28
28
  "@types/columnify": "1.5.1",
29
29
  "ajv": "8.18.0",