@ai-gui/plugin-form 0.11.1 → 0.12.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.cjs +34 -1
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +34 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -393,7 +393,12 @@ function mountForm(host, definition, options) {
|
|
|
393
393
|
for (const element of Array.from(formElement.elements)) if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement) element.disabled = true;
|
|
394
394
|
submit.textContent = options.submittedLabel ?? "Submitted";
|
|
395
395
|
};
|
|
396
|
-
|
|
396
|
+
const stored = options.restore?.(definition.id);
|
|
397
|
+
if (stored) {
|
|
398
|
+
writeControls(definition, controls, stored.values);
|
|
399
|
+
markSubmitted();
|
|
400
|
+
applyOutcome(stored.outcome, gradeAgainstExpectations(stored.values));
|
|
401
|
+
} else if (submitted) markSubmitted();
|
|
397
402
|
const onInput = (event) => {
|
|
398
403
|
const control = event.target;
|
|
399
404
|
if (!(control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement)) return;
|
|
@@ -429,6 +434,14 @@ function mountForm(host, definition, options) {
|
|
|
429
434
|
}).then((result) => {
|
|
430
435
|
markSubmitted();
|
|
431
436
|
applyOutcome(result, graded);
|
|
437
|
+
if (disposed) return;
|
|
438
|
+
const outcome = (0, __ai_gui_core.actionOutcome)(result) ?? graded;
|
|
439
|
+
try {
|
|
440
|
+
options.onSubmitted?.(definition.id, {
|
|
441
|
+
values: validation.values,
|
|
442
|
+
...outcome ? { outcome } : {}
|
|
443
|
+
});
|
|
444
|
+
} catch {}
|
|
432
445
|
}, (error) => {
|
|
433
446
|
if (!disposed && !controller.signal.aborted) {
|
|
434
447
|
actionError.textContent = actionErrorMessage(error);
|
|
@@ -545,6 +558,26 @@ function readControls(definition, controls) {
|
|
|
545
558
|
}
|
|
546
559
|
return values;
|
|
547
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Put a stored submission back into the controls — the inverse of `readControls`.
|
|
563
|
+
*
|
|
564
|
+
* A radio group is several inputs sharing a name, and `controls` holds only the first, so the one
|
|
565
|
+
* that carries the stored value has to be found in the group. Setting `.value` on that first input
|
|
566
|
+
* would rename the option instead of choosing it.
|
|
567
|
+
*/
|
|
568
|
+
function writeControls(definition, controls, values) {
|
|
569
|
+
for (const field of definition.fields) {
|
|
570
|
+
const control = controls.get(field.name);
|
|
571
|
+
const value = values[field.name];
|
|
572
|
+
if (!control || value === void 0) continue;
|
|
573
|
+
if (field.type === "checkbox" && control instanceof HTMLInputElement) control.checked = value === true;
|
|
574
|
+
else if (field.type === "radio") {
|
|
575
|
+
const group = control.form?.elements.namedItem(field.name);
|
|
576
|
+
const inputs = group instanceof RadioNodeList ? Array.from(group) : [control];
|
|
577
|
+
for (const input of inputs) if (input instanceof HTMLInputElement) input.checked = input.value === String(value);
|
|
578
|
+
} else control.value = String(value);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
548
581
|
function renderErrors(errors, controls, elements) {
|
|
549
582
|
for (const name of controls.keys()) {
|
|
550
583
|
const message = errors[name];
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AIGuiPlugin, ActionRuntime } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ActionOutcome, ActionRuntime } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
type FormFieldType = "text" | "textarea" | "number" | "date" | "select" | "checkbox" | "radio";
|
|
@@ -62,16 +62,43 @@ interface FormValidationResult {
|
|
|
62
62
|
values: Record<string, string | number | boolean>;
|
|
63
63
|
errors: Record<string, string>;
|
|
64
64
|
}
|
|
65
|
+
/** What was answered, and how it turned out — enough to put a form back the way it was left. */
|
|
66
|
+
interface FormSubmission {
|
|
67
|
+
values: Record<string, string | number | boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* The verdict, when the host kept one. Omitted, the fields' own `expect` is graded again, so a
|
|
70
|
+
* quiz still comes back coloured without the host having to store the marking.
|
|
71
|
+
*/
|
|
72
|
+
outcome?: ActionOutcome;
|
|
73
|
+
}
|
|
65
74
|
interface FormPluginOptions {
|
|
66
75
|
/** Shared core runtime whose registry is the only allowlist for submitAction. */
|
|
67
76
|
actionRuntime: ActionRuntime;
|
|
68
|
-
/** Mount
|
|
77
|
+
/** Mount every form as already submitted, useful when restoring persisted conversations. */
|
|
69
78
|
submitted?: boolean;
|
|
70
79
|
/** Label shown after a successful or restored submission. */
|
|
71
80
|
submittedLabel?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The submission this form already has, by form id.
|
|
83
|
+
*
|
|
84
|
+
* `submitted` on its own marks a form done without saying what was answered, so a reloaded
|
|
85
|
+
* conversation shows a disabled question with nothing chosen in it — which reads as worse than
|
|
86
|
+
* unanswered, since it claims to have been answered and cannot say with what. Returning the
|
|
87
|
+
* values puts the person's own answer back in front of them.
|
|
88
|
+
*
|
|
89
|
+
* Called once per form as it mounts, so it can read whatever the host has loaded by then.
|
|
90
|
+
*/
|
|
91
|
+
restore?: (formId: string) => FormSubmission | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Called when a submission succeeds, so the host can persist what to restore later.
|
|
94
|
+
*
|
|
95
|
+
* The action handler sees the values too, but not which form they came from — the form id is only
|
|
96
|
+
* in `cardType`, which a host would have to parse. This hands it over directly.
|
|
97
|
+
*/
|
|
98
|
+
onSubmitted?: (formId: string, submission: FormSubmission) => void;
|
|
72
99
|
}
|
|
73
100
|
declare function formPromptSpec(): string;
|
|
74
101
|
declare function form(options: FormPluginOptions): AIGuiPlugin;
|
|
75
102
|
declare function parseFormDefinition(source: string): FormParseResult;
|
|
76
103
|
declare function validateFormValues(definition: FormDefinition, input: Record<string, unknown>): FormValidationResult; //#endregion
|
|
77
|
-
export { FormCheckboxField, FormChoiceField, FormDateField, FormDefinition, FormField, FormFieldType, FormNumberField, FormOption, FormParseResult, FormPluginOptions, FormStringField, FormValidationResult, form, formPromptSpec, parseFormDefinition, validateFormValues };
|
|
104
|
+
export { FormCheckboxField, FormChoiceField, FormDateField, FormDefinition, FormField, FormFieldType, FormNumberField, FormOption, FormParseResult, FormPluginOptions, FormStringField, FormSubmission, FormValidationResult, form, formPromptSpec, parseFormDefinition, validateFormValues };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AIGuiPlugin, ActionRuntime } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ActionOutcome, ActionRuntime } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
type FormFieldType = "text" | "textarea" | "number" | "date" | "select" | "checkbox" | "radio";
|
|
@@ -62,16 +62,43 @@ interface FormValidationResult {
|
|
|
62
62
|
values: Record<string, string | number | boolean>;
|
|
63
63
|
errors: Record<string, string>;
|
|
64
64
|
}
|
|
65
|
+
/** What was answered, and how it turned out — enough to put a form back the way it was left. */
|
|
66
|
+
interface FormSubmission {
|
|
67
|
+
values: Record<string, string | number | boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* The verdict, when the host kept one. Omitted, the fields' own `expect` is graded again, so a
|
|
70
|
+
* quiz still comes back coloured without the host having to store the marking.
|
|
71
|
+
*/
|
|
72
|
+
outcome?: ActionOutcome;
|
|
73
|
+
}
|
|
65
74
|
interface FormPluginOptions {
|
|
66
75
|
/** Shared core runtime whose registry is the only allowlist for submitAction. */
|
|
67
76
|
actionRuntime: ActionRuntime;
|
|
68
|
-
/** Mount
|
|
77
|
+
/** Mount every form as already submitted, useful when restoring persisted conversations. */
|
|
69
78
|
submitted?: boolean;
|
|
70
79
|
/** Label shown after a successful or restored submission. */
|
|
71
80
|
submittedLabel?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The submission this form already has, by form id.
|
|
83
|
+
*
|
|
84
|
+
* `submitted` on its own marks a form done without saying what was answered, so a reloaded
|
|
85
|
+
* conversation shows a disabled question with nothing chosen in it — which reads as worse than
|
|
86
|
+
* unanswered, since it claims to have been answered and cannot say with what. Returning the
|
|
87
|
+
* values puts the person's own answer back in front of them.
|
|
88
|
+
*
|
|
89
|
+
* Called once per form as it mounts, so it can read whatever the host has loaded by then.
|
|
90
|
+
*/
|
|
91
|
+
restore?: (formId: string) => FormSubmission | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Called when a submission succeeds, so the host can persist what to restore later.
|
|
94
|
+
*
|
|
95
|
+
* The action handler sees the values too, but not which form they came from — the form id is only
|
|
96
|
+
* in `cardType`, which a host would have to parse. This hands it over directly.
|
|
97
|
+
*/
|
|
98
|
+
onSubmitted?: (formId: string, submission: FormSubmission) => void;
|
|
72
99
|
}
|
|
73
100
|
declare function formPromptSpec(): string;
|
|
74
101
|
declare function form(options: FormPluginOptions): AIGuiPlugin;
|
|
75
102
|
declare function parseFormDefinition(source: string): FormParseResult;
|
|
76
103
|
declare function validateFormValues(definition: FormDefinition, input: Record<string, unknown>): FormValidationResult; //#endregion
|
|
77
|
-
export { FormCheckboxField, FormChoiceField, FormDateField, FormDefinition, FormField, FormFieldType, FormNumberField, FormOption, FormParseResult, FormPluginOptions, FormStringField, FormValidationResult, form, formPromptSpec, parseFormDefinition, validateFormValues };
|
|
104
|
+
export { FormCheckboxField, FormChoiceField, FormDateField, FormDefinition, FormField, FormFieldType, FormNumberField, FormOption, FormParseResult, FormPluginOptions, FormStringField, FormSubmission, FormValidationResult, form, formPromptSpec, parseFormDefinition, validateFormValues };
|
package/dist/index.js
CHANGED
|
@@ -369,7 +369,12 @@ function mountForm(host, definition, options) {
|
|
|
369
369
|
for (const element of Array.from(formElement.elements)) if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement) element.disabled = true;
|
|
370
370
|
submit.textContent = options.submittedLabel ?? "Submitted";
|
|
371
371
|
};
|
|
372
|
-
|
|
372
|
+
const stored = options.restore?.(definition.id);
|
|
373
|
+
if (stored) {
|
|
374
|
+
writeControls(definition, controls, stored.values);
|
|
375
|
+
markSubmitted();
|
|
376
|
+
applyOutcome(stored.outcome, gradeAgainstExpectations(stored.values));
|
|
377
|
+
} else if (submitted) markSubmitted();
|
|
373
378
|
const onInput = (event) => {
|
|
374
379
|
const control = event.target;
|
|
375
380
|
if (!(control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement)) return;
|
|
@@ -405,6 +410,14 @@ function mountForm(host, definition, options) {
|
|
|
405
410
|
}).then((result) => {
|
|
406
411
|
markSubmitted();
|
|
407
412
|
applyOutcome(result, graded);
|
|
413
|
+
if (disposed) return;
|
|
414
|
+
const outcome = actionOutcome(result) ?? graded;
|
|
415
|
+
try {
|
|
416
|
+
options.onSubmitted?.(definition.id, {
|
|
417
|
+
values: validation.values,
|
|
418
|
+
...outcome ? { outcome } : {}
|
|
419
|
+
});
|
|
420
|
+
} catch {}
|
|
408
421
|
}, (error) => {
|
|
409
422
|
if (!disposed && !controller.signal.aborted) {
|
|
410
423
|
actionError.textContent = actionErrorMessage(error);
|
|
@@ -521,6 +534,26 @@ function readControls(definition, controls) {
|
|
|
521
534
|
}
|
|
522
535
|
return values;
|
|
523
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Put a stored submission back into the controls — the inverse of `readControls`.
|
|
539
|
+
*
|
|
540
|
+
* A radio group is several inputs sharing a name, and `controls` holds only the first, so the one
|
|
541
|
+
* that carries the stored value has to be found in the group. Setting `.value` on that first input
|
|
542
|
+
* would rename the option instead of choosing it.
|
|
543
|
+
*/
|
|
544
|
+
function writeControls(definition, controls, values) {
|
|
545
|
+
for (const field of definition.fields) {
|
|
546
|
+
const control = controls.get(field.name);
|
|
547
|
+
const value = values[field.name];
|
|
548
|
+
if (!control || value === void 0) continue;
|
|
549
|
+
if (field.type === "checkbox" && control instanceof HTMLInputElement) control.checked = value === true;
|
|
550
|
+
else if (field.type === "radio") {
|
|
551
|
+
const group = control.form?.elements.namedItem(field.name);
|
|
552
|
+
const inputs = group instanceof RadioNodeList ? Array.from(group) : [control];
|
|
553
|
+
for (const input of inputs) if (input instanceof HTMLInputElement) input.checked = input.value === String(value);
|
|
554
|
+
} else control.value = String(value);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
524
557
|
function renderErrors(errors, controls, elements) {
|
|
525
558
|
for (const name of controls.keys()) {
|
|
526
559
|
const message = errors[name];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/plugin-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Safe interactive form blocks for AIGUI, backed by the core ActionRuntime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ai-gui/core": "0.
|
|
52
|
+
"@ai-gui/core": "0.12.0"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsdown",
|