@acorex-platform/extension-ai-formbuilder-client 0.0.37 → 0.0.46

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.
@@ -1,162 +1,57 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, NgModule } from '@angular/core';
3
- import { AXMPlatformManagementModule } from '@acorex-platform/module-platform-management-client';
2
+ import { inject, DestroyRef, Injectable, NgModule } from '@angular/core';
4
3
  import { AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER } from '@acorex-platform/framework-client/common';
5
- import { AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER } from '@acorex-platform/framework-client/core';
4
+ import { AXPPageContextRegistry, AXPPageContextPriority, AXP_HOOK_PROVIDER } from '@acorex-platform/framework-client/core';
6
5
  import { provideCommandSetups } from '@acorex-platform/framework-client/runtime';
6
+ import { AXPLayoutDesignerFooterHookKeys, AXPLayoutDesignerHeaderMenuHookKeys } from '@acorex-platform/framework-client/layout/designer';
7
+ import { AXToastService } from '@acorex/components/toast';
7
8
  import { AXTranslationService } from '@acorex/core/translation';
8
- import { AXMMiniChatPanelService } from '@acorex-platform/module-conversation-client';
9
- import { AXPLayoutDesignerHeaderMenuHookKeys } from '@acorex-platform/framework-client/layout/designer';
9
+ import { AXMContextualAssistLauncherService } from '@acorex-platform/extension-ai-conversation-client';
10
+ import { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
11
+ import { coerceUnknownToMultiLanguageString } from '@acorex-platform/framework-shared/core';
12
+ import { axmReadAssistResponseStatus, axmIsAssistStreamingStatus, axmAssistWidgetNodeFromUnknown, AXMConversationRealtimeBus } from '@acorex-platform/module-conversation-client';
13
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14
+ import { merge } from 'rxjs';
15
+ import { extractMessageTextFromPayload, axmSanitizeContextualAssistValue } from '@acorex-platform/module-conversation-client/contracts';
10
16
  import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS } from '@acorex-platform/framework-shared/runtime';
11
17
 
12
- //#region ---- Form designer entity chat ----
13
- /** Entity type for form-template designer mini chats (`FormTemplateManagement.Template`). */
14
- const AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE = 'FormTemplateManagement.Template';
15
- /** Fallback entity id when the designer has no persisted template id yet. */
18
+ //#region ----- Form designer contextual assist -----
19
+ /** Entity type for form-template designer assist (`AssessmentManagement.Template`). */
20
+ const AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE = 'AssessmentManagement.Template';
21
+ /** Fallback record id when the designer has no persisted template id yet. */
16
22
  const AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID = 'draft';
17
- //#endregion
18
-
19
- //#region ---- Imports ----
20
- //#endregion
21
- //#region ---- Service ----
22
- /**
23
- * Form-template designer AI assist entry point: opens the entity-scoped mini chat panel
24
- * bound to the current template (`FormTemplateManagement.Template` + template id).
25
- *
26
- * Layout generation from chat responses is handled separately in the conversation layer.
27
- */
28
- class AXEAiFormbuilderDesignerAiAssistService {
29
- constructor() {
30
- //#region ---- Services & Dependencies ----
31
- this.miniChatPanel = inject(AXMMiniChatPanelService);
32
- this.translationService = inject(AXTranslationService);
33
- }
34
- //#endregion
35
- //#region ---- Public API ----
36
- /**
37
- * Opens (or toggles) the mini chat panel for the designer's current template entity.
38
- */
39
- async runFromDesigner(designer) {
40
- const svc = designer;
41
- const entityId = svc.templateId()?.trim() || AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID;
42
- const title = await this.translationService.translateAsync('@form-template-management:designerAi.dialog-title');
43
- this.miniChatPanel.toggle({
44
- entityId,
45
- entityType: AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE,
46
- title,
47
- showInfoBar: false,
48
- showComposer: true,
49
- });
50
- }
51
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
52
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService }); }
53
- }
54
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService, decorators: [{
55
- type: Injectable
56
- }] });
57
-
58
- //#region ---- Formbuilder designer header root menu ----
59
- /** Registers the layout designer header root-menu entry for AI assist (wand icon). */
60
- const AXEAiFormbuilderDesignerRootMenuHookProvider = {
61
- key: AXPLayoutDesignerHeaderMenuHookKeys.root,
62
- priority: 40,
63
- execute: (payload) => {
64
- const assist = inject(AXEAiFormbuilderDesignerAiAssistService);
65
- payload.items.push({
66
- id: 'extensions-ai-formbuilder-layout-assist',
67
- priority: 20,
68
- labelKey: 'AI Assist',
69
- iconClass: 'fa-solid fa-wand-magic-sparkles',
70
- run: () => {
71
- void assist.runFromDesigner(payload.designer);
72
- },
73
- });
74
- return payload;
75
- },
76
- };
77
- //#endregion
78
-
79
- //#region ---- Imports ----
80
- //#endregion
81
- //#region ---- Formbuilder node segment preset ----
82
- const FORM_BUILDER_NODE_SEGMENT = {
83
- name: 'node',
84
- title: '@ai-management:agents.entities.agent.fields.output-contract.transcript-segment.options.node',
85
- applicableKinds: ['object'],
86
- contract: {
87
- kind: 'object',
88
- transcriptSegment: 'node',
89
- validationCommandName: 'AiFormbuilder:ValidateLayoutJsonForAi',
90
- structuredRetries: 3,
91
- schemaSummary: 'Chat UI: block-layout or table-layout root with mode edit/view. Form Template designer: document-layout → page-layout → fieldset/flex/grid. No raw CSS keys.',
92
- },
93
- };
23
+ /** Launch kind stored on the assist room (generic contextual-assist bag). */
24
+ const AXE_AI_FORMBUILDER_ASSIST_KIND = 'form-template-designer';
25
+ /** Action name passed into {@link AXMContextualAssistLauncherService}. */
26
+ const AXE_AI_FORMBUILDER_ASSIST_ACTION = 'form.template.designer.assist';
94
27
  /**
95
- * Enriches the built-in `node` transcript segment with formbuilder validation and schema hints.
28
+ * Launch-context scenario for Form Template designer rooms
29
+ * (hide chat widget trees; page-context canvas edits).
96
30
  */
97
- class AXPAiFormbuilderOutputContractTranscriptSegmentProvider {
98
- provide() {
99
- return [FORM_BUILDER_NODE_SEGMENT];
100
- }
101
- }
102
- //#endregion
103
-
104
- //#region ---- Imports ----
31
+ const AXE_AI_FORMBUILDER_ASSIST_SCENARIO = 'form-template-designer';
32
+ const AXE_AI_FORMBUILDER_VALIDATE_LAYOUT_COMMAND = 'AiFormbuilder:ValidateLayoutJsonForAi';
33
+ const AXE_AI_FORMBUILDER_APPLY_LAYOUT_COMMAND = 'AiFormbuilder:ApplyLayoutJsonForAi';
34
+ const AXE_AI_FORMBUILDER_OUTLINE_LAYOUT_COMMAND = 'AiFormbuilder:OutlineLayoutForAi';
35
+ const AXE_AI_FORMBUILDER_ADD_WIDGET_COMMAND = 'AiFormbuilder:AddWidgetForAi';
36
+ const AXE_AI_FORMBUILDER_UPDATE_WIDGET_COMMAND = 'AiFormbuilder:UpdateWidgetForAi';
37
+ const AXE_AI_FORMBUILDER_REMOVE_WIDGET_COMMAND = 'AiFormbuilder:RemoveWidgetForAi';
38
+ const AXE_AI_FORMBUILDER_MOVE_WIDGET_COMMAND = 'AiFormbuilder:MoveWidgetForAi';
39
+ /** Page-context registration id while the form designer is the focused page. */
40
+ const AXE_AI_FORMBUILDER_PAGE_CONTEXT_ID = 'layout-designer';
41
+ /** Advertised Page:RunAction id that writes the open designer canvas. */
42
+ const AXE_AI_FORMBUILDER_PAGE_ACTION_APPLY_LAYOUT = 'apply-layout';
43
+ const AXE_AI_FORMBUILDER_PAGE_ACTION_ADD_WIDGET = 'add-widget';
44
+ const AXE_AI_FORMBUILDER_PAGE_ACTION_UPDATE_WIDGET = 'update-widget';
45
+ const AXE_AI_FORMBUILDER_PAGE_ACTION_REMOVE_WIDGET = 'remove-widget';
46
+ const AXE_AI_FORMBUILDER_PAGE_ACTION_MOVE_WIDGET = 'move-widget';
47
+ /** Seeded Form Designer assist (`AiManagement.Assist.name` = form-designer-assistant). Must match `AXMAiFormDesignerAssistId`. */
48
+ const AXE_AI_FORMBUILDER_DESIGNER_ASSIST_ID = 'b0000001-0000-4000-8000-000000000004';
105
49
  //#endregion
106
- //#region ---- Module ----
107
- class AXEAIFormBuilderExtensionModule {
108
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
109
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, imports: [AXMPlatformManagementModule] }); }
110
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, providers: [
111
- AXEAiFormbuilderDesignerAiAssistService,
112
- provideCommandSetups([
113
- {
114
- key: 'AiFormbuilder:ValidateLayoutJsonForAi',
115
- command: () => Promise.resolve().then(function () { return validateLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderValidateLayoutJsonForAiCommand),
116
- },
117
- ]),
118
- {
119
- provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
120
- multi: true,
121
- useValue: AXEAiFormbuilderDesignerRootMenuHookProvider,
122
- },
123
- {
124
- provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
125
- useClass: AXPAiFormbuilderOutputContractTranscriptSegmentProvider,
126
- multi: true,
127
- },
128
- ], imports: [AXMPlatformManagementModule] }); }
129
- }
130
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, decorators: [{
131
- type: NgModule,
132
- args: [{
133
- imports: [AXMPlatformManagementModule],
134
- providers: [
135
- AXEAiFormbuilderDesignerAiAssistService,
136
- provideCommandSetups([
137
- {
138
- key: 'AiFormbuilder:ValidateLayoutJsonForAi',
139
- command: () => Promise.resolve().then(function () { return validateLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderValidateLayoutJsonForAiCommand),
140
- },
141
- ]),
142
- {
143
- provide: AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER,
144
- multi: true,
145
- useValue: AXEAiFormbuilderDesignerRootMenuHookProvider,
146
- },
147
- {
148
- provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
149
- useClass: AXPAiFormbuilderOutputContractTranscriptSegmentProvider,
150
- multi: true,
151
- },
152
- ],
153
- }]
154
- }] });
155
50
 
51
+ //#region ----- Imports -----
156
52
  //#endregion
157
- //#region ---- Validation helpers ----
53
+ //#region ----- Constants -----
158
54
  const MAX_ERRORS = 48;
