@rebasepro/plugin-ai 0.12.1-canary.gf4240e3 → 0.13.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/api.d.ts CHANGED
@@ -54,6 +54,8 @@ export declare function autocompleteStream(props: {
54
54
  */
55
55
  export declare function fetchPromptSuggestions(props: {
56
56
  entityName: string;
57
+ /** Ties suggestions to the domain — see the note on the service's side. */
58
+ entityDescription?: string;
57
59
  input?: string;
58
60
  endpoint?: string;
59
61
  signal?: AbortSignal;
package/dist/index.es.js CHANGED
@@ -3,7 +3,7 @@ import { getFieldId } from "@rebasepro/admin";
3
3
  import { isPropertyBuilder, stripCollectionPath } from "@rebasepro/common";
4
4
  import { getValueInPath } from "@rebasepro/utils";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { Button, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Menu, MenuItem, SendIcon, Separator, TextareaAutosize, Typography, XIcon, cls, focusedDisabled, iconSize } from "@rebasepro/ui";
6
+ import { Button, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Menu, MenuItem, SendIcon, Separator, TextareaAutosize, Typography, XIcon, cls, fieldBackgroundMixin, focusedDisabled, iconSize } from "@rebasepro/ui";
7
7
  import { AIIcon } from "@rebasepro/app";
8
8
  /**
9
9
  * ## No credentials cross this boundary
@@ -175,6 +175,7 @@ async function fetchPromptSuggestions(props) {
175
175
  headers: { "Content-Type": "application/json" },
176
176
  body: JSON.stringify({
177
177
  entityName: props.entityName,
178
+ entityDescription: props.entityDescription,
178
179
  input: props.input
179
180
  }),
180
181
  signal: props.signal
@@ -479,10 +480,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
479
480
  setReview((current) => current && {
480
481
  ...current,
481
482
  status: "ready",
482
- fields: current.fields.map((f) => ({
483
- ...f,
484
- pending: false
485
- }))
483
+ fields: current.fields.filter((f) => !f.pending)
486
484
  });
487
485
  } catch (e) {
488
486
  const message = e instanceof Error ? e.message : "Autofill could not be completed";
@@ -490,10 +488,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
490
488
  ...current,
491
489
  status: "failed",
492
490
  error: message,
493
- fields: current.fields.map((f) => ({
494
- ...f,
495
- pending: false
496
- }))
491
+ fields: current.fields.filter((f) => !f.pending)
497
492
  });
498
493
  }
499
494
  }, [
@@ -535,8 +530,9 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
535
530
  const getSamplePrompts = useCallback((entityName, input) => fetchPromptSuggestions({
536
531
  endpoint,
537
532
  entityName,
533
+ entityDescription: collection.description,
538
534
  input
539
- }), [endpoint]);
535
+ }), [endpoint, collection.description]);
540
536
  const dataEnhancementController = useMemo(() => ({
541
537
  enabled,
542
538
  review,
@@ -619,6 +615,7 @@ function AutofillReviewDialog() {
619
615
  onCheckedChange: () => controller.toggleAll(!allSelected)
620
616
  }), /* @__PURE__ */ jsx(Typography, {
621
617
  variant: "label",
618
+ component: "span",
622
619
  color: "secondary",
623
620
  children: allSelected ? "Deselect all" : "Select all"
624
621
  })]
@@ -687,6 +684,7 @@ function ProposedFieldRow({ field, onToggle }) {
687
684
  children: [
688
685
  /* @__PURE__ */ jsx(Typography, {
689
686
  variant: "label",
687
+ component: "span",
690
688
  children: field.label
691
689
  }),
692
690
  replaces && /* @__PURE__ */ jsx(Typography, {
@@ -855,10 +853,11 @@ function FormEnhanceAction({ path, status, collection, formContext }) {
855
853
  }),
856
854
  /* @__PURE__ */ jsx(Separator, { orientation: "horizontal" }),
857
855
  /* @__PURE__ */ jsxs("div", {
858
- className: cls("my-2 w-[500px] max-w-full flex items-start text-surface-700 dark:text-surface-200"),
859
- children: [
860
- /* @__PURE__ */ jsx(TextareaAutosize, {
861
- className: cls("p-4 rounded-lg resize-none bg-surface-100 dark:bg-surface-950 mx-2 w-full grow outline-hidden max-h-[300px] overflow-auto", focusedDisabled),
856
+ className: cls("my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200"),
857
+ children: [/* @__PURE__ */ jsxs("div", {
858
+ className: "relative w-full grow",
859
+ children: [/* @__PURE__ */ jsx(TextareaAutosize, {
860
+ className: cls("p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto", fieldBackgroundMixin, focusedDisabled),
862
861
  value: instructions,
863
862
  autoFocus: status === "new",
864
863
  disabled: loading,
@@ -876,24 +875,23 @@ function FormEnhanceAction({ path, status, collection, formContext }) {
876
875
  onChange: (e) => {
877
876
  setInstructions(e.target.value);
878
877
  }
879
- }),
880
- /* @__PURE__ */ jsx(IconButton, {
881
- size: "small",
882
- onClick: () => {
883
- setInstructions("");
884
- },
885
- color: !instructions ? "primary" : void 0,
886
- disabled: loading || !instructions,
887
- children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.small })
888
- }),
889
- /* @__PURE__ */ jsxs(IconButton, {
890
- onClick: () => generate(instructions),
891
- size: "small",
892
- color: !instructions ? "primary" : void 0,
893
- disabled: loading || !instructions,
894
- children: [loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }), !loading && /* @__PURE__ */ jsx(SendIcon, { color: "primary" })]
895
- })
896
- ]
878
+ }), instructions.length > 0 && !loading && /* @__PURE__ */ jsx("div", {
879
+ className: "flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2",
880
+ children: /* @__PURE__ */ jsx(IconButton, {
881
+ size: "small",
882
+ onClick: () => {
883
+ setInstructions("");
884
+ },
885
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.small })
886
+ })
887
+ })]
888
+ }), /* @__PURE__ */ jsxs(IconButton, {
889
+ onClick: () => generate(instructions),
890
+ size: "small",
891
+ color: !instructions ? "primary" : void 0,
892
+ disabled: loading || !instructions,
893
+ children: [loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }), !loading && /* @__PURE__ */ jsx(SendIcon, { size: iconSize.small })]
894
+ })]
897
895
  })
