@rebasepro/plugin-ai 0.17.3 → 0.18.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/LICENSE CHANGED
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
-
package/README.md CHANGED
@@ -8,6 +8,10 @@ AI-powered data autofill and text autocomplete plugin for Rebase.
8
8
  pnpm add @rebasepro/plugin-ai
9
9
  ```
10
10
 
11
+ ESM-only: `"type": "module"` with no CommonJS build, so it is loaded with
12
+ `import`. `require()` of it resolves only on Node 22.12+, which supports
13
+ `require(esm)`.
14
+
11
15
  **Peer dependencies:** `react >= 19.2.7`, `react-dom >= 19.2.7`, `react-router ^8`
12
16
 
13
17
  ## What This Package Does
package/dist/api.d.ts CHANGED
@@ -2,6 +2,14 @@ import { AutofillRequest, AutofillResult, AiStatus, SamplePromptsResult } from "
2
2
  /**
3
3
  * The hosted service Rebase runs for this plugin.
4
4
  *
5
+ * **This is where the entity's field values go.** Autofill posts them to
6
+ * generate a suggestion, and unless the host sets `endpoint`, they go to this
7
+ * address — off their machine, to a service Rebase operates. That is disclosed
8
+ * on the plugins page rather than only here, because the person who needs to
9
+ * know is the one deciding whether the data in those fields may leave.
10
+ *
11
+ * No credential travels with them; see the note below.
12
+ *
5
13
  * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that
6
14
  * resolves but serves nothing — every path 404s — so Autofill had never worked
7
15
  * in a Rebase install. This one is served by the control plane
@@ -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/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleProperty(property: Property): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n of: getSimpleProperty(property.of as Property)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n try {\n await autofillStream({\n endpoint,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => setReview(null), []);\n\n const applyReview = useCallback(() => {\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/api.ts","../src/utils/properties.ts","../src/utils/values.ts","../src/editor/useEditorAIController.tsx","../src/components/DataEnhancementControllerProvider.tsx","../src/components/AutofillReviewDialog.tsx","../src/components/FormEnhanceAction.tsx","../src/useDataEnhancementPlugin.tsx"],"sourcesContent":["import {\n AutofillRequest,\n AutofillResult,\n AiStatus,\n SamplePromptsResult\n} from \"./types/data_enhancement_controller\";\n\n/**\n * The hosted service Rebase runs for this plugin.\n *\n * **This is where the entity's field values go.** Autofill posts them to\n * generate a suggestion, and unless the host sets `endpoint`, they go to this\n * address — off their machine, to a service Rebase operates. That is disclosed\n * on the plugins page rather than only here, because the person who needs to\n * know is the one deciding whether the data in those fields may leave.\n *\n * No credential travels with them; see the note below.\n *\n * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that\n * resolves but serves nothing — every path 404s — so Autofill had never worked\n * in a Rebase install. This one is served by the control plane\n * (`saas/backend/functions/ai.ts`). Point `endpoint` somewhere else to run your\n * own; the wire format below is the whole contract.\n */\nexport const DEFAULT_AI_ENDPOINT = \"https://app.rebase.pro/api/functions/ai\";\n\n/**\n * ## No credentials cross this boundary\n *\n * The old client sent the tenant's Rebase JWT as `Authorization: Basic <jwt>`\n * plus a hardcoded `fcms-…` key compiled into the published package. Both were\n * wrong in the same way: a self-hosted backend signs its tokens with its own\n * secret, so no external service can verify one — sending it only handed a live\n * credential to a third party that had no use for it.\n *\n * These requests are anonymous. The service bounds cost by rate limit and daily\n * ceiling rather than by identity, and reports through {@link fetchAiStatus}\n * when it can no longer serve — which is what keeps the UI from offering an\n * action that is going to fail.\n */\nfunction endpointOf(endpoint: string | undefined, path: string): string {\n return (endpoint ?? DEFAULT_AI_ENDPOINT).replace(/\\/+$/, \"\") + path;\n}\n\n/** One `event:`/`data:` pair off the wire. */\ntype ServerSentEvent = { event: string; data: string };\n\n/** Not global: `exec` must not carry `lastIndex` between buffer reads. */\nconst SSE_SEPARATOR = /\\r?\\n\\r?\\n/;\n\n/**\n * Parse an SSE body incrementally.\n *\n * The framing this replaces split each chunk on the literal `\"&$# \"` and\n * `JSON.parse`d the pieces, which corrupted itself the moment a delimiter\n * straddled two reads — and network reads land wherever they land. Buffering\n * until a blank line is the fix, and it is also just what SSE specifies.\n */\nasync function* readServerSentEvents(response: Response): AsyncGenerator<ServerSentEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"The AI service returned no response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // A record ends at a blank line. `\\r\\n` is tolerated because proxies\n // rewrite line endings. The separator is located with `exec` rather\n // than `search` so its actual length is known — a `\\r\\n\\r\\n` boundary\n // is four characters, not two, and slicing by the wrong count leaves a\n // stray newline that swallows the next record's `event:` field.\n let match = SSE_SEPARATOR.exec(buffer);\n while (match) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const parsed = parseEventBlock(raw);\n if (parsed) yield parsed;\n match = SSE_SEPARATOR.exec(buffer);\n }\n }\n}\n\nfunction parseEventBlock(block: string): ServerSentEvent | undefined {\n let event = \"message\";\n const dataLines: string[] = [];\n for (const line of block.split(/\\r?\\n/)) {\n if (line.startsWith(\":\")) continue; // comment / keep-alive\n const separator = line.indexOf(\":\");\n const field = separator === -1 ? line : line.slice(0, separator);\n const rawValue = separator === -1 ? \"\" : line.slice(separator + 1);\n const value = rawValue.startsWith(\" \") ? rawValue.slice(1) : rawValue;\n if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0) return undefined;\n return { event,\ndata: dataLines.join(\"\\n\") };\n}\n\n/** Pull a message out of the control plane's `{ error: { message } }` envelope. */\nasync function errorFrom(response: Response, fallback: string): Promise<Error> {\n try {\n const body = await response.json();\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return new Error(message);\n } catch {\n /* not JSON — fall through */\n }\n return new Error(fallback);\n}\n\n/**\n * Ask the service whether it can serve a request at all.\n *\n * The plugin gates every affordance on this. A missing provider key, an\n * exhausted daily quota or an unreachable host all resolve to `available:\n * false`, and the Autofill button is simply not rendered — rather than\n * rendered, clicked, and failed.\n */\nexport async function fetchAiStatus(props: { endpoint?: string; signal?: AbortSignal }): Promise<AiStatus> {\n const response = await fetch(endpointOf(props.endpoint, \"/status\"), {\n method: \"GET\",\n signal: props.signal\n });\n if (!response.ok) return { available: false };\n const body = await response.json();\n return {\n available: Boolean(body?.available),\n model: typeof body?.model === \"string\" ? body.model : undefined,\n features: Array.isArray(body?.features) ? body.features : undefined\n };\n}\n\n/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleProperty(property: Property): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n of: getSimpleProperty(property.of as Property)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n try {\n await autofillStream({\n endpoint,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => setReview(null), []);\n\n const applyReview = useCallback(() => {\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,32 +1,51 @@
1
1
  {
2
2
  "name": "@rebasepro/plugin-ai",
3
- "type": "module",
4
- "version": "0.17.3",
3
+ "version": "0.18.0",
4
+ "description": "AI plugin for the Rebase admin panel: field generation and content assistance.",
5
+ "keywords": [
6
+ "rebase",
7
+ "plugin",
8
+ "ai",
9
+ "cms"
10
+ ],
11
+ "homepage": "https://rebase.pro",
12
+ "bugs": {
13
+ "url": "https://github.com/rebasepro/rebase/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/rebasepro/rebase.git",
18
+ "directory": "packages/plugin-ai"
19
+ },
5
20
  "license": "MIT",
21
+ "engines": {
22
+ "node": ">=22.22.0"
23
+ },
24
+ "type": "module",
6
25
  "main": "./dist/index.es.js",
7
26
  "module": "./dist/index.es.js",
8
27
  "types": "./dist/index.d.ts",
9
- "source": "src/index.ts",
10
28
  "exports": {
11
29
  ".": {
12
30
  "types": "./dist/index.d.ts",
13
31
  "development": "./dist/index.es.js",
14
- "import": "./dist/index.es.js"
32
+ "import": "./dist/index.es.js",
33
+ "default": "./dist/index.es.js"
15
34
  },
16
35
  "./package.json": "./package.json"
17
36
  },
18
37
  "dependencies": {
19
- "@rebasepro/cms": "0.17.3",
20
- "@rebasepro/cms-types": "0.17.3",
21
- "@rebasepro/app": "0.17.3",
22
- "@rebasepro/ui": "0.17.3",
23
- "@rebasepro/common": "0.17.3",
24
- "@rebasepro/types": "0.17.3",
25
- "@rebasepro/utils": "0.17.3"
38
+ "@rebasepro/cms": "0.18.0",
39
+ "@rebasepro/common": "0.18.0",
40
+ "@rebasepro/app": "0.18.0",
41
+ "@rebasepro/cms-types": "0.18.0",
42
+ "@rebasepro/ui": "0.18.0",
43
+ "@rebasepro/types": "0.18.0",
44
+ "@rebasepro/utils": "0.18.0"
26
45
  },
27
46
  "peerDependencies": {
28
- "react": ">=19.2.7",
29
- "react-dom": ">=19.2.7",
47
+ "react": "^19.2.7",
48
+ "react-dom": "^19.2.7",
30
49
  "react-router": "^8.3.0"
31
50
  },
32
51
  "browserslist": {
@@ -87,21 +106,16 @@
87
106
  ]
88
107
  },
89
108
  "files": [
90
- "dist",
91
- "src"
109
+ "dist"
92
110
  ],
93
111
  "publishConfig": {
94
112
  "access": "public"
95
113
  },
96
114
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
97
- "repository": {
98
- "type": "git",
99
- "url": "https://github.com/rebasepro/rebase.git",
100
- "directory": "packages/plugin-ai"
101
- },
102
115
  "scripts": {
103
116
  "dev": "vite",
104
117
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
105
- "test": "jest --passWithNoTests"
118
+ "test": "jest --passWithNoTests",
119
+ "test:watch": "jest --watch"
106
120
  }
107
121
  }