159
- /** Keys that must not appear on widget nodes — styling belongs in widget options, not DOM/CSS. */
160
55
  const FORBIDDEN_LAYOUT_KEYS = new Set([
161
56
  'style',
162
57
  'styles',
@@ -167,7 +62,64 @@ const FORBIDDEN_LAYOUT_KEYS = new Set([
167
62
  'innerHTML',
168
63
  'dataset',
169
64
  ]);
170
- function stripJsonFences(raw) {
65
+ /** Roots the form-template designer can load (File → Download / Import JSON). */
66
+ const DESIGNER_CANVAS_ROOT_TYPES = new Set(['document-layout', 'page-layout']);
67
+ //#endregion
68
+ //#region ----- Parse & validate -----
69
+ /**
70
+ * AI tool-arg normalization may pass `json` as a string or an already-parsed object.
71
+ * Also accepts `layoutJson` from older registry rows.
72
+ */
73
+ function axeResolveLayoutJsonInput(input) {
74
+ return resolveLayoutJsonString(input?.json) || resolveLayoutJsonString(input?.layoutJson);
75
+ }
76
+ function axeParseAndValidateLayoutJson(json) {
77
+ const raw = resolveLayoutJsonString(json);
78
+ if (!raw) {
79
+ return { valid: false, errors: ['Missing json string.'] };
80
+ }
81
+ const stripped = stripJsonFences$1(raw);
82
+ let parsed;
83
+ try {
84
+ parsed = JSON.parse(stripped);
85
+ }
86
+ catch (e) {
87
+ const msg = e instanceof Error ? e.message : 'Invalid JSON';
88
+ return { valid: false, errors: [`JSON parse error: ${msg}`] };
89
+ }
90
+ const errors = [];
91
+ validateWidgetNodeShape(parsed, '$', errors);
92
+ if (errors.length > 0) {
93
+ return { valid: false, errors };
94
+ }
95
+ return {
96
+ valid: true,
97
+ errors: [],
98
+ normalizedJson: JSON.stringify(parsed),
99
+ };
100
+ }
101
+ function axeIsDesignerCanvasRoot(value) {
102
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
103
+ return false;
104
+ }
105
+ const type = value['type'];
106
+ return typeof type === 'string' && DESIGNER_CANVAS_ROOT_TYPES.has(type.trim());
107
+ }
108
+ function resolveLayoutJsonString(json) {
109
+ if (typeof json === 'string') {
110
+ return json.trim();
111
+ }
112
+ if (json !== null && json !== undefined && typeof json === 'object') {
113
+ try {
114
+ return JSON.stringify(json);
115
+ }
116
+ catch {
117
+ return '';
118
+ }
119
+ }
120
+ return '';
121
+ }
122
+ function stripJsonFences$1(raw) {
171
123
  const t = raw.trim();
172
124
  const fence = /^```(?:json)?\s*\r?\n?([\s\S]*?)\r?\n?```\s*$/i.exec(t);
173
125
  return fence ? fence[1].trim() : t;
@@ -213,105 +165,1933 @@ function validateWidgetNodeShape(value, jsonPath, errors) {
213
165
  }
214
166
  }
215
167
  //#endregion
216
- //#region ---- Command input resolution ----
168
+
169
+ //#region ----- Imports -----
170
+ //#endregion
171
+ //#region ----- Constants -----
172
+ const DESIGNER_ROOT_TYPE_MARKER = /"type"\s*:\s*"(document-layout|page-layout)"/i;
173
+ //#endregion
174
+ //#region ----- Message extract -----
175
+ /** True when this assist reply is finished and may be applied to the canvas. */
176
+ function axeAssistMessageReadyToApply(message) {
177
+ const status = axmReadAssistResponseStatus(message.metadata);
178
+ if (axmIsAssistStreamingStatus(status) || status === 'failed') {
179
+ return false;
180
+ }
181
+ const meta = message.metadata;
182
+ if (meta && meta['isAssistResponse'] === false) {
183
+ return false;
184
+ }
185
+ return true;
186
+ }
217
187
  /**
218
- * AI chat normalizes tool args by parsing JSON-like strings into objects. For this command the
219
- * `json` field may therefore arrive as a string **or** as an already-parsed widget root object.
188
+ * Designer JSON from a completed assist message.
189
+ * Accepts a node payload or JSON pasted inside a text reply (the usual specialist failure mode).
220
190
  */
221
- function resolveValidateLayoutJsonString(json) {
222
- if (typeof json === 'string') {
223
- return json.trim();
191
+ function axeDesignerLayoutJsonFromAssistMessage(message) {
192
+ const payload = message.payload;
193
+ const candidates = [payload];
194
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
195
+ const row = payload;
196
+ if ('content' in row) {
197
+ candidates.push(row['content']);
198
+ }
224
199
  }
225
- if (json !== null && json !== undefined && typeof json === 'object') {
226
- try {
227
- return JSON.stringify(json);
200
+ for (const text of collectMessageTexts(message)) {
201
+ const extracted = axeExtractDesignerLayoutFromText(text);
202
+ if (extracted !== undefined) {
203
+ candidates.push(extracted);
228
204
  }
229
- catch {
230
- return '';
205
+ }
206
+ for (const candidate of candidates) {
207
+ const node = axmAssistWidgetNodeFromUnknown(candidate);
208
+ if (node && axeIsDesignerCanvasRoot(node)) {
209
+ return JSON.stringify(node);
231
210
  }
232
211
  }
233
- return '';
212
+ return null;
234
213
  }
235
- class AXEAiFormbuilderValidateLayoutJsonForAiCommand {
236
- async execute(input) {
237
- const raw = resolveValidateLayoutJsonString(input?.json);
238
- if (!raw) {
239
- return {
240
- success: true,
241
- kind: 'object',
242
- data: { valid: false, errors: ['Missing json string.'] },
243
- message: { text: 'Missing json string.' },
244
- };
214
+ /** Pulls a document-layout / page-layout object out of mixed prose + JSON. */
215
+ function axeExtractDesignerLayoutFromText(raw) {
216
+ const text = stripJsonFences(raw);
217
+ if (!text) {
218
+ return undefined;
219
+ }
220
+ try {
221
+ const parsed = JSON.parse(text);
222
+ const node = axmAssistWidgetNodeFromUnknown(parsed);
223
+ if (node && axeIsDesignerCanvasRoot(node)) {
224
+ return node;
245
225
  }
246
- const stripped = stripJsonFences(raw);
247
- let parsed;
248
- try {
249
- parsed = JSON.parse(stripped);
250
- }
251
- catch (e) {
252
- const msg = e instanceof Error ? e.message : 'Invalid JSON';
253
- return { success: true, kind: 'object',
254
- data: {
255
- valid: false,
256
- errors: [`JSON parse error: ${msg}`],
257
- },
258
- };
226
+ }
227
+ catch {
228
+ // Mixed chat text — scan for an embedded designer root.
229
+ }
230
+ return extractEmbeddedDesignerObject(text);
231
+ }
232
+ function collectMessageTexts(message) {
233
+ const texts = [];
234
+ const fromPayload = extractMessageTextFromPayload(message.payload);
235
+ if (fromPayload.trim()) {
236
+ texts.push(fromPayload);
237
+ }
238
+ const rawPayload = message.payload;
239
+ if (typeof rawPayload === 'string' && rawPayload.trim()) {
240
+ texts.push(rawPayload);
241
+ }
242
+ const meta = message.metadata;
243
+ if (typeof meta?.['text'] === 'string' && meta['text'].trim()) {
244
+ texts.push(meta['text']);
245
+ }
246
+ return texts;
247
+ }
248
+ function stripJsonFences(raw) {
249
+ const t = raw.trim();
250
+ const fence = /^```(?:json)?\s*\r?\n?([\s\S]*?)\r?\n?```\s*$/i.exec(t);
251
+ return fence ? fence[1].trim() : t;
252
+ }
253
+ function extractEmbeddedDesignerObject(text) {
254
+ let searchFrom = 0;
255
+ while (searchFrom < text.length) {
256
+ const slice = text.slice(searchFrom);
257
+ const match = DESIGNER_ROOT_TYPE_MARKER.exec(slice);
258
+ if (!match || match.index < 0) {
259
+ break;
259
260
  }
260
- const errors = [];
261
- validateWidgetNodeShape(parsed, '$', errors);
262
- const valid = errors.length === 0;
263
- if (!valid) {
264
- return { success: true, kind: 'object',
265
- data: { valid: false, errors },
266
- };
261
+ const abs = searchFrom + match.index;
262
+ const start = text.lastIndexOf('{', abs);
263
+ if (start < 0) {
264
+ searchFrom = abs + match[0].length;
265
+ continue;
267
266
  }
268
- const normalizedJson = JSON.stringify(parsed);
269
- return { success: true, kind: 'object',
270
- data: {
271
- valid: true,
272
- errors: [],
273
- normalizedJson,
274
- },
275
- };
267
+ const json = sliceBalancedObject(text, start);
268
+ if (json) {
269
+ try {
270
+ const parsed = JSON.parse(json);
271
+ const node = axmAssistWidgetNodeFromUnknown(parsed);
272
+ if (node && axeIsDesignerCanvasRoot(node)) {
273
+ return node;
274
+ }
275
+ }
276
+ catch {
277
+ // Try the next type marker.
278
+ }
279
+ }
280
+ searchFrom = abs + match[0].length;
276
281
  }
277
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
278
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, providedIn: 'root' }); }
282
+ return undefined;
279
283
  }
280
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, decorators: [{
281
- type: Injectable,
282
- args: [{ providedIn: 'root' }]
283
- }] });
284
-
285
- var validateLayoutJsonForAi_command = /*#__PURE__*/Object.freeze({
286
- __proto__: null,
287
- AXEAiFormbuilderValidateLayoutJsonForAiCommand: AXEAiFormbuilderValidateLayoutJsonForAiCommand
288
- });
284
+ function sliceBalancedObject(text, start) {
285
+ if (text[start] !== '{') {
286
+ return null;
287
+ }
288
+ let depth = 0;
289
+ let inString = false;
290
+ let escape = false;
291
+ for (let i = start; i < text.length; i++) {
292
+ const ch = text[i];
293
+ if (inString) {
294
+ if (escape) {
295
+ escape = false;
296
+ }
297
+ else if (ch === '\\') {
298
+ escape = true;
299
+ }
300
+ else if (ch === '"') {
301
+ inString = false;
302
+ }
303
+ continue;
304
+ }
305
+ if (ch === '"') {
306
+ inString = true;
307
+ continue;
308
+ }
309
+ if (ch === '{') {
310
+ depth += 1;
311
+ }
312
+ else if (ch === '}') {
313
+ depth -= 1;
314
+ if (depth === 0) {
315
+ return text.slice(start, i + 1);
316
+ }
317
+ }
318
+ }
319
+ return null;
320
+ }
321
+ //#endregion
289
322
 
323
+ //#region ----- Imports -----
324
+ //#endregion
325
+ //#region ----- Constants -----
326
+ const TEMPLATE_CONTEXT_FIELDS = ['code', 'title', 'description', 'type', 'categories'];
327
+ //#endregion
328
+ //#region ----- Payload -----
290
329
  /**
291
- * Build-time only. Do not import from Angular modules.
292
- * Source: validate-layout-json-for-ai.command.ts — AXEAiFormbuilderValidateLayoutJsonForAiCommand.execute
330
+ * Snapshot of the live designer chrome for contextual assist.
331
+ * Full layout JSON is not shipped — the specialist outlines and patches the canvas.
293
332
  */
294
- const axeAiFormbuilderValidateLayoutJsonForAiCommandDefinition = defineCommand({
295
- key: 'AiFormbuilder:ValidateLayoutJsonForAi',
296
- title: 'Validate widget layout JSON for AI (form template designer)',
297
- description: 'Parses a JSON string and checks it is a single root AXPWidgetNode (object with string type, optional children/options). Rejects raw CSS/DOM-style keys. Returns validation errors and compact normalized JSON when valid — call before the layout specialist emits its final JSON-only reply.',
298
- executionMode: 'frontend',
299
- inputs: [
300
- {
301
- name: 'json',
302
- title: 'Layout JSON string',
303
- description: 'Stringified widget tree: one root object with "type" (registry name). Optional markdown/json fences are stripped.',
304
- interface: { name: 'large-text-editor' },
305
- validations: [{ rule: 'required' }],
306
- },
307
- ],
308
- outcomes: {
309
- mode: 'static',
310
- values: ['success', 'failure'],
311
- },
312
- ai: {
313
- shortDescription: 'Validate specialist JSON is parseable and shaped like AXPWidgetNode before returning it to chat.',
314
- tags: ['ai-formbuilder', 'layout', 'ai', 'validation'],
333
+ function axeBuildFormbuilderDesignerAssistPayload(designer, args) {
334
+ const [moduleName = 'AssessmentManagement', entityName = 'Template'] = AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE.split('.');
335
+ const document = designer.document();
336
+ const currentPage = designer.currentPage();
337
+ const selected = designer.selectedNode();
338
+ const profile = designer.surfaceProfile();
339
+ const pages = Array.isArray(document?.children) ? document.children : [];
340
+ const payload = {
341
+ moduleName,
342
+ entityName,
343
+ entityTitle: args.entityTitle,
344
+ recordId: args.recordId,
345
+ columns: args.columns,
346
+ scenario: AXE_AI_FORMBUILDER_ASSIST_SCENARIO,
347
+ entityType: AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE,
348
+ surface: 'layout-designer',
349
+ unsavedChanges: designer.unsavedChanges(),
350
+ pageCount: pages.length,
351
+ currentPageIndex: designer.currentPageIndex(),
352
+ currentPage: widgetRef(currentPage, true),
353
+ selectedWidget: selected ? widgetRef(selected, false) : null,
354
+ dataSources: summarizeDataSources(designer),
355
+ widgetCounts: countWidgetTypes(document),
356
+ ...(profile.id ? { surfaceId: profile.id } : {}),
357
+ ...(profile.documentKind ? { documentKind: profile.documentKind } : {}),
358
+ ...(profile.rootWidget ? { rootWidget: profile.rootWidget } : {}),
359
+ };
360
+ return payload;
361
+ }
362
+ /** Template record fields for assist (skips the persisted `template` JSON blob). */
363
+ function axeBuildFormTemplateRecordAssistColumns(row, resolveText) {
364
+ const columns = [];
365
+ for (const name of TEMPLATE_CONTEXT_FIELDS) {
366
+ if (!(name in row) || row[name] == null) {
367
+ continue;
368
+ }
369
+ const raw = row[name];
370
+ const text = resolveText(raw);
371
+ columns.push({
372
+ name,
373
+ title: name,
374
+ value: axmSanitizeContextualAssistValue(text || raw),
375
+ });
376
+ }
377
+ return columns;
378
+ }
379
+ //#endregion
380
+ //#region ----- Designer snapshot -----
381
+ function widgetRef(node, includeChildCount) {
382
+ const type = typeof node?.type === 'string' ? node.type.trim() : '';
383
+ const name = typeof node?.name === 'string' ? node.name.trim() : '';
384
+ const path = typeof node?.path === 'string' ? node.path.trim() : '';
385
+ const childCount = Array.isArray(node?.children) ? node.children.length : 0;
386
+ return {
387
+ ...(type ? { type } : {}),
388
+ ...(name ? { name } : {}),
389
+ ...(path ? { path } : {}),
390
+ ...(includeChildCount ? { childCount } : {}),
391
+ };
392
+ }
393
+ function summarizeDataSources(designer) {
394
+ return (designer.sources() ?? []).slice(0, 12).map((source) => ({
395
+ id: source.id,
396
+ name: source.name,
397
+ itemCount: Array.isArray(source.items) ? source.items.length : 0,
398
+ columnNames: (source.columns ?? []).map((column) => column.name).filter(Boolean).slice(0, 12),
399
+ }));
400
+ }
401
+ function countWidgetTypes(node) {
402
+ const counts = {};
403
+ walkWidgetTypes(node, counts);
404
+ return counts;
405
+ }
406
+ function walkWidgetTypes(node, counts) {
407
+ if (!node || typeof node !== 'object' || Array.isArray(node)) {
408
+ return;
409
+ }
410
+ const row = node;
411
+ const type = typeof row['type'] === 'string' ? row['type'].trim() : '';
412
+ if (type) {
413
+ counts[type] = (counts[type] ?? 0) + 1;
414
+ }
415
+ const children = row['children'];
416
+ if (Array.isArray(children)) {
417
+ for (const child of children) {
418
+ walkWidgetTypes(child, counts);
419
+ }
420
+ }
421
+ }
422
+ //#endregion
423
+
424
+ //#region ----- Imports -----
425
+ //#endregion
426
+ //#region ----- Outline -----
427
+ function axeBuildDesignerOutline(root) {
428
+ const items = [];
429
+ walkOutline(root, null, [], items);
430
+ return items;
431
+ }
432
+ function axeFindDesignerWidget(root, ref) {
433
+ const name = typeof ref.name === 'string' ? ref.name.trim() : '';
434
+ const path = typeof ref.path === 'string' ? ref.path.trim() : '';
435
+ if (!name && !path) {
436
+ return null;
437
+ }
438
+ let named = null;
439
+ let byPath = null;
440
+ walkFind(root, null, [], (hit) => {
441
+ if (name && hit.node.name?.trim() === name && !named) {
442
+ named = hit;
443
+ }
444
+ if (path && hit.path === path) {
445
+ byPath = hit;
446
+ }
447
+ });
448
+ return named ?? byPath;
449
+ }
450
+ function axeResolveWidgetNodeInput(raw) {
451
+ const value = unwrapJson(raw);
452
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
453
+ return null;
454
+ }
455
+ const type = value['type'];
456
+ if (typeof type !== 'string' || !type.trim()) {
457
+ return null;
458
+ }
459
+ return value;
460
+ }
461
+ function axeReadOptionalIndex(raw) {
462
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
463
+ return Math.trunc(raw);
464
+ }
465
+ if (typeof raw === 'string' && raw.trim()) {
466
+ const parsed = Number(raw);
467
+ if (Number.isFinite(parsed)) {
468
+ return Math.trunc(parsed);
469
+ }
470
+ }
471
+ return undefined;
472
+ }
473
+ function axeReadStringArg(raw) {
474
+ return typeof raw === 'string' ? raw.trim() : '';
475
+ }
476
+ /** `set: false` when the caller omitted the arg. Empty array clears triggers. */
477
+ function axeReadOptionalTriggers(raw) {
478
+ if (raw === undefined) {
479
+ return { set: false };
480
+ }
481
+ const value = unwrapJson(raw);
482
+ if (value == null) {
483
+ return { set: true, value: [] };
484
+ }
485
+ if (!Array.isArray(value)) {
486
+ return { set: false };
487
+ }
488
+ const triggers = [];
489
+ for (const item of value) {
490
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
491
+ continue;
492
+ }
493
+ const row = item;
494
+ const event = typeof row['event'] === 'string' ? row['event'].trim() : '';
495
+ const actionRaw = row['action'];
496
+ const action = Array.isArray(actionRaw)
497
+ ? actionRaw.filter((part) => typeof part === 'string' && !!part.trim())
498
+ : typeof actionRaw === 'string'
499
+ ? actionRaw.trim()
500
+ : '';
501
+ if (!event && (Array.isArray(action) ? action.length === 0 : !action)) {
502
+ continue;
503
+ }
504
+ triggers.push({
505
+ event,
506
+ action,
507
+ ...(row['disabled'] === true ? { disabled: true } : {}),
508
+ });
509
+ }
510
+ return { set: true, value: triggers };
511
+ }
512
+ function axeReadOptionalVisible(raw) {
513
+ if (raw === undefined) {
514
+ return { set: false };
515
+ }
516
+ if (typeof raw === 'boolean') {
517
+ return { set: true, value: raw };
518
+ }
519
+ if (typeof raw === 'string') {
520
+ return { set: true, value: raw.trim() };
521
+ }
522
+ return { set: false };
523
+ }
524
+ //#endregion
525
+ //#region ----- Walk -----
526
+ function pathSegment(node, siblingIndex) {
527
+ const name = typeof node.name === 'string' ? node.name.trim() : '';
528
+ if (name) {
529
+ return name;
530
+ }
531
+ const type = typeof node.type === 'string' ? node.type.trim() : 'widget';
532
+ return `${type}[${siblingIndex}]`;
533
+ }
534
+ function walkOutline(value, parentName, pathParts, items, siblingIndex = 0) {
535
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
536
+ return;
537
+ }
538
+ const node = value;
539
+ const type = typeof node.type === 'string' ? node.type.trim() : '';
540
+ if (!type) {
541
+ return;
542
+ }
543
+ const name = typeof node.name === 'string' ? node.name.trim() : '';
544
+ const segment = pathSegment(node, siblingIndex);
545
+ const path = [...pathParts, segment].join('/');
546
+ const label = readLabel(node);
547
+ items.push({
548
+ type,
549
+ path,
550
+ name,
551
+ ...(parentName ? { parentName } : {}),
552
+ ...(label ? { label } : {}),
553
+ });
554
+ const children = node.children;
555
+ if (!Array.isArray(children)) {
556
+ return;
557
+ }
558
+ children.forEach((child, index) => {
559
+ walkOutline(child, name || null, [...pathParts, segment], items, index);
560
+ });
561
+ }
562
+ function walkFind(value, parent, pathParts, visit, siblingIndex = 0) {
563
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
564
+ return;
565
+ }
566
+ const node = value;
567
+ const type = typeof node.type === 'string' ? node.type.trim() : '';
568
+ if (!type) {
569
+ return;
570
+ }
571
+ const segment = pathSegment(node, siblingIndex);
572
+ const path = [...pathParts, segment].join('/');
573
+ visit({ node, parent, path });
574
+ const children = node.children;
575
+ if (!Array.isArray(children)) {
576
+ return;
577
+ }
578
+ children.forEach((child, index) => {
579
+ walkFind(child, node, [...pathParts, segment], visit, index);
580
+ });
581
+ }
582
+ function readLabel(node) {
583
+ const options = node.options;
584
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
585
+ return undefined;
586
+ }
587
+ const label = options['label'];
588
+ if (typeof label === 'string' && label.trim()) {
589
+ return label.trim();
590
+ }
591
+ const title = options['title'];
592
+ if (typeof title === 'string' && title.trim()) {
593
+ return title.trim();
594
+ }
595
+ return undefined;
596
+ }
597
+ function unwrapJson(raw) {
598
+ if (typeof raw !== 'string') {
599
+ return raw;
600
+ }
601
+ const trimmed = raw.trim();
602
+ if (!trimmed) {
603
+ return null;
604
+ }
605
+ try {
606
+ return JSON.parse(trimmed);
607
+ }
608
+ catch {
609
+ return null;
610
+ }
611
+ }
612
+ //#endregion
613
+
614
+ //#region ----- Imports -----
615
+ //#endregion
616
+ //#region ----- Actions -----
617
+ const AXE_AI_FORMBUILDER_PAGE_CONTEXT_ACTIONS = [
618
+ {
619
+ id: AXE_AI_FORMBUILDER_PAGE_ACTION_ADD_WIDGET,
620
+ title: 'Add one widget under a named parent on the live canvas',
621
+ params: [
622
+ {
623
+ name: 'parentName',
624
+ required: false,
625
+ description: 'Parent widget name from snapshot.outline. Prefer this over parentPath.',
626
+ },
627
+ {
628
+ name: 'parentPath',
629
+ required: false,
630
+ description: 'Parent path from snapshot.outline when the parent has no name.',
631
+ },
632
+ {
633
+ name: 'widget',
634
+ required: true,
635
+ description: 'One AXPWidgetNode (type plus name/options/children). Put triggers and visible on this node root when the field calculates or shows/hides. Not the full document-layout.',
636
+ },
637
+ {
638
+ name: 'index',
639
+ required: false,
640
+ description: 'Optional child index under the parent. Omit to append.',
641
+ },
642
+ ],
643
+ },
644
+ {
645
+ id: AXE_AI_FORMBUILDER_PAGE_ACTION_UPDATE_WIDGET,
646
+ title: 'Patch options, validations, triggers, or visible on one canvas widget',
647
+ params: [
648
+ { name: 'name', required: false, description: 'Widget name from snapshot.outline.' },
649
+ { name: 'path', required: false, description: 'Widget path from snapshot.outline when it has no name.' },
650
+ { name: 'options', required: false, description: 'Partial options object merged onto the widget.' },
651
+ { name: 'validations', required: false, description: 'Replaces options.validations (e.g. [{ "rule": "required" }]).' },
652
+ {
653
+ name: 'triggers',
654
+ required: false,
655
+ description: 'Replaces node.triggers (not options.triggers). Array of { event, action } expression strings. Empty array clears. See write-platform-expression.',
656
+ },
657
+ {
658
+ name: 'visible',
659
+ required: false,
660
+ description: 'Replaces node.visible. Boolean or "{{ context.eval(...) }}" expression. See write-platform-expression.',
661
+ },
662
+ ],
663
+ },
664
+ {
665
+ id: AXE_AI_FORMBUILDER_PAGE_ACTION_REMOVE_WIDGET,
666
+ title: 'Remove one widget from the live canvas',
667
+ params: [
668
+ { name: 'name', required: false, description: 'Widget name from snapshot.outline.' },
669
+ { name: 'path', required: false, description: 'Widget path from snapshot.outline when it has no name.' },
670
+ ],
671
+ },
672
+ {
673
+ id: AXE_AI_FORMBUILDER_PAGE_ACTION_MOVE_WIDGET,
674
+ title: 'Move one widget to another parent or index',
675
+ params: [
676
+ { name: 'name', required: false, description: 'Widget name from snapshot.outline.' },
677
+ { name: 'path', required: false, description: 'Widget path from snapshot.outline when it has no name.' },
678
+ { name: 'parentName', required: false, description: 'Destination parent name. Omit to reorder under the current parent.' },
679
+ { name: 'parentPath', required: false, description: 'Destination parent path when the parent has no name.' },
680
+ { name: 'index', required: false, description: 'Child index under the destination parent.' },
681
+ ],
682
+ },
683
+ {
684
+ id: AXE_AI_FORMBUILDER_PAGE_ACTION_APPLY_LAYOUT,
685
+ title: 'Replace the whole canvas with a complete designer tree',
686
+ params: [
687
+ {
688
+ name: 'json',
689
+ required: true,
690
+ description: 'Complete document-layout or page-layout. Use only for a brand-new form or a large restructure.',
691
+ },
692
+ {
693
+ name: 'replaceAll',
694
+ required: false,
695
+ description: 'Set true only when the user asked for a brand-new form. Otherwise a much smaller tree is rejected.',
696
+ },
697
+ ],
698
+ },
699
+ ];
700
+ //#endregion
701
+ //#region ----- Snapshot -----
702
+ function axeBuildFormbuilderDesignerPageContextSnapshot(args) {
703
+ const title = args.title.trim() || 'Layout designer';
704
+ return {
705
+ kind: 'layout-designer',
706
+ title,
707
+ status: 'ready',
708
+ entity: {
709
+ fullName: AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE,
710
+ title,
711
+ },
712
+ ...(args.outline?.length ? { outline: args.outline } : {}),
713
+ actions: AXE_AI_FORMBUILDER_PAGE_CONTEXT_ACTIONS,
714
+ };
715
+ }
716
+ function axeResolvePageContextLayoutJson(args) {
717
+ return axeResolveLayoutJsonInput(args);
718
+ }
719
+ function axeReadReplaceAllFlag(args) {
720
+ const raw = args['replaceAll'];
721
+ return raw === true || raw === 'true';
722
+ }
723
+ /**
724
+ * Blocks apply-layout from wiping a rich canvas with a flattened/partial tree
725
+ * (the usual supervisor failure: rewrite the form as a handful of text-editors).
726
+ */
727
+ function axeDesignerApplyLooksLikeWipe(current, incoming) {
728
+ const currentCount = countWidgetNodes(current);
729
+ const incomingCount = countWidgetNodes(incoming);
730
+ if (currentCount >= 12 && incomingCount < Math.max(8, Math.floor(currentCount * 0.5))) {
731
+ return `apply-layout refused: the new tree has ${incomingCount} widgets but the canvas has ${currentCount}. Use add-widget / update-widget / remove-widget for field edits. Use replaceAll=true only for a brand-new form.`;
732
+ }
733
+ if (layoutHasType(current, 'fieldset-layout') && !layoutHasType(incoming, 'fieldset-layout')) {
734
+ return 'apply-layout refused: the canvas uses fieldset-layout sections. Do not flatten the form. Use add-widget / update-widget / remove-widget for field edits.';
735
+ }
736
+ if (layoutHasType(current, 'form-field') && !layoutHasType(incoming, 'form-field')) {
737
+ return 'apply-layout refused: labeled form-field wrappers were dropped. Keep the existing section/field structure and only change what the user asked.';
738
+ }
739
+ return null;
740
+ }
741
+ function countWidgetNodes(value) {
742
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
743
+ return 0;
744
+ }
745
+ const row = value;
746
+ let count = typeof row['type'] === 'string' && row['type'].trim() ? 1 : 0;
747
+ const children = row['children'];
748
+ if (Array.isArray(children)) {
749
+ for (const child of children) {
750
+ count += countWidgetNodes(child);
751
+ }
752
+ }
753
+ return count;
754
+ }
755
+ function layoutHasType(value, type) {
756
+ if (!value || typeof value !== 'object') {
757
+ return false;
758
+ }
759
+ if (Array.isArray(value)) {
760
+ return value.some((item) => layoutHasType(item, type));
761
+ }
762
+ const row = value;
763
+ if (typeof row['type'] === 'string' && row['type'].trim() === type) {
764
+ return true;
765
+ }
766
+ return layoutHasType(row['children'], type);
767
+ }
768
+ //#endregion
769
+
770
+ //#region ----- Imports -----
771
+ //#endregion
772
+ //#region ----- Service -----
773
+ /**
774
+ * Form-template designer AI assist: opens the dedicated form-designer-assistant
775
+ * and advertises designer API callbacks on page context (Page:GetContext / Page:RunAction).
776
+ */
777
+ class AXEAiFormbuilderDesignerAiAssistService {
778
+ //#endregion
779
+ //#region ----- Lifecycle -----
780
+ constructor() {
781
+ //#region ----- Services & Dependencies -----
782
+ this.launcher = inject(AXMContextualAssistLauncherService, { optional: true });
783
+ this.translationService = inject(AXTranslationService);
784
+ this.toastService = inject(AXToastService);
785
+ this.entityService = inject(AXPEntityService, { optional: true });
786
+ this.pageContext = inject(AXPPageContextRegistry);
787
+ this.realtimeBus = inject(AXMConversationRealtimeBus, { optional: true });
788
+ this.destroyRef = inject(DestroyRef);
789
+ //#endregion
790
+ //#region ----- State Signals -----
791
+ this.designer = null;
792
+ this.lastAppliedJson = '';
793
+ this.pageTitle = '';
794
+ const bus = this.realtimeBus;
795
+ if (!bus) {
796
+ return;
797
+ }
798
+ merge(bus.messageStream$, bus.messageUpdates$)
799
+ .pipe(takeUntilDestroyed(this.destroyRef))
800
+ .subscribe((message) => {
801
+ void this.tryApplyFromAssistMessage(message);
802
+ });
803
+ }
804
+ //#endregion
805
+ //#region ----- Public API -----
806
+ /**
807
+ * Binds the live designer and advertises add/update/remove/move/apply-layout on page context.
808
+ * Pass the header {@link DestroyRef} so the registration clears when the designer unmounts.
809
+ */
810
+ bindDesigner(designer, owner) {
811
+ this.designer = designer;
812
+ this.designer.ensureWidgetTreeMeta?.();
813
+ void this.translationService
814
+ .translateAsync('@form-template-management:designerAi.dialog-title')
815
+ .then((title) => {
816
+ this.pageTitle = title;
817
+ });
818
+ if (!owner) {
819
+ this.pageContext.activate(AXE_AI_FORMBUILDER_PAGE_CONTEXT_ID);
820
+ return;
821
+ }
822
+ this.pageContext.register({
823
+ owner,
824
+ id: AXE_AI_FORMBUILDER_PAGE_CONTEXT_ID,
825
+ priority: AXPPageContextPriority.Page,
826
+ getSnapshot: () => this.buildPageSnapshot(),
827
+ run: (actionId, args) => this.runPageAction(actionId, args),
828
+ });
829
+ this.pageContext.activate(AXE_AI_FORMBUILDER_PAGE_CONTEXT_ID);
830
+ }
831
+ /**
832
+ * Toggles contextual assist for the designer's current template (close if open, otherwise open).
833
+ */
834
+ async toggleFromDesigner(designer) {
835
+ const launcher = this.launcher;
836
+ if (!launcher) {
837
+ return this.warnConversationUnavailable();
838
+ }
839
+ if (launcher.isOpen()) {
840
+ launcher.close();
841
+ return true;
842
+ }
843
+ return this.runFromDesigner(designer);
844
+ }
845
+ /**
846
+ * Opens contextual assist for the designer's current template and binds the canvas for apply.
847
+ */
848
+ async runFromDesigner(designer) {
849
+ const launcher = this.launcher;
850
+ if (!launcher) {
851
+ return this.warnConversationUnavailable();
852
+ }
853
+ const svc = designer;
854
+ this.bindDesigner(svc);
855
+ const recordId = svc.templateId()?.trim() || AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID;
856
+ const entityTitle = await this.translationService.translateAsync('@form-template-management:designerAi.dialog-title');
857
+ this.pageTitle = entityTitle;
858
+ const columns = await this.loadTemplateColumns(recordId);
859
+ const payload = axeBuildFormbuilderDesignerAssistPayload(svc, {
860
+ entityTitle,
861
+ recordId,
862
+ columns,
863
+ });
864
+ await launcher.open({
865
+ kind: AXE_AI_FORMBUILDER_ASSIST_KIND,
866
+ actionName: AXE_AI_FORMBUILDER_ASSIST_ACTION,
867
+ title: entityTitle,
868
+ payload: { ...payload },
869
+ }, { assistId: AXE_AI_FORMBUILDER_DESIGNER_ASSIST_ID, forceNewChat: false });
870
+ return true;
871
+ }
872
+ /**
873
+ * Validates designer JSON and loads it onto the bound canvas (does not save).
874
+ */
875
+ async applyLayoutJson(json, options) {
876
+ const designer = this.designer;
877
+ if (!designer) {
878
+ return {
879
+ applied: false,
880
+ errors: ['The layout designer is not bound. Open the form designer page so page context can apply the canvas.'],
881
+ };
882
+ }
883
+ const parsed = axeParseAndValidateLayoutJson(json);
884
+ if (!parsed.valid || !parsed.normalizedJson) {
885
+ return { applied: false, errors: parsed.errors };
886
+ }
887
+ let root;
888
+ try {
889
+ root = JSON.parse(parsed.normalizedJson);
890
+ }
891
+ catch {
892
+ return { applied: false, errors: parsed.errors };
893
+ }
894
+ if (!axeIsDesignerCanvasRoot(root)) {
895
+ return {
896
+ applied: false,
897
+ errors: [
898
+ 'Designer updates need a document-layout or page-layout root. Chat block-layout trees are not applied to the canvas.',
899
+ ],
900
+ };
901
+ }
902
+ if (!options?.replaceAll) {
903
+ const wipe = this.detectApplyWipe(root);
904
+ if (wipe) {
905
+ return { applied: false, errors: [wipe] };
906
+ }
907
+ }
908
+ if (this.lastAppliedJson === parsed.normalizedJson) {
909
+ return { applied: true, errors: [] };
910
+ }
911
+ try {
912
+ await designer.loadByContent(parsed.normalizedJson);
913
+ designer.unsavedChanges.set(true);
914
+ this.lastAppliedJson = parsed.normalizedJson;
915
+ const title = await this.translationService.translateAsync('@form-template-management:designerAi.loading.complete');
916
+ const content = await this.translationService.translateAsync('@form-template-management:designerAi.applied-hint');
917
+ await this.toastService.show({ color: 'success', title, content });
918
+ return { applied: true, errors: [] };
919
+ }
920
+ catch (error) {
921
+ const message = error instanceof Error ? error.message : 'The layout could not be applied to the designer canvas.';
922
+ return { applied: false, errors: [message] };
923
+ }
924
+ }
925
+ outline() {
926
+ const designer = this.designer;
927
+ if (!designer) {
928
+ return { items: [], errors: [unboundDesignerError()] };
929
+ }
930
+ return { items: axeBuildDesignerOutline(designer.document()), errors: [] };
931
+ }
932
+ addWidget(input) {
933
+ const designer = this.designer;
934
+ if (!designer) {
935
+ return { applied: false, errors: [unboundDesignerError()] };
936
+ }
937
+ const widget = axeResolveWidgetNodeInput(input.widget);
938
+ if (!widget) {
939
+ return { applied: false, errors: ['widget must be an AXPWidgetNode object with a string type.'] };
940
+ }
941
+ const parentHit = this.resolveWidgetRef(input.parentName, input.parentPath);
942
+ if (!parentHit) {
943
+ return { applied: false, errors: [missingWidgetRefError$1('parent', input.parentName, input.parentPath)] };
944
+ }
945
+ try {
946
+ const added = designer.addWidget(parentHit.node, widget);
947
+ designer.ensureWidgetTreeMeta?.();
948
+ const index = axeReadOptionalIndex(input.index);
949
+ if (index != null) {
950
+ moveChildToIndex(parentHit.node, added, index);
951
+ designer.refresh({ widget: parentHit.node });
952
+ }
953
+ designer.unsavedChanges.set(true);
954
+ const outline = axeBuildDesignerOutline(designer.document());
955
+ const addedName = typeof added.name === 'string' ? added.name.trim() : '';
956
+ const addedHit = addedName ? outline.find((item) => item.name === addedName) : undefined;
957
+ return {
958
+ applied: true,
959
+ errors: [],
960
+ ...(addedName ? { name: addedName } : {}),
961
+ ...(addedHit?.path ? { path: addedHit.path } : {}),
962
+ };
963
+ }
964
+ catch (error) {
965
+ return { applied: false, errors: [error instanceof Error ? error.message : 'The widget could not be added.'] };
966
+ }
967
+ }
968
+ updateWidget(input) {
969
+ const designer = this.designer;
970
+ if (!designer) {
971
+ return { applied: false, errors: [unboundDesignerError()] };
972
+ }
973
+ const hit = this.resolveWidgetRef(input.name, input.path);
974
+ if (!hit) {
975
+ return { applied: false, errors: [missingWidgetRefError$1('widget', input.name, input.path)] };
976
+ }
977
+ const options = {};
978
+ if (input.options != null && typeof input.options === 'object' && !Array.isArray(input.options)) {
979
+ Object.assign(options, input.options);
980
+ }
981
+ if (input.validations != null) {
982
+ options['validations'] = input.validations;
983
+ }
984
+ const triggers = axeReadOptionalTriggers(input.triggers);
985
+ const visible = axeReadOptionalVisible(input.visible);
986
+ const patch = {};
987
+ if (Object.keys(options).length > 0) {
988
+ patch['options'] = options;
989
+ }
990
+ if (triggers.set) {
991
+ patch['triggers'] = triggers.value ?? [];
992
+ }
993
+ if (visible.set) {
994
+ patch['visible'] = visible.value;
995
+ }
996
+ if (Object.keys(patch).length === 0) {
997
+ return {
998
+ applied: false,
999
+ errors: ['Provide options, validations, triggers, and/or visible to update.'],
1000
+ };
1001
+ }
1002
+ try {
1003
+ designer.update({ widget: hit.node, values: patch, mode: 'update' });
1004
+ designer.unsavedChanges.set(true);
1005
+ return { applied: true, errors: [] };
1006
+ }
1007
+ catch (error) {
1008
+ return { applied: false, errors: [error instanceof Error ? error.message : 'The widget could not be updated.'] };
1009
+ }
1010
+ }
1011
+ removeWidget(input) {
1012
+ const designer = this.designer;
1013
+ if (!designer) {
1014
+ return { applied: false, errors: [unboundDesignerError()] };
1015
+ }
1016
+ const hit = this.resolveWidgetRef(input.name, input.path);
1017
+ if (!hit) {
1018
+ return { applied: false, errors: [missingWidgetRefError$1('widget', input.name, input.path)] };
1019
+ }
1020
+ try {
1021
+ designer.removeWidget(hit.node);
1022
+ designer.unsavedChanges.set(true);
1023
+ return { applied: true, errors: [] };
1024
+ }
1025
+ catch (error) {
1026
+ return { applied: false, errors: [error instanceof Error ? error.message : 'The widget could not be removed.'] };
1027
+ }
1028
+ }
1029
+ moveWidget(input) {
1030
+ const designer = this.designer;
1031
+ if (!designer) {
1032
+ return { applied: false, errors: [unboundDesignerError()] };
1033
+ }
1034
+ const hit = this.resolveWidgetRef(input.name, input.path);
1035
+ if (!hit?.parent) {
1036
+ return { applied: false, errors: [missingWidgetRefError$1('widget', input.name, input.path)] };
1037
+ }
1038
+ const parentName = axeReadStringArg(input.parentName);
1039
+ const parentPath = axeReadStringArg(input.parentPath);
1040
+ const nextParentNode = parentName || parentPath
1041
+ ? this.resolveWidgetRef(input.parentName, input.parentPath)?.node
1042
+ : hit.parent;
1043
+ if (!nextParentNode) {
1044
+ return { applied: false, errors: [missingWidgetRefError$1('parent', input.parentName, input.parentPath)] };
1045
+ }
1046
+ const index = axeReadOptionalIndex(input.index);
1047
+ try {
1048
+ if (nextParentNode !== hit.parent) {
1049
+ const from = hit.parent.children;
1050
+ if (!Array.isArray(from)) {
1051
+ return { applied: false, errors: ['The current parent has no children.'] };
1052
+ }
1053
+ const at = from.indexOf(hit.node);
1054
+ if (at < 0) {
1055
+ return { applied: false, errors: ['The widget is no longer under its parent.'] };
1056
+ }
1057
+ from.splice(at, 1);
1058
+ nextParentNode.children = nextParentNode.children ?? [];
1059
+ const insertAt = index == null
1060
+ ? nextParentNode.children.length
1061
+ : Math.max(0, Math.min(index, nextParentNode.children.length));
1062
+ nextParentNode.children.splice(insertAt, 0, hit.node);
1063
+ designer.refresh({ widget: hit.parent });
1064
+ designer.refresh({ widget: nextParentNode });
1065
+ }
1066
+ else if (index != null) {
1067
+ moveChildToIndex(hit.parent, hit.node, index);
1068
+ designer.refresh({ widget: hit.parent });
1069
+ }
1070
+ else {
1071
+ return { applied: false, errors: ['Provide parentName/parentPath or index to move the widget.'] };
1072
+ }
1073
+ designer.unsavedChanges.set(true);
1074
+ return { applied: true, errors: [] };
1075
+ }
1076
+ catch (error) {
1077
+ return { applied: false, errors: [error instanceof Error ? error.message : 'The widget could not be moved.'] };
1078
+ }
1079
+ }
1080
+ //#endregion
1081
+ //#region ----- Page context -----
1082
+ buildPageSnapshot() {
1083
+ return axeBuildFormbuilderDesignerPageContextSnapshot({
1084
+ title: this.pageTitle,
1085
+ outline: this.designer ? this.outline().items : undefined,
1086
+ });
1087
+ }
1088
+ detectApplyWipe(incoming) {
1089
+ const designer = this.designer;
1090
+ if (!designer) {
1091
+ return null;
1092
+ }
1093
+ try {
1094
+ const currentRaw = designer.export();
1095
+ const current = JSON.parse(typeof currentRaw === 'string' ? currentRaw : '');
1096
+ return axeDesignerApplyLooksLikeWipe(current, incoming);
1097
+ }
1098
+ catch {
1099
+ return null;
1100
+ }
1101
+ }
1102
+ async runPageAction(actionId, args) {
1103
+ if (actionId === AXE_AI_FORMBUILDER_PAGE_ACTION_ADD_WIDGET) {
1104
+ const result = this.addWidget(args);
1105
+ return this.pageActionResult(result.applied, result.errors[0]);
1106
+ }
1107
+ if (actionId === AXE_AI_FORMBUILDER_PAGE_ACTION_UPDATE_WIDGET) {
1108
+ const result = this.updateWidget(args);
1109
+ return this.pageActionResult(result.applied, result.errors[0]);
1110
+ }
1111
+ if (actionId === AXE_AI_FORMBUILDER_PAGE_ACTION_REMOVE_WIDGET) {
1112
+ const result = this.removeWidget(args);
1113
+ return this.pageActionResult(result.applied, result.errors[0]);
1114
+ }
1115
+ if (actionId === AXE_AI_FORMBUILDER_PAGE_ACTION_MOVE_WIDGET) {
1116
+ const result = this.moveWidget(args);
1117
+ return this.pageActionResult(result.applied, result.errors[0]);
1118
+ }
1119
+ if (actionId === AXE_AI_FORMBUILDER_PAGE_ACTION_APPLY_LAYOUT) {
1120
+ const json = axeResolvePageContextLayoutJson(args);
1121
+ const result = await this.applyLayoutJson(json, { replaceAll: axeReadReplaceAllFlag(args) });
1122
+ return this.pageActionResult(result.applied, result.errors[0] ?? 'Layout was not applied.');
1123
+ }
1124
+ return {
1125
+ success: false,
1126
+ message: `Unknown page action "${actionId}".`,
1127
+ snapshot: this.buildPageSnapshot(),
1128
+ };
1129
+ }
1130
+ pageActionResult(success, error) {
1131
+ return {
1132
+ success,
1133
+ message: success ? undefined : (error ?? 'The designer action failed.'),
1134
+ snapshot: this.buildPageSnapshot(),
1135
+ };
1136
+ }
1137
+ //#endregion
1138
+ //#region ----- Auto apply -----
1139
+ async tryApplyFromAssistMessage(message) {
1140
+ if (!this.designer || !axeAssistMessageReadyToApply(message)) {
1141
+ return;
1142
+ }
1143
+ const json = axeDesignerLayoutJsonFromAssistMessage(message);
1144
+ if (!json) {
1145
+ return;
1146
+ }
1147
+ await this.applyLayoutJson(json);
1148
+ }
1149
+ //#endregion
1150
+ //#region ----- Warnings -----
1151
+ async warnConversationUnavailable() {
1152
+ const title = await this.translationService.translateAsync('@general:messages.generic.warning.title');
1153
+ const content = await this.translationService.translateAsync('@form-template-management:designerAi.no-conversation-module');
1154
+ await this.toastService.show({ color: 'warning', title, content });
1155
+ return false;
1156
+ }
1157
+ //#endregion
1158
+ //#region ----- Template record -----
1159
+ async loadTemplateColumns(recordId) {
1160
+ if (!this.entityService || recordId === AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID) {
1161
+ return [];
1162
+ }
1163
+ try {
1164
+ const row = (await this.entityService.withEntity(AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE).data().byKey(recordId));
1165
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
1166
+ return [];
1167
+ }
1168
+ return axeBuildFormTemplateRecordAssistColumns(row, (value) => this.asAssistDisplayText(value));
1169
+ }
1170
+ catch {
1171
+ return [];
1172
+ }
1173
+ }
1174
+ resolveWidgetRef(name, path) {
1175
+ const designer = this.designer;
1176
+ if (!designer) {
1177
+ return null;
1178
+ }
1179
+ return axeFindDesignerWidget(designer.document(), {
1180
+ name: axeReadStringArg(name),
1181
+ path: axeReadStringArg(path),
1182
+ });
1183
+ }
1184
+ asAssistDisplayText(value) {
1185
+ if (typeof value === 'string') {
1186
+ return value.trim();
1187
+ }
1188
+ if (typeof value === 'number' || typeof value === 'boolean') {
1189
+ return String(value);
1190
+ }
1191
+ const resolved = this.translationService.resolve(coerceUnknownToMultiLanguageString(value))?.trim();
1192
+ return resolved ?? '';
1193
+ }
1194
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1195
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService, providedIn: 'root' }); }
1196
+ }
1197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderDesignerAiAssistService, decorators: [{
1198
+ type: Injectable,
1199
+ args: [{ providedIn: 'root' }]
1200
+ }], ctorParameters: () => [] });
1201
+ function unboundDesignerError() {
1202
+ return 'The layout designer is not bound. Open the form designer page so the canvas can be edited.';
1203
+ }
1204
+ function missingWidgetRefError$1(kind, name, path) {
1205
+ const nameText = axeReadStringArg(name);
1206
+ const pathText = axeReadStringArg(path);
1207
+ if (nameText || pathText) {
1208
+ return `No ${kind} found for ${nameText ? `name "${nameText}"` : `path "${pathText}"`}. Call Page:GetContext and use a name from snapshot.outline.`;
1209
+ }
1210
+ return `Provide ${kind}Name or ${kind}Path from snapshot.outline (Page:GetContext).`;
1211
+ }
1212
+ function moveChildToIndex(parent, child, index) {
1213
+ const children = parent.children;
1214
+ if (!Array.isArray(children)) {
1215
+ return;
1216
+ }
1217
+ const at = children.indexOf(child);
1218
+ if (at < 0) {
1219
+ return;
1220
+ }
1221
+ children.splice(at, 1);
1222
+ const insertAt = Math.max(0, Math.min(index, children.length));
1223
+ children.splice(insertAt, 0, child);
1224
+ }
1225
+
1226
+ //#region ----- Formbuilder designer footer -----
1227
+ /** Registers the layout designer footer-end magic-icon button for AI assist. */
1228
+ const AXEAiFormbuilderDesignerFooterHookProvider = {
1229
+ key: AXPLayoutDesignerFooterHookKeys.end,
1230
+ priority: 40,
1231
+ execute: (payload) => {
1232
+ const assist = inject(AXEAiFormbuilderDesignerAiAssistService);
1233
+ assist.bindDesigner(payload.designer, payload.owner);
1234
+ payload.items.push({
1235
+ id: 'extensions-ai-formbuilder-layout-assist-footer',
1236
+ priority: 20,
1237
+ labelKey: '@form-template-management:designerAi.menu',
1238
+ iconClass: 'fa-solid fa-wand-magic-sparkles',
1239
+ run: () => {
1240
+ void assist.toggleFromDesigner(payload.designer);
1241
+ },
1242
+ });
1243
+ return payload;
1244
+ },
1245
+ };
1246
+ //#endregion
1247
+
1248
+ //#region ----- Formbuilder designer Help menu -----
1249
+ /** Registers the layout designer Help-menu entry for AI assist (wand icon). */
1250
+ const AXEAiFormbuilderDesignerHelpMenuHookProvider = {
1251
+ key: AXPLayoutDesignerHeaderMenuHookKeys.submenu,
1252
+ priority: 40,
1253
+ execute: (payload) => {
1254
+ if (payload.parentMenuId !== 'help') {
1255
+ return payload;
1256
+ }
1257
+ const assist = inject(AXEAiFormbuilderDesignerAiAssistService);
1258
+ assist.bindDesigner(payload.designer, payload.owner);
1259
+ payload.items.push({
1260
+ id: 'extensions-ai-formbuilder-layout-assist-help',
1261
+ priority: 20,
1262
+ labelKey: '@form-template-management:designerAi.menu',
1263
+ iconClass: 'fa-solid fa-wand-magic-sparkles',
1264
+ run: () => {
1265
+ void assist.toggleFromDesigner(payload.designer);
1266
+ },
1267
+ });
1268
+ return payload;
1269
+ },
1270
+ };
1271
+ //#endregion
1272
+
1273
+ //#region ---- Imports ----
1274
+ //#endregion
1275
+ //#region ---- Formbuilder node segment preset ----
1276
+ const FORM_BUILDER_NODE_SEGMENT = {
1277
+ name: 'node',
1278
+ title: '@ai-management:agents.entities.agent.fields.output-contract.transcript-segment.options.node',
1279
+ applicableKinds: ['object'],
1280
+ contract: {
1281
+ kind: 'object',
1282
+ transcriptSegment: 'node',
1283
+ validationCommandName: 'AiFormbuilder:ValidateLayoutJsonForAi',
1284
+ structuredRetries: 3,
1285
+ schemaSummary: 'Chat UI: block-layout or table-layout root with mode edit/view. Form Template designer: document-layout → page-layout → fieldset/flex/grid. No raw CSS keys.',
1286
+ },
1287
+ };
1288
+ /**
1289
+ * Enriches the built-in `node` transcript segment with formbuilder validation and schema hints.
1290
+ */
1291
+ class AXPAiFormbuilderOutputContractTranscriptSegmentProvider {
1292
+ provide() {
1293
+ return [FORM_BUILDER_NODE_SEGMENT];
1294
+ }
1295
+ }
1296
+ //#endregion
1297
+
1298
+ //#region ----- Imports -----
1299
+ //#endregion
1300
+ //#region ----- Module -----
1301
+ /**
1302
+ * Optional AI assist on the form-template designer (Help menu + footer magic icon).
1303
+ * Register next to {@link AXEAIConversationExtensionModule} in the application.
1304
+ */
1305
+ class AXEAIFormBuilderExtensionModule {
1306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1307
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule }); }
1308
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, providers: [
1309
+ provideCommandSetups([
1310
+ {
1311
+ key: AXE_AI_FORMBUILDER_VALIDATE_LAYOUT_COMMAND,
1312
+ command: () => Promise.resolve().then(function () { return validateLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderValidateLayoutJsonForAiCommand),
1313
+ },
1314
+ {
1315
+ key: AXE_AI_FORMBUILDER_APPLY_LAYOUT_COMMAND,
1316
+ command: () => Promise.resolve().then(function () { return applyLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderApplyLayoutJsonForAiCommand),
1317
+ },
1318
+ {
1319
+ key: AXE_AI_FORMBUILDER_OUTLINE_LAYOUT_COMMAND,
1320
+ command: () => Promise.resolve().then(function () { return outlineLayoutForAi_command; }).then((c) => c.AXEAiFormbuilderOutlineLayoutForAiCommand),
1321
+ },
1322
+ {
1323
+ key: AXE_AI_FORMBUILDER_ADD_WIDGET_COMMAND,
1324
+ command: () => Promise.resolve().then(function () { return addWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderAddWidgetForAiCommand),
1325
+ },
1326
+ {
1327
+ key: AXE_AI_FORMBUILDER_UPDATE_WIDGET_COMMAND,
1328
+ command: () => Promise.resolve().then(function () { return updateWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderUpdateWidgetForAiCommand),
1329
+ },
1330
+ {
1331
+ key: AXE_AI_FORMBUILDER_REMOVE_WIDGET_COMMAND,
1332
+ command: () => Promise.resolve().then(function () { return removeWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderRemoveWidgetForAiCommand),
1333
+ },
1334
+ {
1335
+ key: AXE_AI_FORMBUILDER_MOVE_WIDGET_COMMAND,
1336
+ command: () => Promise.resolve().then(function () { return moveWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderMoveWidgetForAiCommand),
1337
+ },
1338
+ ]),
1339
+ {
1340
+ provide: AXP_HOOK_PROVIDER,
1341
+ multi: true,
1342
+ useValue: AXEAiFormbuilderDesignerHelpMenuHookProvider,
1343
+ },
1344
+ {
1345
+ provide: AXP_HOOK_PROVIDER,
1346
+ multi: true,
1347
+ useValue: AXEAiFormbuilderDesignerFooterHookProvider,
1348
+ },
1349
+ {
1350
+ provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
1351
+ useClass: AXPAiFormbuilderOutputContractTranscriptSegmentProvider,
1352
+ multi: true,
1353
+ },
1354
+ ] }); }
1355
+ }
1356
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIFormBuilderExtensionModule, decorators: [{
1357
+ type: NgModule,
1358
+ args: [{
1359
+ providers: [
1360
+ provideCommandSetups([
1361
+ {
1362
+ key: AXE_AI_FORMBUILDER_VALIDATE_LAYOUT_COMMAND,
1363
+ command: () => Promise.resolve().then(function () { return validateLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderValidateLayoutJsonForAiCommand),
1364
+ },
1365
+ {
1366
+ key: AXE_AI_FORMBUILDER_APPLY_LAYOUT_COMMAND,
1367
+ command: () => Promise.resolve().then(function () { return applyLayoutJsonForAi_command; }).then((c) => c.AXEAiFormbuilderApplyLayoutJsonForAiCommand),
1368
+ },
1369
+ {
1370
+ key: AXE_AI_FORMBUILDER_OUTLINE_LAYOUT_COMMAND,
1371
+ command: () => Promise.resolve().then(function () { return outlineLayoutForAi_command; }).then((c) => c.AXEAiFormbuilderOutlineLayoutForAiCommand),
1372
+ },
1373
+ {
1374
+ key: AXE_AI_FORMBUILDER_ADD_WIDGET_COMMAND,
1375
+ command: () => Promise.resolve().then(function () { return addWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderAddWidgetForAiCommand),
1376
+ },
1377
+ {
1378
+ key: AXE_AI_FORMBUILDER_UPDATE_WIDGET_COMMAND,
1379
+ command: () => Promise.resolve().then(function () { return updateWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderUpdateWidgetForAiCommand),
1380
+ },
1381
+ {
1382
+ key: AXE_AI_FORMBUILDER_REMOVE_WIDGET_COMMAND,
1383
+ command: () => Promise.resolve().then(function () { return removeWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderRemoveWidgetForAiCommand),
1384
+ },
1385
+ {
1386
+ key: AXE_AI_FORMBUILDER_MOVE_WIDGET_COMMAND,
1387
+ command: () => Promise.resolve().then(function () { return moveWidgetForAi_command; }).then((c) => c.AXEAiFormbuilderMoveWidgetForAiCommand),
1388
+ },
1389
+ ]),
1390
+ {
1391
+ provide: AXP_HOOK_PROVIDER,
1392
+ multi: true,
1393
+ useValue: AXEAiFormbuilderDesignerHelpMenuHookProvider,
1394
+ },
1395
+ {
1396
+ provide: AXP_HOOK_PROVIDER,
1397
+ multi: true,
1398
+ useValue: AXEAiFormbuilderDesignerFooterHookProvider,
1399
+ },
1400
+ {
1401
+ provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
1402
+ useClass: AXPAiFormbuilderOutputContractTranscriptSegmentProvider,
1403
+ multi: true,
1404
+ },
1405
+ ],
1406
+ }]
1407
+ }] });
1408
+
1409
+ //#region ----- Imports -----
1410
+ //#endregion
1411
+ //#region ----- Outline -----
1412
+ function axeOutlineLayoutJson(input) {
1413
+ const parsed = parseLayoutTree(input);
1414
+ if (!parsed.root) {
1415
+ return { items: [], errors: parsed.errors };
1416
+ }
1417
+ return { items: axeBuildDesignerOutline(parsed.root), errors: [] };
1418
+ }
1419
+ //#endregion
1420
+ //#region ----- Mutations -----
1421
+ function axeAddWidgetToLayoutJson(input) {
1422
+ const parsed = parseLayoutTree(input);
1423
+ if (!parsed.root) {
1424
+ return fail(parsed.errors);
1425
+ }
1426
+ const widget = axeResolveWidgetNodeInput(input.widget);
1427
+ if (!widget) {
1428
+ return fail(['widget must be an AXPWidgetNode object with a string type.']);
1429
+ }
1430
+ const parent = axeFindDesignerWidget(parsed.root, {
1431
+ name: axeReadStringArg(input.parentName),
1432
+ path: axeReadStringArg(input.parentPath),
1433
+ });
1434
+ if (!parent) {
1435
+ return fail([missingWidgetRefError('parent', input.parentName, input.parentPath)]);
1436
+ }
1437
+ parent.node.children = parent.node.children ?? [];
1438
+ const index = axeReadOptionalIndex(input.index);
1439
+ const insertAt = index == null ? parent.node.children.length : Math.max(0, Math.min(index, parent.node.children.length));
1440
+ parent.node.children.splice(insertAt, 0, widget);
1441
+ return success(parsed.root, widget);
1442
+ }
1443
+ function axeUpdateWidgetInLayoutJson(input) {
1444
+ const parsed = parseLayoutTree(input);
1445
+ if (!parsed.root) {
1446
+ return fail(parsed.errors);
1447
+ }
1448
+ const hit = axeFindDesignerWidget(parsed.root, {
1449
+ name: axeReadStringArg(input.name),
1450
+ path: axeReadStringArg(input.path),
1451
+ });
1452
+ if (!hit) {
1453
+ return fail([missingWidgetRefError('widget', input.name, input.path)]);
1454
+ }
1455
+ const options = {
1456
+ ...(hit.node.options && typeof hit.node.options === 'object' && !Array.isArray(hit.node.options)
1457
+ ? hit.node.options
1458
+ : {}),
1459
+ };
1460
+ if (input.options != null && typeof input.options === 'object' && !Array.isArray(input.options)) {
1461
+ Object.assign(options, input.options);
1462
+ }
1463
+ if (input.validations != null) {
1464
+ options['validations'] = input.validations;
1465
+ }
1466
+ const triggers = axeReadOptionalTriggers(input.triggers);
1467
+ const visible = axeReadOptionalVisible(input.visible);
1468
+ const touchedOptions = input.options != null || input.validations != null;
1469
+ if (!touchedOptions && !triggers.set && !visible.set) {
1470
+ return fail(['Provide options, validations, triggers, and/or visible to update.']);
1471
+ }
1472
+ if (touchedOptions) {
1473
+ hit.node.options = options;
1474
+ }
1475
+ if (triggers.set) {
1476
+ hit.node.triggers = triggers.value ?? [];
1477
+ }
1478
+ if (visible.set) {
1479
+ hit.node.visible = visible.value;
1480
+ }
1481
+ return success(parsed.root, hit.node, hit.path);
1482
+ }
1483
+ function axeRemoveWidgetFromLayoutJson(input) {
1484
+ const parsed = parseLayoutTree(input);
1485
+ if (!parsed.root) {
1486
+ return fail(parsed.errors);
1487
+ }
1488
+ const hit = axeFindDesignerWidget(parsed.root, {
1489
+ name: axeReadStringArg(input.name),
1490
+ path: axeReadStringArg(input.path),
1491
+ });
1492
+ if (!hit) {
1493
+ return fail([missingWidgetRefError('widget', input.name, input.path)]);
1494
+ }
1495
+ if (!hit.parent) {
1496
+ return fail(['Cannot remove the root widget.']);
1497
+ }
1498
+ const children = hit.parent.children;
1499
+ if (!Array.isArray(children)) {
1500
+ return fail(['The parent has no children.']);
1501
+ }
1502
+ const at = children.indexOf(hit.node);
1503
+ if (at < 0) {
1504
+ return fail(['The widget is no longer under its parent.']);
1505
+ }
1506
+ children.splice(at, 1);
1507
+ return { applied: true, errors: [], json: JSON.stringify(parsed.root) };
1508
+ }
1509
+ function axeMoveWidgetInLayoutJson(input) {
1510
+ const parsed = parseLayoutTree(input);
1511
+ if (!parsed.root) {
1512
+ return fail(parsed.errors);
1513
+ }
1514
+ const hit = axeFindDesignerWidget(parsed.root, {
1515
+ name: axeReadStringArg(input.name),
1516
+ path: axeReadStringArg(input.path),
1517
+ });
1518
+ if (!hit?.parent) {
1519
+ return fail([missingWidgetRefError('widget', input.name, input.path)]);
1520
+ }
1521
+ const parentName = axeReadStringArg(input.parentName);
1522
+ const parentPath = axeReadStringArg(input.parentPath);
1523
+ const nextParent = parentName || parentPath
1524
+ ? axeFindDesignerWidget(parsed.root, { name: parentName, path: parentPath })?.node
1525
+ : hit.parent;
1526
+ if (!nextParent) {
1527
+ return fail([missingWidgetRefError('parent', input.parentName, input.parentPath)]);
1528
+ }
1529
+ const index = axeReadOptionalIndex(input.index);
1530
+ const from = hit.parent.children;
1531
+ if (!Array.isArray(from)) {
1532
+ return fail(['The current parent has no children.']);
1533
+ }
1534
+ const at = from.indexOf(hit.node);
1535
+ if (at < 0) {
1536
+ return fail(['The widget is no longer under its parent.']);
1537
+ }
1538
+ if (nextParent === hit.parent && index == null) {
1539
+ return fail(['Provide parentName/parentPath or index to move the widget.']);
1540
+ }
1541
+ from.splice(at, 1);
1542
+ nextParent.children = nextParent.children ?? [];
1543
+ const insertAt = index == null
1544
+ ? nextParent.children.length
1545
+ : Math.max(0, Math.min(index, nextParent.children.length));
1546
+ nextParent.children.splice(insertAt, 0, hit.node);
1547
+ return success(parsed.root, hit.node);
1548
+ }
1549
+ //#endregion
1550
+ //#region ----- Parse -----
1551
+ function parseLayoutTree(input) {
1552
+ const raw = axeResolveLayoutJsonInput(input);
1553
+ if (!raw) {
1554
+ return { root: null, errors: ['Missing json. Pass the AXPWidgetNode tree as json.'] };
1555
+ }
1556
+ try {
1557
+ const parsed = JSON.parse(raw);
1558
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
1559
+ return { root: null, errors: ['json must be an AXPWidgetNode object.'] };
1560
+ }
1561
+ const type = parsed['type'];
1562
+ if (typeof type !== 'string' || !type.trim()) {
1563
+ return { root: null, errors: ['json type must be a widget registry name.'] };
1564
+ }
1565
+ return { root: parsed, errors: [] };
1566
+ }
1567
+ catch (error) {
1568
+ return {
1569
+ root: null,
1570
+ errors: [error instanceof Error ? `JSON parse error: ${error.message}` : 'Invalid JSON.'],
1571
+ };
1572
+ }
1573
+ }
1574
+ function success(root, node, knownPath) {
1575
+ const json = JSON.stringify(root);
1576
+ const name = typeof node.name === 'string' ? node.name.trim() : '';
1577
+ const outline = axeBuildDesignerOutline(root);
1578
+ const hit = name
1579
+ ? outline.find((item) => item.name === name)
1580
+ : knownPath
1581
+ ? outline.find((item) => item.path === knownPath)
1582
+ : undefined;
1583
+ return {
1584
+ applied: true,
1585
+ errors: [],
1586
+ json,
1587
+ ...(name ? { name } : {}),
1588
+ ...(hit?.path || knownPath ? { path: hit?.path ?? knownPath } : {}),
1589
+ };
1590
+ }
1591
+ function fail(errors) {
1592
+ return { applied: false, errors };
1593
+ }
1594
+ function missingWidgetRefError(kind, name, path) {
1595
+ const nameText = axeReadStringArg(name);
1596
+ const pathText = axeReadStringArg(path);
1597
+ if (nameText || pathText) {
1598
+ return `No ${kind} found for ${nameText ? `name "${nameText}"` : `path "${pathText}"`}. Call OutlineLayoutForAi with the same json.`;
1599
+ }
1600
+ return `Provide ${kind}Name or ${kind}Path from OutlineLayoutForAi.`;
1601
+ }
1602
+ //#endregion
1603
+
1604
+ //#endregion
1605
+ //#region ----- Command -----
1606
+ class AXEAiFormbuilderValidateLayoutJsonForAiCommand {
1607
+ async execute(input) {
1608
+ const raw = axeResolveLayoutJsonInput(input);
1609
+ const data = axeParseAndValidateLayoutJson(raw);
1610
+ return {
1611
+ success: true,
1612
+ kind: 'object',
1613
+ data,
1614
+ ...(data.valid ? {} : { message: { text: data.errors[0] ?? 'Invalid layout JSON.' } }),
1615
+ };
1616
+ }
1617
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1618
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, providedIn: 'root' }); }
1619
+ }
1620
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderValidateLayoutJsonForAiCommand, decorators: [{
1621
+ type: Injectable,
1622
+ args: [{ providedIn: 'root' }]
1623
+ }] });
1624
+
1625
+ var validateLayoutJsonForAi_command = /*#__PURE__*/Object.freeze({
1626
+ __proto__: null,
1627
+ AXEAiFormbuilderValidateLayoutJsonForAiCommand: AXEAiFormbuilderValidateLayoutJsonForAiCommand
1628
+ });
1629
+
1630
+ /**
1631
+ * Build-time only. Do not import from Angular modules.
1632
+ * Source: validate-layout-json-for-ai.command.ts — AXEAiFormbuilderValidateLayoutJsonForAiCommand.execute
1633
+ */
1634
+ const axeAiFormbuilderValidateLayoutJsonForAiCommandDefinition = defineCommand({
1635
+ key: 'AiFormbuilder:ValidateLayoutJsonForAi',
1636
+ title: 'Validate widget layout JSON for AI (form template designer)',
1637
+ description: 'Parses a JSON string and checks it is a single root AXPWidgetNode (object with string type, optional children/options). Rejects raw CSS/DOM-style keys.',
1638
+ executionMode: 'frontend',
1639
+ inputs: [
1640
+ {
1641
+ name: 'json',
1642
+ title: 'Layout JSON string',
1643
+ description: 'Stringified widget tree: one root object with "type" (registry name). Optional markdown/json fences are stripped.',
1644
+ interface: { name: 'large-text-editor' },
1645
+ validations: [{ rule: 'required' }],
1646
+ },
1647
+ ],
1648
+ outcomes: {
1649
+ mode: 'static',
1650
+ values: ['success', 'failure'],
1651
+ },
1652
+ ai: {
1653
+ shortDescription: 'Validate specialist JSON is parseable and shaped like AXPWidgetNode before returning it to chat.',
1654
+ tags: ['ai-formbuilder', 'layout', 'ai', 'validation'],
1655
+ },
1656
+ capabilities: ['ai'],
1657
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
1658
+ });
1659
+
1660
+ //#endregion
1661
+ //#region ----- Command -----
1662
+ /** Applies validated designer JSON to the open form-template canvas. */
1663
+ class AXEAiFormbuilderApplyLayoutJsonForAiCommand {
1664
+ constructor() {
1665
+ //#region ----- Services & Dependencies -----
1666
+ this.assist = inject(AXEAiFormbuilderDesignerAiAssistService);
1667
+ }
1668
+ //#endregion
1669
+ async execute(input) {
1670
+ const raw = axeResolveLayoutJsonInput(input);
1671
+ const data = await this.assist.applyLayoutJson(raw);
1672
+ return {
1673
+ success: true,
1674
+ kind: 'object',
1675
+ data,
1676
+ ...(data.applied ? {} : { message: { text: data.errors[0] ?? 'Layout was not applied.' } }),
1677
+ };
1678
+ }
1679
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderApplyLayoutJsonForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1680
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderApplyLayoutJsonForAiCommand, providedIn: 'root' }); }
1681
+ }
1682
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderApplyLayoutJsonForAiCommand, decorators: [{
1683
+ type: Injectable,
1684
+ args: [{ providedIn: 'root' }]
1685
+ }] });
1686
+
1687
+ var applyLayoutJsonForAi_command = /*#__PURE__*/Object.freeze({
1688
+ __proto__: null,
1689
+ AXEAiFormbuilderApplyLayoutJsonForAiCommand: AXEAiFormbuilderApplyLayoutJsonForAiCommand
1690
+ });
1691
+
1692
+ /**
1693
+ * Build-time only. Do not import from Angular modules.
1694
+ * Source: apply-layout-json-for-ai.command.ts — AXEAiFormbuilderApplyLayoutJsonForAiCommand.execute
1695
+ */
1696
+ const axeAiFormbuilderApplyLayoutJsonForAiCommandDefinition = defineCommand({
1697
+ key: 'AiFormbuilder:ApplyLayoutJsonForAi',
1698
+ title: 'Apply widget layout JSON to the form designer',
1699
+ description: 'Validates a root AXPWidgetNode and loads it onto the open Form Template designer canvas (document-layout or page-layout). Prefer Page:RunAction apply-layout on form-designer-assistant for live canvas writes. Does not save — the user uses File → Save.',
1700
+ executionMode: 'frontend',
1701
+ inputs: [
1702
+ {
1703
+ name: 'json',
1704
+ title: 'Layout JSON string',
1705
+ description: 'Stringified designer widget tree. Root must be document-layout (form template) or page-layout (widget template).',
1706
+ interface: { name: 'large-text-editor' },
1707
+ validations: [{ rule: 'required' }],
1708
+ },
1709
+ ],
1710
+ outcomes: {
1711
+ mode: 'static',
1712
+ values: ['success', 'failure'],
1713
+ },
1714
+ ai: {
1715
+ shortDescription: 'Write validated designer JSON onto the open form-template canvas so the user sees the change immediately.',
1716
+ tags: ['ai-formbuilder', 'layout', 'ai', 'apply'],
1717
+ },
1718
+ capabilities: ['ai'],
1719
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
1720
+ });
1721
+
1722
+ //#endregion
1723
+ //#region ----- Command -----
1724
+ class AXEAiFormbuilderOutlineLayoutForAiCommand {
1725
+ async execute(input = {}) {
1726
+ const data = axeOutlineLayoutJson(input);
1727
+ return {
1728
+ success: true,
1729
+ kind: 'object',
1730
+ data,
1731
+ ...(data.errors[0] ? { message: { text: data.errors[0] } } : {}),
1732
+ };
1733
+ }
1734
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderOutlineLayoutForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1735
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderOutlineLayoutForAiCommand, providedIn: 'root' }); }
1736
+ }
1737
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderOutlineLayoutForAiCommand, decorators: [{
1738
+ type: Injectable,
1739
+ args: [{ providedIn: 'root' }]
1740
+ }] });
1741
+
1742
+ var outlineLayoutForAi_command = /*#__PURE__*/Object.freeze({
1743
+ __proto__: null,
1744
+ AXEAiFormbuilderOutlineLayoutForAiCommand: AXEAiFormbuilderOutlineLayoutForAiCommand
1745
+ });
1746
+
1747
+ /**
1748
+ * Build-time only. Do not import from Angular modules.
1749
+ * Source: outline-layout-for-ai.command.ts — AXEAiFormbuilderOutlineLayoutForAiCommand.execute
1750
+ */
1751
+ const axeAiFormbuilderOutlineLayoutForAiCommandDefinition = defineCommand({
1752
+ key: 'AiFormbuilder:OutlineLayoutForAi',
1753
+ title: 'Outline a layout JSON tree',
1754
+ description: 'Returns a compact list of widgets (name, type, label, parent, path) from an AXPWidgetNode JSON tree. Does not touch the live designer. Pass the same json to Add/Update/Remove/Move.',
1755
+ executionMode: 'frontend',
1756
+ inputs: [
1757
+ {
1758
+ name: 'json',
1759
+ title: 'Layout JSON',
1760
+ description: 'AXPWidgetNode tree (string or object). Chat block-layout or designer document-layout.',
1761
+ interface: { name: 'large-text-editor' },
1762
+ validations: [{ rule: 'required' }],
1763
+ },
1764
+ ],
1765
+ outcomes: {
1766
+ mode: 'static',
1767
+ values: ['success', 'failure'],
1768
+ },
1769
+ ai: {
1770
+ shortDescription: 'List widgets in a layout JSON tree by name/type/path without rewriting the tree.',
1771
+ tags: ['ai-formbuilder', 'layout', 'ai', 'outline', 'json'],
1772
+ },
1773
+ capabilities: ['ai'],
1774
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
1775
+ });
1776
+
1777
+ //#endregion
1778
+ //#region ----- Command -----
1779
+ class AXEAiFormbuilderAddWidgetForAiCommand {
1780
+ async execute(input) {
1781
+ const data = axeAddWidgetToLayoutJson(input);
1782
+ return {
1783
+ success: true,
1784
+ kind: 'object',
1785
+ data,
1786
+ ...(data.applied ? {} : { message: { text: data.errors[0] ?? 'The widget was not added.' } }),
1787
+ };
1788
+ }
1789
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderAddWidgetForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1790
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderAddWidgetForAiCommand, providedIn: 'root' }); }
1791
+ }
1792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderAddWidgetForAiCommand, decorators: [{
1793
+ type: Injectable,
1794
+ args: [{ providedIn: 'root' }]
1795
+ }] });
1796
+
1797
+ var addWidgetForAi_command = /*#__PURE__*/Object.freeze({
1798
+ __proto__: null,
1799
+ AXEAiFormbuilderAddWidgetForAiCommand: AXEAiFormbuilderAddWidgetForAiCommand
1800
+ });
1801
+
1802
+ /**
1803
+ * Build-time only. Do not import from Angular modules.
1804
+ * Source: add-widget-for-ai.command.ts — AXEAiFormbuilderAddWidgetForAiCommand.execute
1805
+ */
1806
+ const axeAiFormbuilderAddWidgetForAiCommandDefinition = defineCommand({
1807
+ key: 'AiFormbuilder:AddWidgetForAi',
1808
+ title: 'Add a widget in layout JSON',
1809
+ description: 'Inserts one AXPWidgetNode under a parent inside a layout JSON tree and returns the updated json. Does not touch the live designer. Address the parent by name or path from OutlineLayoutForAi.',
1810
+ executionMode: 'frontend',
1811
+ inputs: [
1812
+ {
1813
+ name: 'json',
1814
+ title: 'Layout JSON',
1815
+ description: 'Current AXPWidgetNode tree (string or object).',
1816
+ interface: { name: 'large-text-editor' },
1817
+ validations: [{ rule: 'required' }],
1818
+ },
1819
+ {
1820
+ name: 'parentName',
1821
+ title: 'Parent widget name',
1822
+ description: 'name from OutlineLayoutForAi. Prefer this over parentPath.',
1823
+ interface: { name: 'text-editor' },
1824
+ },
1825
+ {
1826
+ name: 'parentPath',
1827
+ title: 'Parent widget path',
1828
+ description: 'path from OutlineLayoutForAi when the parent has no name.',
1829
+ interface: { name: 'text-editor' },
1830
+ },
1831
+ {
1832
+ name: 'widget',
1833
+ title: 'Widget node',
1834
+ description: 'One AXPWidgetNode object (type plus options/children). Not the full tree.',
1835
+ interface: { name: 'json' },
1836
+ validations: [{ rule: 'required' }],
1837
+ },
1838
+ {
1839
+ name: 'index',
1840
+ title: 'Insert index',
1841
+ description: 'Optional child index under the parent. Omit to append.',
1842
+ interface: { name: 'number-editor' },
1843
+ },
1844
+ ],
1845
+ outcomes: {
1846
+ mode: 'static',
1847
+ values: ['success', 'failure'],
1848
+ },
1849
+ ai: {
1850
+ shortDescription: 'Add one widget under a named parent in a layout JSON tree and return the new json.',
1851
+ tags: ['ai-formbuilder', 'layout', 'ai', 'add', 'json'],
1852
+ },
1853
+ capabilities: ['ai'],
1854
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
1855
+ });
1856
+
1857
+ //#endregion
1858
+ //#region ----- Command -----
1859
+ class AXEAiFormbuilderUpdateWidgetForAiCommand {
1860
+ async execute(input) {
1861
+ const data = axeUpdateWidgetInLayoutJson(input);
1862
+ return {
1863
+ success: true,
1864
+ kind: 'object',
1865
+ data,
1866
+ ...(data.applied ? {} : { message: { text: data.errors[0] ?? 'The widget was not updated.' } }),
1867
+ };
1868
+ }
1869
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderUpdateWidgetForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1870
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderUpdateWidgetForAiCommand, providedIn: 'root' }); }
1871
+ }
1872
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderUpdateWidgetForAiCommand, decorators: [{
1873
+ type: Injectable,
1874
+ args: [{ providedIn: 'root' }]
1875
+ }] });
1876
+
1877
+ var updateWidgetForAi_command = /*#__PURE__*/Object.freeze({
1878
+ __proto__: null,
1879
+ AXEAiFormbuilderUpdateWidgetForAiCommand: AXEAiFormbuilderUpdateWidgetForAiCommand
1880
+ });
1881
+
1882
+ /**
1883
+ * Build-time only. Do not import from Angular modules.
1884
+ * Source: update-widget-for-ai.command.ts — AXEAiFormbuilderUpdateWidgetForAiCommand.execute
1885
+ */
1886
+ const axeAiFormbuilderUpdateWidgetForAiCommandDefinition = defineCommand({
1887
+ key: 'AiFormbuilder:UpdateWidgetForAi',
1888
+ title: 'Update a widget in layout JSON',
1889
+ description: 'Patches options, validations, triggers, and/or visible on one widget inside a layout JSON tree and returns the updated json. triggers and visible sit on the node root, not inside options. Does not touch the live designer.',
1890
+ executionMode: 'frontend',
1891
+ inputs: [
1892
+ {
1893
+ name: 'json',
1894
+ title: 'Layout JSON',
1895
+ description: 'Current AXPWidgetNode tree (string or object).',
1896
+ interface: { name: 'large-text-editor' },
1897
+ validations: [{ rule: 'required' }],
1898
+ },
1899
+ {
1900
+ name: 'name',
1901
+ title: 'Widget name',
1902
+ description: 'name from OutlineLayoutForAi. Prefer this over path.',
1903
+ interface: { name: 'text-editor' },
1904
+ },
1905
+ {
1906
+ name: 'path',
1907
+ title: 'Widget path',
1908
+ description: 'path from OutlineLayoutForAi when the widget has no name.',
1909
+ interface: { name: 'text-editor' },
1910
+ },
1911
+ {
1912
+ name: 'options',
1913
+ title: 'Options patch',
1914
+ description: 'Partial options object merged onto the widget (label, hint, cols, …).',
1915
+ interface: { name: 'json' },
1916
+ },
1917
+ {
1918
+ name: 'validations',
1919
+ title: 'Validations',
1920
+ description: 'Replaces options.validations on the widget (e.g. [{ "rule": "required" }]).',
1921
+ interface: { name: 'json' },
1922
+ },
1923
+ {
1924
+ name: 'triggers',
1925
+ title: 'Triggers',
1926
+ description: 'Replaces node.triggers: [{ event, action }] expression strings. Empty array clears.',
1927
+ interface: { name: 'json' },
1928
+ },
1929
+ {
1930
+ name: 'visible',
1931
+ title: 'Visible',
1932
+ description: 'Replaces node.visible (boolean or "{{ context.eval(...) }}").',
1933
+ interface: { name: 'text-editor' },
1934
+ },
1935
+ ],
1936
+ outcomes: {
1937
+ mode: 'static',
1938
+ values: ['success', 'failure'],
1939
+ },
1940
+ ai: {
1941
+ shortDescription: 'Patch options, validations, triggers, or visible on one widget in a layout JSON tree.',
1942
+ tags: ['ai-formbuilder', 'layout', 'ai', 'update', 'json'],
1943
+ },
1944
+ capabilities: ['ai'],
1945
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
1946
+ });
1947
+
1948
+ //#endregion
1949
+ //#region ----- Command -----
1950
+ class AXEAiFormbuilderRemoveWidgetForAiCommand {
1951
+ async execute(input) {
1952
+ const data = axeRemoveWidgetFromLayoutJson(input);
1953
+ return {
1954
+ success: true,
1955
+ kind: 'object',
1956
+ data,
1957
+ ...(data.applied ? {} : { message: { text: data.errors[0] ?? 'The widget was not removed.' } }),
1958
+ };
1959
+ }
1960
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderRemoveWidgetForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1961
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderRemoveWidgetForAiCommand, providedIn: 'root' }); }
1962
+ }
1963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderRemoveWidgetForAiCommand, decorators: [{
1964
+ type: Injectable,
1965
+ args: [{ providedIn: 'root' }]
1966
+ }] });
1967
+
1968
+ var removeWidgetForAi_command = /*#__PURE__*/Object.freeze({
1969
+ __proto__: null,
1970
+ AXEAiFormbuilderRemoveWidgetForAiCommand: AXEAiFormbuilderRemoveWidgetForAiCommand
1971
+ });
1972
+
1973
+ /**
1974
+ * Build-time only. Do not import from Angular modules.
1975
+ * Source: remove-widget-for-ai.command.ts — AXEAiFormbuilderRemoveWidgetForAiCommand.execute
1976
+ */
1977
+ const axeAiFormbuilderRemoveWidgetForAiCommandDefinition = defineCommand({
1978
+ key: 'AiFormbuilder:RemoveWidgetForAi',
1979
+ title: 'Remove a widget from layout JSON',
1980
+ description: 'Removes one widget from a layout JSON tree and returns the updated json. Does not touch the live designer. Cannot remove the root. Address the widget by name or path from OutlineLayoutForAi.',
1981
+ executionMode: 'frontend',
1982
+ inputs: [
1983
+ {
1984
+ name: 'json',
1985
+ title: 'Layout JSON',
1986
+ description: 'Current AXPWidgetNode tree (string or object).',
1987
+ interface: { name: 'large-text-editor' },
1988
+ validations: [{ rule: 'required' }],
1989
+ },
1990
+ {
1991
+ name: 'name',
1992
+ title: 'Widget name',
1993
+ description: 'name from OutlineLayoutForAi. Prefer this over path.',
1994
+ interface: { name: 'text-editor' },
1995
+ },
1996
+ {
1997
+ name: 'path',
1998
+ title: 'Widget path',
1999
+ description: 'path from OutlineLayoutForAi when the widget has no name.',
2000
+ interface: { name: 'text-editor' },
2001
+ },
2002
+ ],
2003
+ outcomes: {
2004
+ mode: 'static',
2005
+ values: ['success', 'failure'],
2006
+ },
2007
+ ai: {
2008
+ shortDescription: 'Remove one widget from a layout JSON tree and return the new json.',
2009
+ tags: ['ai-formbuilder', 'layout', 'ai', 'remove', 'json'],
2010
+ },
2011
+ capabilities: ['ai'],
2012
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
2013
+ });
2014
+
2015
+ //#endregion
2016
+ //#region ----- Command -----
2017
+ class AXEAiFormbuilderMoveWidgetForAiCommand {
2018
+ async execute(input) {
2019
+ const data = axeMoveWidgetInLayoutJson(input);
2020
+ return {
2021
+ success: true,
2022
+ kind: 'object',
2023
+ data,
2024
+ ...(data.applied ? {} : { message: { text: data.errors[0] ?? 'The widget was not moved.' } }),
2025
+ };
2026
+ }
2027
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderMoveWidgetForAiCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2028
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderMoveWidgetForAiCommand, providedIn: 'root' }); }
2029
+ }
2030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiFormbuilderMoveWidgetForAiCommand, decorators: [{
2031
+ type: Injectable,
2032
+ args: [{ providedIn: 'root' }]
2033
+ }] });
2034
+
2035
+ var moveWidgetForAi_command = /*#__PURE__*/Object.freeze({
2036
+ __proto__: null,
2037
+ AXEAiFormbuilderMoveWidgetForAiCommand: AXEAiFormbuilderMoveWidgetForAiCommand
2038
+ });
2039
+
2040
+ /**
2041
+ * Build-time only. Do not import from Angular modules.
2042
+ * Source: move-widget-for-ai.command.ts — AXEAiFormbuilderMoveWidgetForAiCommand.execute
2043
+ */
2044
+ const axeAiFormbuilderMoveWidgetForAiCommandDefinition = defineCommand({
2045
+ key: 'AiFormbuilder:MoveWidgetForAi',
2046
+ title: 'Move a widget in layout JSON',
2047
+ description: 'Moves one widget to another parent and/or index inside a layout JSON tree and returns the updated json. Does not touch the live designer. Address widgets by name or path from OutlineLayoutForAi.',
2048
+ executionMode: 'frontend',
2049
+ inputs: [
2050
+ {
2051
+ name: 'json',
2052
+ title: 'Layout JSON',
2053
+ description: 'Current AXPWidgetNode tree (string or object).',
2054
+ interface: { name: 'large-text-editor' },
2055
+ validations: [{ rule: 'required' }],
2056
+ },
2057
+ {
2058
+ name: 'name',
2059
+ title: 'Widget name',
2060
+ description: 'name from OutlineLayoutForAi. Prefer this over path.',
2061
+ interface: { name: 'text-editor' },
2062
+ },
2063
+ {
2064
+ name: 'path',
2065
+ title: 'Widget path',
2066
+ description: 'path from OutlineLayoutForAi when the widget has no name.',
2067
+ interface: { name: 'text-editor' },
2068
+ },
2069
+ {
2070
+ name: 'parentName',
2071
+ title: 'New parent name',
2072
+ description: 'Destination parent name from OutlineLayoutForAi. Omit to reorder under the current parent.',
2073
+ interface: { name: 'text-editor' },
2074
+ },
2075
+ {
2076
+ name: 'parentPath',
2077
+ title: 'New parent path',
2078
+ description: 'Destination parent path when the parent has no name.',
2079
+ interface: { name: 'text-editor' },
2080
+ },
2081
+ {
2082
+ name: 'index',
2083
+ title: 'Insert index',
2084
+ description: 'Child index under the destination parent.',
2085
+ interface: { name: 'number-editor' },
2086
+ },
2087
+ ],
2088
+ outcomes: {
2089
+ mode: 'static',
2090
+ values: ['success', 'failure'],
2091
+ },
2092
+ ai: {
2093
+ shortDescription: 'Move one widget in a layout JSON tree and return the new json.',
2094
+ tags: ['ai-formbuilder', 'layout', 'ai', 'move', 'json'],
315
2095
  },
316
2096
  capabilities: ['ai'],
317
2097
  categories: [AXP_COMMAND_DEFINITION_CATEGORY_AI_TOOLS],
@@ -321,5 +2101,5 @@ const axeAiFormbuilderValidateLayoutJsonForAiCommandDefinition = defineCommand({
321
2101
  * Generated bundle index. Do not edit.
322
2102
  */
323
2103
 
324
- export { AXEAIFormBuilderExtensionModule, AXEAiFormbuilderDesignerAiAssistService, AXEAiFormbuilderDesignerRootMenuHookProvider, AXEAiFormbuilderValidateLayoutJsonForAiCommand, AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID, AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE, axeAiFormbuilderValidateLayoutJsonForAiCommandDefinition };
2104
+ export { AXEAIFormBuilderExtensionModule, AXEAiFormbuilderAddWidgetForAiCommand, AXEAiFormbuilderApplyLayoutJsonForAiCommand, AXEAiFormbuilderDesignerAiAssistService, AXEAiFormbuilderDesignerFooterHookProvider, AXEAiFormbuilderDesignerHelpMenuHookProvider, AXEAiFormbuilderMoveWidgetForAiCommand, AXEAiFormbuilderOutlineLayoutForAiCommand, AXEAiFormbuilderRemoveWidgetForAiCommand, AXEAiFormbuilderUpdateWidgetForAiCommand, AXEAiFormbuilderValidateLayoutJsonForAiCommand, AXE_AI_FORMBUILDER_ADD_WIDGET_COMMAND, AXE_AI_FORMBUILDER_APPLY_LAYOUT_COMMAND, AXE_AI_FORMBUILDER_ASSIST_ACTION, AXE_AI_FORMBUILDER_ASSIST_KIND, AXE_AI_FORMBUILDER_ASSIST_SCENARIO, AXE_AI_FORMBUILDER_DESIGNER_ASSIST_ID, AXE_AI_FORMBUILDER_DESIGNER_DRAFT_ENTITY_ID, AXE_AI_FORMBUILDER_DESIGNER_ENTITY_TYPE, AXE_AI_FORMBUILDER_MOVE_WIDGET_COMMAND, AXE_AI_FORMBUILDER_OUTLINE_LAYOUT_COMMAND, AXE_AI_FORMBUILDER_PAGE_ACTION_ADD_WIDGET, AXE_AI_FORMBUILDER_PAGE_ACTION_APPLY_LAYOUT, AXE_AI_FORMBUILDER_PAGE_ACTION_MOVE_WIDGET, AXE_AI_FORMBUILDER_PAGE_ACTION_REMOVE_WIDGET, AXE_AI_FORMBUILDER_PAGE_ACTION_UPDATE_WIDGET, AXE_AI_FORMBUILDER_PAGE_CONTEXT_ID, AXE_AI_FORMBUILDER_REMOVE_WIDGET_COMMAND, AXE_AI_FORMBUILDER_UPDATE_WIDGET_COMMAND, AXE_AI_FORMBUILDER_VALIDATE_LAYOUT_COMMAND, axeAddWidgetToLayoutJson, axeAiFormbuilderAddWidgetForAiCommandDefinition, axeAiFormbuilderApplyLayoutJsonForAiCommandDefinition, axeAiFormbuilderMoveWidgetForAiCommandDefinition, axeAiFormbuilderOutlineLayoutForAiCommandDefinition, axeAiFormbuilderRemoveWidgetForAiCommandDefinition, axeAiFormbuilderUpdateWidgetForAiCommandDefinition, axeAiFormbuilderValidateLayoutJsonForAiCommandDefinition, axeMoveWidgetInLayoutJson, axeOutlineLayoutJson, axeRemoveWidgetFromLayoutJson, axeUpdateWidgetInLayoutJson };
325
2105
  //# sourceMappingURL=acorex-platform-extension-ai-formbuilder-client.mjs.map