@designcrowd/library.brand-page 5.17.6 → 5.17.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BI2KuOEF.js → CreatedOnBrandCrowd-de-de-BUQFMhZb.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BYLJCjLJ.js → CreatedOnBrandCrowd-en-us-QS-5r4XI.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-Y_D6cuGE.js → CreatedOnBrandCrowd-es-es-CP2iH-n0.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CqCBRjYh.js → CreatedOnBrandCrowd-fr-ca-qfD4NmQZ.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CqCBRjYh.js → CreatedOnBrandCrowd-fr-fr-qfD4NmQZ.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Bt0YF6DA.js → CreatedOnBrandCrowd-pt-br-CfmaB7zE.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Bt0YF6DA.js → CreatedOnBrandCrowd-pt-pt-CfmaB7zE.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CEk8FBEG.js → CreatedOnDcom-de-de-BxSUWfGN.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DWw4Tm2L.js → CreatedOnDcom-en-us-CSeUdR6v.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-DpG-8o98.js → CreatedOnDcom-es-es-g7rCfAtP.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-CEla7RmR.js → CreatedOnDcom-fr-ca-Bh_BTb-O.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DRXeVTlV.js → CreatedOnDcom-fr-fr-Clgd_Ujm.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-Bzu_phZc.js → CreatedOnDcom-pt-br-Dlgk0myX.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DWax8Er7.js → CreatedOnDcom-pt-pt-B14APfrn.js} +1 -1
  15. package/lib/src/brand-page/models/brand-page-context.model.d.ts +2 -1
  16. package/lib/src/brand-page/models/brand-page-context.model.js +5 -0
  17. package/lib/src/brand-page/models/index.d.ts +1 -0
  18. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +62 -2
  19. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +1 -0
  20. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +60 -2
  21. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +30 -1
  22. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +60 -2
  23. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +60 -2
  24. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +60 -2
  25. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +60 -2
  26. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +60 -2
  27. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +60 -2
  28. package/lib/src/brand-page-maker/composables/ai-chat/useAiChat.js +3 -1
  29. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -1
  30. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.js +6 -6
  31. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.d.ts +2 -0
  32. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +258 -0
  33. package/lib/src/brand-page-maker/constants/sidebar-constant.d.ts +1 -0
  34. package/lib/src/brand-page-maker/constants/sidebar-constant.js +8 -0
  35. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +16 -0
  36. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +14 -0
  37. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +62 -3
  38. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +2 -0
  39. package/lib/src/brand-page-maker/stores/index.d.ts +1 -0
  40. package/lib/src/brand-page-maker/stores/index.js +1 -0
  41. package/lib/src/brand-page-maker/stores/snapshot/snapshot-store.d.ts +2 -0
  42. package/lib/src/brand-page-maker/stores/snapshot/snapshot-store.js +9 -0
  43. package/lib/src/clients/ai-assistant-client.d.ts +8 -0
  44. package/lib/src/clients/ai-assistant-client.js +11 -1
  45. package/lib/src/composables/useSnapshot.js +33 -0
  46. package/lib/src/css/library.brand-page-brandCrowd.min.css +19 -6
  47. package/lib/src/css/library.brand-page-brandPage.min.css +19 -6
  48. package/lib/src/css/library.brand-page-designCom.min.css +19 -6
  49. package/lib/src/css/library.brand-page-preview.min.css +19 -6
  50. package/lib/src/index.d.ts +1 -0
  51. package/lib/src/index.mjs +42 -41
  52. package/lib/src/{lib-DODSa6QK.js → lib-pOkCQP0J.js} +28820 -26841
  53. package/lib/src/lib.js +1 -0
  54. package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -1
  55. package/lib/src/metadata-builder/ai-metadata-executor.js +68 -7
  56. package/lib/src/metadata-builder/builders/add-page-builder.d.ts +14 -0
  57. package/lib/src/metadata-builder/builders/add-page-builder.js +66 -0
  58. package/lib/src/metadata-builder/builders/remove-page-builder.d.ts +13 -0
  59. package/lib/src/metadata-builder/builders/remove-page-builder.js +50 -0
  60. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.d.ts +6 -0
  61. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +17 -0
  62. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.d.ts +6 -0
  63. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +25 -0
  64. package/lib/src/metadata-builder/builders/section-builders/base-section-builder.d.ts +40 -0
  65. package/lib/src/metadata-builder/builders/section-builders/base-section-builder.js +108 -0
  66. package/lib/src/metadata-builder/builders/section-builders/button-section-builder.d.ts +6 -0
  67. package/lib/src/metadata-builder/builders/section-builders/button-section-builder.js +16 -0
  68. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.d.ts +6 -0
  69. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +27 -0
  70. package/lib/src/metadata-builder/builders/section-builders/delete-section-builder.d.ts +13 -0
  71. package/lib/src/metadata-builder/builders/section-builders/delete-section-builder.js +37 -0
  72. package/lib/src/metadata-builder/builders/section-builders/divider-section-builder.d.ts +6 -0
  73. package/lib/src/metadata-builder/builders/section-builders/divider-section-builder.js +16 -0
  74. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.d.ts +6 -0
  75. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +76 -0
  76. package/lib/src/metadata-builder/builders/section-builders/image-section-builder.d.ts +6 -0
  77. package/lib/src/metadata-builder/builders/section-builders/image-section-builder.js +16 -0
  78. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.d.ts +6 -0
  79. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +158 -0
  80. package/lib/src/metadata-builder/builders/section-builders/index.d.ts +17 -0
  81. package/lib/src/metadata-builder/builders/section-builders/index.js +17 -0
  82. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.d.ts +6 -0
  83. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +17 -0
  84. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.d.ts +6 -0
  85. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +16 -0
  86. package/lib/src/metadata-builder/builders/section-builders/move-section-builder.d.ts +15 -0
  87. package/lib/src/metadata-builder/builders/section-builders/move-section-builder.js +62 -0
  88. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.d.ts +6 -0
  89. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +16 -0
  90. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.d.ts +6 -0
  91. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +162 -0
  92. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.d.ts +6 -0
  93. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +50 -0
  94. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.d.ts +6 -0
  95. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +133 -0
  96. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.d.ts +6 -0
  97. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +154 -0
  98. package/lib/src/metadata-builder/builders/section-content-factory.d.ts +3 -0
  99. package/lib/src/metadata-builder/builders/section-content-factory.js +13 -0
  100. package/lib/src/metadata-builder/builders/section-field-maps.d.ts +12 -0
  101. package/lib/src/metadata-builder/builders/section-field-maps.js +180 -0
  102. package/lib/src/metadata-builder/builders/section-field-utils.d.ts +11 -0
  103. package/lib/src/metadata-builder/builders/section-field-utils.js +109 -0
  104. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +6 -0
  105. package/lib/src/metadata-builder/helpers/rte.helpers.js +16 -1
  106. package/lib/src/metadata-builder/metadata-executor.js +71 -1
  107. package/lib/src/metadata-builder/models.d.ts +54 -0
  108. package/lib/src/metadata-builder/models.js +58 -0
  109. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +51 -3
  110. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +110 -13
  111. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +285 -0
  112. package/lib/src/metadata-builder/parsers/section-tool-arguments.js +7 -0
  113. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +60 -2
  114. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +60 -2
  115. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +60 -2
  116. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +60 -2
  117. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +90 -3
  118. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +61 -2
  119. package/lib/src/shared/side-bar/components/SideBar.mixin.js +6 -0
  120. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +60 -2
  121. package/lib/src/utils/minify-brand-page-metadata.d.ts +22 -0
  122. package/lib/src/utils/minify-brand-page-metadata.js +344 -0
  123. package/package.json +1 -1
  124. package/src/brand-page/models/brand-page-context.model.ts +12 -3
  125. package/src/brand-page/models/index.ts +1 -0
  126. package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +80 -0
  127. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +4 -0
  128. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +9 -2
  129. package/src/brand-page-maker/composables/ai-chat/useAiChat.ts +4 -1
  130. package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +6 -6
  131. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +363 -0
  132. package/src/brand-page-maker/constants/sidebar-constant.ts +8 -0
  133. package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +19 -0
  134. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +3 -1
  135. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +4 -0
  136. package/src/brand-page-maker/stores/index.ts +1 -0
  137. package/src/brand-page-maker/stores/snapshot/snapshot-store.ts +12 -0
  138. package/src/clients/ai-assistant-client.ts +17 -1
  139. package/src/composables/useSnapshot.ts +47 -0
  140. package/src/lib.ts +4 -0
  141. package/src/metadata-builder/ai-metadata-executor.ts +116 -26
  142. package/src/metadata-builder/builders/add-page-builder.ts +98 -0
  143. package/src/metadata-builder/builders/remove-page-builder.ts +83 -0
  144. package/src/metadata-builder/builders/section-builders/about-section-builder.ts +28 -0
  145. package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +45 -0
  146. package/src/metadata-builder/builders/section-builders/base-section-builder.ts +160 -0
  147. package/src/metadata-builder/builders/section-builders/button-section-builder.ts +24 -0
  148. package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +43 -0
  149. package/src/metadata-builder/builders/section-builders/delete-section-builder.ts +63 -0
  150. package/src/metadata-builder/builders/section-builders/divider-section-builder.ts +27 -0
  151. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +111 -0
  152. package/src/metadata-builder/builders/section-builders/image-section-builder.ts +24 -0
  153. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +223 -0
  154. package/src/metadata-builder/builders/section-builders/index.ts +21 -0
  155. package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +29 -0
  156. package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +27 -0
  157. package/src/metadata-builder/builders/section-builders/move-section-builder.ts +100 -0
  158. package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +27 -0
  159. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +231 -0
  160. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +79 -0
  161. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +194 -0
  162. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +234 -0
  163. package/src/metadata-builder/builders/section-content-factory.ts +20 -0
  164. package/src/metadata-builder/builders/section-field-maps.ts +235 -0
  165. package/src/metadata-builder/builders/section-field-utils.ts +157 -0
  166. package/src/metadata-builder/helpers/rte.helpers.ts +23 -1
  167. package/src/metadata-builder/metadata-executor.ts +96 -4
  168. package/src/metadata-builder/models.ts +62 -0
  169. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +141 -17
  170. package/src/metadata-builder/parsers/section-tool-arguments.ts +417 -0
  171. package/src/shared/side-bar/components/SideBar.mixin.ts +10 -0
  172. package/src/utils/minify-brand-page-metadata.ts +407 -0
