react-formule 1.4.0 → 1.6.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.
Files changed (32) hide show
  1. package/README.md +235 -11
  2. package/dist/admin/formComponents/PropKeyEditorObjectFieldTemplate.d.ts +4 -0
  3. package/dist/admin/formComponents/SortableBox.d.ts +15 -17
  4. package/dist/admin/utils/fieldTypes.d.ts +852 -410
  5. package/dist/admin/utils/index.d.ts +1 -0
  6. package/dist/ai/AiChatFooter.d.ts +9 -0
  7. package/dist/ai/AiDiff.d.ts +9 -0
  8. package/dist/ai/AiSettingsDialog.d.ts +7 -0
  9. package/dist/ai/defaults.d.ts +4 -0
  10. package/dist/ai/hooks.d.ts +4 -0
  11. package/dist/ai/utils.d.ts +5 -0
  12. package/dist/contexts/CustomizationContext.d.ts +3 -1
  13. package/dist/exposed.d.ts +8 -18
  14. package/dist/forms/Form.d.ts +2 -0
  15. package/dist/forms/fields/base/CodeEditorField.d.ts +1 -2
  16. package/dist/forms/fields/base/FileField.d.ts +12 -0
  17. package/dist/forms/fields/base/index.d.ts +2 -0
  18. package/dist/forms/templates/ArrayFieldTemplates/{NormalArrayFieldTemplate.d.ts → ArrayFieldTemplate.d.ts} +3 -3
  19. package/dist/forms/templates/Field/FieldCollapsible.d.ts +6 -0
  20. package/dist/forms/templates/utils/index.d.ts +1 -0
  21. package/dist/forms/widgets/base/MaskedInput/MaskedInput.d.ts +2 -1
  22. package/dist/index.d.ts +8 -1
  23. package/dist/react-formule.js +153784 -143696
  24. package/dist/react-formule.umd.cjs +693 -544
  25. package/dist/store/configureStore.d.ts +8 -0
  26. package/dist/store/schemaWizard.d.ts +16 -1
  27. package/dist/types/index.d.ts +108 -0
  28. package/dist/utils/CodeDiffViewer.d.ts +2 -1
  29. package/dist/utils/FieldMessageTag.d.ts +4 -0
  30. package/package.json +7 -4
  31. package/dist/admin/formComponents/RenderSortable.d.ts +0 -2
  32. package/dist/vite.svg +0 -1
package/README.md CHANGED
@@ -70,10 +70,10 @@ Formule includes a variety of predefined field types, grouped in three categorie
70
70
  - **Collections**:
71
71
  - `Object`: Use it of you want to group fields or to add several of them inside of a `List`.
72
72
  - `List`: It allows you to have as many instances of a field or `Object` as you want.
73
- - `Accordion`: When containing a `List`, it works as a `List` with collapsible entries.
74
- - `Layer`: When containing a `List`, it works as a `List` whose entries will open in a dialog window.
73
+ - `Accordion`: It works as a `List` with collapsible entries.
74
+ - `Layer`: It works as a `List` whose entries will open in a dialog window.
75
75
  - `Tab`: It's commonly supposed to be used as a wrapper around the rest of the elements. You will normally want to add an `Object` inside and you can use it to separate the form in different pages or sections.
76
- - **Advanced fields**: More complex or situational fields such as `URI`, `Rich/Latex editor`, `Tags`, `ID Fetcher` and `Code Editor`.
76
+ - **Advanced fields**: More complex or situational fields such as `URI`, `Rich/Latex editor`, `Tags`, `ID Fetcher`, `Code Editor` and `Files`.
77
77
 
78
78
  You can freely remove some of these predefined fields and add your own custom fields and widgets following the JSON Schema specifications. More details below.
79
79
 