898
896
  ]
899
897
  }), /* @__PURE__ */ jsx(AutofillReviewDialog, {})] });
@@ -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 * 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/**\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\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.\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\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 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 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 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/admin\";\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","export function flatMapEntityValues<M extends object>(values: M, path = \"\"): object {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (typeof value === \"object\") {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {})\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/admin\";\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 } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/admin-types\";\nimport { autofillStream, fetchAiStatus, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues } 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 getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig\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 /** The host app's own opt-out. */\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection })));\n }, [getConfigForPath, path, collection]);\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 useEffect(() => {\n if (!allowedHere) return;\n const abort = new AbortController();\n fetchAiStatus({ endpoint,\nsignal: abort.signal })\n .then((status) => setServiceAvailable(status.available))\n .catch(() => setServiceAvailable(false));\n return () => abort.abort();\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 = flatMapEntityValues(params.values ?? {}) as Record<string, unknown>;\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 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: current.fields.map((f) => ({ ...f,\npending: false }))\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 fields: current.fields.map((f) => ({ ...f,\npending: false }))\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({ endpoint,\nentityName,\ninput }),\n [endpoint]\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 <Typography variant={\"label\"} 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 <Typography variant={\"label\"}>{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 Button,\n CircularProgress,\n cls,\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/admin-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 trigger={<Button variant={\"filled\"}\n color={\"neutral\"}\n size={\"small\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n Autofill\n </Button>}>\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 <div\n className={cls(\n \"my-2 w-[500px] max-w-full flex items-start text-surface-700 dark:text-surface-200\"\n )}>\n\n <TextareaAutosize\n className={cls(\"p-4 rounded-lg resize-none bg-surface-100 dark:bg-surface-950 mx-2 w-full grow outline-hidden max-h-[300px] overflow-auto\", 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 <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n <XIcon size={iconSize.small}/>\n </IconButton>\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 {!loading &&\n <SendIcon color={\"primary\"}/>}\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/admin-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 * 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/**\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\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.\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\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 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/admin\";\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","export function flatMapEntityValues<M extends object>(values: M, path = \"\"): object {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (typeof value === \"object\") {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {})\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/admin\";\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 } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/admin-types\";\nimport { autofillStream, fetchAiStatus, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues } 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 getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig\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 /** The host app's own opt-out. */\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection })));\n }, [getConfigForPath, path, collection]);\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 useEffect(() => {\n if (!allowedHere) return;\n const abort = new AbortController();\n fetchAiStatus({ endpoint,\nsignal: abort.signal })\n .then((status) => setServiceAvailable(status.available))\n .catch(() => setServiceAvailable(false));\n return () => abort.abort();\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 = flatMapEntityValues(params.values ?? {}) as Record<string, unknown>;\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 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 Button,\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/admin-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 trigger={<Button variant={\"filled\"}\n color={\"neutral\"}\n size={\"small\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n Autofill\n </Button>}>\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/admin-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,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/plugin-ai",
3
3
  "type": "module",
4
- "version": "0.12.1-canary.gf4240e3",
4
+ "version": "0.13.0",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.es.js",
7
7
  "module": "./dist/index.es.js",
@@ -16,13 +16,13 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "dependencies": {
19
- "@rebasepro/admin": "0.12.1-canary.gf4240e3",
20
- "@rebasepro/admin-types": "0.12.1-canary.gf4240e3",
21
- "@rebasepro/app": "0.12.1-canary.gf4240e3",
22
- "@rebasepro/types": "0.12.1-canary.gf4240e3",
23
- "@rebasepro/common": "0.12.1-canary.gf4240e3",
24
- "@rebasepro/ui": "0.12.1-canary.gf4240e3",
25
- "@rebasepro/utils": "0.12.1-canary.gf4240e3"
19
+ "@rebasepro/admin": "0.13.0",
20
+ "@rebasepro/admin-types": "0.13.0",
21
+ "@rebasepro/app": "0.13.0",
22
+ "@rebasepro/common": "0.13.0",
23
+ "@rebasepro/types": "0.13.0",
24
+ "@rebasepro/ui": "0.13.0",
25
+ "@rebasepro/utils": "0.13.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=19.2.7",
@@ -44,6 +44,7 @@
44
44
  "devDependencies": {
45
45
  "@testing-library/jest-dom": "^7.0.0",
46
46
  "@testing-library/react": "^16.3.2",
47
+ "@testing-library/user-event": "^14.6.1",
47
48
  "@types/jest": "^30.0.0",
48
49
  "@types/node": "^26.1.2",
49
50
  "@types/react": "^19.2.17",
package/src/api.ts CHANGED
@@ -233,6 +233,8 @@ export async function autocompleteStream(props: {
233
233
  */
234
234
  export async function fetchPromptSuggestions(props: {
235
235
  entityName: string;
236
+ /** Ties suggestions to the domain — see the note on the service's side. */
237
+ entityDescription?: string;
236
238
  input?: string;
237
239
  endpoint?: string;
238
240
  signal?: AbortSignal;
@@ -243,6 +245,7 @@ export async function fetchPromptSuggestions(props: {
243
245
  headers: { "Content-Type": "application/json" },
244
246
  body: JSON.stringify({
245
247
  entityName: props.entityName,
248
+ entityDescription: props.entityDescription,
246
249
  input: props.input
247
250
  }),
248
251
  signal: props.signal
@@ -69,7 +69,12 @@ export function AutofillReviewDialog() {
69
69
  size={"small"}
70
70
  onCheckedChange={() => controller.toggleAll(!allSelected)}
71
71
  />
72
- <Typography variant={"label"} color={"secondary"}>
72
+ {/* `component="span"`: the Typography `label`
73
+ variant renders a <label> element, and this sits
74
+ inside the row's own <label>. Nested labels are
75
+ invalid HTML and stop the text toggling the
76
+ checkbox — clicking "Select all" did nothing. */}
77
+ <Typography variant={"label"} component={"span"} color={"secondary"}>
73
78
  {allSelected ? "Deselect all" : "Select all"}
74
79
  </Typography>
75
80
  </label>
@@ -150,7 +155,8 @@ function ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle:
150
155
 
151
156
  <div className={"flex flex-col gap-1 min-w-0 grow"}>
152
157
  <div className={"flex items-center gap-2"}>
153
- <Typography variant={"label"}>{field.label}</Typography>
158
+ {/* See the note above: never a bare `label` variant inside a <label>. */}
159
+ <Typography variant={"label"} component={"span"}>{field.label}</Typography>
154
160
  {replaces && (
155
161
  <Typography variant={"caption"} color={"secondary"}>
156
162
  replaces the current value
@@ -187,8 +187,13 @@ proposed: String(existing.proposed ?? "") + text }
187
187
  setReview((current) => current && {
188
188
  ...current,
189
189
  status: "ready",
190
- fields: current.fields.map((f) => ({ ...f,
191
- pending: false }))
190
+ // Fields still pending when the run ended never received a final
191
+ // value — the model's JSON was cut off mid-string, so all we
192
+ // hold is a half-written sentence. Marking them complete would
193
+ // make that sentence applicable, which is the exact outcome the
194
+ // review exists to prevent. They are dropped instead: the review
195
+ // only ever offers what the service actually finished.
196
+ fields: current.fields.filter((f) => !f.pending)
192
197
  });
193
198
  } catch (e: unknown) {
194
199
  const message = e instanceof Error ? e.message : "Autofill could not be completed";
@@ -199,8 +204,9 @@ pending: false }))
199
204
  ...current,
200
205
  status: "failed",
201
206
  error: message,
202
- fields: current.fields.map((f) => ({ ...f,
203
- pending: false }))
207
+ // Same rule as the success path: a field interrupted mid-write
208
+ // is not something the operator can be offered.
209
+ fields: current.fields.filter((f) => !f.pending)
204
210
  });
205
211
  }
206
212
  }, [collection, endpoint, upsertField]);
@@ -238,10 +244,13 @@ selected }))
238
244
  const editorAIController = useEditorAIController({ endpoint });
239
245
 
240
246
  const getSamplePrompts = useCallback(
241
- (entityName: string, input?: string) => fetchPromptSuggestions({ endpoint,
242
- entityName,
243
- input }),
244
- [endpoint]
247
+ (entityName: string, input?: string) => fetchPromptSuggestions({
248
+ endpoint,
249
+ entityName,
250
+ entityDescription: collection.description,
251
+ input
252
+ }),
253
+ [endpoint, collection.description]
245
254
  );
246
255
 
247
256
  const dataEnhancementController: DataEnhancementController = useMemo(() => ({