@@ -0,0 +1,234 @@
1
+ import type { BuilderResult } from '../../models';
2
+ import type {
3
+ TextContent,
4
+ TextItem,
5
+ TextStyleDeclaration,
6
+ BrandPageModel,
7
+ } from '../../../brand-page/models';
8
+ import { updateRTEText } from '../../helpers/rte.helpers';
9
+ import type {
10
+ TextSectionArguments,
11
+ TextSectionItem,
12
+ } from '../../parsers/section-tool-arguments';
13
+ import { applyScalarFields, applyStyleFields } from '../section-field-utils';
14
+ import { BaseSectionBuilder } from './base-section-builder';
15
+
16
+ const createTextItem = (
17
+ item: TextSectionItem,
18
+ baseStyle: TextStyleDeclaration,
19
+ ): TextItem => {
20
+ const textStyle = (
21
+ item.text_style ? { ...baseStyle, ...item.text_style } : { ...baseStyle }
22
+ ) as TextStyleDeclaration;
23
+
24
+ return {
25
+ text: item.text || '',
26
+ textStyle,
27
+ };
28
+ };
29
+
30
+ const applyTextItems = (
31
+ content: TextContent,
32
+ items: TextSectionItem[],
33
+ ): void => {
34
+ content.textItems = items.map((item) =>
35
+ createTextItem(item, content.textStyle || {}),
36
+ );
37
+ };
38
+
39
+ const mergeTextItems = (
40
+ content: TextContent,
41
+ items: TextSectionItem[],
42
+ replaceAll: boolean,
43
+ ): void => {
44
+ const hasIndexedItems = items.some((item) => item.item_index !== undefined);
45
+
46
+ if (hasIndexedItems) {
47
+ for (const item of items) {
48
+ const targetIndex = item.item_index;
49
+
50
+ if (
51
+ targetIndex === undefined ||
52
+ targetIndex < 0 ||
53
+ !content.textItems?.[targetIndex]
54
+ )
55
+ continue;
56
+
57
+ const existing = content.textItems[targetIndex];
58
+
59
+ if (item.text !== undefined) existing.text = item.text;
60
+
61
+ if (item.text_style && typeof item.text_style === 'object') {
62
+ const mergedStyle = (
63
+ item.text_style
64
+ ? { ...(existing.textStyle || {}), ...item.text_style }
65
+ : existing.textStyle || {}
66
+ ) as TextStyleDeclaration;
67
+
68
+ existing.textStyle = mergedStyle;
69
+ existing.text = updateRTEText(existing.text, mergedStyle);
70
+ }
71
+ }
72
+
73
+ return;
74
+ }
75
+
76
+ const updatedItems = items.map((item, index) => {
77
+ const existing = content.textItems?.[index];
78
+ const baseStyle = existing?.textStyle || content.textStyle || {};
79
+
80
+ const mergedStyle = (
81
+ item.text_style ? { ...baseStyle, ...item.text_style } : baseStyle
82
+ ) as TextStyleDeclaration;
83
+
84
+ const text = item.text ?? existing?.text ?? '';
85
+
86
+ return {
87
+ text: item.text_style ? updateRTEText(text, mergedStyle) : text,
88
+ textStyle: mergedStyle,
89
+ };
90
+ });
91
+
92
+ if (
93
+ !replaceAll &&
94
+ content.textItems &&
95
+ content.textItems.length > items.length
96
+ ) {
97
+ updatedItems.push(...content.textItems.slice(items.length));
98
+ }
99
+
100
+ content.textItems = updatedItems;
101
+ };
102
+
103
+ /**
104
+ * Handles the single `text` + `text_style` fields (BrandPage/BrandContact
105
+ * or shorthand for the first item).
106
+ */
107
+ const applySingleTextField = (
108
+ content: TextContent,
109
+ args: TextSectionArguments,
110
+ ): void => {
111
+ if (typeof args.text === 'string') {
112
+ if (Array.isArray(content.textItems) && content.textItems.length > 0) {
113
+ content.textItems[0] = {
114
+ ...content.textItems[0],
115
+ text: args.text,
116
+ };
117
+ } else {
118
+ content.textItems = [
119
+ {
120
+ text: args.text,
121
+ textStyle: { ...(content.textStyle || {}) },
122
+ },
123
+ ];
124
+ }
125
+ }
126
+
127
+ if (
128
+ args.text_style &&
129
+ typeof args.text_style === 'object' &&
130
+ Array.isArray(content.textItems) &&
131
+ content.textItems.length > 0
132
+ ) {
133
+ const mergedStyle = {
134
+ ...(content.textItems[0].textStyle || {}),
135
+ ...(content.textStyle || {}),
136
+ };
137
+
138
+ content.textItems[0] = {
139
+ ...content.textItems[0],
140
+ textStyle: mergedStyle,
141
+ text: updateRTEText(content.textItems[0].text, mergedStyle),
142
+ };
143
+ }
144
+ };
145
+
146
+ export class TextSectionBuilder extends BaseSectionBuilder<TextContent> {
147
+ async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
148
+ this.log('Build called');
149
+
150
+ const validation = await this.validateAsync();
151
+
152
+ if (validation.errors.length) {
153
+ return { request: this.request, model, errors: validation.errors };
154
+ }
155
+
156
+ const resolved = this.resolveContent(model);
157
+
158
+ if ('error' in resolved) return resolved.error;
159
+
160
+ const content = resolved.content;
161
+
162
+ applyScalarFields(content, this.args, this.sectionType);
163
+ applyStyleFields(content, this.args, this.sectionType);
164
+
165
+ const args = this.args as TextSectionArguments;
166
+
167
+ if (args.items && Array.isArray(args.items)) {
168
+ if (this.mode === 'create') {
169
+ applyTextItems(content, args.items);
170
+ } else {
171
+ mergeTextItems(content, args.items, !!args._replace_items);
172
+ }
173
+ } else {
174
+ applySingleTextField(content, args);
175
+ }
176
+
177
+ // Phase 2: Item operations (remove, insert, reorder)
178
+ if (args._remove_item_indices?.length && content.textItems) {
179
+ const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
180
+
181
+ for (const idx of sorted) {
182
+ if (idx >= 0 && idx < content.textItems.length) {
183
+ content.textItems.splice(idx, 1);
184
+ }
185
+ }
186
+ }
187
+
188
+ if (args.items && Array.isArray(args.items)) {
189
+ const insertItems = args.items.filter(
190
+ (item) => item.insert_at !== undefined && item.item_index === undefined,
191
+ );
192
+ const sortedInserts = [...insertItems].sort(
193
+ (a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0),
194
+ );
195
+
196
+ for (const item of sortedInserts) {
197
+ const pos = Math.max(
198
+ 0,
199
+ Math.min(item.insert_at ?? 0, content.textItems?.length ?? 0),
200
+ );
201
+ const baseStyle = content.textStyle || {};
202
+ const textStyle = (
203
+ item.text_style
204
+ ? { ...baseStyle, ...item.text_style }
205
+ : { ...baseStyle }
206
+ ) as TextStyleDeclaration;
207
+ const newItem: TextItem = {
208
+ text: item.text ?? '',
209
+ textStyle,
210
+ };
211
+
212
+ content.textItems = content.textItems ?? [];
213
+ content.textItems.splice(pos, 0, newItem);
214
+ }
215
+ }
216
+
217
+ if (args._reorder_items?.length && content.textItems) {
218
+ const newOrder = args._reorder_items;
219
+ const unique = new Set(newOrder);
220
+
221
+ if (unique.size === newOrder.length) {
222
+ const reordered = newOrder
223
+ .filter((idx) => idx >= 0 && idx < content.textItems!.length)
224
+ .map((idx) => content.textItems![idx]);
225
+
226
+ if (reordered.length === content.textItems.length) {
227
+ content.textItems = reordered;
228
+ }
229
+ }
230
+ }
231
+
232
+ return { request: this.request, model };
233
+ }
234
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Factory that creates default Content objects for each section type.
3
+ * Used by BaseSectionBuilder when adding a new section to a page.
4
+ */
5
+ import { CONTENT_SETTINGS_CONFIGS } from '../../brand-page-maker/constants/config-settings-constant';
6
+ import type { Content, ContentType } from '../../brand-page/models';
7
+ import type { SectionType } from '../parsers/section-tool-arguments';
8
+
9
+ export const createDefaultSectionContent = (
10
+ sectionType: SectionType,
11
+ ): Content | null => {
12
+ const configs = CONTENT_SETTINGS_CONFIGS();
13
+ const config = configs[sectionType as ContentType];
14
+
15
+ if (!config) {
16
+ return null;
17
+ }
18
+
19
+ return config.createInitialContent() as Content;
20
+ };
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Maps AI tool call argument names → BrandPageModel content field names
3
+ * per section type.
4
+ *
5
+ * Reference: service.ai-pipelines/src/agents/website/section_schemas.py
6
+ * Reference: library.brand-page/src/brand-page/models/index.ts
7
+ */
8
+
9
+ import type {
10
+ Content,
11
+ BannerSectionContent,
12
+ AboutSectionContent,
13
+ TestimonialSectionContent,
14
+ ImagesSectionContent,
15
+ ShapesSectionContent,
16
+ TextContent,
17
+ ProfileContent,
18
+ ContactFormContent,
19
+ MailingListContent,
20
+ SocialLinksContent,
21
+ DividerSectionContent,
22
+ ImageContent,
23
+ ButtonContent,
24
+ ImageGalleryContent,
25
+ } from '../../brand-page/models';
26
+
27
+ export type SectionFieldMap = Record<string, string>;
28
+
29
+ /**
30
+ * Identity function that validates field map values are actual properties
31
+ * of the given Content subtype at compile time.
32
+ */
33
+ function defineFieldMap<C extends Content>(
34
+ map: Record<string, keyof C & string>,
35
+ ): Record<string, keyof C & string> {
36
+ return map;
37
+ }
38
+
39
+ const bannerFields = defineFieldMap<BannerSectionContent>({
40
+ title: 'title',
41
+ description: 'description',
42
+ button_text: 'buttonText',
43
+ button_url: 'url',
44
+ background_image_url: 'imageUrl',
45
+ display_style: 'displayStyle',
46
+ });
47
+
48
+ const aboutFields = defineFieldMap<AboutSectionContent>({
49
+ title: 'title',
50
+ paragraph: 'paragraph',
51
+ about_image_url: 'imageUrl',
52
+ display_style: 'displayStyle',
53
+ });
54
+
55
+ const testimonialFields = defineFieldMap<TestimonialSectionContent>({
56
+ title: 'title',
57
+ display_style: 'displayStyle',
58
+ // items handled separately
59
+ });
60
+
61
+ const imagesSectionFields = defineFieldMap<ImagesSectionContent>({
62
+ title: 'title',
63
+ description: 'description',
64
+ display_style: 'displayStyle',
65
+ aspect_ratio: 'aspectRatio',
66
+ are_images_full_width: 'areImagesFullWidth',
67
+ // items handled separately
68
+ });
69
+
70
+ const shapesSectionFields = defineFieldMap<ShapesSectionContent>({
71
+ title: 'title',
72
+ description: 'description',
73
+ display_style: 'displayStyle',
74
+ size: 'size',
75
+ // items handled separately
76
+ });
77
+
78
+ const textFields = defineFieldMap<TextContent>({
79
+ text: 'text',
80
+ });
81
+
82
+ const profileFields = defineFieldMap<ProfileContent>({
83
+ name: 'name',
84
+ title: 'title',
85
+ description: 'description',
86
+ avatar_image_url: 'avatarImageUrl',
87
+ });
88
+
89
+ const contactFormFields = defineFieldMap<ContactFormContent>({
90
+ title: 'title',
91
+ description: 'description',
92
+ button_text: 'buttonText',
93
+ success_message: 'successMessage',
94
+ response_email: 'responseEmail',
95
+ header_image_url: 'imageUrl',
96
+ display_style: 'displayStyle',
97
+ layout_style: 'layoutStyle',
98
+ });
99
+
100
+ const mailingListFields = defineFieldMap<MailingListContent>({
101
+ title: 'title',
102
+ button_text: 'buttonText',
103
+ });
104
+
105
+ const socialLinksFields = defineFieldMap<SocialLinksContent>({
106
+ title: 'title',
107
+ icon_style: 'iconDisplayStyle',
108
+ // links handled separately
109
+ });
110
+
111
+ const dividerFields = defineFieldMap<DividerSectionContent>({
112
+ divider_style: 'dividerStyle',
113
+ });
114
+
115
+ const imageFields = defineFieldMap<ImageContent>({
116
+ image_url: 'imageUrl',
117
+ image_title: 'imageTitle',
118
+ });
119
+
120
+ const imageGalleryFields = defineFieldMap<ImageGalleryContent>({
121
+ // images handled separately
122
+ });
123
+
124
+ const buttonWithIconFields = defineFieldMap<ButtonContent>({
125
+ title: 'title',
126
+ url: 'url',
127
+ icon_image_url: 'iconUrl',
128
+ });
129
+
130
+ const emailButtonFields = defineFieldMap<ButtonContent>({
131
+ title: 'title',
132
+ email: 'url',
133
+ icon_image_url: 'iconUrl',
134
+ });
135
+
136
+ const phoneButtonFields = defineFieldMap<ButtonContent>({
137
+ title: 'title',
138
+ phone_number: 'url',
139
+ icon_image_url: 'iconUrl',
140
+ });
141
+
142
+ const socialButtonFields = defineFieldMap<ButtonContent>({
143
+ title: 'title',
144
+ url: 'url',
145
+ });
146
+
147
+ export const SECTION_FIELD_MAPS: Record<string, SectionFieldMap> = {
148
+ 'banner-section': bannerFields,
149
+ 'about-section': aboutFields,
150
+ 'testimonial-section': testimonialFields,
151
+ 'images-section': imagesSectionFields,
152
+ 'shapes-section': shapesSectionFields,
153
+ text: textFields,
154
+ profile: profileFields,
155
+ 'contact-form': contactFormFields,
156
+ 'mailing-list': mailingListFields,
157
+ 'social-links': socialLinksFields,
158
+ 'divider-section': dividerFields,
159
+ image: imageFields,
160
+ 'image-gallery': imageGalleryFields,
161
+ 'general-link': buttonWithIconFields,
162
+ website: buttonWithIconFields,
163
+ email: emailButtonFields,
164
+ phone: phoneButtonFields,
165
+ facebook: socialButtonFields,
166
+ x: socialButtonFields,
167
+ instagram: socialButtonFields,
168
+ youtube: socialButtonFields,
169
+ linkedin: socialButtonFields,
170
+ pinterest: socialButtonFields,
171
+ twitch: socialButtonFields,
172
+ tiktok: socialButtonFields,
173
+ };
174
+
175
+ export const getSectionFieldMap = (
176
+ sectionType: string,
177
+ ): SectionFieldMap | undefined => {
178
+ return SECTION_FIELD_MAPS[sectionType];
179
+ };
180
+
181
+ const bannerStyleFields = defineFieldMap<BannerSectionContent>({
182
+ title_style: 'titleStyle',
183
+ description_style: 'descriptionStyle',
184
+ button_text_style: 'buttonTextStyle',
185
+ });
186
+
187
+ const aboutStyleFields = defineFieldMap<AboutSectionContent>({
188
+ title_style: 'titleStyle',
189
+ paragraph_style: 'paragraphStyle',
190
+ });
191
+
192
+ const textStyleFields = defineFieldMap<TextContent>({
193
+ text_style: 'textStyle',
194
+ });
195
+
196
+ const testimonialStyleFields = defineFieldMap<TestimonialSectionContent>({
197
+ title_style: 'titleStyle',
198
+ testimonial_style: 'testimonialStyle',
199
+ name_style: 'nameStyle',
200
+ position_style: 'positionStyle',
201
+ });
202
+
203
+ const contactFormStyleFields = defineFieldMap<ContactFormContent>({
204
+ title_style: 'titleStyle',
205
+ description_style: 'descriptionStyle',
206
+ button_text_style: 'buttonTextStyle',
207
+ });
208
+
209
+ const imagesSectionStyleFields = defineFieldMap<ImagesSectionContent>({
210
+ title_style: 'titleStyle',
211
+ description_style: 'descriptionStyle',
212
+ heading_style: 'headingStyle',
213
+ paragraph_style: 'paragraphStyle',
214
+ });
215
+
216
+ const shapesSectionStyleFields = defineFieldMap<ShapesSectionContent>({
217
+ title_style: 'titleStyle',
218
+ description_style: 'descriptionStyle',
219
+ heading_style: 'headingStyle',
220
+ paragraph_style: 'paragraphStyle',
221
+ });
222
+
223
+ export const STYLE_FIELD_MAPS: Record<string, SectionFieldMap> = {
224
+ 'banner-section': bannerStyleFields,
225
+ 'about-section': aboutStyleFields,
226
+ text: textStyleFields,
227
+ 'testimonial-section': testimonialStyleFields,
228
+ 'contact-form': contactFormStyleFields,
229
+ 'images-section': imagesSectionStyleFields,
230
+ 'shapes-section': shapesSectionStyleFields,
231
+ };
232
+
233
+ export const getStyleFieldMap = (
234
+ sectionType: string,
235
+ ): SectionFieldMap | undefined => STYLE_FIELD_MAPS[sectionType];
@@ -0,0 +1,157 @@
1
+ import type { Content, TextStyleDeclaration } from '../../brand-page/models';
2
+ import { RTE_ALLOWED_SECTION_TYPES } from '../../brand-page-maker/utils';
3
+ import { updateRTEText } from '../helpers/rte.helpers';
4
+ import type { SectionToolArguments } from '../parsers/section-tool-arguments';
5
+ import { getSectionFieldMap, getStyleFieldMap } from './section-field-maps';
6
+
7
+ /**
8
+ * Content subtypes (BannerSectionContent, TextContent, etc.) extend the base
9
+ * Content interface with additional properties. These utility functions apply
10
+ * fields dynamically via field maps resolved at runtime, so static typing
11
+ * can't know the concrete subtype.
12
+ *
13
+ * IndexableContent captures this: it's Content that can be indexed by string
14
+ * keys — a valid downcast because every Content instance at runtime is a
15
+ * subtype with extra properties beyond the base interface.
16
+ */
17
+ type IndexableContent = Content & Record<string, unknown>;
18
+
19
+ const isUrlField = (aiKey: string): boolean => aiKey.endsWith('_url');
20
+
21
+ const isValidUrl = (value: unknown): boolean => {
22
+ if (typeof value !== 'string' || !value) return false;
23
+
24
+ try {
25
+ new URL(value);
26
+
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ };
32
+
33
+ export const applyScalarFields = (
34
+ content: Content,
35
+ args: SectionToolArguments,
36
+ sectionType: string,
37
+ ): void => {
38
+ const fieldMap = getSectionFieldMap(sectionType);
39
+
40
+ if (!fieldMap) return;
41
+
42
+ const record = content as IndexableContent;
43
+
44
+ for (const [aiKey, modelKey] of Object.entries(fieldMap)) {
45
+ if (aiKey in args) {
46
+ // Skip invalid URLs — don't overwrite model with malformed values
47
+ if (isUrlField(aiKey) && !isValidUrl(args[aiKey])) continue;
48
+
49
+ record[modelKey] = args[aiKey];
50
+ }
51
+ }
52
+ };
53
+
54
+ /**
55
+ * Convert AI-sent style value to the format expected by the model.
56
+ * - letterSpacing: unitless 0-100 → em string (e.g., 50 → '0.5em')
57
+ * - null → '' (explicitly clear/reset the property)
58
+ * - everything else passes through as-is
59
+ */
60
+ const normalizeStyleValue = (key: string, value: unknown): unknown => {
61
+ if (value === null) return '';
62
+
63
+ if (value === undefined) return undefined;
64
+
65
+ if (key === 'letterSpacing' && typeof value === 'number') {
66
+ return `${value / 100}em`;
67
+ }
68
+
69
+ if (key === 'lineHeight' && typeof value === 'number') {
70
+ return String(value);
71
+ }
72
+
73
+ return value;
74
+ };
75
+
76
+ export const applyStyleFields = (
77
+ content: Content,
78
+ args: SectionToolArguments,
79
+ sectionType: string,
80
+ ): void => {
81
+ const styleMap = getStyleFieldMap(sectionType);
82
+
83
+ if (!styleMap) return;
84
+
85
+ const record = content as IndexableContent;
86
+
87
+ for (const [aiKey, modelKey] of Object.entries(styleMap)) {
88
+ if (aiKey in args && args[aiKey] && typeof args[aiKey] === 'object') {
89
+ const existing = (content[modelKey] as Record<string, unknown>) || {};
90
+ const aiStyle = args[aiKey] as Record<string, unknown>;
91
+
92
+ const filtered: Record<string, unknown> = {};
93
+
94
+ for (const [k, v] of Object.entries(aiStyle)) {
95
+ const normalized = normalizeStyleValue(k, v);
96
+
97
+ if (normalized !== undefined) filtered[k] = normalized;
98
+ }
99
+
100
+ record[modelKey] = {
101
+ ...existing,
102
+ ...filtered,
103
+ textStyleType: undefined,
104
+ };
105
+ }
106
+ }
107
+ };
108
+
109
+ /**
110
+ * RTE field → style field pairs for fields that support rich text editing.
111
+ * Matches the RTE_ALLOWED_FIELDS constant from rich-text-editor.util.ts:
112
+ * ['title', 'description', 'text', 'textItems']
113
+ *
114
+ * textItems is excluded here because it requires per-item handling
115
+ * (each TextItem has its own textStyle) — handled by TextSectionBuilder.
116
+ */
117
+ const RTE_FIELD_STYLE_PAIRS: {
118
+ textField: string;
119
+ styleField: string;
120
+ argKey: string;
121
+ }[] = [
122
+ { textField: 'title', styleField: 'titleStyle', argKey: 'title_style' },
123
+ {
124
+ textField: 'description',
125
+ styleField: 'descriptionStyle',
126
+ argKey: 'description_style',
127
+ },
128
+ { textField: 'text', styleField: 'textStyle', argKey: 'text_style' },
129
+ ];
130
+
131
+ /**
132
+ * Re-applies merged styles to RTE content fields after applyStyleFields has
133
+ * updated the style objects. Only runs for section types that support RTE
134
+ * (defined by RTE_ALLOWED_SECTION_TYPES) and only touches fields in the
135
+ * RTE_ALLOWED_FIELDS list.
136
+ */
137
+ export const applyRTEStyles = (
138
+ content: Content,
139
+ args: SectionToolArguments,
140
+ sectionType: string,
141
+ ): void => {
142
+ if (!RTE_ALLOWED_SECTION_TYPES.includes(sectionType)) return;
143
+
144
+ const record = content as IndexableContent;
145
+ const argsRecord = args as Record<string, unknown>;
146
+
147
+ for (const { textField, styleField, argKey } of RTE_FIELD_STYLE_PAIRS) {
148
+ if (!argsRecord[argKey]) continue;
149
+
150
+ const text = record[textField];
151
+ const style = record[styleField];
152
+
153
+ if (typeof text === 'string' && style) {
154
+ record[textField] = updateRTEText(text, style as TextStyleDeclaration);
155
+ }
156
+ }
157
+ };
@@ -1,5 +1,6 @@
1
1
  import type { TextStyleDeclaration } from '../../brand-page';
2
- import { getRTEColor } from '../../brand-page-maker/utils';
2
+ import { getRTEColor, hasRTEContent } from '../../brand-page-maker/utils';
3
+ import { useExternalRichTextEditor } from '../../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
3
4
  import { getRenderText } from '../../brand-page/utils';
4
5
  import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
5
6
  import type { Editor } from '@tiptap/vue-3';
@@ -72,3 +73,24 @@ export const applyStyleToRTEText = (
72
73
 
73
74
  return html;
74
75
  };
76
+
77
+ /**
78
+ * If `text` contains RTE markup, re-applies `style` to the RTE content
79
+ * so inline formatting matches the updated TextStyleDeclaration.
80
+ * Returns the original text unchanged when it has no RTE content.
81
+ */
82
+ export const updateRTEText = (
83
+ text: string,
84
+ style: TextStyleDeclaration,
85
+ ): string => {
86
+ if (!text || typeof text !== 'string' || !hasRTEContent(text)) {
87
+ return text;
88
+ }
89
+
90
+ const { externalEditor } = useExternalRichTextEditor();
91
+ const updatedText = applyStyleToRTEText(externalEditor, text, style);
92
+
93
+ externalEditor.destroy();
94
+
95
+ return updatedText;
96
+ };