@@ -110,27 +110,39 @@ return (
110
110
 
111
111
  ### Customizing and adding new field types
112
112
 
113
- Override (if existing) or create your own field types (rjsf type definitions) similarly to how it's done in `fieldTypes.jsx`, passing them as `customFieldTypes`. Implement your own custom fields and widgets (react components) by passing them as `customFields` and/or `customWidgets` (see `forms/fields/` and `forms/widgets/` for examples). If you also want to use a different published version of a field or widget, pass the component in `customPublishedFields` or `customPublishedWidgets`.
113
+ Override (if existing) or create your own field types (rjsf type definitions) similarly to how it's done in `fieldTypes.jsx`, passing them as `customFieldTypes`. Implement your own custom fields and widgets (react components) by passing them as `customFields` and/or `customWidgets` (see `forms/fields/` and `forms/widgets/` for examples). If you also want to use a different published version of a field or widget, pass the component in `customPublishedFields` or `customPublishedWidgets`. You can read more about the difference between fields and widgets and how to customize or wrap them in the [rjsf docs](https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields), but make sure you provide Formule with something like the following:
114
114
 
115
115
  ```jsx
116
+ const CustomWidget = ({value, required, onChange}) => {
117
+ return (
118
+ <input
119
+ type='text'
120
+ className='custom'
121
+ value={value}
122
+ required={required}
123
+ onChange={(event) => onChange(event.target.value)}
124
+ />
125
+ );
126
+ };
127
+
116
128
  const customFieldTypes = {
117
129
  advanced: {
118
- myfield: {
130
+ myCustomWidget: {
119
131
  title: ...
120
132
  ...
121
133
  }
122
134
  }
123
135
  }
124
136
 
125
- const customFields: {
126
- myfield: MyField // react component
137
+ const customWidgets: {
138
+ myCustomWidget: CustomWidget
127
139
  }
128
140
 
129
141
  <FormuleContext
130
142
  theme={{token: {colorPrimary: "blue"}}} // antd theme
131
143
  customFieldTypes={customFieldTypes}
132
- customFields={customFields}
133
- customWidgets={...}
144
+ customFields={...}
145
+ customWidgets={customWidgets}
134
146
  customPublishedFields={...}
135
147
  customPublishedWidgets={...}>
136
148
  // ...
@@ -176,9 +188,221 @@ const handleFormuleStateChange = (newState) => {
176
188
 
177
189
  Alternatively, you can pull the current state on demand by calling `getFormuleState` at any moment.
178
190
 
179
- > [!TIP]
180
- > For more examples, feel free to browse around formule-demo and the [CERN Analysis Preservation](https://github.com/cernanalysispreservation/analysispreservation.cern.ch) repository, where we use all the features mentioned above.
191
+ ### Loading form data / prefill form
192
+
193
+ If you want to prefill the form with existing data, you can provide the form data to `FormuleForm`. This will fill in the corresponding fields with the information in `formData`:
194
+
195
+ ```jsx
196
+ <FormuleForm
197
+ formData={{
198
+ name: "Mule",
199
+ age: 20,
200
+ weight: 370,
201
+ }}
202
+ />
203
+ ```
204
+
205
+ ### Using the Files field
206
+
207
+ In order to keep Formule's philosophy of storing forms and completion data as simple JSON objects, Formule doesn't directly store files. Instead, it stores only UIDs and leaves the specifics of how, where and when to store the corresponding files up to the user.
208
+
209
+ <details>
210
+ <summary>More info about <b>fetching and storing files</b> with examples</summary>
211
+
212
+ #### Fetching files
213
+
214
+ In order to fetch files from a URL (which can be your backend or a public URL), you will have to provide a `fetchFile` callback function in `customFunctions`. Formule will call this function when first loading a Files field for each of the file UIDs associated to this field, passing the file UID. This function should return a file URL.
215
+
216
+ ```jsx
217
+ <FormuleContext
218
+ customFunctions={{
219
+ file: {
220
+ // You can either directly return the file URL (useful also if you want
221
+ // to do some kind of caching):
222
+ fetchFile: (uid) => {
223
+ return `https://example.com/files/${uid}`;
224
+ },
225
+ // Or, if you need to manage e.g. authentication, you can always fetch
226
+ // the image yourself, doing any processing you find necessary and finally
227
+ // create an object URL with URL.createObjectURL() and return it:
228
+ fetchFile: (uid) => {
229
+ return fetch(`https://example.com/files/${uid}`)
230
+ .then((response) => response.blob())
231
+ .then((blob) => URL.createObjectURL(blob));
232
+ },
233
+ },
234
+ }}
235
+ >
236
+ // ...
237
+ </FormuleContext>
238
+ ```
239
+
240
+ #### Storing files
241
+
242
+ Formule temporarily stores object URLs of files uploaded in the current session in the Formule state under `files.new`. If you want to persist files you can simply monitor the formule state (see [Syncing Formule state](#syncing-formule-state)) and whenever you want to save them (usually you will want to do it on submission, but you could do it on change) you can read new files from `files.new` and deleted files from `files.deleted`. You can then use that data to trigger the corresponding upload and delete actions in your backend.
243
+
244
+ ```jsx
245
+ // Example of a custom function to handle form submission
246
+ // Only showing file-related logic
247
+ // Assumes you have synchronized the formule state on formuleState
248
+ const handleSubmit = () => {
249
+ // Upload new files
250
+ formuleState.files.new.map({uid, url} => {
251
+ const response = await fetch(url);
252
+ const blob = await response.blob();
253
+
254
+ const formData = new FormData();
255
+ formData.append('file', blob);
256
+ formData.append('uid', uid);
257
+
258
+ const uploadResponse = await fetch('https://example.com/upload', {
259
+ method: 'POST',
260
+ body: formData,
261
+ });
262
+ // handle response...
263
+ })
264
+
265
+ // Remove deleted files. You only need to handle this for form edition
266
+ // (forms already filled-in and saved that are being further modified)
267
+ // unless you are persisting files on change instead of on submission.
268
+ formuleState.files.deleted.map(uid => {
269
+ const response = await fetch(`https://example.com/files/${uid}`, {
270
+ method: 'DELETE',
271
+ });
272
+ // handle response...
273
+ });
274
+ };
275
+ ```
276
+
277
+ </details>
278
+
279
+ ## ✨ FormuleAI
280
+
281
+ FormuleAI brings Artificial Intelligence capabilities to Formule, allowing users to **generate and modify form schemas using natural language** prompts. This feature leverages Large Language Models to understand user requirements and automatically create or update form structures.
282
+
283
+ ### How it works
284
+
285
+ FormuleAI integrates AI providers (like OpenAI or Gemini) to process natural language requests and generate corresponding JSON schemas. The AI understands the current form context and can add new fields, modify existing ones, or restructure entire sections based on your prompts.
286
+
287
+ By default FormuleAI includes providers for **OpenAI** and **Gemini**, and has been tested to work particularly with `GPT 4.1 Mini` and `Gemini 2.0 Flash`, although it allows you to choose any other model offered by these providers. You can also provide your own API key.
288
+
289
+ Once you send a request and receive a response back from the LLM, a popover will be displayed showing the diff between the current form and the suggestion, as well as between both JSON schemas, and you will be able to either reject or approve the changes. In the FormuleAI settings, you can also activate the "Vibe Mode", which will auto-apply any change without showing you a diff and asking for approval (use at your own risk).
290
+
291
+ **Note:** For the moment FormuleAI doesn't keep conversation history, so make sure to always be clear and provide all necessary details in each request.
292
+
293
+ ### Basic usage
294
+
295
+ The main component you will need to use is **`AiChatFooter`**, a chat interface where users can input their prompts, toggle diffs, and accept or reject changes. It will also allow you to configure some settings and provide some usage instructions.
296
+
297
+ It can receive the following props: `onApply` and `onReject` callbacks, `hideHelp` and `hideSettings`, and `vibeMode` (false to disable, true to enable, unset to leave the decision up to users via settings).
298
+
299
+ A basic configuration would be simply:
300
+
301
+ ```jsx
302
+ import { FormuleContext, AiChatFooter } from "react-formule";
303
+
304
+ <FormuleContext>
305
+ // your other main formule components
306
+ <AiChatFooter />
307
+ </FormuleContext>;
308
+ ```
309
+
310
+ <details>
311
+ <summary>Adding <b>custom providers</b> and advanced configuration</summary>
312
+
313
+ ### Customizing AI providers
314
+
315
+ You can add a new provider, whether commercial or self-hosted, in the following way:
316
+
317
+ ```jsx
318
+ import { defaultProviders } from "react-formule";
319
+
320
+ const customProviders = {
321
+ "local-llama": {
322
+ label: <span>Local llama</span>,
323
+ // Optional, otherwise users can provide their own via settings
324
+ apiKey: "your-api-key",
325
+ // Optional, otherwise users can select one via settings (you need to define fetchModels for that)
326
+ model: "llama3.1",
327
+ // Optional (not needed when providing a model), it preselects that model in the model list
328
+ recommendedModel: { id: "llama3.1", name: "LLaMA 3.1" }
329
+ // Optional, needed only if you don't provide a model
330
+ fetchModels: async (apiKey) => {
331
+ const response = await fetch("https://your-ai-endpoint/models", {
332
+ headers: { Authorization: `Bearer ${apiKey}` },
333
+ });
334
+ const data = await response.json();
335
+ return data.models.map((model) => ({
336
+ id: model.id,
337
+ name: model.display_name,
338
+ }));
339
+ },
340
+ generateSchema: async (
341
+ prompt,
342
+ currentSchema,
343
+ fieldTypes,
344
+ apiKey,
345
+ model
346
+ ) => {
347
+ const response = await fetch("https://your-ai-endpoint", {
348
+ method: "POST",
349
+ headers: { Authorization: `Bearer ${apiKey}` },
350
+ body: JSON.stringify({
351
+ model: model,
352
+ messages: [
353
+ {
354
+ role: "system",
355
+ content: "<your system prompt>",
356
+ },
357
+ {
358
+ role: "user",
359
+ content: "<your user prompt including fieldTpes and currentSchema>"
360
+ }
361
+ ],
362
+ response_format: { type: "json_object" }
363
+ }),
364
+ });
365
+ return {
366
+ schema: content.schema,
367
+ uiSchema: content.uiSchema,
368
+ usage, // Optionally you can include token usage stats to be displayed to users
369
+ };
370
+ // If error, `return { error: "the error message" }` instead
371
+ },
372
+ },
373
+ };
374
+
375
+ <FormuleContext ai={{ providers: { ...defaultProviders, ...customProviders } }}>
376
+ // ...
377
+ </FormuleContext>;
378
+ ```
379
+
380
+ If you want to keep the default providers along with your custom ones, you can import `defaultProviders` and include it in your providers object (as you can see in the previous example). Otherwise your new providers will override that configuration.
381
+
382
+ If an API key or a model is defined in a provider, users will not be able to modify them in the settings for that provider.
383
+
384
+ ### Utility functions, hooks and components
385
+
386
+ FormuleAI exports several utilities for advanced use cases:
387
+
388
+ - **`useGenerateSchema`**: Hook for triggering schema generation programmatically
389
+ - **`useGetProvider`**: Hook to access configured AI providers. It will return the provider selected by the user (from localStorage) or otherwise a valid provider which is fully configured (with API key and model) in `ai.providers` if any.
390
+ - **`generatePatches`**: Utility to create JSON patches between schemas, used by FormuleAI for the form diff.
391
+ - **`defaultProviders`**: Configuration of the default providers, mentioned in the examples above.
392
+ - **`defaultGenerationPrompt`**: The default system prompt used by the current providers. It can help as a starting point to experiment with custom providers, but you will likely have to do some adjustments for each one.
393
+
394
+ There are also two more components that you would normally not need to use explicitly (they are already used by default from `AiChatFooter`) but which are still exposed to give you more flexiility in case you want further customization or to use them on your own custom chat interface implementation:
395
+
396
+ - **`AiDiff`**: Shows a visual diff of proposed changes before applying them
397
+ - **`AiSettingsDialog`**: Configuration panel for API keys and model selection
398
+
399
+ For implementation examples and advanced configurations, refer to the default provider implementations in the codebase.
400
+
401
+ </details>
181
402
 
182
403
  ## :space_invader: Local demo & how to contribute
183
404
 
184
405
  Apart from trying the online [demo](https://cern-sis.github.io/react-formule/) you can clone the repo and run `formule-demo` to play around. Follow the instructions in its [README](./formule-demo/README.md): it will explain how to install `react-formule` as a local dependency so that you can modify Formule and test the changes live in your host app, which will be ideal if you want to troubleshoot or contribute to the project. Your contributions are welcome! :rocket:
406
+
407
+ > [!TIP]
408
+ > For more examples, feel free to browse around formule-demo and the [CERN Analysis Preservation](https://github.com/cernanalysispreservation/analysispreservation.cern.ch) repository, where we use most of the features mentioned above.
@@ -0,0 +1,4 @@
1
+ export default PropKeyEditorObjectFieldTemplate;
2
+ declare function PropKeyEditorObjectFieldTemplate({ properties }: {
3
+ properties: any;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -1,18 +1,16 @@
1
- import { default as PropTypes } from 'prop-types';
2
- export default SortableBox;
3
- declare function SortableBox({ parent, children, id, index, moveCard }: {
4
- parent: any;
5
- children: any;
6
- id: any;
7
- index: any;
8
- moveCard: any;
9
- }): import("react/jsx-runtime").JSX.Element;
10
- declare namespace SortableBox {
11
- namespace propTypes {
12
- let parent: PropTypes.Requireable<string>;
13
- let children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
14
- let id: PropTypes.Requireable<number>;
15
- let index: PropTypes.Requireable<number>;
16
- let moveCard: PropTypes.Requireable<(...args: any[]) => any>;
17
- }
1
+ import { ReactNode } from 'react';
2
+
3
+ interface SortableBoxProps {
4
+ children: ReactNode;
5
+ parent: string;
6
+ id: number;
7
+ index: number;
8
+ onDragStart: () => void;
9
+ onDragEnd: () => void;
10
+ onDragCancel: () => void;
11
+ updateVisualOrder: (dragIndex: number, hoverIndex: number) => void;
12
+ moveItem: () => void;
18
13
  }
14
+ declare function SortableBox({ children, parent, id, index, onDragStart, onDragEnd, onDragCancel, moveItem, updateVisualOrder, }: SortableBoxProps): import("react/jsx-runtime").JSX.Element;
15
+ declare const _default: import('react').MemoExoticComponent<typeof SortableBox>;
16
+ export default _default;