@rebasepro/plugin-ai 0.21.2-canary.g1ea48be → 0.23.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/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
- import { AIIcon, useAuthController } from "@rebasepro/app";
2
+ import { AIIcon, isDisabled, isHidden, isReadOnly, useAuthController } from "@rebasepro/app";
3
3
  import { getFieldId } from "@rebasepro/cms";
4
4
  import { isPropertyBuilder, stripCollectionPath } from "@rebasepro/common";
5
5
  import { getValueInPath } from "@rebasepro/utils";
@@ -239,7 +239,18 @@ function getSimplifiedProperties(properties, values, path = "") {
239
239
  ...b
240
240
  }), {});
241
241
  }
242
- function getSimpleProperty(property) {
242
+ /**
243
+ * Whether the form will not let anyone edit this field.
244
+ *
245
+ * The same three gates the form lays a field out with, so a literal
246
+ * `conditions.readOnly` or a date the backend stamps is locked here too. A
247
+ * locked container locks everything under it: the form disables a map's
248
+ * children with the map, so they are not the model's to fill either.
249
+ */
250
+ function isLocked(property, lockedByParent) {
251
+ return lockedByParent || isReadOnly(property) || isDisabled(property) || isHidden(property);
252
+ }
253
+ function getSimpleProperty(property, lockedByParent = false) {
243
254
  const fieldId = getFieldId(property);
244
255
  if (!fieldId) {
245
256
  console.error("No fieldId found for property", property);
@@ -251,11 +262,12 @@ function getSimpleProperty(property) {
251
262
  type: property.type,
252
263
  fieldConfigId: fieldId,
253
264
  enum: "enum" in property && property.enum ? getSimpleEnumValues(property.enum) : void 0,
254
- disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)
265
+ disabled: isLocked(property, lockedByParent)
255
266
  };
256
267
  }
257
- function getSimplifiedProperty(property, path, value) {
268
+ function getSimplifiedProperty(property, path, value, lockedByParent = false) {
258
269
  if (isPropertyBuilder(property)) return {};
270
+ const locked = isLocked(property, lockedByParent);
259
271
  if (property.type === "array") {
260
272
  if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {
261
273
  const arrayParentProperty = {
@@ -263,8 +275,8 @@ function getSimplifiedProperty(property, path, value) {
263
275
  description: property.description,
264
276
  type: property.type,
265
277
  fieldConfigId: "repeat",
266
- disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),
267
- of: getSimpleProperty(property.of)
278
+ disabled: locked,
279
+ of: getSimpleProperty(property.of, locked)
268
280
  };
269
281
  return { [path]: arrayParentProperty };
270
282
  } else if (property.oneOf) {
@@ -273,11 +285,11 @@ function getSimplifiedProperty(property, path, value) {
273
285
  description: property.description,
274
286
  type: property.type,
275
287
  fieldConfigId: "block",
276
- disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),
288
+ disabled: locked,
277
289
  oneOf: {
278
290
  typeField: property.oneOf.typeField,
279
291
  valueField: property.oneOf.valueField,
280
- properties: Object.entries(property.oneOf.properties).map(([key, prop]) => ({ [key]: getSimpleProperty(prop) })).reduce((a, b) => ({
292
+ properties: Object.entries(property.oneOf.properties).map(([key, prop]) => ({ [key]: getSimpleProperty(prop, locked) })).reduce((a, b) => ({
281
293
  ...a,
282
294
  ...b
283
295
  }), {})
@@ -295,7 +307,7 @@ function getSimplifiedProperty(property, path, value) {
295
307
  console.error(`No property found for type ${oneOfType}`, property.oneOf.properties);
296
308
  return {};
297
309
  }
298
- const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);
310
+ const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue, locked);
299
311
  return {
300
312
  [`${path}.${i}.${typeKey}`]: oneOfType,
301
313
  ...simplifiedProperty
@@ -308,7 +320,7 @@ function getSimplifiedProperty(property, path, value) {
308
320
  } else if (property.type === "map") {
309
321
  if (property.properties) {
310
322
  const mapProperties = Object.entries(property.properties).map(([key, childProperty]) => {
311
- return getSimplifiedProperty(childProperty, key, value && typeof value === "object" ? value[key] : void 0);
323
+ return getSimplifiedProperty(childProperty, key, value && typeof value === "object" ? value[key] : void 0, locked);
312
324
  }).map((o) => attachPathToKeys(o, path)).reduce((a, b) => ({
313
325
  ...a,
314
326
  ...b
@@ -319,7 +331,7 @@ function getSimplifiedProperty(property, path, value) {
319
331
  description: property.description,
320
332
  type: property.type,
321
333
  fieldConfigId: "group",
322
- disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)
334
+ disabled: locked
323
335
  };
324
336
  return {
325
337
  [path]: mapParentProperty,
@@ -331,7 +343,7 @@ function getSimplifiedProperty(property, path, value) {
331
343
  console.warn(`No fieldId found for property ${path} with type ${property.type}`);
332
344
  return {};
333
345
  }
334
- return { [path]: getSimpleProperty(property) };
346
+ return { [path]: getSimpleProperty(property, lockedByParent) };
335
347
  }
336
348
  return {};
337
349
  }
@@ -509,6 +521,22 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
509
521
  };
510
522
  }, [allowedHere, endpoint]);
511
523
  const enabled = allowedHere && serviceAvailable;
524
+ /**
525
+ * The run whose review is on screen.
526
+ *
527
+ * A run outlives the review it was started for: the operator can discard
528
+ * it, or start another, while its stream is still arriving. Every callback
529
+ * checks it is still the current run before touching the review, and a run
530
+ * that stops being current has its request cancelled. Without this, a
531
+ * discarded run kept writing into the next run's review and, when it ended,
532
+ * marked that review ready while the new run was still writing.
533
+ */
534
+ const currentRunRef = useRef(null);
535
+ const cancelCurrentRun = useCallback(() => {
536
+ currentRunRef.current?.abort();
537
+ currentRunRef.current = null;
538
+ }, []);
539
+ useEffect(() => cancelCurrentRun, [cancelCurrentRun]);
512
540
  /** Add or update one row in the review, preserving arrival order. */
513
541
  const upsertField = useCallback((key, update) => {
514
542
  setReview((current) => {
@@ -531,9 +559,14 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
531
559
  instructions: params.instructions
532
560
  });
533
561
  const labelFor = (key) => currentProperties[key]?.name ?? key;
562
+ cancelCurrentRun();
563
+ const run = new AbortController();
564
+ currentRunRef.current = run;
565
+ const isCurrent = () => currentRunRef.current === run;
534
566
  try {
535
567
  await autofillStream({
536
568
  endpoint,
569
+ signal: run.signal,
537
570
  request: {
538
571
  entityName: collection.singularName ?? collection.name,
539
572
  entityDescription: collection.description,
@@ -544,6 +577,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
544
577
  instructions: params.instructions
545
578
  },
546
579
  onDelta: (key, text) => {
580
+ if (!isCurrent()) return;
547
581
  upsertField(key, (existing) => existing ? {
548
582
  ...existing,
549
583
  proposed: String(existing.proposed ?? "") + text
@@ -557,6 +591,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
557
591
  });
558
592
  },
559
593
  onValue: (key, value) => {
594
+ if (!isCurrent()) return;
560
595
  const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));
561
596
  upsertField(key, (existing) => ({
562
597
  key,
@@ -568,12 +603,16 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
568
603
  }));
569
604
  }
570
605
  });
606
+ if (!isCurrent()) return;
607
+ currentRunRef.current = null;
571
608
  setReview((current) => current && {
572
609
  ...current,
573
610
  status: "ready",
574
611
  fields: current.fields.filter((f) => !f.pending)
575
612
  });
576
613
  } catch (e) {
614
+ if (!isCurrent()) return;
615
+ currentRunRef.current = null;
577
616
  const message = e instanceof Error ? e.message : "Autofill could not be completed";
578
617
  setReview((current) => current && {
579
618
  ...current,
@@ -585,7 +624,8 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
585
624
  }, [
586
625
  collection,
587
626
  endpoint,
588
- upsertField
627
+ upsertField,
628
+ cancelCurrentRun
589
629
  ]);
590
630
  const toggleField = useCallback((key) => {
591
631
  setReview((current) => current && {
@@ -605,8 +645,12 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
605
645
  }))
606
646
  });
607
647
  }, []);
608
- const dismissReview = useCallback(() => setReview(null), []);
648
+ const dismissReview = useCallback(() => {
649
+ cancelCurrentRun();
650
+ setReview(null);
651
+ }, [cancelCurrentRun]);
609
652
  const applyReview = useCallback(() => {
653
+ cancelCurrentRun();
610
654
  setReview((current) => {
611
655
  if (!current) return null;
612
656
  for (const field of current.fields) {
@@ -616,7 +660,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
616
660
  }
617
661
  return null;
618
662
  });
619
- }, [formContext]);
663
+ }, [formContext, cancelCurrentRun]);
620
664
  const editorAIController = useEditorAIController({ endpoint });
621
665
  const getSamplePrompts = useCallback((entityName, input) => fetchPromptSuggestions({
622
666
  endpoint,
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/api.ts","../src/utils/properties.ts","../src/utils/values.ts","../src/editor/useEditorAIController.tsx","../src/components/DataEnhancementControllerProvider.tsx","../src/components/AutofillReviewDialog.tsx","../src/components/FormEnhanceAction.tsx","../src/useDataEnhancementPlugin.tsx"],"sourcesContent":["import {\n AutofillRequest,\n AutofillResult,\n AiStatus,\n SamplePromptsResult\n} from \"./types/data_enhancement_controller\";\n\n/**\n * The hosted service Rebase runs for this plugin.\n *\n * **This is where the entity's field values go.** Autofill posts them to\n * generate a suggestion, and unless the host sets `endpoint`, they go to this\n * address — off their machine, to a service Rebase operates. That is disclosed\n * on the plugins page rather than only here, because the person who needs to\n * know is the one deciding whether the data in those fields may leave.\n *\n * No credential travels with them; see the note below.\n *\n * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that\n * resolves but serves nothing — every path 404s — so Autofill had never worked\n * in a Rebase install. This one is served by the control plane\n * (`saas/backend/functions/ai.ts`). Point `endpoint` somewhere else to run your\n * own; the wire format below is the whole contract.\n */\nexport const DEFAULT_AI_ENDPOINT = \"https://app.rebase.pro/api/functions/ai\";\n\n/**\n * ## No credentials cross this boundary\n *\n * The old client sent the tenant's Rebase JWT as `Authorization: Basic <jwt>`\n * plus a hardcoded `fcms-…` key compiled into the published package. Both were\n * wrong in the same way: a self-hosted backend signs its tokens with its own\n * secret, so no external service can verify one — sending it only handed a live\n * credential to a third party that had no use for it.\n *\n * These requests are anonymous. The service bounds cost by rate limit and daily\n * ceiling rather than by identity, and reports through {@link fetchAiStatus}\n * when it can no longer serve — which is what keeps the UI from offering an\n * action that is going to fail.\n */\nfunction endpointOf(endpoint: string | undefined, path: string): string {\n return (endpoint ?? DEFAULT_AI_ENDPOINT).replace(/\\/+$/, \"\") + path;\n}\n\n/** One `event:`/`data:` pair off the wire. */\ntype ServerSentEvent = { event: string; data: string };\n\n/** Not global: `exec` must not carry `lastIndex` between buffer reads. */\nconst SSE_SEPARATOR = /\\r?\\n\\r?\\n/;\n\n/**\n * Parse an SSE body incrementally.\n *\n * The framing this replaces split each chunk on the literal `\"&$# \"` and\n * `JSON.parse`d the pieces, which corrupted itself the moment a delimiter\n * straddled two reads — and network reads land wherever they land. Buffering\n * until a blank line is the fix, and it is also just what SSE specifies.\n */\nasync function* readServerSentEvents(response: Response): AsyncGenerator<ServerSentEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"The AI service returned no response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // A record ends at a blank line. `\\r\\n` is tolerated because proxies\n // rewrite line endings. The separator is located with `exec` rather\n // than `search` so its actual length is known — a `\\r\\n\\r\\n` boundary\n // is four characters, not two, and slicing by the wrong count leaves a\n // stray newline that swallows the next record's `event:` field.\n let match = SSE_SEPARATOR.exec(buffer);\n while (match) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const parsed = parseEventBlock(raw);\n if (parsed) yield parsed;\n match = SSE_SEPARATOR.exec(buffer);\n }\n }\n}\n\nfunction parseEventBlock(block: string): ServerSentEvent | undefined {\n let event = \"message\";\n const dataLines: string[] = [];\n for (const line of block.split(/\\r?\\n/)) {\n if (line.startsWith(\":\")) continue; // comment / keep-alive\n const separator = line.indexOf(\":\");\n const field = separator === -1 ? line : line.slice(0, separator);\n const rawValue = separator === -1 ? \"\" : line.slice(separator + 1);\n const value = rawValue.startsWith(\" \") ? rawValue.slice(1) : rawValue;\n if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0) return undefined;\n return { event,\ndata: dataLines.join(\"\\n\") };\n}\n\n/** Pull a message out of the control plane's `{ error: { message } }` envelope. */\nasync function errorFrom(response: Response, fallback: string): Promise<Error> {\n try {\n const body = await response.json();\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return new Error(message);\n } catch {\n /* not JSON — fall through */\n }\n return new Error(fallback);\n}\n\n/**\n * Ask the service whether it can serve a request at all.\n *\n * The plugin gates every affordance on this. A missing provider key, an\n * exhausted daily quota or an unreachable host all resolve to `available:\n * false`, and the Autofill button is simply not rendered — rather than\n * rendered, clicked, and failed.\n */\nexport async function fetchAiStatus(props: { endpoint?: string; signal?: AbortSignal }): Promise<AiStatus> {\n const response = await fetch(endpointOf(props.endpoint, \"/status\"), {\n method: \"GET\",\n signal: props.signal\n });\n if (!response.ok) return { available: false };\n const body = await response.json();\n return {\n available: Boolean(body?.available),\n model: typeof body?.model === \"string\" ? body.model : undefined,\n features: Array.isArray(body?.features) ? body.features : undefined\n };\n}\n\n/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleProperty(property: Property): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n of: getSimpleProperty(property.of as Property)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n try {\n await autofillStream({\n endpoint,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => setReview(null), []);\n\n const applyReview = useCallback(() => {\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/api.ts","../src/utils/properties.ts","../src/utils/values.ts","../src/editor/useEditorAIController.tsx","../src/components/DataEnhancementControllerProvider.tsx","../src/components/AutofillReviewDialog.tsx","../src/components/FormEnhanceAction.tsx","../src/useDataEnhancementPlugin.tsx"],"sourcesContent":["import {\n AutofillRequest,\n AutofillResult,\n AiStatus,\n SamplePromptsResult\n} from \"./types/data_enhancement_controller\";\n\n/**\n * The hosted service Rebase runs for this plugin.\n *\n * **This is where the entity's field values go.** Autofill posts them to\n * generate a suggestion, and unless the host sets `endpoint`, they go to this\n * address — off their machine, to a service Rebase operates. That is disclosed\n * on the plugins page rather than only here, because the person who needs to\n * know is the one deciding whether the data in those fields may leave.\n *\n * No credential travels with them; see the note below.\n *\n * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that\n * resolves but serves nothing — every path 404s — so Autofill had never worked\n * in a Rebase install. This one is served by the control plane\n * (`saas/backend/functions/ai.ts`). Point `endpoint` somewhere else to run your\n * own; the wire format below is the whole contract.\n */\nexport const DEFAULT_AI_ENDPOINT = \"https://app.rebase.pro/api/functions/ai\";\n\n/**\n * ## No credentials cross this boundary\n *\n * The old client sent the tenant's Rebase JWT as `Authorization: Basic <jwt>`\n * plus a hardcoded `fcms-…` key compiled into the published package. Both were\n * wrong in the same way: a self-hosted backend signs its tokens with its own\n * secret, so no external service can verify one — sending it only handed a live\n * credential to a third party that had no use for it.\n *\n * These requests are anonymous. The service bounds cost by rate limit and daily\n * ceiling rather than by identity, and reports through {@link fetchAiStatus}\n * when it can no longer serve — which is what keeps the UI from offering an\n * action that is going to fail.\n */\nfunction endpointOf(endpoint: string | undefined, path: string): string {\n return (endpoint ?? DEFAULT_AI_ENDPOINT).replace(/\\/+$/, \"\") + path;\n}\n\n/** One `event:`/`data:` pair off the wire. */\ntype ServerSentEvent = { event: string; data: string };\n\n/** Not global: `exec` must not carry `lastIndex` between buffer reads. */\nconst SSE_SEPARATOR = /\\r?\\n\\r?\\n/;\n\n/**\n * Parse an SSE body incrementally.\n *\n * The framing this replaces split each chunk on the literal `\"&$# \"` and\n * `JSON.parse`d the pieces, which corrupted itself the moment a delimiter\n * straddled two reads — and network reads land wherever they land. Buffering\n * until a blank line is the fix, and it is also just what SSE specifies.\n */\nasync function* readServerSentEvents(response: Response): AsyncGenerator<ServerSentEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"The AI service returned no response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // A record ends at a blank line. `\\r\\n` is tolerated because proxies\n // rewrite line endings. The separator is located with `exec` rather\n // than `search` so its actual length is known — a `\\r\\n\\r\\n` boundary\n // is four characters, not two, and slicing by the wrong count leaves a\n // stray newline that swallows the next record's `event:` field.\n let match = SSE_SEPARATOR.exec(buffer);\n while (match) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const parsed = parseEventBlock(raw);\n if (parsed) yield parsed;\n match = SSE_SEPARATOR.exec(buffer);\n }\n }\n}\n\nfunction parseEventBlock(block: string): ServerSentEvent | undefined {\n let event = \"message\";\n const dataLines: string[] = [];\n for (const line of block.split(/\\r?\\n/)) {\n if (line.startsWith(\":\")) continue; // comment / keep-alive\n const separator = line.indexOf(\":\");\n const field = separator === -1 ? line : line.slice(0, separator);\n const rawValue = separator === -1 ? \"\" : line.slice(separator + 1);\n const value = rawValue.startsWith(\" \") ? rawValue.slice(1) : rawValue;\n if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0) return undefined;\n return { event,\ndata: dataLines.join(\"\\n\") };\n}\n\n/** Pull a message out of the control plane's `{ error: { message } }` envelope. */\nasync function errorFrom(response: Response, fallback: string): Promise<Error> {\n try {\n const body = await response.json();\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return new Error(message);\n } catch {\n /* not JSON — fall through */\n }\n return new Error(fallback);\n}\n\n/**\n * Ask the service whether it can serve a request at all.\n *\n * The plugin gates every affordance on this. A missing provider key, an\n * exhausted daily quota or an unreachable host all resolve to `available:\n * false`, and the Autofill button is simply not rendered — rather than\n * rendered, clicked, and failed.\n */\nexport async function fetchAiStatus(props: { endpoint?: string; signal?: AbortSignal }): Promise<AiStatus> {\n const response = await fetch(endpointOf(props.endpoint, \"/status\"), {\n method: \"GET\",\n signal: props.signal\n });\n if (!response.ok) return { available: false };\n const body = await response.json();\n return {\n available: Boolean(body?.available),\n model: typeof body?.model === \"string\" ? body.model : undefined,\n features: Array.isArray(body?.features) ? body.features : undefined\n };\n}\n\n/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\nimport { isDisabled, isHidden, isReadOnly } from \"@rebasepro/app\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\n/**\n * Whether the form will not let anyone edit this field.\n *\n * The same three gates the form lays a field out with, so a literal\n * `conditions.readOnly` or a date the backend stamps is locked here too. A\n * locked container locks everything under it: the form disables a map's\n * children with the map, so they are not the model's to fill either.\n */\nfunction isLocked(property: Property, lockedByParent: boolean): boolean {\n return lockedByParent || isReadOnly(property) || isDisabled(property) || isHidden(property);\n}\n\nfunction getSimpleProperty(property: Property, lockedByParent = false): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: isLocked(property, lockedByParent)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown, lockedByParent = false): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n const locked = isLocked(property, lockedByParent);\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: locked,\n of: getSimpleProperty(property.of as Property, locked)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: locked,\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop, locked) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue, locked);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue, locked);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: locked\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property, lockedByParent)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /**\n * The run whose review is on screen.\n *\n * A run outlives the review it was started for: the operator can discard\n * it, or start another, while its stream is still arriving. Every callback\n * checks it is still the current run before touching the review, and a run\n * that stops being current has its request cancelled. Without this, a\n * discarded run kept writing into the next run's review and, when it ended,\n * marked that review ready while the new run was still writing.\n */\n const currentRunRef = useRef<AbortController | null>(null);\n\n const cancelCurrentRun = useCallback(() => {\n currentRunRef.current?.abort();\n currentRunRef.current = null;\n }, []);\n\n // A form closed mid-run has nobody left to review what it produces.\n useEffect(() => cancelCurrentRun, [cancelCurrentRun]);\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n cancelCurrentRun();\n const run = new AbortController();\n currentRunRef.current = run;\n const isCurrent = () => currentRunRef.current === run;\n\n try {\n await autofillStream({\n endpoint,\n signal: run.signal,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n if (!isCurrent()) return;\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n if (!isCurrent()) return;\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n if (!isCurrent()) return;\n currentRunRef.current = null;\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n // A run that was discarded or replaced fails with the abort, and\n // has nothing left to report to.\n if (!isCurrent()) return;\n currentRunRef.current = null;\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField, cancelCurrentRun]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => {\n cancelCurrentRun();\n setReview(null);\n }, [cancelCurrentRun]);\n\n const applyReview = useCallback(() => {\n // Applied mid-stream, the rest of the run has no review to land in.\n cancelCurrentRun();\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext, cancelCurrentRun]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/plugin-ai",
3
- "version": "0.21.2-canary.g1ea48be",
3
+ "version": "0.23.0",
4
4
  "description": "AI plugin for the Rebase admin panel: field generation and content assistance.",
5
5
  "keywords": [
6
6
  "rebase",
@@ -35,13 +35,13 @@
35
35
  "./package.json": "./package.json"
36
36
  },
37
37
  "dependencies": {
38
- "@rebasepro/cms": "0.21.2-canary.g1ea48be",
39
- "@rebasepro/cms-types": "0.21.2-canary.g1ea48be",
40
- "@rebasepro/types": "0.21.2-canary.g1ea48be",
41
- "@rebasepro/common": "0.21.2-canary.g1ea48be",
42
- "@rebasepro/utils": "0.21.2-canary.g1ea48be",
43
- "@rebasepro/app": "0.21.2-canary.g1ea48be",
44
- "@rebasepro/ui": "0.21.2-canary.g1ea48be"
38
+ "@rebasepro/cms": "0.23.0",
39
+ "@rebasepro/cms-types": "0.23.0",
40
+ "@rebasepro/common": "0.23.0",
41
+ "@rebasepro/app": "0.23.0",
42
+ "@rebasepro/types": "0.23.0",
43
+ "@rebasepro/ui": "0.23.0",
44
+ "@rebasepro/utils": "0.23.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^19.2.7",