@rebasepro/plugin-ai 0.21.2-canary.g1ea48be → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.es.js +59 -15
- package/dist/index.es.js.map +1 -1
- package/package.json +8 -8
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { AIIcon, useAuthController } from "@rebasepro/app";
|
|
2
|
+
import { AIIcon, isDisabled, isHidden, isReadOnly, useAuthController } from "@rebasepro/app";
|
|
3
3
|
import { getFieldId } from "@rebasepro/cms";
|
|
4
4
|
import { isPropertyBuilder, stripCollectionPath } from "@rebasepro/common";
|
|
5
5
|
import { getValueInPath } from "@rebasepro/utils";
|
|
@@ -239,7 +239,18 @@ function getSimplifiedProperties(properties, values, path = "") {
|
|
|
239
239
|
...b
|
|
240
240
|
}), {});
|
|
241
241
|
}
|
|
242
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Whether the form will not let anyone edit this field.
|
|
244
|
+
*
|
|
245
|
+
* The same three gates the form lays a field out with, so a literal
|
|
246
|
+
* `conditions.readOnly` or a date the backend stamps is locked here too. A
|
|
247
|
+
* locked container locks everything under it: the form disables a map's
|
|
248
|
+
* children with the map, so they are not the model's to fill either.
|
|
249
|
+
*/
|
|
250
|
+
function isLocked(property, lockedByParent) {
|
|
251
|
+
return lockedByParent || isReadOnly(property) || isDisabled(property) || isHidden(property);
|
|
252
|
+
}
|
|
253
|
+
function getSimpleProperty(property, lockedByParent = false) {
|
|
243
254
|
const fieldId = getFieldId(property);
|
|
244
255
|
if (!fieldId) {
|
|
245
256
|
console.error("No fieldId found for property", property);
|
|
@@ -251,11 +262,12 @@ function getSimpleProperty(property) {
|
|
|
251
262
|
type: property.type,
|
|
252
263
|
fieldConfigId: fieldId,
|
|
253
264
|
enum: "enum" in property && property.enum ? getSimpleEnumValues(property.enum) : void 0,
|
|
254
|
-
disabled:
|
|
265
|
+
disabled: isLocked(property, lockedByParent)
|
|
255
266
|
};
|
|
256
267
|
}
|
|
257
|
-
function getSimplifiedProperty(property, path, value) {
|
|
268
|
+
function getSimplifiedProperty(property, path, value, lockedByParent = false) {
|
|
258
269
|
if (isPropertyBuilder(property)) return {};
|
|
270
|
+
const locked = isLocked(property, lockedByParent);
|
|
259
271
|
if (property.type === "array") {
|
|
260
272
|
if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {
|
|
261
273
|
const arrayParentProperty = {
|
|
@@ -263,8 +275,8 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
263
275
|
description: property.description,
|
|
264
276
|
type: property.type,
|
|
265
277
|
fieldConfigId: "repeat",
|
|
266
|
-
disabled:
|
|
267
|
-
of: getSimpleProperty(property.of)
|
|
278
|
+
disabled: locked,
|
|
279
|
+
of: getSimpleProperty(property.of, locked)
|
|
268
280
|
};
|
|
269
281
|
return { [path]: arrayParentProperty };
|
|
270
282
|
} else if (property.oneOf) {
|
|
@@ -273,11 +285,11 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
273
285
|
description: property.description,
|
|
274
286
|
type: property.type,
|
|
275
287
|
fieldConfigId: "block",
|
|
276
|
-
disabled:
|
|
288
|
+
disabled: locked,
|
|
277
289
|
oneOf: {
|
|
278
290
|
typeField: property.oneOf.typeField,
|
|
279
291
|
valueField: property.oneOf.valueField,
|
|
280
|
-
properties: Object.entries(property.oneOf.properties).map(([key, prop]) => ({ [key]: getSimpleProperty(prop) })).reduce((a, b) => ({
|
|
292
|
+
properties: Object.entries(property.oneOf.properties).map(([key, prop]) => ({ [key]: getSimpleProperty(prop, locked) })).reduce((a, b) => ({
|
|
281
293
|
...a,
|
|
282
294
|
...b
|
|
283
295
|
}), {})
|
|
@@ -295,7 +307,7 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
295
307
|
console.error(`No property found for type ${oneOfType}`, property.oneOf.properties);
|
|
296
308
|
return {};
|
|
297
309
|
}
|
|
298
|
-
const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);
|
|
310
|
+
const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue, locked);
|
|
299
311
|
return {
|
|
300
312
|
[`${path}.${i}.${typeKey}`]: oneOfType,
|
|
301
313
|
...simplifiedProperty
|
|
@@ -308,7 +320,7 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
308
320
|
} else if (property.type === "map") {
|
|
309
321
|
if (property.properties) {
|
|
310
322
|
const mapProperties = Object.entries(property.properties).map(([key, childProperty]) => {
|
|
311
|
-
return getSimplifiedProperty(childProperty, key, value && typeof value === "object" ? value[key] : void 0);
|
|
323
|
+
return getSimplifiedProperty(childProperty, key, value && typeof value === "object" ? value[key] : void 0, locked);
|
|
312
324
|
}).map((o) => attachPathToKeys(o, path)).reduce((a, b) => ({
|
|
313
325
|
...a,
|
|
314
326
|
...b
|
|
@@ -319,7 +331,7 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
319
331
|
description: property.description,
|
|
320
332
|
type: property.type,
|
|
321
333
|
fieldConfigId: "group",
|
|
322
|
-
disabled:
|
|
334
|
+
disabled: locked
|
|
323
335
|
};
|
|
324
336
|
return {
|
|
325
337
|
[path]: mapParentProperty,
|
|
@@ -331,7 +343,7 @@ function getSimplifiedProperty(property, path, value) {
|
|
|
331
343
|
console.warn(`No fieldId found for property ${path} with type ${property.type}`);
|
|
332
344
|
return {};
|
|
333
345
|
}
|
|
334
|
-
return { [path]: getSimpleProperty(property) };
|
|
346
|
+
return { [path]: getSimpleProperty(property, lockedByParent) };
|
|
335
347
|
}
|
|
336
348
|
return {};
|
|
337
349
|
}
|
|
@@ -509,6 +521,22 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
509
521
|
};
|
|
510
522
|
}, [allowedHere, endpoint]);
|
|
511
523
|
const enabled = allowedHere && serviceAvailable;
|
|
524
|
+
/**
|
|
525
|
+
* The run whose review is on screen.
|
|
526
|
+
*
|
|
527
|
+
* A run outlives the review it was started for: the operator can discard
|
|
528
|
+
* it, or start another, while its stream is still arriving. Every callback
|
|
529
|
+
* checks it is still the current run before touching the review, and a run
|
|
530
|
+
* that stops being current has its request cancelled. Without this, a
|
|
531
|
+
* discarded run kept writing into the next run's review and, when it ended,
|
|
532
|
+
* marked that review ready while the new run was still writing.
|
|
533
|
+
*/
|
|
534
|
+
const currentRunRef = useRef(null);
|
|
535
|
+
const cancelCurrentRun = useCallback(() => {
|
|
536
|
+
currentRunRef.current?.abort();
|
|
537
|
+
currentRunRef.current = null;
|
|
538
|
+
}, []);
|
|
539
|
+
useEffect(() => cancelCurrentRun, [cancelCurrentRun]);
|
|
512
540
|
/** Add or update one row in the review, preserving arrival order. */
|
|
513
541
|
const upsertField = useCallback((key, update) => {
|
|
514
542
|
setReview((current) => {
|
|
@@ -531,9 +559,14 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
531
559
|
instructions: params.instructions
|
|
532
560
|
});
|
|
533
561
|
const labelFor = (key) => currentProperties[key]?.name ?? key;
|
|
562
|
+
cancelCurrentRun();
|
|
563
|
+
const run = new AbortController();
|
|
564
|
+
currentRunRef.current = run;
|
|
565
|
+
const isCurrent = () => currentRunRef.current === run;
|
|
534
566
|
try {
|
|
535
567
|
await autofillStream({
|
|
536
568
|
endpoint,
|
|
569
|
+
signal: run.signal,
|
|
537
570
|
request: {
|
|
538
571
|
entityName: collection.singularName ?? collection.name,
|
|
539
572
|
entityDescription: collection.description,
|
|
@@ -544,6 +577,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
544
577
|
instructions: params.instructions
|
|
545
578
|
},
|
|
546
579
|
onDelta: (key, text) => {
|
|
580
|
+
if (!isCurrent()) return;
|
|
547
581
|
upsertField(key, (existing) => existing ? {
|
|
548
582
|
...existing,
|
|
549
583
|
proposed: String(existing.proposed ?? "") + text
|
|
@@ -557,6 +591,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
557
591
|
});
|
|
558
592
|
},
|
|
559
593
|
onValue: (key, value) => {
|
|
594
|
+
if (!isCurrent()) return;
|
|
560
595
|
const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));
|
|
561
596
|
upsertField(key, (existing) => ({
|
|
562
597
|
key,
|
|
@@ -568,12 +603,16 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
568
603
|
}));
|
|
569
604
|
}
|
|
570
605
|
});
|
|
606
|
+
if (!isCurrent()) return;
|
|
607
|
+
currentRunRef.current = null;
|
|
571
608
|
setReview((current) => current && {
|
|
572
609
|
...current,
|
|
573
610
|
status: "ready",
|
|
574
611
|
fields: current.fields.filter((f) => !f.pending)
|
|
575
612
|
});
|
|
576
613
|
} catch (e) {
|
|
614
|
+
if (!isCurrent()) return;
|
|
615
|
+
currentRunRef.current = null;
|
|
577
616
|
const message = e instanceof Error ? e.message : "Autofill could not be completed";
|
|
578
617
|
setReview((current) => current && {
|
|
579
618
|
...current,
|
|
@@ -585,7 +624,8 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
585
624
|
}, [
|
|
586
625
|
collection,
|
|
587
626
|
endpoint,
|
|
588
|
-
upsertField
|
|
627
|
+
upsertField,
|
|
628
|
+
cancelCurrentRun
|
|
589
629
|
]);
|
|
590
630
|
const toggleField = useCallback((key) => {
|
|
591
631
|
setReview((current) => current && {
|
|
@@ -605,8 +645,12 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
605
645
|
}))
|
|
606
646
|
});
|
|
607
647
|
}, []);
|
|
608
|
-
const dismissReview = useCallback(() =>
|
|
648
|
+
const dismissReview = useCallback(() => {
|
|
649
|
+
cancelCurrentRun();
|
|
650
|
+
setReview(null);
|
|
651
|
+
}, [cancelCurrentRun]);
|
|
609
652
|
const applyReview = useCallback(() => {
|
|
653
|
+
cancelCurrentRun();
|
|
610
654
|
setReview((current) => {
|
|
611
655
|
if (!current) return null;
|
|
612
656
|
for (const field of current.fields) {
|
|
@@ -616,7 +660,7 @@ function DataEnhancementControllerProvider({ getConfigForPath, children, endpoin
|
|
|
616
660
|
}
|
|
617
661
|
return null;
|
|
618
662
|
});
|
|
619
|
-
}, [formContext]);
|
|
663
|
+
}, [formContext, cancelCurrentRun]);
|
|
620
664
|
const editorAIController = useEditorAIController({ endpoint });
|
|
621
665
|
const getSamplePrompts = useCallback((entityName, input) => fetchPromptSuggestions({
|
|
622
666
|
endpoint,
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":[],"sources":["../src/api.ts","../src/utils/properties.ts","../src/utils/values.ts","../src/editor/useEditorAIController.tsx","../src/components/DataEnhancementControllerProvider.tsx","../src/components/AutofillReviewDialog.tsx","../src/components/FormEnhanceAction.tsx","../src/useDataEnhancementPlugin.tsx"],"sourcesContent":["import {\n AutofillRequest,\n AutofillResult,\n AiStatus,\n SamplePromptsResult\n} from \"./types/data_enhancement_controller\";\n\n/**\n * The hosted service Rebase runs for this plugin.\n *\n * **This is where the entity's field values go.** Autofill posts them to\n * generate a suggestion, and unless the host sets `endpoint`, they go to this\n * address — off their machine, to a service Rebase operates. That is disclosed\n * on the plugins page rather than only here, because the person who needs to\n * know is the one deciding whether the data in those fields may leave.\n *\n * No credential travels with them; see the note below.\n *\n * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that\n * resolves but serves nothing — every path 404s — so Autofill had never worked\n * in a Rebase install. This one is served by the control plane\n * (`saas/backend/functions/ai.ts`). Point `endpoint` somewhere else to run your\n * own; the wire format below is the whole contract.\n */\nexport const DEFAULT_AI_ENDPOINT = \"https://app.rebase.pro/api/functions/ai\";\n\n/**\n * ## No credentials cross this boundary\n *\n * The old client sent the tenant's Rebase JWT as `Authorization: Basic <jwt>`\n * plus a hardcoded `fcms-…` key compiled into the published package. Both were\n * wrong in the same way: a self-hosted backend signs its tokens with its own\n * secret, so no external service can verify one — sending it only handed a live\n * credential to a third party that had no use for it.\n *\n * These requests are anonymous. The service bounds cost by rate limit and daily\n * ceiling rather than by identity, and reports through {@link fetchAiStatus}\n * when it can no longer serve — which is what keeps the UI from offering an\n * action that is going to fail.\n */\nfunction endpointOf(endpoint: string | undefined, path: string): string {\n return (endpoint ?? DEFAULT_AI_ENDPOINT).replace(/\\/+$/, \"\") + path;\n}\n\n/** One `event:`/`data:` pair off the wire. */\ntype ServerSentEvent = { event: string; data: string };\n\n/** Not global: `exec` must not carry `lastIndex` between buffer reads. */\nconst SSE_SEPARATOR = /\\r?\\n\\r?\\n/;\n\n/**\n * Parse an SSE body incrementally.\n *\n * The framing this replaces split each chunk on the literal `\"&$# \"` and\n * `JSON.parse`d the pieces, which corrupted itself the moment a delimiter\n * straddled two reads — and network reads land wherever they land. Buffering\n * until a blank line is the fix, and it is also just what SSE specifies.\n */\nasync function* readServerSentEvents(response: Response): AsyncGenerator<ServerSentEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"The AI service returned no response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // A record ends at a blank line. `\\r\\n` is tolerated because proxies\n // rewrite line endings. The separator is located with `exec` rather\n // than `search` so its actual length is known — a `\\r\\n\\r\\n` boundary\n // is four characters, not two, and slicing by the wrong count leaves a\n // stray newline that swallows the next record's `event:` field.\n let match = SSE_SEPARATOR.exec(buffer);\n while (match) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const parsed = parseEventBlock(raw);\n if (parsed) yield parsed;\n match = SSE_SEPARATOR.exec(buffer);\n }\n }\n}\n\nfunction parseEventBlock(block: string): ServerSentEvent | undefined {\n let event = \"message\";\n const dataLines: string[] = [];\n for (const line of block.split(/\\r?\\n/)) {\n if (line.startsWith(\":\")) continue; // comment / keep-alive\n const separator = line.indexOf(\":\");\n const field = separator === -1 ? line : line.slice(0, separator);\n const rawValue = separator === -1 ? \"\" : line.slice(separator + 1);\n const value = rawValue.startsWith(\" \") ? rawValue.slice(1) : rawValue;\n if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0) return undefined;\n return { event,\ndata: dataLines.join(\"\\n\") };\n}\n\n/** Pull a message out of the control plane's `{ error: { message } }` envelope. */\nasync function errorFrom(response: Response, fallback: string): Promise<Error> {\n try {\n const body = await response.json();\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return new Error(message);\n } catch {\n /* not JSON — fall through */\n }\n return new Error(fallback);\n}\n\n/**\n * Ask the service whether it can serve a request at all.\n *\n * The plugin gates every affordance on this. A missing provider key, an\n * exhausted daily quota or an unreachable host all resolve to `available:\n * false`, and the Autofill button is simply not rendered — rather than\n * rendered, clicked, and failed.\n */\nexport async function fetchAiStatus(props: { endpoint?: string; signal?: AbortSignal }): Promise<AiStatus> {\n const response = await fetch(endpointOf(props.endpoint, \"/status\"), {\n method: \"GET\",\n signal: props.signal\n });\n if (!response.ok) return { available: false };\n const body = await response.json();\n return {\n available: Boolean(body?.available),\n model: typeof body?.model === \"string\" ? body.model : undefined,\n features: Array.isArray(body?.features) ? body.features : undefined\n };\n}\n\n/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleProperty(property: Property): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n of: getSimpleProperty(property.of as Property)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly),\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: Boolean(property.admin?.disabled || property.admin?.readOnly)\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n try {\n await autofillStream({\n endpoint,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => setReview(null), []);\n\n const applyReview = useCallback(() => {\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,WAAW,UAA8B,MAAsB;CACpE,QAAQ,YAAA,0CAAA,CAAiC,QAAQ,QAAQ,EAAE,IAAI;AACnE;;AAMA,IAAM,gBAAgB;;;;;;;;;AAUtB,gBAAgB,qBAAqB,UAAqD;CACtF,MAAM,SAAS,SAAS,MAAM,UAAU;CACxC,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,0CAA0C;CAEvE,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,SAAS;CAEb,SAAS;EACL,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;EAC1C,IAAI,MAAM;EACV,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;EAOhD,IAAI,QAAQ,cAAc,KAAK,MAAM;EACrC,OAAO,OAAO;GACV,MAAM,MAAM,OAAO,MAAM,GAAG,MAAM,KAAK;GACvC,SAAS,OAAO,MAAM,MAAM,QAAQ,MAAM,EAAE,CAAC,MAAM;GACnD,MAAM,SAAS,gBAAgB,GAAG;GAClC,IAAI,QAAQ,MAAM;GAClB,QAAQ,cAAc,KAAK,MAAM;EACrC;CACJ;AACJ;AAEA,SAAS,gBAAgB,OAA4C;CACjE,IAAI,QAAQ;CACZ,MAAM,YAAsB,CAAC;CAC7B,KAAK,MAAM,QAAQ,MAAM,MAAM,OAAO,GAAG;EACrC,IAAI,KAAK,WAAW,GAAG,GAAG;EAC1B,MAAM,YAAY,KAAK,QAAQ,GAAG;EAClC,MAAM,QAAQ,cAAc,KAAK,OAAO,KAAK,MAAM,GAAG,SAAS;EAC/D,MAAM,WAAW,cAAc,KAAK,KAAK,KAAK,MAAM,YAAY,CAAC;EACjE,MAAM,QAAQ,SAAS,WAAW,GAAG,IAAI,SAAS,MAAM,CAAC,IAAI;EAC7D,IAAI,UAAU,SAAS,QAAQ;OAC1B,IAAI,UAAU,QAAQ,UAAU,KAAK,KAAK;CACnD;CACA,IAAI,UAAU,WAAW,GAAG,OAAO,KAAA;CACnC,OAAO;EAAE;EACb,MAAM,UAAU,KAAK,IAAI;CAAE;AAC3B;;AAGA,eAAe,UAAU,UAAoB,UAAkC;CAC3E,IAAI;EAEA,MAAM,WAAU,MADG,SAAS,KAAK,EAAA,EACX,OAAO;EAC7B,IAAI,OAAO,YAAY,YAAY,SAAS,OAAO,IAAI,MAAM,OAAO;CACxE,QAAQ,CAER;CACA,OAAO,IAAI,MAAM,QAAQ;AAC7B;;;;;;;;;AAUA,eAAsB,cAAc,OAAuE;CACvG,MAAM,WAAW,MAAM,MAAM,WAAW,MAAM,UAAU,SAAS,GAAG;EAChE,QAAQ;EACR,QAAQ,MAAM;CAClB,CAAC;CACD,IAAI,CAAC,SAAS,IAAI,OAAO,EAAE,WAAW,MAAM;CAC5C,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,OAAO;EACH,WAAW,QAAQ,MAAM,SAAS;EAClC,OAAO,OAAO,MAAM,UAAU,WAAW,KAAK,QAAQ,KAAA;EACtD,UAAU,MAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,WAAW,KAAA;CAC9D;AACJ;;AAGA,IAAM,+BAAe,IAAI,IAA+B;;;;;;;;;;;;;;;AAgBxD,SAAgB,oBAAoB,OAAiD;CACjF,MAAM,MAAM,WAAW,MAAM,UAAU,SAAS;CAChD,MAAM,WAAW,aAAa,IAAI,GAAG;CACrC,IAAI,UAAU,OAAO;CACrB,MAAM,QAAQ,cAAc,EAAE,UAAU,MAAM,SAAS,CAAC,CAAC,CACpD,aAAa,EAAE,WAAW,MAAM,EAAc;CACnD,aAAa,IAAI,KAAK,KAAK;CAC3B,OAAO;AACX;;;;;;;;;;AAgBA,eAAsB,eAAe,OAMT;CACxB,MAAM,WAAW,MAAM,MAAM,WAAW,MAAM,UAAU,WAAW,GAAG;EAClE,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU,MAAM,OAAO;EAClC,QAAQ,MAAM;CAClB,CAAC;CAED,IAAI,CAAC,SAAS,IACV,MAAM,MAAM,UAAU,UAAU,iDAAiD;CAGrF,IAAI,SAAyB,EAAE,aAAa,CAAC,EAAE;CAC/C,IAAI,OAAO;CACX,IAAI,YAAY;CAChB,IAAI,YAAY;CAEhB,WAAW,MAAM,EAAE,OAAO,UAAU,qBAAqB,QAAQ,GAAG;EAChE,IAAI;EACJ,IAAI;GACA,UAAU,KAAK,MAAM,IAAI;EAC7B,QAAQ;GAKJ;GACA;EACJ;EAEA,IAAI,UAAU,oBAAoB;GAC9B;GACA,MAAM,QAAQ,QAAQ,KAAK,QAAQ,IAAI;EAC3C,OAAO,IAAI,UAAU,cAAc;GAC/B;GACA,MAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK;EAC5C,OAAO,IAAI,UAAU,QAAQ;GACzB,OAAO;GACP,SAAS;IACL,aAAa,QAAQ,eAAe,CAAC;IACrC,OAAO,QAAQ;GACnB;EACJ,OAAO,IAAI,UAAU,SACjB,MAAM,IAAI,MAAM,QAAQ,WAAW,mCAAmC;CAE9E;CASA,IAAI,CAAC,MACD,MAAM,IAAI,MAAM,4DAA4D;CAEhF,IAAI,YAAY,KAAK,cAAc,GAC/B,MAAM,IAAI,MAAM,8CAA8C;CAGlE,OAAO;AACX;;AAGA,eAAsB,mBAAmB,OAMrB;CAChB,MAAM,WAAW,MAAM,MAAM,WAAW,MAAM,UAAU,eAAe,GAAG;EACtE,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACjB,YAAY,MAAM,cAAc;GAChC,WAAW,MAAM,aAAa;EAClC,CAAC;EACD,QAAQ,MAAM;CAClB,CAAC;CAED,IAAI,CAAC,SAAS,IACV,MAAM,MAAM,UAAU,UAAU,iDAAiD;CAGrF,IAAI,OAAO;CACX,WAAW,MAAM,EAAE,OAAO,UAAU,qBAAqB,QAAQ,GAAG;EAChE,IAAI;EACJ,IAAI;GACA,UAAU,KAAK,MAAM,IAAI;EAC7B,QAAQ;GACJ;EACJ;EACA,IAAI,UAAU,SACV,MAAM,IAAI,MAAM,SAAS,WAAW,mCAAmC;EAE3E,IAAI,UAAU,WAAW,OAAO,SAAS,SAAS,UAAU;GACxD,QAAQ,QAAQ;GAChB,MAAM,QAAQ,QAAQ,IAAI;EAC9B;CACJ;CACA,OAAO;AACX;;;;;;;;AASA,eAAsB,uBAAuB,OAOZ;CAC7B,IAAI;EACA,MAAM,WAAW,MAAM,MAAM,WAAW,MAAM,UAAU,UAAU,GAAG;GACjE,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU;IACjB,YAAY,MAAM;IAClB,mBAAmB,MAAM;IACzB,OAAO,MAAM;GACjB,CAAC;GACD,QAAQ,MAAM;EAClB,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,OAAO,EAAE,SAAS,CAAC,EAAE;EACvC,MAAM,OAAO,MAAM,SAAS,KAAK;EAEjC,OAAO,EACH,UAFsB,MAAM,QAAQ,MAAM,OAAO,IAAI,KAAK,UAAU,CAAC,EAAA,CAGhE,QAAQ,MAAmB,OAAO,MAAM,QAAQ,CAAC,CACjD,KAAK,YAAY;GAAE;GACpC,MAAM;EAAkB,EAAE,EAClB;CACJ,QAAQ;EACJ,OAAO,EAAE,SAAS,CAAC,EAAE;CACzB;AACJ;;;ACjUA,SAAgB,wBAAuD,YAAwB,QAAW,OAAO,IAAmC;CAChJ,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,OAAO,OAAO,QAAQ,UAAU,CAAC,CAC5B,KAAK,CAAC,KAAK,cAAc;EACtB,IAAI,kBAAkB,QAAQ,GAAG,OAAO,CAAC;EACzC,MAAM,UAAU,OAAO,GAAG,KAAK,GAAG,QAAQ;EAE1C,OAAO,sBAAsB,UAAU,SADnB,eAAe,QAAQ,OACK,CAAW;CAC/D,CAAC,CAAC,CACD,QAAQ,GAAG,OAAO;EAAE,GAAG;EAChC,GAAG;CAAE,IAAI,CAAC,CAAC;AACX;AAEA,SAAS,kBAAkB,UAAmC;CAC1D,MAAM,UAAU,WAAW,QAAQ;CACnC,IAAI,CAAC,SAAS;EACV,QAAQ,MAAM,iCAAiC,QAAQ;EACvD,MAAM,IAAI,MAAM,oBAAoB;CACxC;CACA,OAAO;EACH,MAAM,SAAS;EACf,aAAa,SAAS;EACtB,MAAM,SAAS;EACf,eAAe;EACf,MAAM,UAAU,YAAY,SAAS,OAC/B,oBAAoB,SAAS,IAAI,IACjC,KAAA;EACN,UAAU,QAAQ,SAAS,OAAO,YAAY,SAAS,OAAO,QAAQ;CAC1E;AACJ;AAEA,SAAS,sBAAsB,UAAoB,MAAc,OAAgD;CAC7G,IAAI,kBAAkB,QAAQ,GAAG,OAAO,CAAC;CACzC,IAAI,SAAS,SAAS;MAEd,SAAS,MAAM,CAAC,MAAM,QAAQ,SAAS,EAAE,KAAK,CAAC,kBAAkB,SAAS,EAAE,GAAG;GAC/E,MAAM,sBAAqC;IACvC,MAAM,SAAS;IACf,aAAa,SAAS;IACtB,MAAM,SAAS;IACf,eAAe;IACf,UAAU,QAAQ,SAAS,OAAO,YAAY,SAAS,OAAO,QAAQ;IACtE,IAAI,kBAAkB,SAAS,EAAc;GACjD;GAsBA,OAAO,GApBW,OAAO,oBAoBlB;EACX,OAAO,IAAI,SAAS,OAAO;GAEvB,MAAM,sBAAqC;IACvC,MAAM,SAAS;IACf,aAAa,SAAS;IACtB,MAAM,SAAS;IACf,eAAe;IACf,UAAU,QAAQ,SAAS,OAAO,YAAY,SAAS,OAAO,QAAQ;IACtE,OAAO;KACH,WAAW,SAAS,MAAM;KAC1B,YAAY,SAAS,MAAM;KAC3B,YAAY,OAAO,QAAQ,SAAS,MAAM,UAAU,CAAC,CAChD,KAAK,CAAC,KAAK,WAAW,GAAG,MAAM,kBAAkB,IAAI,EAAE,EAAE,CAAC,CAC1D,QAAQ,GAAG,OAAO;MAAE,GAAG;MAChD,GAAG;KAAE,IAAI,CAAC,CAAC;IACK;GACJ;GAEA,IAAI,CAAC,MAAM,QAAQ,KAAK,GACpB,OAAO,GAAG,OAAO,oBAAoB;GAGzC,OAAO,MAAM,KAAK,GAAG,MAAM;IACvB,IAAI,KAAK,MAAM,OAAO,CAAC;IACvB,MAAM,UAAU,SAAS,MAAO,aAAa;IAC7C,MAAM,YAAY,EAAE;IACpB,MAAM,WAAW,SAAS,MAAO,cAAc;IAC/C,MAAM,aAAa,EAAE;IACrB,MAAM,gBAAgB,SAAS,MAAO,WAAW;IACjD,IAAI,kBAAkB,KAAA,GAAW;KAC7B,QAAQ,MAAM,8BAA8B,aAAa,SAAS,MAAO,UAAU;KACnF,OAAO,CAAC;IACZ;IACA,MAAM,qBAAqB,sBAAsB,eAAe,GAAG,KAAK,GAAG,EAAE,GAAG,YAAY,UAAU;IACtG,OAAO;MACF,GAAG,KAAK,GAAG,EAAE,GAAG,YAAY;KAC7B,GAAG;IACP;GACJ,CAAC,CAAC,CAAC,QAAQ,GAAG,OAAO;IAAE,GAAG;IACtC,GAAG;GAAE,IAAI,GAAG,OAAO,oBAAoB,CAAC;EAChC;QACG,IAAI,SAAS,SAAS;MACrB,SAAS,YAAY;GACrB,MAAM,gBAA+C,OAAO,QAAQ,SAAS,UAAU,CAAC,CACnF,KAAK,CAAC,KAAK,mBAAmB;IAE3B,OAAO,sBAAsB,eAAe,KADzB,SAAS,OAAO,UAAU,WAAY,MAAkC,OAAO,KAAA,CACvC;GAC/D,CAAC,CAAC,CACD,KAAI,MAAK,iBAAiB,GAAG,IAAI,CAAC,CAAC,CACnC,QAAQ,GAAG,OAAO;IAAE,GAAG;IACxC,GAAG;GAAE,IAAI,CAAC,CAAC;GAEC,IAAI,OAAO,KAAK,aAAa,CAAC,CAAC,WAAW,GAAG,OAAO,CAAC;GACrD,MAAM,oBAAmC;IACrC,MAAM,SAAS;IACf,aAAa,SAAS;IACtB,MAAM,SAAS;IACf,eAAe;IACf,UAAU,QAAQ,SAAS,OAAO,YAAY,SAAS,OAAO,QAAQ;GAC1E;GACA,OAAO;KACF,OAAO;IACR,GAAG;GACP;EACJ;QACG;EAEH,IAAI,CADY,WAAW,QACtB,GAAS;GACV,QAAQ,KAAK,iCAAiC,KAAK,aAAa,SAAS,MAAM;GAC/E,OAAO,CAAC;EACZ;EACA,OAAO,GACF,OAAO,kBAAkB,QAAQ,EACtC;CACJ;CACA,OAAO,CAAC;AACZ;AAGA,SAAS,iBAAiB,KAAoC,OAAO,IAAmC;CACpG,OAAO,OAAO,QAAQ,GAAG,CAAC,CACrB,KAAK,CAAC,KAAK,WAAW;EAEnB,OAAO,GADS,OAAO,GAAG,KAAK,GAAG,QAAQ,MACtB,MAAM;CAC9B,CAAC,CAAC,CACD,QAAQ,GAAG,OAAO;EAAE,GAAG;EAChC,GAAG;CAAE,IAAI,CAAC,CAAC;AACX;AAEA,SAAS,oBAAoB,YAAkC;CAC3D,IAAI,MAAM,QAAQ,UAAU,GACxB,OAAO,WAAW,KAAI,MAAK,OAAO,EAAE,EAAE,CAAC;CAC3C,IAAI,OAAO,eAAe,UACtB,OAAO,OAAO,KAAK,UAAU;CACjC,MAAM,MAAM,yCAAyC;AACzD;;;;;;;;;;;;;;;;;;;;ACpJA,SAAgB,oBAAsC,QAAW,OAAO,IAA6B;CACjG,IAAI,CAAC,QAAQ,OAAO,CAAC;CACrB,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EACpD,MAAM,cAAc,OAAO,GAAG,KAAK,GAAG,QAAQ;EAC9C,IAAI,cAAc,KAAK,GACnB,OAAO,oBAAoB,OAAO,WAAW;OAE7C,OAAO,GAAG,cAAc,MAAM;CAEtC,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU;EAAE,GAAG;EACnC,GAAG;CAAK,IAAI,CAAC,CAAC;AACd;;;;;;;AAQA,SAAS,cAAc,OAAkD;CACrE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,OAAO,UAAU,OAAO,aAAa,UAAU;AACnD;;;;;;;;;;;;;;AAeA,SAAgB,mBACZ,QACA,YACuB;CACvB,MAAM,WAAW,OAAO,QAAQ,cAAc,CAAC,CAAC,CAAC,CAG5C,QAAQ,GAAG,cAAc,YAAY,OAAO,aAAa,YAAY,SAAS,QAAQ,CAAC,CACvF,KAAK,CAAC,SAAS,GAAG;CACvB,IAAI,SAAS,WAAW,GAAG,OAAO;CAClC,OAAO,OAAO,YACV,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SAC5B,CAAC,SAAS,MAAM,WAAW,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,EAAE,CAAC,CAAC,CAClF;AACJ;;;;;;;;;;;;AC1DA,SAAgB,sBAAsB,EAAE,aAAoC,CAAC,GAAuB;CAChG,OAAO,MAAM,eAAe,EACxB,eAAe,YAAoB,WAAmB,aAClD,mBAAmB;EACf;EACA;EACA;EACA,SAAS;CACb,CAAC,EACT,IAAI,CAAC,QAAQ,CAAC;AAClB;;;ACLA,IAAM,mCAAmC,MAAM,cAAyC,IAAkC;AAmB1H,IAAa,qCAAgE,WAAW,gCAAgC;AAExH,SAAS,mBAAmB,YAA2C,aAAgD;CACnH,IAAI,eAAe,YACf,OAAO,WAAW;CAEtB,MAAM,QAAQ,YAAY,MAAM,GAAG;CACnC,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;CAC/B,OAAO,mBAAmB,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;AACtE;;;;;;;;;;AAWA,SAAS,iBAAiB,OAAgB,UAA8C;CACpF,IAAI,UAAU,SAAS,UAAU,OAAO,UAAU,UAAU;EACxD,MAAM,OAAO,IAAI,KAAK,KAAK;EAC3B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,KAAA,IAAY;CACtD;CACA,OAAO;AACX;AAEA,SAAgB,kCAAkC,EAC9C,kBACA,UACA,UACA,MACA,YACA,eACkF;CAElF,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,KAAK;CAC9D,MAAM,CAAC,QAAQ,aAAa,SAAgC,IAAI;CAEhE,MAAM,aAAa,cACT,wBAAwB,WAAW,YAAY,aAAa,UAAU,CAAC,CAAC,GAC9E,CAAC,WAAW,YAAY,aAAa,MAAM,CAC/C;;;;;;;;;CAUA,MAAM,gBAAgB,OAAO,UAAU;CACvC,cAAc,UAAU;CAWxB,MAAM,OADiB,kBACG,CAAA,EAAgB,QAAQ;CAElD,gBAAgB;EACZ,IAAI,CAAC,kBAAkB;GACnB,eAAe,IAAI;GACnB;EACJ;EACA,eAAe,QAAQ,iBAAiB;GAAE;GAClD;GACA;EAAK,CAAC,CAAC,CAAC;CACJ,GAAG;EAAC;EAAkB;EAAM;EAAY;CAAI,CAAC;;;;;;;;;;;;;;;CAgB7C,gBAAgB;EACZ,IAAI,CAAC,aAAa;EAClB,IAAI,YAAY;EAChB,oBAAoB,EAAE,SAAS,CAAC,CAAC,CAC5B,MAAM,WAAW;GACd,IAAI,CAAC,WAAW,oBAAoB,OAAO,SAAS;EACxD,CAAC;EACL,aAAa;GACT,YAAY;EAChB;CACJ,GAAG,CAAC,aAAa,QAAQ,CAAC;CAE1B,MAAM,UAAU,eAAe;;CAG/B,MAAM,cAAc,aAAa,KAAa,WAAmE;EAC7G,WAAW,YAAY;GACnB,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,QAAQ,QAAQ,OAAO,WAAW,MAAM,EAAE,QAAQ,GAAG;GAC3D,MAAM,OAAO,OAAO,UAAU,KAAK,KAAA,IAAY,QAAQ,OAAO,MAAM;GACpE,MAAM,SAAS,UAAU,KACnB,CAAC,GAAG,QAAQ,QAAQ,IAAI,IACxB,QAAQ,OAAO,KAAK,GAAG,MAAO,MAAM,QAAQ,OAAO,CAAE;GAC3D,OAAO;IAAE,GAAG;IACxB;GAAO;EACC,CAAC;CACL,GAAG,CAAC,CAAC;CAEL,MAAM,WAAW,YAAY,OAAO,WAAmE;EAEnG,MAAM,oBAAoB,cAAc;EACxC,MAAM,aAAa,mBACf,oBAAoB,OAAO,UAAU,CAAC,CAAC,GACvC,iBACJ;EAEA,UAAU;GACN,QAAQ;GACR,QAAQ,CAAC;GACT,cAAc,OAAO;EACzB,CAAC;EAED,MAAM,YAAY,QAAgB,kBAAkB,IAAI,EAAE,QAAQ;EAElE,IAAI;GACA,MAAM,eAAe;IACjB;IACA,SAAS;KACL,YAAY,WAAW,gBAAgB,WAAW;KAClD,mBAAmB,WAAW;KAU9B,QAAQ;KACR,YAAY;KACZ,aAAa,OAAO;KACpB,sBAAsB,OAAO;KAC7B,cAAc,OAAO;IACzB;IACA,UAAU,KAAK,SAAS;KACpB,YAAY,MAAM,aAAa,WACzB;MAAE,GAAG;MAC/B,UAAU,OAAO,SAAS,YAAY,EAAE,IAAI;KAAK,IACvB;MACE;MACA,OAAO,SAAS,GAAG;MACnB,cAAc,eAAe,OAAO,QAAQ,GAAG;MAC/C,UAAU;MACV,SAAS;MACT,UAAU;KACd,CAAC;IACT;IACA,UAAU,KAAK,UAAU;KACrB,MAAM,UAAU,iBAAiB,OAAO,mBAAmB,mBAAmB,GAAG,CAAC;KAClF,YAAY,MAAM,cAAc;MAC5B;MACA,OAAO,UAAU,SAAS,SAAS,GAAG;MACtC,cAAc,UAAU,gBAAgB,eAAe,OAAO,QAAQ,GAAG;MACzE,UAAU;MACV,SAAS;MAGT,UAAU,UAAU,YAAY;KACpC,EAAE;IACN;GACJ,CAAC;GAED,WAAW,YAAY,WAAW;IAC9B,GAAG;IACH,QAAQ;IAOR,QAAQ,QAAQ,OAAO,QAAQ,MAAM,CAAC,EAAE,OAAO;GACnD,CAAC;EACL,SAAS,GAAY;GACjB,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU;GAIjD,WAAW,YAAY,WAAW;IAC9B,GAAG;IACH,QAAQ;IACR,OAAO;IAGP,QAAQ,QAAQ,OAAO,QAAQ,MAAM,CAAC,EAAE,OAAO;GACnD,CAAC;EACL;CACJ,GAAG;EAAC;EAAY;EAAU;CAAW,CAAC;CAEtC,MAAM,cAAc,aAAa,QAAgB;EAC7C,WAAW,YAAY,WAAW;GAC9B,GAAG;GACH,QAAQ,QAAQ,OAAO,KAAK,MAAO,EAAE,QAAQ,MAAM;IAAE,GAAG;IACpE,UAAU,CAAC,EAAE;GAAS,IAAI,CAAE;EACpB,CAAC;CACL,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,aAAa,aAAsB;EACjD,WAAW,YAAY,WAAW;GAC9B,GAAG;GACH,QAAQ,QAAQ,OAAO,KAAK,OAAO;IAAE,GAAG;IACpD;GAAS,EAAE;EACH,CAAC;CACL,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,kBAAkB,UAAU,IAAI,GAAG,CAAC,CAAC;CAE3D,MAAM,cAAc,kBAAkB;EAClC,WAAW,YAAY;GACnB,IAAI,CAAC,SAAS,OAAO;GACrB,KAAK,MAAM,SAAS,QAAQ,QAAQ;IAChC,IAAI,CAAC,MAAM,YAAY,MAAM,SAAS;IACtC,IAAI,MAAM,aAAa,KAAA,KAAa,MAAM,aAAa,MAAM;IAC7D,aAAa,cAAc,MAAM,KAAK,MAAM,QAAQ;GACxD;GACA,OAAO;EACX,CAAC;CACL,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,qBAAqB,sBAAsB,EAAE,SAAS,CAAC;CAE7D,MAAM,mBAAmB,aACpB,YAAoB,UAAmB,uBAAuB;EAC3D;EACA;EACA,mBAAmB,WAAW;EAC9B;CACJ,CAAC,GACD,CAAC,UAAU,WAAW,WAAW,CACrC;CAEA,MAAM,4BAAuD,eAAe;EACxE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACJ,IAAI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACJ,CAAC;CAED,OACI,oBAAC,iCAAiC,UAAlC;EACI,OAAO;EACN;CACsC,CAAA;AAEnD;;;;;;;;;;;;;;;;;AC/RA,SAAgB,uBAAuB;CAEnC,MAAM,aAAa,6BAA6B;CAChD,MAAM,SAAS,YAAY;CAE3B,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,aAAa,OAAO,WAAW;CACrC,MAAM,aAAa,OAAO,OAAO,QAAQ,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ;CACvE,MAAM,cAAc,OAAO,OAAO,SAAS,KAAK,OAAO,OAAO,OAAO,MAAM,EAAE,QAAQ;CAErF,OACI,qBAAC,QAAD;EACI,MAAM;EACN,UAAU;EACV,eAAe,SAAS;GACpB,IAAI,CAAC,MAAM,WAAW,cAAc;EACxC;YALJ;GAOI,oBAAC,aAAD;IAAa,SAAS;IAAa,cAAc;cAAO;GAE3C,CAAA;GAEb,qBAAC,eAAD;IAAe,WAAW;cAA1B;KAEK,OAAO,gBACJ,qBAAC,YAAD;MAAY,SAAS;MAAS,OAAO;MAAa,WAAW;gBAA7D;OAAuE;OACjE,OAAO;OAAa;MACd;;KAGf,OAAO,OAAO,SAAS,KACpB,qBAAA,UAAA,EAAA,UAAA,CACI,qBAAC,SAAD;MAAO,WAAW;gBAAlB,CACI,oBAAC,UAAD;OACI,SAAS;OACT,MAAM;OACN,uBAAuB,WAAW,UAAU,CAAC,WAAW;MAC3D,CAAA,GAMD,oBAAC,YAAD;OAAY,SAAS;OAAS,WAAW;OAAQ,OAAO;iBACnD,cAAc,iBAAiB;MACxB,CAAA,CACT;SACP,oBAAC,WAAD;MAAW,aAAa;MAAc,WAAW;KAAQ,CAAA,CAC3D,EAAA,CAAA;KAGN,oBAAC,OAAD;MAAK,WAAW;gBACX,OAAO,OAAO,KAAK,UAChB,oBAAC,kBAAD;OAEW;OACP,gBAAgB,WAAW,YAAY,MAAM,GAAG;MACnD,GAHQ,MAAM,GAGd,CACJ;KACA,CAAA;KAEJ,cACG,qBAAC,OAAD;MAAK,WAAW;gBAAhB,CACI,oBAAC,kBAAD,EAAkB,MAAM,WAAY,CAAA,GACpC,oBAAC,YAAD;OAAY,SAAS;OAAS,OAAO;iBAChC,OAAO,OAAO,WAAW,IAAI,cAAc;MACpC,CAAA,CACX;;KAGR,OAAO,WAAW,YACf,qBAAC,YAAD;MAAY,SAAS;MAAS,WAAW;gBAAzC,CACK,OAAO,OACP,OAAO,OAAO,SAAS,KAAK,0DACrB;;KAGf,CAAC,cAAc,OAAO,OAAO,WAAW,KAAK,OAAO,WAAW,YAC5D,oBAAC,YAAD;MAAY,SAAS;MAAS,OAAO;MAAa,WAAW;gBAAQ;KAGzD,CAAA;IAGL;;GAEf,qBAAC,eAAD,EAAA,UAAA,CACI,oBAAC,QAAD;IAAQ,SAAS;IACb,OAAO;IACP,SAAS,WAAW;cAE0C;GAE1D,CAAA,GACR,oBAAC,QAAD;IAAQ,SAAS;IACb,UAAU,WAAW,WAAW;IAChC,SAAS,WAAW;cACnB,WAAW,WAAW,IAAI,kBAAkB,SAAS,WAAW,OAAO;GACpE,CAAA,CACG,EAAA,CAAA;EAEX;;AAEhB;AAEA,SAAS,iBAAiB,EAAE,OAAO,YAA4D;CAE3F,MAAM,WAAW,SAAS,MAAM,YAAY,KAAK,CAAC,YAAY,MAAM,cAAc,MAAM,QAAQ;CAEhG,OACI,qBAAC,SAAD;EAAO,WAAW,IACd,8CACA,CAAC,MAAM,YAAY,YACvB;YAHA,CAII,oBAAC,OAAD;GAAK,WAAW;aACZ,oBAAC,UAAD;IACI,SAAS,MAAM;IACf,MAAM;IACN,iBAAiB;GACpB,CAAA;EACA,CAAA,GAEL,qBAAC,OAAD;GAAK,WAAW;aAAhB;IACI,qBAAC,OAAD;KAAK,WAAW;eAAhB;MAEI,oBAAC,YAAD;OAAY,SAAS;OAAS,WAAW;iBAAS,MAAM;MAAkB,CAAA;MACzE,YACG,oBAAC,YAAD;OAAY,SAAS;OAAW,OAAO;iBAAa;MAExC,CAAA;MAEf,MAAM,WAAW,oBAAC,kBAAD,EAAkB,MAAM,WAAY,CAAA;KACrD;;IAEJ,YACG,oBAAC,YAAD;KACI,SAAS;KACT,OAAO;KACP,WAAW;eACV,YAAY,MAAM,YAAY;IACvB,CAAA;IAGhB,oBAAC,YAAD;KAAY,SAAS;KAAS,WAAW;eACpC,YAAY,MAAM,QAAQ;IACnB,CAAA;GACX;IACF;;AAEf;AAEA,SAAS,SAAS,OAAyB;CACvC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,KAAK,CAAC,CAAC,SAAS;CAC5D,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,SAAS;CAChD,OAAO;AACX;AAEA,SAAS,YAAY,GAAY,GAAqB;CAClD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,aAAa,QAAQ,aAAa,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,QAAQ;CAC7E,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GACnC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,YAAY,GAAG,EAAE,EAAE,CAAC;CAE1E,OAAO;AACX;;AAGA,SAAS,YAAY,OAAwB;CACzC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,iBAAiB,MAAM,OAAO,MAAM,eAAe;CACvD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,MAAM,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;CAC3E,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,QAAQ;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAC1D,OAAO,OAAO,KAAK;AACvB;;;ACtLA,SAAgB,kBAAkB,EAC9B,MACA,QACA,YACA,eACsB;CAEtB,MAAM,aAAa,sBAAsB,MAAM,MAAM;CAErD,MAAM,4BAA4B,6BAA6B;CAE/D,MAAM,CAAC,eAAe,oBAAoB,MAAM,SAAqC,KAAA,CAAS;CAC9F,MAAM,CAAC,cAAc,mBAAmB,MAAM,SAAiB,EAAE;CAEjE,MAAM,mBAAmB,2BAA2B;;;;;;;;CASpD,MAAM,UAAU,2BAA2B,QAAQ,WAAW;CAE9D,MAAM,iBAAiB,OAAO,KAAK;CACnC,MAAM,yBAAyB,YAAY,eAAe,uBAAuB,cAAuB;EACpG,IAAI,CAAC,kBAAkB;EACvB,IAAI,eAAe,SAAS;EAC5B,eAAe,UAAU;EACzB,MAAM,UAAU,WAAW,SACpB,MAAM,iBAAiB,WAAW,gBAAgB,WAAW,MAAM,YAAY,EAAA,CAAG,UACnF,8BAA8B,WAAW,UAAU;EAEzD,MAAM,2BAA2B,4BAA4B,UAAU;EACvE,MAAM,gBAAgB,yBAAyB,KAAI,WAAU,OAAO,MAAM;EAC1E,iBAAiB,CAAC,GAAG,0BAA0B,GAAG,QAAQ,QAAO,MAAK,CAAC,cAAc,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;EACrH,eAAe,UAAU;CAC7B,GACI;EAAC,WAAW;EAAM,WAAW;EAAc;EAAkB;CAAM,CAAC;CAExE,gBAAgB;EACZ,IAAI,CAAC,2BAA2B;EAChC,IAAI,CAAC,eAAe;GAChB,iBAAiB,4BAA4B,UAAU,CAAC;GACxD,uBAAuB,CAAC,CAAC,KAAK;EAClC;CACJ,GAAG;EAAC;EAA2B;EAAe;EAAY;EAAwB;EAAc;CAAM,CAAC;CAEvG,gBAAgB;EACZ,IAAI,CAAC,2BAA2B;EAChC,uBAAuB,CAAC,CAAC,KAAK;CAClC,GAAG,CAAC,2BAA2B,MAAM,CAAC;;;;;CAMtC,MAAM,YAAY,WAAoB;EAClC,IAAI,CAAC,6BAA6B,CAAC,aAAa,QAAQ;EACxD,IAAI,QAAQ;GACR,gBAAgB,YAAY,MAAM;GAClC,iBAAiB,CAAC;IACd;IACA,MAAM;GACV,GAAG,IAAI,iBAAiB,CAAC,EAAA,CAAG,MAAM,GAAG,CAAC,CAAC,CAAC;EAC5C;EAIA,0BAA0B,SAAS;GAC/B,QAAQ,YAAY;GACpB,cAAc;EAClB,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;CAC5B;CAEA,IAAI,CAAC,2BAA2B,SAC5B,OAAO;CAEX,SAAS,SAAS;EACd,SAAS,YAAY;CACzB;CAEA,OACI,qBAAA,UAAA,EAAA,UAAA,CACI,qBAAC,MAAD;EACI,OAAO;EACP,YAAY;EACZ,WAAW;EAQX,SAAS,qBAAC,YAAD;GAAY,SAAS;GAC1B,MAAM;GACN,cAAY;GACZ,OAAO;GACP,UAAU;aAJL,CAKJ,CAAC,WAAW,oBAAC,QAAD,EAAQ,MAAM,QAAS,CAAA,GACnC,WAAW,oBAAC,kBAAD,EAAkB,MAAM,QAAS,CAAA,CACrC;;YAlBhB;GAoBI,qBAAC,UAAD;IAAU,WAAW;IACjB,eAAe;KACX,SAAS;IACb;cAHJ,CAII,oBAAC,QAAD,EAAQ,MAAM,QAAS,CAAA,GAAC,uCAElB;;GAEV,oBAAC,WAAD;IAAW,aAAa;IAAc,WAAW;GAAQ,CAAA;GAExD,eAAe,KAAK,cAAc,UAAU;IACzC,OAAO,qBAAC,UAAD;KAEH,eAAe;MACX,gBAAgB,aAAa,MAAM;MACnC,SAAS,aAAa,MAAM;KAChC;eALG,CAOH,oBAAC,OAAD;MAAK,WAAW;gBACX,aAAa;KACb,CAAA,GAEJ,aAAa,SAAS,YAAY,oBAAC,YAAD;MAC/B,UAAU,MAAM;OACZ,EAAE,eAAe;OACjB,EAAE,gBAAgB;OAClB,mBAAmB,YAAY,aAAa,MAAM;OAClD,kBAAkB,iBAAiB,CAAC,EAAA,CAAG,QAAO,MAAK,EAAE,WAAW,aAAa,MAAM,CAAC;MACxF;MACA,MAAM;gBAEN,oBAAC,OAAD,EAAO,MAAM,SAAS,SAAU,CAAA;KACxB,CAAA,CAEN;OAtBD,QAAQ,MAAM,aAAa,MAsB1B;GACd,CAAC;GAED,oBAAC,WAAD,EAAW,aAAa,aAAc,CAAA;GAOtC,qBAAC,OAAD;IACI,WAAW,IACP,oGACJ;cAHJ,CAKI,qBAAC,OAAD;KAAK,WAAW;eAAhB,CAKI,oBAAC,kBAAD;MACI,WAAW,IAAI,sFAAsF,sBAAsB,eAAe;MAC1I,OAAO;MACP,WAAW,WAAW;MACtB,UAAU;MACV,UAAU,UAAU;OAChB,MAAM,gBAAgB;MAC1B;MACA,aAAa;MACb,YAAY,MAAM;OACd,EAAE,gBAAgB;OAClB,IAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;QAClC,EAAE,eAAe;QACjB,OAAO;OACX;MAEJ;MACA,WAAW,MAAM;OACb,gBAAgB,EAAE,OAAO,KAAK;MAClC;KACH,CAAA,GAOA,aAAa,SAAS,KAAK,CAAC,WACzB,oBAAC,OAAD;MACI,WAAW;gBACX,oBAAC,YAAD;OACI,MAAM;OACN,eAAe;QACX,gBAAgB,EAAE;OACtB;iBACA,oBAAC,OAAD,EAAO,MAAM,SAAS,MAAO,CAAA;MACrB,CAAA;KACX,CAAA,CAER;QAEL,qBAAC,YAAD;KACI,eAAe,SAAS,YAAY;KACpC,MAAM;KACN,OAAO,CAAC,eAAe,YAAY,KAAA;KACnC,UAAU,WAAW,CAAC;eAJ1B,CAKK,WACG,oBAAC,kBAAD,EAAkB,MAAM,WAAY,CAAA,GAOvC,CAAC,WACE,oBAAC,UAAD,EAAU,MAAM,SAAS,MAAO,CAAA,CAC5B;MAEX;;EAEH;KAEN,oBAAC,sBAAD,CAAsB,CAAA,CACxB,EAAA,CAAA;AAEV;AAEA,SAAS,8BAA8B,YAAwC;CAE3E,MAAM,sBAAsB,OAAO,OAAO,UAAU,CAAC,CAAC,QAAQ,MAAgB;EAC1E,IAAI,kBAAkB,CAAC,GACnB,OAAO;EAEX,OAAO,EAAE,SAAS,aAAa,EAAE,OAAO,YAAY,EAAE,OAAO;CACjE,CAAC;CAED,MAAM,kBAAwC,oBAAoB,SAAS,IACrE,oBAAoB,KAAK,MAAM,KAAK,OAAO,IAAI,oBAAoB,MAAM,KACzE,KAAA;CAEN,MAAM,UAAU,CACZ,2BACA,+BACJ;CACA,IAAI,iBACA,QAAQ,KAAK,wBAAwB,gBAAgB,KAAK,EAAE;CAEhE,OAAO,QAAQ,KAAI,OAAM;EACrB,QAAQ;EACR,MAAM;CACV,EAAE;AACN;AAEA,IAAM,yBAAyB,MAAc,WAAyB;CAElE,OAAO,qBADc,WAAW,QAAQ,QAAQ,WACP,IAAI,oBAAoB,IAAI;AACzE;AAEA,IAAM,+BAA+B,eAAuC;CACxE,MAAM,OAAO,aAAa,QAAQ,UAAU;CAC5C,OAAO,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,KAAK,OAAe;EAC/C,QAAQ;EACR,MAAM;CACV,EAAE,IAAI,CAAC;AACX;AAEA,IAAM,mBAAmB,YAAoB,WAAmB;CAC5D,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC,CAAC,WAAW,GACpC;CAEJ,MAAM,gBAAgB,4BAA4B,UAAU;CAC5D,aAAa,QAAQ,YAAY,KAAK,UAAU,CAAC,QAAQ,GAAG,cACvD,KAAI,MAAK,EAAE,MAAM,CAAC,CAClB,QAAO,MAAK,MAAM,MAAM,CAAC,CACzB,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AACtB;AAEA,IAAM,sBAAsB,YAAoB,WAAmB;CAC/D,aAAa,QAAQ,YAAY,KAAK,UAAU,4BAA4B,UAAU,CAAC,CAClF,KAAI,MAAK,EAAE,MAAM,CAAC,CAClB,QAAO,MAAK,MAAM,MAAM,CAAC,CAAC;AACnC;;;;;;;;ACxQA,SAAgB,yBAAyB,OAAkD;CAEvF,MAAM,mBAAmB,OAAO;CAChC,MAAM,WAAW,OAAO;CAExB,OAAO,MAAM,eAAe;EACxB,KAAK;EACL,OAAO,CACH;GACI,MAAM;GACN,WAAW;GACX,OAAO;EACX,CACJ;EACA,WAAW,CACP;GACI,OAAO;GACP,WAAW;GACX,OAAO;IACH;IACA;GACJ;EACJ,CACJ;CACJ,IAAI,CAAC,kBAAkB,QAAQ,CAAC;AACpC"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../src/api.ts","../src/utils/properties.ts","../src/utils/values.ts","../src/editor/useEditorAIController.tsx","../src/components/DataEnhancementControllerProvider.tsx","../src/components/AutofillReviewDialog.tsx","../src/components/FormEnhanceAction.tsx","../src/useDataEnhancementPlugin.tsx"],"sourcesContent":["import {\n AutofillRequest,\n AutofillResult,\n AiStatus,\n SamplePromptsResult\n} from \"./types/data_enhancement_controller\";\n\n/**\n * The hosted service Rebase runs for this plugin.\n *\n * **This is where the entity's field values go.** Autofill posts them to\n * generate a suggestion, and unless the host sets `endpoint`, they go to this\n * address — off their machine, to a service Rebase operates. That is disclosed\n * on the plugins page rather than only here, because the person who needs to\n * know is the one deciding whether the data in those fields may leave.\n *\n * No credential travels with them; see the note below.\n *\n * The previous value here was `https://api.rebase.pro`, a FireCMS-era host that\n * resolves but serves nothing — every path 404s — so Autofill had never worked\n * in a Rebase install. This one is served by the control plane\n * (`saas/backend/functions/ai.ts`). Point `endpoint` somewhere else to run your\n * own; the wire format below is the whole contract.\n */\nexport const DEFAULT_AI_ENDPOINT = \"https://app.rebase.pro/api/functions/ai\";\n\n/**\n * ## No credentials cross this boundary\n *\n * The old client sent the tenant's Rebase JWT as `Authorization: Basic <jwt>`\n * plus a hardcoded `fcms-…` key compiled into the published package. Both were\n * wrong in the same way: a self-hosted backend signs its tokens with its own\n * secret, so no external service can verify one — sending it only handed a live\n * credential to a third party that had no use for it.\n *\n * These requests are anonymous. The service bounds cost by rate limit and daily\n * ceiling rather than by identity, and reports through {@link fetchAiStatus}\n * when it can no longer serve — which is what keeps the UI from offering an\n * action that is going to fail.\n */\nfunction endpointOf(endpoint: string | undefined, path: string): string {\n return (endpoint ?? DEFAULT_AI_ENDPOINT).replace(/\\/+$/, \"\") + path;\n}\n\n/** One `event:`/`data:` pair off the wire. */\ntype ServerSentEvent = { event: string; data: string };\n\n/** Not global: `exec` must not carry `lastIndex` between buffer reads. */\nconst SSE_SEPARATOR = /\\r?\\n\\r?\\n/;\n\n/**\n * Parse an SSE body incrementally.\n *\n * The framing this replaces split each chunk on the literal `\"&$# \"` and\n * `JSON.parse`d the pieces, which corrupted itself the moment a delimiter\n * straddled two reads — and network reads land wherever they land. Buffering\n * until a blank line is the fix, and it is also just what SSE specifies.\n */\nasync function* readServerSentEvents(response: Response): AsyncGenerator<ServerSentEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"The AI service returned no response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // A record ends at a blank line. `\\r\\n` is tolerated because proxies\n // rewrite line endings. The separator is located with `exec` rather\n // than `search` so its actual length is known — a `\\r\\n\\r\\n` boundary\n // is four characters, not two, and slicing by the wrong count leaves a\n // stray newline that swallows the next record's `event:` field.\n let match = SSE_SEPARATOR.exec(buffer);\n while (match) {\n const raw = buffer.slice(0, match.index);\n buffer = buffer.slice(match.index + match[0].length);\n const parsed = parseEventBlock(raw);\n if (parsed) yield parsed;\n match = SSE_SEPARATOR.exec(buffer);\n }\n }\n}\n\nfunction parseEventBlock(block: string): ServerSentEvent | undefined {\n let event = \"message\";\n const dataLines: string[] = [];\n for (const line of block.split(/\\r?\\n/)) {\n if (line.startsWith(\":\")) continue; // comment / keep-alive\n const separator = line.indexOf(\":\");\n const field = separator === -1 ? line : line.slice(0, separator);\n const rawValue = separator === -1 ? \"\" : line.slice(separator + 1);\n const value = rawValue.startsWith(\" \") ? rawValue.slice(1) : rawValue;\n if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0) return undefined;\n return { event,\ndata: dataLines.join(\"\\n\") };\n}\n\n/** Pull a message out of the control plane's `{ error: { message } }` envelope. */\nasync function errorFrom(response: Response, fallback: string): Promise<Error> {\n try {\n const body = await response.json();\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return new Error(message);\n } catch {\n /* not JSON — fall through */\n }\n return new Error(fallback);\n}\n\n/**\n * Ask the service whether it can serve a request at all.\n *\n * The plugin gates every affordance on this. A missing provider key, an\n * exhausted daily quota or an unreachable host all resolve to `available:\n * false`, and the Autofill button is simply not rendered — rather than\n * rendered, clicked, and failed.\n */\nexport async function fetchAiStatus(props: { endpoint?: string; signal?: AbortSignal }): Promise<AiStatus> {\n const response = await fetch(endpointOf(props.endpoint, \"/status\"), {\n method: \"GET\",\n signal: props.signal\n });\n if (!response.ok) return { available: false };\n const body = await response.json();\n return {\n available: Boolean(body?.available),\n model: typeof body?.model === \"string\" ? body.model : undefined,\n features: Array.isArray(body?.features) ? body.features : undefined\n };\n}\n\n/** One in-flight or settled probe per endpoint, for the life of the page. */\nconst statusProbes = new Map<string, Promise<AiStatus>>();\n\n/**\n * {@link fetchAiStatus}, asked once per endpoint per session.\n *\n * The provider is form-scoped, so the uncached call meant one request to the\n * host every time any record was opened — a beacon on an install that may never\n * click Autofill, and enough traffic from one NAT'd office to spend the host's\n * per-IP rate limit on nothing, which reads back as `available: false` and makes\n * the button flicker in and out for everyone behind it.\n *\n * Availability changes on the order of a deploy or a daily quota reset, not of a\n * form open, so a session-long answer is the right resolution. Failures resolve\n * to `available: false` and are cached like any other answer — retrying per form\n * open is the behaviour this replaces.\n */\nexport function fetchAiStatusCached(props: { endpoint?: string }): Promise<AiStatus> {\n const key = endpointOf(props.endpoint, \"/status\");\n const existing = statusProbes.get(key);\n if (existing) return existing;\n const probe = fetchAiStatus({ endpoint: props.endpoint })\n .catch(() => ({ available: false }) as AiStatus);\n statusProbes.set(key, probe);\n return probe;\n}\n\n/** Forget every cached probe, so the next caller asks again. */\nexport function clearAiStatusCache(): void {\n statusProbes.clear();\n}\n\n/**\n * Fill a record, streaming each field as the service writes it.\n *\n * `onDelta` fires with more text for a field still being written; `onValue`\n * fires once a field is complete and carries its final, correctly typed value.\n * A caller that implements only `onValue` still ends up with the right record —\n * the deltas exist so a long text field fills in visibly instead of appearing\n * all at once.\n */\nexport async function autofillStream(props: {\n request: AutofillRequest;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (key: string, text: string) => void;\n onValue: (key: string, value: unknown) => void;\n}): Promise<AutofillResult> {\n const response = await fetch(endpointOf(props.endpoint, \"/autofill\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(props.request),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let result: AutofillResult = { suggestions: {} };\n let done = false;\n let delivered = 0;\n let discarded = 0;\n\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n // One malformed record must not abort a stream that is otherwise\n // delivering good fields — but it is counted, because a run that\n // delivered nothing *but* malformed records is a failure, not an\n // answer of \"there was nothing to fill in\".\n discarded++;\n continue;\n }\n\n if (event === \"suggestion_delta\") {\n delivered++;\n props.onDelta(payload.key, payload.text);\n } else if (event === \"suggestion\") {\n delivered++;\n props.onValue(payload.key, payload.value);\n } else if (event === \"done\") {\n done = true;\n result = {\n suggestions: payload.suggestions ?? {},\n usage: payload.usage\n };\n } else if (event === \"error\") {\n throw new Error(payload.message ?? \"The AI service reported an error.\");\n }\n }\n\n // The service closes every run it finished with a `done` record — including\n // the run that had nothing to fill, which is an empty `done` and not an\n // empty body. So a body that simply stops is a truncation: a rolled pod, a\n // proxy timeout, a dropped connection. Reported as one, because the caller's\n // only other reading of an empty result is \"nothing needed filling\", and\n // telling an operator that their empty fields are fields the model would not\n // improve on is a confident, wrong answer they have no way to question.\n if (!done) {\n throw new Error(\"The connection to the AI service ended before it finished.\");\n }\n if (discarded > 0 && delivered === 0) {\n throw new Error(\"The AI service's response could not be read.\");\n }\n\n return result;\n}\n\n/** Inline continuation for the rich-text editor. Streams plain text. */\nexport async function autocompleteStream(props: {\n textBefore?: string;\n textAfter?: string;\n endpoint?: string;\n signal?: AbortSignal;\n onDelta: (text: string) => void;\n}): Promise<string> {\n const response = await fetch(endpointOf(props.endpoint, \"/autocomplete\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n textBefore: props.textBefore ?? \"\",\n textAfter: props.textAfter ?? \"\"\n }),\n signal: props.signal\n });\n\n if (!response.ok) {\n throw await errorFrom(response, \"The AI service could not complete this request.\");\n }\n\n let text = \"\";\n for await (const { event, data } of readServerSentEvents(response)) {\n let payload: any;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n if (event === \"error\") {\n throw new Error(payload?.message ?? \"The AI service reported an error.\");\n }\n if (event === \"delta\" && typeof payload?.text === \"string\") {\n text += payload.text;\n props.onDelta(payload.text);\n }\n }\n return text;\n}\n\n/**\n * Sample prompts for the Autofill menu.\n *\n * Failure is deliberately not thrown: the menu has built-in prompts to fall\n * back on, and an empty suggestion list is a far better outcome than an error\n * toast for something nobody asked for.\n */\nexport async function fetchPromptSuggestions(props: {\n entityName: string;\n /** Ties suggestions to the domain — see the note on the service's side. */\n entityDescription?: string;\n input?: string;\n endpoint?: string;\n signal?: AbortSignal;\n}): Promise<SamplePromptsResult> {\n try {\n const response = await fetch(endpointOf(props.endpoint, \"/prompts\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n entityName: props.entityName,\n entityDescription: props.entityDescription,\n input: props.input\n }),\n signal: props.signal\n });\n if (!response.ok) return { prompts: [] };\n const body = await response.json();\n const prompts: string[] = Array.isArray(body?.prompts) ? body.prompts : [];\n return {\n prompts: prompts\n .filter((p): p is string => typeof p === \"string\")\n .map((prompt) => ({ prompt,\ntype: \"sample\" as const }))\n };\n } catch {\n return { prompts: [] };\n }\n}\n","import { getFieldId } from \"@rebasepro/cms\";\nimport { EnumValues, Properties, Property } from \"@rebasepro/types\";\nimport { isPropertyBuilder } from \"@rebasepro/common\";\nimport { InputProperty } from \"../types/data_enhancement_controller\";\nimport { getValueInPath } from \"@rebasepro/utils\";\nimport { isDisabled, isHidden, isReadOnly } from \"@rebasepro/app\";\n\nexport function getSimplifiedProperties<M extends Record<string, any>>(properties: Properties, values: M, path = \"\"): Record<string, InputProperty> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (isPropertyBuilder(property)) return {};\n const fullKey = path ? `${path}.${key}` : key;\n const valueInPath = getValueInPath(values, fullKey);\n return getSimplifiedProperty(property, fullKey, valueInPath)\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\n/**\n * Whether the form will not let anyone edit this field.\n *\n * The same three gates the form lays a field out with, so a literal\n * `conditions.readOnly` or a date the backend stamps is locked here too. A\n * locked container locks everything under it: the form disables a map's\n * children with the map, so they are not the model's to fill either.\n */\nfunction isLocked(property: Property, lockedByParent: boolean): boolean {\n return lockedByParent || isReadOnly(property) || isDisabled(property) || isHidden(property);\n}\n\nfunction getSimpleProperty(property: Property, lockedByParent = false): InputProperty {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.error(\"No fieldId found for property\", property);\n throw new Error(\"Field id not found\");\n }\n return {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: fieldId,\n enum: \"enum\" in property && property.enum\n ? getSimpleEnumValues(property.enum)\n : undefined,\n disabled: isLocked(property, lockedByParent)\n };\n}\n\nfunction getSimplifiedProperty(property: Property, path: string, value?: unknown, lockedByParent = false): Record<string, InputProperty> {\n if (isPropertyBuilder(property)) return {};\n const locked = isLocked(property, lockedByParent);\n if (property.type === \"array\") {\n\n if (property.of && !Array.isArray(property.of) && !isPropertyBuilder(property.of)) {\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"repeat\",\n disabled: locked,\n of: getSimpleProperty(property.of as Property, locked)\n };\n\n const result = { [path]: arrayParentProperty };\n // if (Array.isArray(value)) {\n // result = {\n // ...result,\n // ...value\n // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i}`, v))\n // .reduce((a, b) => ({ ...a, ...b }), {})\n // };\n // }\n //\n // const existingValuesCount = Array.isArray(value) ? value.length : 0;\n //\n // const newValuesCount = property.of && !isPropertyBuilder<any, any>(property.of) && (property.of as Property).type === \"map\" ? 1 : 3;\n // result = {\n // ...result,\n // // ...Array.from(Array(newValuesCount))\n // // .map((v, i) => getSimplifiedProperty(property.of, `${path}.${i + existingValuesCount}`, v))\n // // .reduce((a, b) => ({ ...a, ...b }), {})\n // }\n\n return result;\n } else if (property.oneOf) {\n\n const arrayParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"block\",\n disabled: locked,\n oneOf: {\n typeField: property.oneOf.typeField,\n valueField: property.oneOf.valueField,\n properties: Object.entries(property.oneOf.properties)\n .map(([key, prop]) => ({ [key]: getSimpleProperty(prop, locked) }))\n .reduce((a, b) => ({ ...a,\n...b }), {})\n }\n };\n\n if (!Array.isArray(value)) {\n return { [path]: arrayParentProperty };\n }\n\n return value.map((v, i) => {\n if (v == null) return {};\n const typeKey = property.oneOf!.typeField ?? \"type\";\n const oneOfType = v[typeKey];\n const valueKey = property.oneOf!.valueField ?? \"value\";\n const oneOfValue = v[valueKey];\n const childProperty = property.oneOf!.properties[oneOfType];\n if (childProperty === undefined) {\n console.error(`No property found for type ${oneOfType}`, property.oneOf!.properties);\n return {};\n }\n const simplifiedProperty = getSimplifiedProperty(childProperty, `${path}.${i}.${valueKey}`, oneOfValue, locked);\n return {\n [`${path}.${i}.${typeKey}`]: oneOfType,\n ...simplifiedProperty\n };\n }).reduce((a, b) => ({ ...a,\n...b }), { [path]: arrayParentProperty });\n }\n } else if (property.type === \"map\") {\n if (property.properties) {\n const mapProperties: Record<string, InputProperty> = Object.entries(property.properties)\n .map(([key, childProperty]) => {\n const childValue = value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined;\n return getSimplifiedProperty(childProperty, key, childValue, locked);\n })\n .map(o => attachPathToKeys(o, path))\n .reduce((a, b) => ({ ...a,\n...b }), {});\n\n if (Object.keys(mapProperties).length === 0) return {};\n const mapParentProperty: InputProperty = {\n name: property.name,\n description: property.description,\n type: property.type,\n fieldConfigId: \"group\",\n disabled: locked\n };\n return {\n [path]: mapParentProperty,\n ...mapProperties\n } as Record<string, InputProperty>;\n }\n } else {\n const fieldId = getFieldId(property);\n if (!fieldId) {\n console.warn(`No fieldId found for property ${path} with type ${property.type}`);\n return {};\n }\n return {\n [path]: getSimpleProperty(property, lockedByParent)\n };\n }\n return {};\n}\n\n// attach a path to every key in an object\nfunction attachPathToKeys(obj: Record<string, InputProperty>, path = \"\"): Record<string, InputProperty> {\n return Object.entries(obj)\n .map(([key, value]) => {\n const fullKey = path ? `${path}.${key}` : key;\n return { [fullKey]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {});\n}\n\nfunction getSimpleEnumValues(enumValues: EnumValues): string[] {\n if (Array.isArray(enumValues))\n return enumValues.map(v => String(v.id));\n if (typeof enumValues === \"object\")\n return Object.keys(enumValues);\n throw Error(\"getSimpleEnumValues: Invalid enumValues\");\n}\n","import { InputProperty } from \"../types/data_enhancement_controller\";\n\n/**\n * Flatten a record onto the dotted paths the property map uses.\n *\n * The two halves of an autofill request have to be keyed the same way: the\n * service decides a field is empty by looking up `values[key]` for every `key`\n * in `properties`, so a value filed under a key the property map has never\n * heard of is a value the service cannot see.\n *\n * Only plain objects are containers. This used to recurse into anything\n * `typeof value === \"object\"`, which is both an array and a `Date` — so\n * `tags: [\"a\", \"b\"]` was sent as `tags.0`/`tags.1` while the property map still\n * called it `tags`, and a `Date` disappeared entirely (`Object.entries(date)` is\n * `[]`). Both then read as empty on the far side and came back in the review\n * pre-ticked to replace a value the record already had. `getSimplifiedProperties`\n * names an array by its own path and never descends into one, so neither does\n * this.\n */\nexport function flatMapEntityValues<M extends object>(values: M, path = \"\"): Record<string, unknown> {\n if (!values) return {};\n return Object.entries(values).flatMap(([key, value]) => {\n const currentPath = path ? `${path}.${key}` : key;\n if (isPlainObject(value)) {\n return flatMapEntityValues(value, currentPath);\n } else {\n return { [currentPath]: value };\n }\n }).reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n}\n\n/**\n * A container, as opposed to a leaf value.\n *\n * Arrays, dates, files and every other class instance are values in their own\n * right — a map property is the only thing whose children are separate fields.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Drop the values of properties the panel will not let anyone edit.\n *\n * A `readOnly` or `disabled` property is already excluded from what the service\n * may fill, but the values map was built from the whole record, and the prompt\n * includes every value it is given as context. So a field marked read-only\n * because a backend hook owns it — an internal note, a customer id — was still\n * being transmitted and pasted into the prompt. The collection config lives\n * here, so this is the honest place to decide it: disabled means neither\n * fillable nor context.\n *\n * Prefixes match too: a disabled map takes its children with it.\n */\nexport function omitDisabledValues(\n values: Record<string, unknown>,\n properties: Record<string, InputProperty>\n): Record<string, unknown> {\n const disabled = Object.entries(properties ?? {})\n // A property map can carry a non-object at a path (see the `oneOf`\n // branch of `getSimplifiedProperty`), and `\"disabled\" in aString` throws.\n .filter(([, property]) => property && typeof property === \"object\" && property.disabled)\n .map(([key]) => key);\n if (disabled.length === 0) return values;\n return Object.fromEntries(\n Object.entries(values).filter(([key]) =>\n !disabled.some((prefix) => key === prefix || key.startsWith(`${prefix}.`)))\n );\n}\n","import React from \"react\";\nimport { autocompleteStream } from \"../api\";\nimport { EditorAIController } from \"@rebasepro/cms\";\n\n/**\n * Inline continuation for the rich-text editor's slash command.\n *\n * No token is threaded through any more. The previous version demanded a\n * Firebase ID token and threw `\"Firebase token is required\"` when it could not\n * get one — in a Rebase app there is no such thing, and the token it actually\n * sent was a Rebase JWT the receiving service had no way to verify. The hosted\n * service authenticates nobody; see `src/api.ts`.\n */\nexport function useEditorAIController({ endpoint }: { endpoint?: string } = {}): EditorAIController {\n return React.useMemo(() => ({\n autocomplete: (textBefore: string, textAfter: string, onUpdate: (delta: string) => void) =>\n autocompleteStream({\n endpoint,\n textBefore,\n textAfter,\n onDelta: onUpdate\n })\n }), [endpoint]);\n}\n","import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n AutofillReview,\n DataEnhancementController,\n GenerateParams,\n InputProperty,\n ProposedField\n} from \"../types/data_enhancement_controller\";\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { autofillStream, fetchAiStatusCached, fetchPromptSuggestions } from \"../api\";\nimport { getSimplifiedProperties } from \"../utils/properties\";\nimport { flatMapEntityValues, omitDisabledValues } from \"../utils/values\";\nimport { useEditorAIController } from \"../editor/useEditorAIController\";\nimport { getValueInPath } from \"@rebasepro/utils\";\n\nconst DataEnhancementControllerContext = React.createContext<DataEnhancementController>(null! as DataEnhancementController);\n\ntype DataEnhancementControllerProviderProps = {\n\n /**\n * Kept in step with `DataEnhancementPluginProps.getConfigForPath`, which is\n * the signature the host app actually writes against: the plugin hands this\n * component through as `ComponentType<any>`, so nothing but agreement here\n * makes the two match.\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n endpoint?: string;\n}\n\nexport const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);\n\nfunction getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {\n if (propertyKey in properties) {\n return properties[propertyKey];\n }\n const split = propertyKey.split(\".\");\n if (split.length === 1) return undefined;\n return getPropertyFromKey(properties, split.slice(0, -1).join(\".\"));\n}\n\n/**\n * Convert a value off the wire into what the form field expects.\n *\n * Only dates need converting: the service answers ISO-8601 strings because JSON\n * has no date type, and handing a date field a string stores the wrong type\n * without complaining. Everything else — strings, numbers, booleans, arrays of\n * scalars — is already the shape the field wants, which is the point of having\n * the service constrain its answer to a schema derived from these properties.\n */\nfunction coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {\n if (property?.type === \"date\" && typeof value === \"string\") {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n }\n return value;\n}\n\nexport function DataEnhancementControllerProvider({\n getConfigForPath,\n children,\n endpoint,\n path,\n collection,\n formContext\n}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {\n\n const [allowedHere, setAllowedHere] = useState(false);\n const [serviceAvailable, setServiceAvailable] = useState(false);\n const [review, setReview] = useState<AutofillReview | null>(null);\n\n const properties = useMemo(\n () => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),\n [collection.properties, formContext?.values]\n );\n\n /**\n * Read inside the streaming callbacks, which outlive the render that\n * started the run.\n *\n * The operator is free to keep typing while the model works — nothing here\n * writes to the form — so the callbacks must not close over a stale\n * property map from whichever render happened to kick the run off.\n */\n const propertiesRef = useRef(properties);\n propertiesRef.current = properties;\n\n /**\n * The host app's own opt-out.\n *\n * `user` is part of the documented signature and was never passed, so\n * `getConfigForPath: ({ user }) => user?.roles?.includes(\"editor\")` was\n * `Boolean(undefined)` for everyone — an access rule that silently decided\n * nothing, in whichever direction the host had written it.\n */\n const authController = useAuthController();\n const user: User | null = authController?.user ?? null;\n\n useEffect(() => {\n if (!getConfigForPath) {\n setAllowedHere(true);\n return;\n }\n setAllowedHere(Boolean(getConfigForPath({ path,\ncollection,\nuser })));\n }, [getConfigForPath, path, collection, user]);\n\n /**\n * The service's own availability.\n *\n * Nothing renders until this comes back true. An unreachable host, an\n * unconfigured provider key or an exhausted daily quota all land here, and\n * all of them mean the same thing to the operator: no Autofill button,\n * rather than a button that fails when clicked.\n *\n * Asked through the session cache: this provider is form-scoped, so an\n * uncached probe is one request to the host per record opened, by an install\n * that may never use the feature. The probe is shared rather than aborted on\n * unmount — cancelling it would cancel it for whatever else is waiting on the\n * same answer — so unmounting only stops this component from reading it.\n */\n useEffect(() => {\n if (!allowedHere) return;\n let cancelled = false;\n fetchAiStatusCached({ endpoint })\n .then((status) => {\n if (!cancelled) setServiceAvailable(status.available);\n });\n return () => {\n cancelled = true;\n };\n }, [allowedHere, endpoint]);\n\n const enabled = allowedHere && serviceAvailable;\n\n /**\n * The run whose review is on screen.\n *\n * A run outlives the review it was started for: the operator can discard\n * it, or start another, while its stream is still arriving. Every callback\n * checks it is still the current run before touching the review, and a run\n * that stops being current has its request cancelled. Without this, a\n * discarded run kept writing into the next run's review and, when it ended,\n * marked that review ready while the new run was still writing.\n */\n const currentRunRef = useRef<AbortController | null>(null);\n\n const cancelCurrentRun = useCallback(() => {\n currentRunRef.current?.abort();\n currentRunRef.current = null;\n }, []);\n\n // A form closed mid-run has nobody left to review what it produces.\n useEffect(() => cancelCurrentRun, [cancelCurrentRun]);\n\n /** Add or update one row in the review, preserving arrival order. */\n const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {\n setReview((current) => {\n if (!current) return current;\n const index = current.fields.findIndex((f) => f.key === key);\n const next = update(index === -1 ? undefined : current.fields[index]);\n const fields = index === -1\n ? [...current.fields, next]\n : current.fields.map((f, i) => (i === index ? next : f));\n return { ...current,\nfields };\n });\n }, []);\n\n const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {\n\n const currentProperties = propertiesRef.current;\n const flatValues = omitDisabledValues(\n flatMapEntityValues(params.values ?? {}),\n currentProperties\n );\n\n setReview({\n status: \"generating\",\n fields: [],\n instructions: params.instructions\n });\n\n const labelFor = (key: string) => currentProperties[key]?.name ?? key;\n\n cancelCurrentRun();\n const run = new AbortController();\n currentRunRef.current = run;\n const isCurrent = () => currentRunRef.current === run;\n\n try {\n await autofillStream({\n endpoint,\n signal: run.signal,\n request: {\n entityName: collection.singularName ?? collection.name,\n entityDescription: collection.description,\n // Flattened to dotted paths so the keys line up with the\n // property map: the service is told about `seo.title`, so it\n // has to be told the value of `seo.title` too, not of `seo`.\n // Exactly the same rule in both directions — an array or a\n // date is one value under one key here because it is one\n // property under one key there. Where they disagreed, the\n // service read a filled field as empty and offered to\n // rewrite it. Values of properties nobody may edit do not\n // travel at all.\n values: flatValues,\n properties: currentProperties,\n propertyKey: params.propertyKey,\n propertyInstructions: params.propertyInstructions,\n instructions: params.instructions\n },\n onDelta: (key, text) => {\n if (!isCurrent()) return;\n upsertField(key, (existing) => existing\n ? { ...existing,\nproposed: String(existing.proposed ?? \"\") + text }\n : {\n key,\n label: labelFor(key),\n currentValue: getValueInPath(params.values, key),\n proposed: text,\n pending: true,\n selected: true\n });\n },\n onValue: (key, value) => {\n if (!isCurrent()) return;\n const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));\n upsertField(key, (existing) => ({\n key,\n label: existing?.label ?? labelFor(key),\n currentValue: existing?.currentValue ?? getValueInPath(params.values, key),\n proposed: coerced,\n pending: false,\n // A row the operator already deselected mid-stream stays\n // deselected when its final value lands.\n selected: existing?.selected ?? true\n }));\n }\n });\n\n if (!isCurrent()) return;\n currentRunRef.current = null;\n setReview((current) => current && {\n ...current,\n status: \"ready\",\n // Fields still pending when the run ended never received a final\n // value — the model's JSON was cut off mid-string, so all we\n // hold is a half-written sentence. Marking them complete would\n // make that sentence applicable, which is the exact outcome the\n // review exists to prevent. They are dropped instead: the review\n // only ever offers what the service actually finished.\n fields: current.fields.filter((f) => !f.pending)\n });\n } catch (e: unknown) {\n // A run that was discarded or replaced fails with the abort, and\n // has nothing left to report to.\n if (!isCurrent()) return;\n currentRunRef.current = null;\n const message = e instanceof Error ? e.message : \"Autofill could not be completed\";\n // Kept in the review rather than fired into a snackbar: a run that\n // produced three good fields and then failed should still let the\n // operator apply the three.\n setReview((current) => current && {\n ...current,\n status: \"failed\",\n error: message,\n // Same rule as the success path: a field interrupted mid-write\n // is not something the operator can be offered.\n fields: current.fields.filter((f) => !f.pending)\n });\n }\n }, [collection, endpoint, upsertField, cancelCurrentRun]);\n\n const toggleField = useCallback((key: string) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => (f.key === key ? { ...f,\nselected: !f.selected } : f))\n });\n }, []);\n\n const toggleAll = useCallback((selected: boolean) => {\n setReview((current) => current && {\n ...current,\n fields: current.fields.map((f) => ({ ...f,\nselected }))\n });\n }, []);\n\n const dismissReview = useCallback(() => {\n cancelCurrentRun();\n setReview(null);\n }, [cancelCurrentRun]);\n\n const applyReview = useCallback(() => {\n // Applied mid-stream, the rest of the run has no review to land in.\n cancelCurrentRun();\n setReview((current) => {\n if (!current) return null;\n for (const field of current.fields) {\n if (!field.selected || field.pending) continue;\n if (field.proposed === undefined || field.proposed === null) continue;\n formContext?.setFieldValue(field.key, field.proposed);\n }\n return null;\n });\n }, [formContext, cancelCurrentRun]);\n\n const editorAIController = useEditorAIController({ endpoint });\n\n const getSamplePrompts = useCallback(\n (entityName: string, input?: string) => fetchPromptSuggestions({\n endpoint,\n entityName,\n entityDescription: collection.description,\n input\n }),\n [endpoint, collection.description]\n );\n\n const dataEnhancementController: DataEnhancementController = useMemo(() => ({\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n }), [\n enabled,\n review,\n generate,\n toggleField,\n toggleAll,\n applyReview,\n dismissReview,\n getSamplePrompts,\n editorAIController\n ]);\n\n return (\n <DataEnhancementControllerContext.Provider\n value={dataEnhancementController}>\n {children}\n </DataEnhancementControllerContext.Provider>\n );\n}\n","import React from \"react\";\n\nimport {\n Button,\n Checkbox,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Separator,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { ProposedField } from \"../types/data_enhancement_controller\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\n\n/**\n * The review step.\n *\n * Autofill used to write generated text into the live form as it streamed —\n * fields mutating under the cursor, half-written sentences that looked like\n * bugs, and a pile of heuristics deciding whether each token should append to\n * or replace what the operator had already typed. Getting the old value back\n * meant retyping it.\n *\n * So the generated values land here instead. Streaming still happens, and is\n * still worth having — rows appear and fill in as the model works, so a long\n * run shows progress — but it happens in a surface that owns nothing. The\n * record changes on **Apply**, once, for the rows still ticked.\n */\nexport function AutofillReviewDialog() {\n\n const controller = useDataEnhancementController();\n const review = controller?.review;\n\n if (!review) return null;\n\n const generating = review.status === \"generating\";\n const applicable = review.fields.filter((f) => !f.pending && f.selected);\n const allSelected = review.fields.length > 0 && review.fields.every((f) => f.selected);\n\n return (\n <Dialog\n open={true}\n maxWidth={\"2xl\"}\n onOpenChange={(open) => {\n if (!open) controller.dismissReview();\n }}>\n\n <DialogTitle variant={\"subtitle1\"} gutterBottom={false}>\n Review autofill\n </DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-2\"}>\n\n {review.instructions && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"italic\"}>\n “{review.instructions}”\n </Typography>\n )}\n\n {review.fields.length > 1 && (\n <>\n <label className={\"flex items-center gap-3 py-1 cursor-pointer select-none\"}>\n <Checkbox\n checked={allSelected}\n size={\"small\"}\n onCheckedChange={() => controller.toggleAll(!allSelected)}\n />\n {/* `component=\"span\"`: the Typography `label`\n variant renders a <label> element, and this sits\n inside the row's own <label>. Nested labels are\n invalid HTML and stop the text toggling the\n checkbox — clicking \"Select all\" did nothing. */}\n <Typography variant={\"label\"} component={\"span\"} color={\"secondary\"}>\n {allSelected ? \"Deselect all\" : \"Select all\"}\n </Typography>\n </label>\n <Separator orientation={\"horizontal\"} className={\"my-0\"}/>\n </>\n )}\n\n <div className={\"flex flex-col divide-y divide-surface-accent-100 dark:divide-surface-accent-800\"}>\n {review.fields.map((field) => (\n <ProposedFieldRow\n key={field.key}\n field={field}\n onToggle={() => controller.toggleField(field.key)}\n />\n ))}\n </div>\n\n {generating && (\n <div className={\"flex items-center gap-3 py-4 text-text-secondary dark:text-text-secondary-dark\"}>\n <CircularProgress size={\"smallest\"}/>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {review.fields.length === 0 ? \"Thinking…\" : \"Writing the remaining fields…\"}\n </Typography>\n </div>\n )}\n\n {review.status === \"failed\" && (\n <Typography variant={\"body2\"} className={\"py-2 text-red-600 dark:text-red-400\"}>\n {review.error}\n {review.fields.length > 0 && \" You can still apply what was written before it stopped.\"}\n </Typography>\n )}\n\n {!generating && review.fields.length === 0 && review.status !== \"failed\" && (\n <Typography variant={\"body2\"} color={\"secondary\"} className={\"py-4\"}>\n Nothing to fill in — every field either already has a value the model would not\n improve on, or is not one it can write.\n </Typography>\n )}\n\n </DialogContent>\n\n <DialogActions>\n <Button variant={\"text\"}\n color={\"neutral\"}\n onClick={controller.dismissReview}>\n {/* Named for what it does to the record, not to the dialog:\n nothing has been written, so there is nothing to undo. */}\n Discard\n </Button>\n <Button variant={\"filled\"}\n disabled={applicable.length === 0}\n onClick={controller.applyReview}>\n {applicable.length === 1 ? \"Apply 1 field\" : `Apply ${applicable.length} fields`}\n </Button>\n </DialogActions>\n\n </Dialog>\n );\n}\n\nfunction ProposedFieldRow({ field, onToggle }: { field: ProposedField, onToggle: () => void }) {\n\n const replaces = hasValue(field.currentValue) && !isSameValue(field.currentValue, field.proposed);\n\n return (\n <label className={cls(\n \"flex items-start gap-3 py-3 cursor-pointer\",\n !field.selected && \"opacity-50\"\n )}>\n <div className={\"mt-0.5 shrink-0\"}>\n <Checkbox\n checked={field.selected}\n size={\"small\"}\n onCheckedChange={onToggle}\n />\n </div>\n\n <div className={\"flex flex-col gap-1 min-w-0 grow\"}>\n <div className={\"flex items-center gap-2\"}>\n {/* See the note above: never a bare `label` variant inside a <label>. */}\n <Typography variant={\"label\"} component={\"span\"}>{field.label}</Typography>\n {replaces && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n replaces the current value\n </Typography>\n )}\n {field.pending && <CircularProgress size={\"smallest\"}/>}\n </div>\n\n {replaces && (\n <Typography\n variant={\"body2\"}\n color={\"secondary\"}\n className={\"line-through whitespace-pre-wrap break-words\"}>\n {renderValue(field.currentValue)}\n </Typography>\n )}\n\n <Typography variant={\"body2\"} className={\"whitespace-pre-wrap break-words\"}>\n {renderValue(field.proposed)}\n </Typography>\n </div>\n </label>\n );\n}\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nfunction isSameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => isSameValue(v, b[i]));\n }\n return false;\n}\n\n/** Values are shown, never edited here — so a readable string is all that is needed. */\nfunction renderValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toLocaleString();\n if (Array.isArray(value)) return value.map((v) => renderValue(v)).join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n CircularProgress,\n cls,\n fieldBackgroundMixin,\n focusedDisabled,\n IconButton,\n iconSize,\n Menu,\n MenuItem,\n SendIcon,\n Separator,\n TextareaAutosize,\n XIcon\n} from \"@rebasepro/ui\";\nimport {\n AIIcon\n} from \"@rebasepro/app\";\nimport { EntityStatus, Properties, Property } from \"@rebasepro/types\";\nimport { PluginFormActionProps } from \"@rebasepro/cms-types\";\nimport { isPropertyBuilder, stripCollectionPath } from \"@rebasepro/common\";\nimport { useDataEnhancementController } from \"./DataEnhancementControllerProvider\";\nimport { AutofillReviewDialog } from \"./AutofillReviewDialog\";\nimport { SamplePrompt } from \"../types/data_enhancement_controller\";\n\nexport function FormEnhanceAction({\n path,\n status,\n collection,\n formContext\n}: PluginFormActionProps) {\n\n const storageKey = createLocalStorageKey(path, status);\n\n const dataEnhancementController = useDataEnhancementController();\n\n const [samplePrompts, setSamplePrompts] = React.useState<SamplePrompt[] | undefined>(undefined);\n const [instructions, setInstructions] = React.useState<string>(\"\");\n\n const getSamplePrompts = dataEnhancementController?.getSamplePrompts;\n\n /**\n * Driven by the controller rather than by local state.\n *\n * There is exactly one run at a time, and the review owns it — a second\n * `loading` flag here could disagree with the dialog about whether the\n * model is still writing.\n */\n const loading = dataEnhancementController?.review?.status === \"generating\";\n\n const loadingPrompts = useRef(false);\n const updateSuggestedPrompts = useCallback(async function updateSuggestedPrompts(instructions?: string) {\n if (!getSamplePrompts) return;\n if (loadingPrompts.current) return;\n loadingPrompts.current = true;\n const prompts = status === \"new\"\n ? (await getSamplePrompts(collection.singularName ?? collection.name, instructions)).prompts\n : getPromptsForExistingEntities(collection.properties);\n\n const recentPromptsFromStorage = getRecentPromptsFromStorage(storageKey);\n const recentPrompts = recentPromptsFromStorage.map(prompt => prompt.prompt);\n setSamplePrompts([...recentPromptsFromStorage, ...prompts.filter(p => !recentPrompts.includes(p.prompt))].slice(0, 5));\n loadingPrompts.current = false;\n },\n [collection.name, collection.singularName, getSamplePrompts, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n if (!samplePrompts) {\n setSamplePrompts(getRecentPromptsFromStorage(storageKey));\n updateSuggestedPrompts().then();\n }\n }, [dataEnhancementController, samplePrompts, storageKey, updateSuggestedPrompts, instructions, status]);\n\n useEffect(() => {\n if (!dataEnhancementController) return;\n updateSuggestedPrompts().then();\n }, [dataEnhancementController, status]);\n\n /**\n * Starts a run and opens the review. Nothing is written to the form here —\n * see {@link AutofillReviewDialog}.\n */\n const generate = (prompt?: string) => {\n if (!dataEnhancementController || !formContext?.values) return;\n if (prompt) {\n addRecentPrompt(storageKey, prompt);\n setSamplePrompts([{\n prompt,\n type: \"recent\"\n }, ...(samplePrompts ?? []).slice(0, 5)]);\n }\n // The controller records a failure in the review itself, so there is\n // nothing to catch here — but the promise is still explicitly handled\n // so a rejection can never surface as an unhandled one.\n dataEnhancementController.generate({\n values: formContext.values,\n instructions: prompt\n }).catch(() => undefined);\n };\n\n if (!dataEnhancementController?.enabled)\n return null;\n\n function submit() {\n generate(instructions);\n }\n\n return (\n <>\n <Menu\n align={\"end\"}\n sideOffset={8}\n className={\"max-w-[100vw]\"}\n // Never full width: this used to stretch to fill the form's\n // `w-80 2xl:w-96` side rail in full screen. That rail is gone, and\n // in the footer a stretched button reads as the primary action.\n // Icon only. The label is carried by `aria-label`/`title` — a\n // `Tooltip` here would swallow the menu: both it and\n // `DropdownMenu.Trigger` render `asChild`, and `Tooltip` drops\n // the props Radix clones onto it, so the menu never opens.\n trigger={<IconButton variant={\"filled\"}\n size={\"small\"}\n aria-label={\"Autofill\"}\n title={\"Autofill\"}\n disabled={loading}>\n {!loading && <AIIcon size={\"small\"}/>}\n {loading && <CircularProgress size={\"small\"}/>}\n </IconButton>}>\n\n <MenuItem className={\"py-4\"}\n onClick={() => {\n generate();\n }}>\n <AIIcon size={\"small\"}/>\n Autofill based on the current content\n </MenuItem>\n\n <Separator orientation={\"horizontal\"} className={\"mt-2\"}/>\n\n {samplePrompts?.map((samplePrompt, index) => {\n return <MenuItem\n key={index + \"_\" + samplePrompt.prompt}\n onClick={() => {\n setInstructions(samplePrompt.prompt);\n generate(samplePrompt.prompt);\n }}\n >\n <div className={\"pl-9 grow text-text-secondary dark:text-text-secondary-dark\"}>\n {samplePrompt.prompt}\n </div>\n\n {samplePrompt.type === \"recent\" && <IconButton\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n removeRecentPrompt(storageKey, samplePrompt.prompt);\n setSamplePrompts((samplePrompts ?? []).filter(p => p.prompt !== samplePrompt.prompt));\n }}\n size={\"smallest\"}\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n }\n </MenuItem>;\n })}\n\n <Separator orientation={\"horizontal\"}/>\n\n {/* `px-4` and `gap-4` are MenuItem's own paddings, so the input\n row lines up with the items above it instead of sitting 8px\n to their left — which is what `mx-2` on the textarea did. */}\n {/* `items-center` so the send button sits on the field's centre\n line rather than pinned to its top edge as the textarea grows. */}\n <div\n className={cls(\n \"my-2 px-4 py-2 gap-4 w-[500px] max-w-full flex items-center text-surface-700 dark:text-surface-200\"\n )}>\n\n <div className={\"relative w-full grow\"}>\n {/* `fieldBackgroundMixin`, the same surface every other input\n in the codebase uses. It was `dark:bg-surface-950`, which\n theme.css defines as literal `#000000` — a pure black\n rectangle inside an already-dark menu. */}\n <TextareaAutosize\n className={cls(\"p-3 pr-12 rounded-lg resize-none w-full outline-hidden max-h-[300px] overflow-auto\", fieldBackgroundMixin, focusedDisabled)}\n value={instructions}\n autoFocus={status === \"new\"}\n disabled={loading}\n onFocus={(event) => {\n event.stopPropagation();\n }}\n placeholder={\"...or provide instructions\"}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n\n }}\n onChange={(e) => {\n setInstructions(e.target.value);\n }}\n />\n\n {/* Inside the field and only when there is something to\n clear — a permanently-visible X on an empty box is a\n control that does nothing. Positioned exactly as\n `TextFieldBinding` positions its own clearable X, so\n it stays centred as the textarea grows. */}\n {instructions.length > 0 && !loading && (\n <div\n className={\"flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-2\"}>\n <IconButton\n size={\"small\"}\n onClick={() => {\n setInstructions(\"\");\n }}>\n <XIcon size={iconSize.small}/>\n </IconButton>\n </div>\n )}\n </div>\n\n <IconButton\n onClick={() => generate(instructions)}\n size={\"small\"}\n color={!instructions ? \"primary\" : undefined}\n disabled={loading || !instructions}>\n {loading &&\n <CircularProgress size={\"smallest\"}/>}\n {/* Sized, and no `color`. These icons are re-exported\n straight from lucide, so `color` lands on the SVG as\n a CSS colour — and `\"primary\"` is not one, which is\n why this button rendered empty. Every other icon in\n the codebase passes `size` alone and inherits\n `currentColor` from the button. */}\n {!loading &&\n <SendIcon size={iconSize.small}/>}\n </IconButton>\n\n </div>\n\n </Menu>\n\n <AutofillReviewDialog/>\n </>\n );\n}\n\nfunction getPromptsForExistingEntities(properties: Properties): SamplePrompt[] {\n\n const multilineProperties = Object.values(properties).filter((p: Property) => {\n if (isPropertyBuilder(p)) {\n return false;\n }\n return p.type === \"string\" && (p.admin?.markdown || p.admin?.multiline);\n });\n\n const multilinePrompt: Property | undefined = multilineProperties.length > 0\n ? multilineProperties[Math.floor(Math.random() * multilineProperties.length)] as Property\n : undefined;\n\n const prompts = [\n \"Fill the missing fields\",\n \"Translate the missing content\"\n ];\n if (multilinePrompt) {\n prompts.push(`Add 2 paragraphs to '${multilinePrompt.name}'`);\n }\n return prompts.map(p => ({\n prompt: p,\n type: \"sample\"\n }));\n}\n\nconst createLocalStorageKey = (path: string, status: EntityStatus) => {\n const statusString = status === \"new\" ? \"new\" : \"existing\";\n return `data_enhancement::${statusString}::${stripCollectionPath(path)}`;\n};\n\nconst getRecentPromptsFromStorage = (storageKey: string): SamplePrompt[] => {\n const item = localStorage.getItem(storageKey);\n return item ? JSON.parse(item).map((e: string) => ({\n prompt: e,\n type: \"recent\"\n })) : [];\n};\n\nconst addRecentPrompt = (storageKey: string, prompt: string) => {\n if (!prompt || prompt.trim().length === 0) {\n return;\n }\n const recentPrompts = getRecentPromptsFromStorage(storageKey);\n localStorage.setItem(storageKey, JSON.stringify([prompt, ...recentPrompts\n .map(e => e.prompt)\n .filter(e => e !== prompt)\n .slice(0, 5)]));\n};\n\nconst removeRecentPrompt = (storageKey: string, prompt: string) => {\n localStorage.setItem(storageKey, JSON.stringify(getRecentPromptsFromStorage(storageKey)\n .map(e => e.prompt)\n .filter(e => e !== prompt)));\n};\n","import React from \"react\";\n\nimport { CollectionConfig, User } from \"@rebasepro/types\";\nimport { RebasePlugin } from \"@rebasepro/cms-types\";\nimport { DataEnhancementControllerProvider } from \"./components/DataEnhancementControllerProvider\";\nimport { FormEnhanceAction } from \"./components/FormEnhanceAction\";\n\nexport interface DataEnhancementPluginProps {\n\n /**\n * Use this function to determine if the data enhancement plugin should be enabled for a given path.\n * If this function is not provided, the plugin will be enabled for all paths.\n * If the function returns false, the plugin will be disabled for the given path.\n *\n * @param path\n * @param collection\n */\n getConfigForPath?: (props: {\n path: string,\n collection: CollectionConfig,\n user: User | null\n }) => boolean;\n\n /**\n * Base URL of the AI service.\n *\n * Defaults to the one Rebase hosts, which is free to use and needs no\n * configuration. Point it at your own deployment to keep generation inside\n * your infrastructure — the wire format is documented in `src/api.ts`, and\n * the reference implementation is `saas/backend/functions/ai.ts`.\n *\n * Whatever it points at, the plugin renders nothing until that host's\n * `GET /status` reports itself available.\n */\n endpoint?: string;\n}\n\n/**\n * Use this hook to initialise the data enhancement plugin.\n * This is likely the only hook you will need to use.\n * @param props\n */\nexport function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {\n\n const getConfigForPath = props?.getConfigForPath;\n const endpoint = props?.endpoint;\n\n return React.useMemo(() => ({\n key: \"data_enhancement\",\n slots: [\n {\n slot: \"form.actions\",\n Component: FormEnhanceAction,\n order: 40\n }\n ],\n providers: [\n {\n scope: \"form\" as const,\n Component: DataEnhancementControllerProvider as React.ComponentType<any>,\n props: {\n getConfigForPath,\n endpoint\n }\n }\n ]\n }), [getConfigForPath, endpoint]);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/plugin-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "AI plugin for the Rebase admin panel: field generation and content assistance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rebase",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"./package.json": "./package.json"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@rebasepro/cms": "0.
|
|
39
|
-
"@rebasepro/cms-types": "0.
|
|
40
|
-
"@rebasepro/
|
|
41
|
-
"@rebasepro/
|
|
42
|
-
"@rebasepro/
|
|
43
|
-
"@rebasepro/
|
|
44
|
-
"@rebasepro/
|
|
38
|
+
"@rebasepro/cms": "0.23.0",
|
|
39
|
+
"@rebasepro/cms-types": "0.23.0",
|
|
40
|
+
"@rebasepro/common": "0.23.0",
|
|
41
|
+
"@rebasepro/app": "0.23.0",
|
|
42
|
+
"@rebasepro/types": "0.23.0",
|
|
43
|
+
"@rebasepro/ui": "0.23.0",
|
|
44
|
+
"@rebasepro/utils": "0.23.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"react": "^19.2.7",
|