@esheet/renderer 0.0.4-0 → 0.0.4-1
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.d.ts +9 -291
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -115
- package/dist/lib/EsheetRenderer.d.ts +116 -0
- package/dist/lib/EsheetRenderer.d.ts.map +1 -0
- package/dist/lib/EsheetRenderer.js +115 -0
- package/dist/lib/components/FieldNode.d.ts +17 -0
- package/dist/lib/components/FieldNode.d.ts.map +1 -0
- package/dist/lib/components/FieldNode.js +96 -0
- package/dist/lib/components/RendererBody.d.ts +14 -0
- package/dist/lib/components/RendererBody.d.ts.map +1 -0
- package/dist/lib/components/RendererBody.js +45 -0
- package/dist/lib/components/index.d.ts +3 -0
- package/dist/lib/components/index.d.ts.map +1 -0
- package/dist/lib/components/index.js +2 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/hooks/index.d.ts.map +1 -0
- package/dist/lib/hooks/index.js +1 -0
- package/dist/lib/hooks/useRendererInit.d.ts +12 -0
- package/dist/lib/hooks/useRendererInit.d.ts.map +1 -0
- package/dist/lib/hooks/useRendererInit.js +87 -0
- package/dist/lib/mcp/index.d.ts +7 -0
- package/dist/lib/mcp/index.d.ts.map +1 -0
- package/dist/lib/mcp/index.js +4 -0
- package/dist/lib/mcp/system-prompt.d.ts +2 -0
- package/dist/lib/mcp/system-prompt.d.ts.map +1 -0
- package/dist/lib/mcp/system-prompt.js +24 -0
- package/dist/lib/mcp/tool-definitions.d.ts +10 -0
- package/dist/lib/mcp/tool-definitions.d.ts.map +1 -0
- package/dist/lib/mcp/tool-definitions.js +66 -0
- package/dist/lib/mcp/tool-executor.d.ts +5 -0
- package/dist/lib/mcp/tool-executor.d.ts.map +1 -0
- package/dist/lib/mcp/tool-executor.js +55 -0
- package/dist/lib/mcp/useRendererToolBridge.d.ts +28 -0
- package/dist/lib/mcp/useRendererToolBridge.d.ts.map +1 -0
- package/dist/lib/mcp/useRendererToolBridge.js +53 -0
- package/dist/lib/register-defaults.d.ts +2 -0
- package/dist/lib/register-defaults.d.ts.map +1 -0
- package/dist/lib/register-defaults.js +36 -0
- package/dist/lib/renderer-tools.d.ts +56 -0
- package/dist/lib/renderer-tools.d.ts.map +1 -0
- package/dist/lib/renderer-tools.js +305 -0
- package/dist/lib/renderer.d.ts +28 -0
- package/dist/lib/renderer.d.ts.map +1 -0
- package/dist/lib/renderer.js +33 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EsheetRenderer.d.ts","sourceRoot":"","sources":["../../src/lib/EsheetRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAW9E,MAAM,WAAW,mBAAmB;IAClC;;+FAE2F;IAC3F,aAAa,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC;oFACgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD;AAMD,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE/C,gCAAgC;AAChC,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,GAAG;QACvD,gGAAgG;QAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED,uDAAuD;AACvD,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,GAAG,QAAQ,IAC/D,CAAC,SAAS,MAAM,GAAG,yBAAyB,GAAG,YAAY,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,cAAc,EAAE,MAAM,YAAY,CAAC;IACnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,WAAW,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,QAAQ,EAC/C,OAAO,CAAC,EAAE,kBAAkB,GAAG;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,KAC1C,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC1B,8BAA8B;IAC9B,YAAY,EAAE,MAAM,SAAS,CAAC;IAC9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM;QACtB,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,eAAe,EAAE,CAAC;KAC3B,CAAC;IACF,sDAAsD;IACtD,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,iHAAiH;IACjH,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,+FAA+F;IAC/F,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,kGA2BzB,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createFormStore, createUIStore, normalizeResponses, hydrateDefinition, validateForm, } from '@esheet/core';
|
|
4
|
+
import { exportResponseToFhir, } from '@esheet/adapters';
|
|
5
|
+
import { createRendererTools } from './renderer-tools.js';
|
|
6
|
+
import { FormStoreContext, UIContext, ZodIssuesPanel, useTouchMode, } from '@esheet/fields';
|
|
7
|
+
import { ensureDefaultFieldComponentsRegistered } from './register-defaults.js';
|
|
8
|
+
import { useRendererInit } from './hooks/useRendererInit.js';
|
|
9
|
+
import { RendererBody } from './components/RendererBody.js';
|
|
10
|
+
/**
|
|
11
|
+
* EsheetRenderer - Read-only questionnaire form renderer
|
|
12
|
+
*
|
|
13
|
+
* Renders a form in fill-out mode with conditional visibility logic.
|
|
14
|
+
* Reuses all field components from @esheet/fields.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const rendererRef = useRef<EsheetRendererHandle>(null);
|
|
19
|
+
*
|
|
20
|
+
* <EsheetRenderer
|
|
21
|
+
* formData={myFormDefinition}
|
|
22
|
+
* initialResponses={{ field1: 'answer' }}
|
|
23
|
+
* ref={rendererRef}
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* // Later: get responses
|
|
27
|
+
* const responses = rendererRef.current?.getRawResponse();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const EsheetRenderer = React.forwardRef(function EsheetRenderer(props, ref) {
|
|
31
|
+
ensureDefaultFieldComponentsRegistered();
|
|
32
|
+
const formStore = React.useMemo(() => createFormStore(), []);
|
|
33
|
+
const uiStore = React.useMemo(() => createUIStore(), []);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (props.onRendererToolsReady)
|
|
36
|
+
props.onRendererToolsReady(createRendererTools(formStore));
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
}, [formStore]);
|
|
39
|
+
return (_jsx(FormStoreContext.Provider, { value: formStore, children: _jsx(UIContext.Provider, { value: uiStore, children: _jsx(EsheetRendererInner, { ...props, formStore: formStore, uiStore: uiStore, ref: ref }) }) }));
|
|
40
|
+
});
|
|
41
|
+
const EsheetRendererInner = React.forwardRef(function EsheetRendererInner({ formDataInput: formData, className = '', initialResponses, strict = false, onReady, formStore, uiStore, touchMode, onTouchModeChange, }, ref) {
|
|
42
|
+
const [validationErrors, setValidationErrors] = React.useState([]);
|
|
43
|
+
// Touch mode state using shared hook
|
|
44
|
+
const { isTouchEnabled, isManualOverride, setTouchMode: setTouchModeInternal, resetTouchMode: resetTouchModeInternal, } = useTouchMode({ mode: touchMode, onChange: onTouchModeChange });
|
|
45
|
+
// Initialize form definition and set preview mode
|
|
46
|
+
useRendererInit(formStore, uiStore, formData, initialResponses, setValidationErrors, strict, onReady);
|
|
47
|
+
// Expose ref API
|
|
48
|
+
React.useImperativeHandle(ref, () => ({
|
|
49
|
+
getRawResponse: () => formStore.getState().responses,
|
|
50
|
+
getResponse: (options) => {
|
|
51
|
+
const state = formStore.getState();
|
|
52
|
+
const responses = state.responses;
|
|
53
|
+
if (options?.format === 'fhir') {
|
|
54
|
+
// Build FHIR QuestionnaireResponse
|
|
55
|
+
const normalizedAnswers = normalizeResponses(responses);
|
|
56
|
+
// Try to get questionnaire URL from options or formSourceData
|
|
57
|
+
const fhirMeta = state.formSourceData;
|
|
58
|
+
const questionnaireUrl = options.fhir?.questionnaireUrl ??
|
|
59
|
+
fhirMeta?.url ??
|
|
60
|
+
`urn:uuid:${state.formId}`;
|
|
61
|
+
const exportOptions = {
|
|
62
|
+
questionnaireUrl,
|
|
63
|
+
status: options.fhir?.status ?? 'completed',
|
|
64
|
+
subject: options.fhir?.subject,
|
|
65
|
+
author: options.fhir?.author,
|
|
66
|
+
resourceId: options.fhir?.resourceId,
|
|
67
|
+
};
|
|
68
|
+
// Rebuild form definition from normalized state
|
|
69
|
+
const formDefinition = {
|
|
70
|
+
id: state.formId,
|
|
71
|
+
fields: hydrateDefinition(state.normalized),
|
|
72
|
+
_sourceData: state.formSourceData,
|
|
73
|
+
};
|
|
74
|
+
return exportResponseToFhir(formDefinition, normalizedAnswers, exportOptions);
|
|
75
|
+
}
|
|
76
|
+
// Native format (default)
|
|
77
|
+
return responses;
|
|
78
|
+
},
|
|
79
|
+
getFormStore: () => formStore,
|
|
80
|
+
getUIStore: () => uiStore,
|
|
81
|
+
getValidResponse: () => {
|
|
82
|
+
const state = formStore.getState();
|
|
83
|
+
const errors = validateForm(state.normalized, state.responses);
|
|
84
|
+
return {
|
|
85
|
+
response: errors.length === 0 ? state.responses : null,
|
|
86
|
+
errors,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
isTouchModeEnabled: () => isTouchEnabled,
|
|
90
|
+
setTouchMode: setTouchModeInternal,
|
|
91
|
+
resetTouchMode: resetTouchModeInternal,
|
|
92
|
+
}), [
|
|
93
|
+
formStore,
|
|
94
|
+
uiStore,
|
|
95
|
+
isTouchEnabled,
|
|
96
|
+
setTouchModeInternal,
|
|
97
|
+
resetTouchModeInternal,
|
|
98
|
+
]);
|
|
99
|
+
// Determine if touch mode class should be applied
|
|
100
|
+
// true: always apply, 'auto'/undefined: apply based on isTouchEnabled state, false: never apply (CSS only)
|
|
101
|
+
const applyTouchClass = touchMode === true ||
|
|
102
|
+
((touchMode === 'auto' || touchMode === undefined) && isTouchEnabled);
|
|
103
|
+
// Apply disabled class when user explicitly disabled touch mode (prevents CSS media query)
|
|
104
|
+
const applyDisabledClass = isManualOverride && !isTouchEnabled;
|
|
105
|
+
const rootClasses = [
|
|
106
|
+
'esheet-renderer-root',
|
|
107
|
+
applyTouchClass && 'esheet-touch-active',
|
|
108
|
+
applyDisabledClass && 'touch-mode-disabled',
|
|
109
|
+
'ms:w-full ms:max-w-2xl ms:mx-auto ms:p-4 ms:text-mstext',
|
|
110
|
+
className,
|
|
111
|
+
]
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.join(' ');
|
|
114
|
+
return (_jsxs("div", { className: rootClasses, children: [_jsx(ZodIssuesPanel, { issues: validationErrors }), _jsx(RendererBody, { form: formStore, ui: uiStore })] }));
|
|
115
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FormStore, UIStore } from '@esheet/core';
|
|
3
|
+
export interface FieldNodeProps {
|
|
4
|
+
id: string;
|
|
5
|
+
form: FormStore;
|
|
6
|
+
ui: UIStore;
|
|
7
|
+
/** Nesting depth for visual styling (default: 0) */
|
|
8
|
+
depth?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* FieldNode - Renders a single field or section with recursive children
|
|
12
|
+
*
|
|
13
|
+
* Looks up the field component from the registry and passes field props.
|
|
14
|
+
* For sections, recursively renders visible children.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FieldNode: React.NamedExoticComponent<FieldNodeProps>;
|
|
17
|
+
//# sourceMappingURL=FieldNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldNode.d.ts","sourceRoot":"","sources":["../../../src/lib/components/FieldNode.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAuB,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,4CAiKpB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { getFieldComponent } from '@esheet/fields';
|
|
4
|
+
/**
|
|
5
|
+
* FieldNode - Renders a single field or section with recursive children
|
|
6
|
+
*
|
|
7
|
+
* Looks up the field component from the registry and passes field props.
|
|
8
|
+
* For sections, recursively renders visible children.
|
|
9
|
+
*/
|
|
10
|
+
export const FieldNode = React.memo(function FieldNode({ id, form, ui, depth = 1, }) {
|
|
11
|
+
// Subscribe to field data
|
|
12
|
+
const field = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().getField(id), () => form.getState().getField(id));
|
|
13
|
+
const normalized = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().normalized, () => form.getState().normalized);
|
|
14
|
+
const responses = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().responses, () => form.getState().responses);
|
|
15
|
+
// Get visible children for sections
|
|
16
|
+
const visibleChildIds = React.useMemo(() => {
|
|
17
|
+
if (!field || field.definition.fieldType !== 'section')
|
|
18
|
+
return [];
|
|
19
|
+
const node = normalized.byId[id];
|
|
20
|
+
if (!node || node.childIds.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const cache = new Map();
|
|
23
|
+
const isFieldRenderable = (fieldId) => {
|
|
24
|
+
const cached = cache.get(fieldId);
|
|
25
|
+
if (cached !== undefined)
|
|
26
|
+
return cached;
|
|
27
|
+
const isVisible = form.getState().isVisible(fieldId);
|
|
28
|
+
if (!isVisible) {
|
|
29
|
+
cache.set(fieldId, false);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const childNode = normalized.byId[fieldId];
|
|
33
|
+
if (!childNode) {
|
|
34
|
+
cache.set(fieldId, false);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (childNode.definition.fieldType !== 'section') {
|
|
38
|
+
cache.set(fieldId, true);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const hasRenderableChild = childNode.childIds.some((cid) => isFieldRenderable(cid));
|
|
42
|
+
cache.set(fieldId, hasRenderableChild);
|
|
43
|
+
return hasRenderableChild;
|
|
44
|
+
};
|
|
45
|
+
return node.childIds.filter((childId) => isFieldRenderable(childId));
|
|
46
|
+
}, [field, id, form, normalized, responses]);
|
|
47
|
+
// Render nested children for sections
|
|
48
|
+
const nestedChildren = React.useMemo(() => {
|
|
49
|
+
if (!field ||
|
|
50
|
+
field.definition.fieldType !== 'section' ||
|
|
51
|
+
visibleChildIds.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const containerClass = depth === 1
|
|
55
|
+
? 'section-children ms:space-y-2'
|
|
56
|
+
: 'section-children ms:space-y-2 ms:border-l ms:border-msborder ms:pl-3';
|
|
57
|
+
return (_jsx("div", { className: containerClass, "data-depth": depth, children: visibleChildIds.map((childId) => (_jsx(FieldNode, { id: childId, form: form, ui: ui, depth: depth + 1 }, childId))) }));
|
|
58
|
+
}, [field, visibleChildIds, id, form, ui, depth]);
|
|
59
|
+
if (!field)
|
|
60
|
+
return null;
|
|
61
|
+
const Component = getFieldComponent(field.definition.fieldType);
|
|
62
|
+
// Check if field is enabled/required via conditional logic
|
|
63
|
+
const isVisible = form.getState().isVisible(field.definition.id);
|
|
64
|
+
const isEnabled = form.getState().isEnabled(field.definition.id);
|
|
65
|
+
const isRequired = form.getState().isRequired(field.definition.id);
|
|
66
|
+
const response = form.getState().getResponse(field.definition.id);
|
|
67
|
+
if (!isVisible)
|
|
68
|
+
return null;
|
|
69
|
+
const props = {
|
|
70
|
+
field,
|
|
71
|
+
form,
|
|
72
|
+
ui,
|
|
73
|
+
isSelected: false,
|
|
74
|
+
isPreview: true,
|
|
75
|
+
isEnabled,
|
|
76
|
+
isRequired,
|
|
77
|
+
response,
|
|
78
|
+
onRemove: () => undefined, // No-op in renderer
|
|
79
|
+
onUpdate: () => undefined, // No-op in renderer
|
|
80
|
+
onResponse: (value) => form.getState().setResponse(field.definition.id, value),
|
|
81
|
+
};
|
|
82
|
+
const isSection = field.definition.fieldType === 'section';
|
|
83
|
+
const parentNode = field.parentId
|
|
84
|
+
? form.getState().getField(field.parentId)
|
|
85
|
+
: null;
|
|
86
|
+
const isChildOfSection = parentNode?.definition.fieldType === 'section';
|
|
87
|
+
const wrapperClass = `field-wrapper ms:bg-mssurface${isSection ? ' ms:mb-2 ms:border ms:border-msborder ms:rounded' : ''}${!isSection && !isChildOfSection
|
|
88
|
+
? ' ms:mb-2 ms:p-6 ms:border ms:border-msborder ms:rounded'
|
|
89
|
+
: ''}${isChildOfSection
|
|
90
|
+
? ' ms:p-6 ms:border-b ms:border-msborder ms:last:border-b-0'
|
|
91
|
+
: ''}${!isEnabled ? ' ms:opacity-50 ms:pointer-events-none' : ''}`;
|
|
92
|
+
return (_jsx("div", { className: wrapperClass, "data-field-type": field.definition.fieldType, "data-field-id": field.definition.id, "aria-disabled": !isEnabled || undefined, children: field.definition.fieldType === 'section' ? ((() => {
|
|
93
|
+
const SectionComponent = Component;
|
|
94
|
+
return (_jsx(SectionComponent, { ...props, nestedChildren: nestedChildren }));
|
|
95
|
+
})()) : (_jsx(Component, { ...props })) }));
|
|
96
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FormStore, UIStore } from '@esheet/core';
|
|
2
|
+
export interface RendererBodyProps {
|
|
3
|
+
form: FormStore;
|
|
4
|
+
ui: UIStore;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* RendererBody - Iterates over visible root fields and renders them
|
|
8
|
+
*
|
|
9
|
+
* Respects conditional visibility logic from form store.
|
|
10
|
+
* Only renders fields where isVisible() returns true.
|
|
11
|
+
* Sections recursively render their visible children.
|
|
12
|
+
*/
|
|
13
|
+
export declare function RendererBody({ form, ui }: RendererBodyProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=RendererBody.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RendererBody.d.ts","sourceRoot":"","sources":["../../../src/lib/components/RendererBody.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,iBAAiB,2CAyD3D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FieldNode } from './FieldNode.js';
|
|
4
|
+
/**
|
|
5
|
+
* RendererBody - Iterates over visible root fields and renders them
|
|
6
|
+
*
|
|
7
|
+
* Respects conditional visibility logic from form store.
|
|
8
|
+
* Only renders fields where isVisible() returns true.
|
|
9
|
+
* Sections recursively render their visible children.
|
|
10
|
+
*/
|
|
11
|
+
export function RendererBody({ form, ui }) {
|
|
12
|
+
// Subscribe to form state for visibility updates and responses
|
|
13
|
+
const normalized = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().normalized, () => form.getState().normalized);
|
|
14
|
+
const responses = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().responses, () => form.getState().responses);
|
|
15
|
+
// Compute visible root fields
|
|
16
|
+
const visibleRootIds = React.useMemo(() => {
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
const isFieldRenderable = (fieldId) => {
|
|
19
|
+
const cached = cache.get(fieldId);
|
|
20
|
+
if (cached !== undefined)
|
|
21
|
+
return cached;
|
|
22
|
+
const isVisible = form.getState().isVisible(fieldId);
|
|
23
|
+
if (!isVisible) {
|
|
24
|
+
cache.set(fieldId, false);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const node = normalized.byId[fieldId];
|
|
28
|
+
if (!node) {
|
|
29
|
+
cache.set(fieldId, false);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Non-section fields are renderable if visible
|
|
33
|
+
if (node.definition.fieldType !== 'section') {
|
|
34
|
+
cache.set(fieldId, true);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Sections are renderable only if they have at least one renderable child
|
|
38
|
+
const hasRenderableChild = node.childIds.some((childId) => isFieldRenderable(childId));
|
|
39
|
+
cache.set(fieldId, hasRenderableChild);
|
|
40
|
+
return hasRenderableChild;
|
|
41
|
+
};
|
|
42
|
+
return normalized.rootIds.filter((id) => isFieldRenderable(id));
|
|
43
|
+
}, [form, normalized, responses]);
|
|
44
|
+
return (_jsx("div", { className: "canvas-fields renderer-body ms:space-y-0", children: visibleRootIds.map((id) => (_jsx(FieldNode, { id: id, form: form, ui: ui }, id))) }));
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useRendererInit } from './useRendererInit.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type FormResponse, type FormStore, type UIStore } from '@esheet/core';
|
|
2
|
+
/**
|
|
3
|
+
* Initialize renderer with form definition.
|
|
4
|
+
*
|
|
5
|
+
* Auto-detects and converts the following input formats:
|
|
6
|
+
* - eSheet FormDefinition (object or JSON/YAML string)
|
|
7
|
+
* - FHIR R4 Questionnaire resource
|
|
8
|
+
* - MCP elicitation/create envelope
|
|
9
|
+
* - SurveyJS schema (has top-level `pages` or `elements` array)
|
|
10
|
+
*/
|
|
11
|
+
export declare function useRendererInit(form: FormStore, ui: UIStore, formData: unknown, initialResponses?: FormResponse, onValidationError?: (errors: string[]) => void, strict?: boolean, onReady?: () => void): void;
|
|
12
|
+
//# sourceMappingURL=useRendererInit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRendererInit.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/useRendererInit.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAYtB;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,OAAO,EACjB,gBAAgB,CAAC,EAAE,YAAY,EAC/B,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,EAC9C,MAAM,UAAQ,EACd,OAAO,CAAC,EAAE,MAAM,IAAI,GACnB,IAAI,CAqFN"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import YAML from 'js-yaml';
|
|
3
|
+
import { formatZodValidationError, formDefinitionSchema, } from '@esheet/core';
|
|
4
|
+
import { importFromMcp, convertSurveyJS, isSurveyJSSchema, isMcpElicitationRequest, importFromFhir, isFhirQuestionnaire, } from '@esheet/adapters';
|
|
5
|
+
/**
|
|
6
|
+
* Initialize renderer with form definition.
|
|
7
|
+
*
|
|
8
|
+
* Auto-detects and converts the following input formats:
|
|
9
|
+
* - eSheet FormDefinition (object or JSON/YAML string)
|
|
10
|
+
* - FHIR R4 Questionnaire resource
|
|
11
|
+
* - MCP elicitation/create envelope
|
|
12
|
+
* - SurveyJS schema (has top-level `pages` or `elements` array)
|
|
13
|
+
*/
|
|
14
|
+
export function useRendererInit(form, ui, formData, initialResponses, onValidationError, strict = false, onReady) {
|
|
15
|
+
// Store onReady in a ref so it never causes the effect to re-run
|
|
16
|
+
const onReadyRef = React.useRef(onReady);
|
|
17
|
+
onReadyRef.current = onReady;
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
try {
|
|
20
|
+
// Parse input if string
|
|
21
|
+
let parsed;
|
|
22
|
+
if (typeof formData === 'string') {
|
|
23
|
+
const trimmed = formData.trim();
|
|
24
|
+
// Detect format: YAML if starts with non-brace, JSON otherwise
|
|
25
|
+
const isYaml = !trimmed.startsWith('{') && !trimmed.startsWith('[');
|
|
26
|
+
parsed = isYaml ? YAML.load(trimmed) : JSON.parse(trimmed);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
parsed = formData;
|
|
30
|
+
}
|
|
31
|
+
// Auto-detect and convert FHIR, MCP or SurveyJS input (skipped in strict mode)
|
|
32
|
+
if (!strict && isFhirQuestionnaire(parsed)) {
|
|
33
|
+
parsed = importFromFhir(parsed);
|
|
34
|
+
}
|
|
35
|
+
else if (!strict && isMcpElicitationRequest(parsed)) {
|
|
36
|
+
const mcpReq = parsed;
|
|
37
|
+
if (mcpReq.params.mode !== 'url') {
|
|
38
|
+
parsed = importFromMcp(mcpReq.params.requestedSchema, {
|
|
39
|
+
mcpId: mcpReq.id,
|
|
40
|
+
mcpMessage: mcpReq.params.message,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (!strict && isSurveyJSSchema(parsed)) {
|
|
45
|
+
parsed = convertSurveyJS(parsed);
|
|
46
|
+
}
|
|
47
|
+
// Validate schema
|
|
48
|
+
const validated = formDefinitionSchema.safeParse(parsed);
|
|
49
|
+
if (!validated.success) {
|
|
50
|
+
const errors = validated.error.issues.map(formatZodValidationError);
|
|
51
|
+
console.error('[EsheetRenderer] Invalid form definition:', validated.error.issues);
|
|
52
|
+
onValidationError?.(errors);
|
|
53
|
+
// Load empty form instead of crashing
|
|
54
|
+
form.getState().loadDefinition({
|
|
55
|
+
id: 'invalid-form',
|
|
56
|
+
title: 'Invalid Form',
|
|
57
|
+
fields: [],
|
|
58
|
+
});
|
|
59
|
+
ui.getState().setMode('preview');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Clear any previous validation errors on success
|
|
63
|
+
onValidationError?.([]);
|
|
64
|
+
// Load validated definition
|
|
65
|
+
form.getState().loadDefinition(validated.data);
|
|
66
|
+
// Apply initial responses if provided
|
|
67
|
+
if (initialResponses && Object.keys(initialResponses).length > 0) {
|
|
68
|
+
for (const [fieldId, value] of Object.entries(initialResponses)) {
|
|
69
|
+
form.getState().setResponse(fieldId, value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Set preview mode
|
|
73
|
+
ui.getState().setMode('preview');
|
|
74
|
+
onReadyRef.current?.();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('[EsheetRenderer] Failed to initialize:', error);
|
|
78
|
+
// Load empty form as fallback
|
|
79
|
+
form.getState().loadDefinition({
|
|
80
|
+
id: 'error-form',
|
|
81
|
+
title: 'Error',
|
|
82
|
+
fields: [],
|
|
83
|
+
});
|
|
84
|
+
ui.getState().setMode('preview');
|
|
85
|
+
}
|
|
86
|
+
}, [form, ui, formData, initialResponses, onValidationError, strict]);
|
|
87
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { executeToolCall } from './tool-executor.js';
|
|
2
|
+
export { RENDERER_TOOL_DEFINITIONS } from './tool-definitions.js';
|
|
3
|
+
export { RENDERER_SYSTEM_PROMPT } from './system-prompt.js';
|
|
4
|
+
export type { ToolDefinition } from './tool-definitions.js';
|
|
5
|
+
export { useRendererMcpToolHandler } from './useRendererToolBridge.js';
|
|
6
|
+
export type { UseRendererMcpToolHandlerOptions } from './useRendererToolBridge.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/system-prompt.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,QAwByC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const RENDERER_SYSTEM_PROMPT = 'Form renderer assistant. You can read the current form state, inspect field values, fill in responses, and clear answers.\n\n' +
|
|
2
|
+
'FILL WORKFLOW:\n' +
|
|
3
|
+
'1. Call get_form — returns {totalFields, filledCount, unfilledFields, filledFieldIds}.\n' +
|
|
4
|
+
'2. Fill every field in unfilledFields using fill_field.\n' +
|
|
5
|
+
'3. After each fill, check unfilledFields in the response for remaining empty fields. Fill them immediately.\n' +
|
|
6
|
+
'4. You are done when unfilledFields is empty.\n' +
|
|
7
|
+
'NEVER skip a field because you are unsure of its value — generate a realistic mock value for every field including dates, times, and appointment times.\n' +
|
|
8
|
+
'NEVER plan all fills upfront — conditional logic reveals new fields dynamically and you must respond to unfilledFields after each fill.\n\n' +
|
|
9
|
+
'TEXT FIELD FORMAT RULES — each text field includes a valueFormat property; use it exactly:\n' +
|
|
10
|
+
' valueFormat "YYYY-MM-DD" → date, e.g. "2026-05-21"\n' +
|
|
11
|
+
' valueFormat "YYYY-MM-DDTHH:mm" → datetime-local, e.g. "2026-05-21T14:30"\n' +
|
|
12
|
+
' valueFormat "YYYY-MM" → month, e.g. "2026-05"\n' +
|
|
13
|
+
' valueFormat "HH:mm" → time (24-hour), e.g. "14:30"\n' +
|
|
14
|
+
'Wrong formats are rejected with an error — always use the valueFormat from the field schema.\n\n' +
|
|
15
|
+
'OTHER FIELD RULES:\n' +
|
|
16
|
+
' radio/dropdown/boolean/rating/slider → single string matching an option value\n' +
|
|
17
|
+
' check/multiselectdropdown → array of strings matching option values\n' +
|
|
18
|
+
' ranking → ordered array of option value strings\n' +
|
|
19
|
+
' multitext → array of strings, one per option slot\n' +
|
|
20
|
+
' singlematrix → { "Row Label": "Column Label" }\n' +
|
|
21
|
+
' multimatrix → { "Row Label": ["Col1", "Col2"] }\n\n' +
|
|
22
|
+
'Use get_form_raw to see ALL fields. Use get_form_tree for visibility/enabled/required state. ' +
|
|
23
|
+
'Use get_valid_response before submitting. Use clear_responses to reset. ' +
|
|
24
|
+
'For conversational questions reply in plain text without calling a tool.';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ToolDefinition {
|
|
2
|
+
type: string;
|
|
3
|
+
function: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare const RENDERER_TOOL_DEFINITIONS: readonly ToolDefinition[];
|
|
10
|
+
//# sourceMappingURL=tool-definitions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-definitions.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/tool-definitions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC;CACH;AAED,eAAO,MAAM,yBAAyB,EAAE,SAAS,cAAc,EAsE9D,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const RENDERER_TOOL_DEFINITIONS = [
|
|
2
|
+
{
|
|
3
|
+
type: 'function',
|
|
4
|
+
function: {
|
|
5
|
+
name: 'get_form',
|
|
6
|
+
description: 'Returns visible fields split into filled vs unfilled. Response: {formId, totalFields, filledCount, unfilledFields (full details), filledFieldIds (just IDs)}. Use unfilledFields to see what needs filling.\n\nFILL LOOP PATTERN:\n1. Call get_form — fill every field in unfilledFields using fill_field.\n2. Call get_form again. If new fields appeared in unfilledFields (revealed by conditional logic), fill those too.\n3. Repeat until unfilledFields is empty.\n\nNever assume you are done after a single pass — conditional logic frequently reveals new fields.',
|
|
7
|
+
parameters: { type: 'object', properties: {} },
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
type: 'function',
|
|
12
|
+
function: {
|
|
13
|
+
name: 'get_form_raw',
|
|
14
|
+
description: 'Returns ALL fields in the form regardless of visibility, conditional rules, or enabled state. Use this to see the full form schema.',
|
|
15
|
+
parameters: { type: 'object', properties: {} },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: 'function',
|
|
20
|
+
function: {
|
|
21
|
+
name: 'get_responses',
|
|
22
|
+
description: 'Get the current raw response values for all fields.',
|
|
23
|
+
parameters: { type: 'object', properties: {} },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'function',
|
|
28
|
+
function: {
|
|
29
|
+
name: 'get_valid_response',
|
|
30
|
+
description: 'Run form validation. Returns the full response object if all required fields pass, or null plus field-level errors if validation fails. Call this before submitting.',
|
|
31
|
+
parameters: { type: 'object', properties: {} },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'function',
|
|
36
|
+
function: {
|
|
37
|
+
name: 'fill_field',
|
|
38
|
+
description: 'Set a response value for ONE visible field. Returns { result, filledCount, unfilledFields } where unfilledFields contains ONLY the fields still needing values (with full details including options/valueFormat). You are done when unfilledFields is empty.\n\nField type rules:\n- radio/dropdown/boolean/rating/slider: pass a single string matching one of the options values\n- check/multiselectdropdown: pass an array of strings matching option values\n- ranking: pass an ordered array of option value strings representing the desired rank order\n- multitext: pass an array of strings, one per option slot in order\n- singlematrix: pass an object { "Row Label": "Column Label" } for each row\n- multimatrix: pass an object { "Row Label": ["Col1", "Col2"] } for each row\n- text: use the valueFormat from the field\'s schema (e.g. YYYY-MM-DD for date, YYYY-MM-DDTHH:mm for datetime-local, YYYY-MM for month, HH:mm for time). Wrong formats are rejected with an error.',
|
|
39
|
+
parameters: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
fieldId: { type: 'string' },
|
|
43
|
+
fieldQuestion: { type: 'string' },
|
|
44
|
+
value: {},
|
|
45
|
+
},
|
|
46
|
+
required: ['value'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'function',
|
|
52
|
+
function: {
|
|
53
|
+
name: 'clear_responses',
|
|
54
|
+
description: 'Clear all current form responses.',
|
|
55
|
+
parameters: { type: 'object', properties: {} },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'function',
|
|
60
|
+
function: {
|
|
61
|
+
name: 'get_form_tree',
|
|
62
|
+
description: 'Get the full rendered field tree including visibility, enabled, and required state for each field given current responses.',
|
|
63
|
+
parameters: { type: 'object', properties: {} },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RendererTools } from '../renderer-tools.js';
|
|
2
|
+
type ToolArgs = Record<string, unknown>;
|
|
3
|
+
export declare function executeToolCall(toolName: string, args: ToolArgs, tools: RendererTools): string | Record<string, unknown>;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=tool-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-executor.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/tool-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,aAAa,GACnB,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoBlC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function executeToolCall(toolName, args, tools) {
|
|
2
|
+
switch (toolName) {
|
|
3
|
+
case 'get_form':
|
|
4
|
+
return getFormOptimized(tools);
|
|
5
|
+
case 'get_form_raw':
|
|
6
|
+
return tools.getFormRaw();
|
|
7
|
+
case 'get_responses':
|
|
8
|
+
return tools.getResponses();
|
|
9
|
+
case 'get_valid_response':
|
|
10
|
+
return tools.getValidResponse();
|
|
11
|
+
case 'fill_field':
|
|
12
|
+
return fillField(args, tools);
|
|
13
|
+
case 'clear_responses':
|
|
14
|
+
tools.clearResponses();
|
|
15
|
+
return 'Responses cleared';
|
|
16
|
+
case 'get_form_tree':
|
|
17
|
+
return { fields: tools.getFormTree() };
|
|
18
|
+
default:
|
|
19
|
+
return `Unknown tool: ${toolName}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Optimized get_form: returns full details only for unfilled fields to reduce token usage. */
|
|
23
|
+
function getFormOptimized(tools) {
|
|
24
|
+
const form = tools.getForm();
|
|
25
|
+
const filledFields = form.fields.filter((f) => f.hasValue);
|
|
26
|
+
const unfilledFields = form.fields.filter((f) => !f.hasValue);
|
|
27
|
+
return {
|
|
28
|
+
formId: form.formId,
|
|
29
|
+
totalFields: form.fieldCount,
|
|
30
|
+
filledCount: filledFields.length,
|
|
31
|
+
// Only unfilled fields need full details (options, questions, etc.)
|
|
32
|
+
unfilledFields,
|
|
33
|
+
// Filled fields: just IDs for reference
|
|
34
|
+
filledFieldIds: filledFields.map((f) => f.id),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function fillField(args, tools) {
|
|
38
|
+
const fieldId = tools.resolveFieldId(args['fieldId'], args['fieldQuestion']);
|
|
39
|
+
if (!fieldId)
|
|
40
|
+
return 'Error: field not found — provide fieldId or fieldQuestion';
|
|
41
|
+
const result = tools.fillField(fieldId, args['value']);
|
|
42
|
+
if (typeof result === 'string')
|
|
43
|
+
return result; // format validation error
|
|
44
|
+
if (!result)
|
|
45
|
+
return `Error: field "${fieldId}" is not currently visible — call get_form to see the current visible fields`;
|
|
46
|
+
const form = tools.getForm();
|
|
47
|
+
// Return only unfilled fields with full details (token optimization)
|
|
48
|
+
const filledFields = form.fields.filter((f) => f.hasValue);
|
|
49
|
+
const unfilledFields = form.fields.filter((f) => !f.hasValue);
|
|
50
|
+
return {
|
|
51
|
+
result: `Field "${fieldId}" updated`,
|
|
52
|
+
filledCount: filledFields.length,
|
|
53
|
+
unfilledFields,
|
|
54
|
+
};
|
|
55
|
+
}
|