@rebasepro/plugin-ai 0.12.1-canary.gf5f1d39 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -18
- package/dist/api.d.ts +58 -30
- package/dist/components/AutofillReviewDialog.d.ts +16 -0
- package/dist/components/DataEnhancementControllerProvider.d.ts +2 -3
- package/dist/components/FormEnhanceAction.d.ts +1 -1
- package/dist/editor/useEditorAIController.d.ts +11 -2
- package/dist/index.es.js +601 -382
- package/dist/index.es.js.map +1 -1
- package/dist/types/data_enhancement_controller.d.ts +84 -28
- package/dist/useDataEnhancementPlugin.d.ts +10 -5
- package/package.json +24 -19
- package/src/api.ts +241 -174
- package/src/components/AutofillReviewDialog.tsx +209 -0
- package/src/components/DataEnhancementControllerProvider.tsx +203 -262
- package/src/components/FormEnhanceAction.tsx +154 -128
- package/src/editor/useEditorAIController.tsx +20 -33
- package/src/tests/AutofillReviewDialog.test.tsx +340 -0
- package/src/tests/api.test.ts +283 -0
- package/src/tests/properties.test.ts +420 -0
- package/src/tests/review.test.tsx +393 -0
- package/src/tests/useDataEnhancementPlugin.test.tsx +36 -15
- package/src/tests/useEditorAIController.test.ts +98 -0
- package/src/types/data_enhancement_controller.tsx +98 -31
- package/src/useDataEnhancementPlugin.tsx +13 -12
- package/dist/utils/diffStrings.d.ts +0 -7
- package/dist/utils/strings_counter.d.ts +0 -2
- package/dist/utils/suggestions.d.ts +0 -1
- package/src/tests/diffStrings.test.ts +0 -128
- package/src/tests/strings_counter.test.ts +0 -117
- package/src/tests/suggestions.test.ts +0 -53
- package/src/utils/diffStrings.ts +0 -70
- package/src/utils/strings_counter.ts +0 -22
- package/src/utils/suggestions.ts +0 -6
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import React, { PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AutofillReview,
|
|
4
5
|
DataEnhancementController,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
GenerateParams,
|
|
7
|
+
InputProperty,
|
|
8
|
+
ProposedField
|
|
8
9
|
} from "../types/data_enhancement_controller";
|
|
9
|
-
import {
|
|
10
|
-
useAuthController,
|
|
11
|
-
useCustomizationController,
|
|
12
|
-
useSnackbarController
|
|
13
|
-
} from "@rebasepro/app";
|
|
14
|
-
import { useUrlController } from "@rebasepro/admin";
|
|
15
|
-
import { DataDriver, Entity, CollectionConfig } from "@rebasepro/types";
|
|
10
|
+
import { CollectionConfig } from "@rebasepro/types";
|
|
16
11
|
import { PluginFormActionProps } from "@rebasepro/admin-types";
|
|
17
|
-
import {
|
|
18
|
-
import { getAppendableSuggestion } from "../utils/suggestions";
|
|
12
|
+
import { autofillStream, fetchAiStatus, fetchPromptSuggestions } from "../api";
|
|
19
13
|
import { getSimplifiedProperties } from "../utils/properties";
|
|
14
|
+
import { flatMapEntityValues } from "../utils/values";
|
|
20
15
|
import { useEditorAIController } from "../editor/useEditorAIController";
|
|
21
16
|
import { getValueInPath } from "@rebasepro/utils";
|
|
22
17
|
|
|
@@ -24,311 +19,259 @@ const DataEnhancementControllerContext = React.createContext<DataEnhancementCont
|
|
|
24
19
|
|
|
25
20
|
type DataEnhancementControllerProviderProps = {
|
|
26
21
|
|
|
27
|
-
apiKey: string;
|
|
28
|
-
|
|
29
22
|
getConfigForPath?: (props: {
|
|
30
23
|
path: string,
|
|
31
24
|
collection: CollectionConfig
|
|
32
25
|
}) => boolean;
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
endpoint?: string;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
30
|
export const useDataEnhancementController = (): DataEnhancementController => useContext(DataEnhancementControllerContext);
|
|
38
31
|
|
|
39
|
-
function getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string) {
|
|
32
|
+
function getPropertyFromKey(properties: Record<string, InputProperty>, propertyKey: string): InputProperty | undefined {
|
|
40
33
|
if (propertyKey in properties) {
|
|
41
34
|
return properties[propertyKey];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
const parentKey = split.slice(0, split.length - 1).join(".");
|
|
49
|
-
return getPropertyFromKey(properties, parentKey);
|
|
35
|
+
}
|
|
36
|
+
const split = propertyKey.split(".");
|
|
37
|
+
if (split.length === 1) return undefined;
|
|
38
|
+
return getPropertyFromKey(properties, split.slice(0, -1).join("."));
|
|
39
|
+
}
|
|
50
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Convert a value off the wire into what the form field expects.
|
|
43
|
+
*
|
|
44
|
+
* Only dates need converting: the service answers ISO-8601 strings because JSON
|
|
45
|
+
* has no date type, and handing a date field a string stores the wrong type
|
|
46
|
+
* without complaining. Everything else — strings, numbers, booleans, arrays of
|
|
47
|
+
* scalars — is already the shape the field wants, which is the point of having
|
|
48
|
+
* the service constrain its answer to a schema derived from these properties.
|
|
49
|
+
*/
|
|
50
|
+
function coerceToProperty(value: unknown, property: InputProperty | undefined): unknown {
|
|
51
|
+
if (property?.type === "date" && typeof value === "string") {
|
|
52
|
+
const date = new Date(value);
|
|
53
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
51
54
|
}
|
|
55
|
+
return value;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
export function DataEnhancementControllerProvider({
|
|
55
|
-
apiKey,
|
|
56
59
|
getConfigForPath,
|
|
57
60
|
children,
|
|
58
|
-
|
|
61
|
+
endpoint,
|
|
59
62
|
path,
|
|
60
63
|
collection,
|
|
61
64
|
formContext
|
|
62
65
|
}: PropsWithChildren<DataEnhancementControllerProviderProps & PluginFormActionProps>) {
|
|
63
66
|
|
|
64
|
-
const [
|
|
65
|
-
const [
|
|
66
|
-
const [
|
|
67
|
-
|
|
68
|
-
const enhancingInProgress = useRef(false);
|
|
69
|
-
|
|
70
|
-
const authController = useAuthController();
|
|
71
|
-
const snackbarController = useSnackbarController();
|
|
72
|
-
|
|
67
|
+
const [allowedHere, setAllowedHere] = useState(false);
|
|
68
|
+
const [serviceAvailable, setServiceAvailable] = useState(false);
|
|
69
|
+
const [review, setReview] = useState<AutofillReview | null>(null);
|
|
73
70
|
|
|
74
|
-
const properties = useMemo(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!enhancingInProgress.current)
|
|
79
|
-
valuesRef.current = formContext?.values ?? {};
|
|
80
|
-
}, [formContext?.values]);
|
|
81
|
-
|
|
82
|
-
const allowReferenceDataSelection = false;
|
|
83
|
-
|
|
84
|
-
const updateConfig = useCallback(async () => {
|
|
85
|
-
if (!getConfigForPath) return;
|
|
86
|
-
const config = getConfigForPath({
|
|
87
|
-
path,
|
|
88
|
-
collection
|
|
89
|
-
});
|
|
90
|
-
if (config) {
|
|
91
|
-
setEnabled(true);
|
|
92
|
-
}
|
|
93
|
-
}, [collection, getConfigForPath, path]);
|
|
71
|
+
const properties = useMemo(
|
|
72
|
+
() => getSimplifiedProperties(collection.properties, formContext?.values ?? {}),
|
|
73
|
+
[collection.properties, formContext?.values]
|
|
74
|
+
);
|
|
94
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Read inside the streaming callbacks, which outlive the render that
|
|
78
|
+
* started the run.
|
|
79
|
+
*
|
|
80
|
+
* The operator is free to keep typing while the model works — nothing here
|
|
81
|
+
* writes to the form — so the callbacks must not close over a stale
|
|
82
|
+
* property map from whichever render happened to kick the run off.
|
|
83
|
+
*/
|
|
84
|
+
const propertiesRef = useRef(properties);
|
|
85
|
+
propertiesRef.current = properties;
|
|
86
|
+
|
|
87
|
+
/** The host app's own opt-out. */
|
|
95
88
|
useEffect(() => {
|
|
96
89
|
if (!getConfigForPath) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
updateConfig();
|
|
90
|
+
setAllowedHere(true);
|
|
91
|
+
return;
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
setAllowedHere(Boolean(getConfigForPath({ path,
|
|
94
|
+
collection })));
|
|
95
|
+
}, [getConfigForPath, path, collection]);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The service's own availability.
|
|
99
|
+
*
|
|
100
|
+
* Nothing renders until this comes back true. An unreachable host, an
|
|
101
|
+
* unconfigured provider key or an exhausted daily quota all land here, and
|
|
102
|
+
* all of them mean the same thing to the operator: no Autofill button,
|
|
103
|
+
* rather than a button that fails when clicked.
|
|
104
|
+
*/
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!allowedHere) return;
|
|
107
|
+
const abort = new AbortController();
|
|
108
|
+
fetchAiStatus({ endpoint,
|
|
109
|
+
signal: abort.signal })
|
|
110
|
+
.then((status) => setServiceAvailable(status.available))
|
|
111
|
+
.catch(() => setServiceAvailable(false));
|
|
112
|
+
return () => abort.abort();
|
|
113
|
+
}, [allowedHere, endpoint]);
|
|
114
|
+
|
|
115
|
+
const enabled = allowedHere && serviceAvailable;
|
|
116
|
+
|
|
117
|
+
/** Add or update one row in the review, preserving arrival order. */
|
|
118
|
+
const upsertField = useCallback((key: string, update: (existing: ProposedField | undefined) => ProposedField) => {
|
|
119
|
+
setReview((current) => {
|
|
120
|
+
if (!current) return current;
|
|
121
|
+
const index = current.fields.findIndex((f) => f.key === key);
|
|
122
|
+
const next = update(index === -1 ? undefined : current.fields[index]);
|
|
123
|
+
const fields = index === -1
|
|
124
|
+
? [...current.fields, next]
|
|
125
|
+
: current.fields.map((f, i) => (i === index ? next : f));
|
|
126
|
+
return { ...current,
|
|
127
|
+
fields };
|
|
115
128
|
});
|
|
116
129
|
}, []);
|
|
117
130
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const property = getPropertyFromKey(properties, propertyKey);
|
|
121
|
-
if (delta === null || property?.disabled) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// clearSuggestion(propertyKey);
|
|
126
|
-
const value = getValueInPath(valuesRef.current, propertyKey);
|
|
131
|
+
const generate = useCallback(async (params: GenerateParams<Record<string, unknown>>): Promise<void> => {
|
|
127
132
|
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
// if (currentValue.length === 0) updatedValue = updatedValue.trimStart();
|
|
131
|
-
valuesRef.current = {
|
|
132
|
-
...valuesRef.current,
|
|
133
|
-
[propertyKey]: updatedValue
|
|
134
|
-
};
|
|
135
|
-
formContext?.setFieldValue(propertyKey, updatedValue, false);
|
|
136
|
-
setSuggestions(prev => ({
|
|
137
|
-
...prev,
|
|
138
|
-
[propertyKey]: (prev[propertyKey] ?? "") + delta
|
|
139
|
-
}));
|
|
140
|
-
}, [properties, formContext]);
|
|
133
|
+
const currentProperties = propertiesRef.current;
|
|
134
|
+
const flatValues = flatMapEntityValues(params.values ?? {}) as Record<string, unknown>;
|
|
141
135
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
setReview({
|
|
137
|
+
status: "generating",
|
|
138
|
+
fields: [],
|
|
139
|
+
instructions: params.instructions
|
|
146
140
|
});
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const value = getValueInPath(currentValues, propertyKey);
|
|
151
|
-
const property = getPropertyFromKey(properties, propertyKey);
|
|
152
|
-
|
|
153
|
-
if (!property || suggestion === null || property?.disabled) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (typeof suggestion === "number") {
|
|
158
|
-
formContext?.setFieldValue(propertyKey, suggestion);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (replaceValues) {
|
|
163
|
-
formContext?.setFieldValue(propertyKey, suggestion);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const appendableValue = getAppendableSuggestion(suggestion, value);
|
|
142
|
+
const labelFor = (key: string) => currentProperties[key]?.name ?? key;
|
|
168
143
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
144
|
+
try {
|
|
145
|
+
await autofillStream({
|
|
146
|
+
endpoint,
|
|
147
|
+
request: {
|
|
148
|
+
entityName: collection.singularName ?? collection.name,
|
|
149
|
+
entityDescription: collection.description,
|
|
150
|
+
// Flattened to dotted paths so the keys line up with the
|
|
151
|
+
// property map: the service is told about `seo.title`, so it
|
|
152
|
+
// has to be told the value of `seo.title` too, not of `seo`.
|
|
153
|
+
values: flatValues,
|
|
154
|
+
properties: currentProperties,
|
|
155
|
+
propertyKey: params.propertyKey,
|
|
156
|
+
propertyInstructions: params.propertyInstructions,
|
|
157
|
+
instructions: params.instructions
|
|
158
|
+
},
|
|
159
|
+
onDelta: (key, text) => {
|
|
160
|
+
upsertField(key, (existing) => existing
|
|
161
|
+
? { ...existing,
|
|
162
|
+
proposed: String(existing.proposed ?? "") + text }
|
|
163
|
+
: {
|
|
164
|
+
key,
|
|
165
|
+
label: labelFor(key),
|
|
166
|
+
currentValue: getValueInPath(params.values, key),
|
|
167
|
+
proposed: text,
|
|
168
|
+
pending: true,
|
|
169
|
+
selected: true
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
onValue: (key, value) => {
|
|
173
|
+
const coerced = coerceToProperty(value, getPropertyFromKey(currentProperties, key));
|
|
174
|
+
upsertField(key, (existing) => ({
|
|
175
|
+
key,
|
|
176
|
+
label: existing?.label ?? labelFor(key),
|
|
177
|
+
currentValue: existing?.currentValue ?? getValueInPath(params.values, key),
|
|
178
|
+
proposed: coerced,
|
|
179
|
+
pending: false,
|
|
180
|
+
// A row the operator already deselected mid-stream stays
|
|
181
|
+
// deselected when its final value lands.
|
|
182
|
+
selected: existing?.selected ?? true
|
|
183
|
+
}));
|
|
179
184
|
}
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
setSuggestions(prev => ({
|
|
184
|
-
...prev,
|
|
185
|
-
...Object.keys(updatedValues)
|
|
186
|
-
.reduce((acc, key) => {
|
|
187
|
-
const value = getValueInPath(formContext?.values, key);
|
|
188
|
-
const suggestion = updatedValues[key];
|
|
189
|
-
return {
|
|
190
|
-
...acc,
|
|
191
|
-
[key]: getAppendableSuggestion(suggestion, value) ?? suggestion
|
|
192
|
-
};
|
|
193
|
-
}, {})
|
|
194
|
-
}));
|
|
195
|
-
}, [properties, formContext]);
|
|
196
|
-
|
|
197
|
-
const displayNeededSubscriptionSnackbar = useCallback((projectId: unknown) => {
|
|
198
|
-
snackbarController.open({
|
|
199
|
-
type: "warning",
|
|
200
|
-
message: "A valid subscription is needed in order to use this function.",
|
|
201
|
-
autoHideDuration: 4000
|
|
202
|
-
});
|
|
203
|
-
}, [snackbarController]);
|
|
204
|
-
|
|
205
|
-
const editorAIController = useEditorAIController({ getAuthToken: authController.getAuthToken });
|
|
206
|
-
|
|
207
|
-
const clearAllSuggestions = useCallback(() => {
|
|
208
|
-
setSuggestions({});
|
|
209
|
-
}, []);
|
|
210
|
-
|
|
211
|
-
const enhance = useCallback(async (props: EnhanceParams<Record<string, unknown>>): Promise<EnhancedDataResult | null> => {
|
|
212
|
-
|
|
213
|
-
if (!authController.user) {
|
|
214
|
-
snackbarController.open({
|
|
215
|
-
type: "warning",
|
|
216
|
-
message: "You need to be logged in to enhance data"
|
|
217
185
|
});
|
|
218
|
-
return Promise.reject(new Error("Not logged in"));
|
|
219
|
-
}
|
|
220
186
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
187
|
+
setReview((current) => current && {
|
|
188
|
+
...current,
|
|
189
|
+
status: "ready",
|
|
190
|
+
// Fields still pending when the run ended never received a final
|
|
191
|
+
// value — the model's JSON was cut off mid-string, so all we
|
|
192
|
+
// hold is a half-written sentence. Marking them complete would
|
|
193
|
+
// make that sentence applicable, which is the exact outcome the
|
|
194
|
+
// review exists to prevent. They are dropped instead: the review
|
|
195
|
+
// only ever offers what the service actually finished.
|
|
196
|
+
fields: current.fields.filter((f) => !f.pending)
|
|
197
|
+
});
|
|
198
|
+
} catch (e: unknown) {
|
|
199
|
+
const message = e instanceof Error ? e.message : "Autofill could not be completed";
|
|
200
|
+
// Kept in the review rather than fired into a snackbar: a run that
|
|
201
|
+
// produced three good fields and then failed should still let the
|
|
202
|
+
// operator apply the three.
|
|
203
|
+
setReview((current) => current && {
|
|
204
|
+
...current,
|
|
205
|
+
status: "failed",
|
|
206
|
+
error: message,
|
|
207
|
+
// Same rule as the success path: a field interrupted mid-write
|
|
208
|
+
// is not something the operator can be offered.
|
|
209
|
+
fields: current.fields.filter((f) => !f.pending)
|
|
210
|
+
});
|
|
228
211
|
}
|
|
212
|
+
}, [collection, endpoint, upsertField]);
|
|
229
213
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
214
|
+
const toggleField = useCallback((key: string) => {
|
|
215
|
+
setReview((current) => current && {
|
|
216
|
+
...current,
|
|
217
|
+
fields: current.fields.map((f) => (f.key === key ? { ...f,
|
|
218
|
+
selected: !f.selected } : f))
|
|
219
|
+
});
|
|
220
|
+
}, []);
|
|
234
221
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const projectId = data?.projectId;
|
|
243
|
-
displayNeededSubscriptionSnackbar(projectId);
|
|
244
|
-
} else {
|
|
245
|
-
console.error("Enhance error", e);
|
|
246
|
-
}
|
|
247
|
-
reject(e);
|
|
248
|
-
enhancingInProgress.current = false;
|
|
249
|
-
}
|
|
222
|
+
const toggleAll = useCallback((selected: boolean) => {
|
|
223
|
+
setReview((current) => current && {
|
|
224
|
+
...current,
|
|
225
|
+
fields: current.fields.map((f) => ({ ...f,
|
|
226
|
+
selected }))
|
|
227
|
+
});
|
|
228
|
+
}, []);
|
|
250
229
|
|
|
251
|
-
|
|
252
|
-
enhanceDataAPIStream({
|
|
253
|
-
...props,
|
|
254
|
-
host,
|
|
255
|
-
apiKey,
|
|
256
|
-
properties,
|
|
257
|
-
path: resolvedPath,
|
|
258
|
-
entityName: collection.singularName ?? collection.name,
|
|
259
|
-
entityDescription: collection.description,
|
|
230
|
+
const dismissReview = useCallback(() => setReview(null), []);
|
|
260
231
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
appendValueDelta(propertyKey, partialValue);
|
|
269
|
-
},
|
|
270
|
-
onError,
|
|
271
|
-
onEnd: (result) => {
|
|
272
|
-
console.debug("de onEnd", result);
|
|
273
|
-
if (result.errors) {
|
|
274
|
-
result.errors.forEach((error) => {
|
|
275
|
-
snackbarController.open({
|
|
276
|
-
type: "warning",
|
|
277
|
-
message: error
|
|
278
|
-
})
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (Object.keys(result.suggestions).length === 0) {
|
|
282
|
-
snackbarController.open({
|
|
283
|
-
type: "info",
|
|
284
|
-
autoHideDuration: 1800,
|
|
285
|
-
message: "No fields were updated"
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
setLoadingSuggestions([]);
|
|
289
|
-
resolve(result);
|
|
290
|
-
enhancingInProgress.current = false;
|
|
291
|
-
}
|
|
292
|
-
}).catch(onError);
|
|
293
|
-
} catch (e: unknown) {
|
|
294
|
-
onError(e);
|
|
232
|
+
const applyReview = useCallback(() => {
|
|
233
|
+
setReview((current) => {
|
|
234
|
+
if (!current) return null;
|
|
235
|
+
for (const field of current.fields) {
|
|
236
|
+
if (!field.selected || field.pending) continue;
|
|
237
|
+
if (field.proposed === undefined || field.proposed === null) continue;
|
|
238
|
+
formContext?.setFieldValue(field.key, field.proposed);
|
|
295
239
|
}
|
|
240
|
+
return null;
|
|
296
241
|
});
|
|
297
|
-
}, [
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
]);
|
|
242
|
+
}, [formContext]);
|
|
243
|
+
|
|
244
|
+
const editorAIController = useEditorAIController({ endpoint });
|
|
301
245
|
|
|
302
|
-
const getSamplePrompts = useCallback(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
host,
|
|
246
|
+
const getSamplePrompts = useCallback(
|
|
247
|
+
(entityName: string, input?: string) => fetchPromptSuggestions({
|
|
248
|
+
endpoint,
|
|
306
249
|
entityName,
|
|
307
|
-
|
|
308
|
-
apiKey,
|
|
250
|
+
entityDescription: collection.description,
|
|
309
251
|
input
|
|
310
|
-
})
|
|
311
|
-
|
|
252
|
+
}),
|
|
253
|
+
[endpoint, collection.description]
|
|
254
|
+
);
|
|
312
255
|
|
|
313
256
|
const dataEnhancementController: DataEnhancementController = useMemo(() => ({
|
|
314
257
|
enabled,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
258
|
+
review,
|
|
259
|
+
generate,
|
|
260
|
+
toggleField,
|
|
261
|
+
toggleAll,
|
|
262
|
+
applyReview,
|
|
263
|
+
dismissReview,
|
|
320
264
|
getSamplePrompts,
|
|
321
|
-
loadingSuggestions,
|
|
322
265
|
editorAIController
|
|
323
266
|
}), [
|
|
324
267
|
enabled,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
268
|
+
review,
|
|
269
|
+
generate,
|
|
270
|
+
toggleField,
|
|
271
|
+
toggleAll,
|
|
272
|
+
applyReview,
|
|
273
|
+
dismissReview,
|
|
330
274
|
getSamplePrompts,
|
|
331
|
-
loadingSuggestions,
|
|
332
275
|
editorAIController
|
|
333
276
|
]);
|
|
334
277
|
|
|
@@ -339,5 +282,3 @@ export function DataEnhancementControllerProvider({
|
|
|
339
282
|
</DataEnhancementControllerContext.Provider>
|
|
340
283
|
);
|
|
341
284
|
}
|
|
342
|
-
|
|
343
|
-
|