@designcrowd/library.brand-page 6.0.26-json-prereq-2 → 6.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-CKBqjDH3.js → CreatedOnBrandCrowd-de-de-DU6YEVcW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CRXiimvT.js → CreatedOnBrandCrowd-en-us-DNWWK6Tj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DCYdY6sj.js → CreatedOnBrandCrowd-es-es-DVFvV9Qp.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-ca-B4ZkjNdw.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-fr-B4ZkjNdw.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-br-DP7AQGgW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-pt-DP7AQGgW.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-ZSLkWMwM.js → CreatedOnDcom-de-de-CaC3GtG9.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BgdIGfT3.js → CreatedOnDcom-en-us-D16yT0-P.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-D0KemkCZ.js → CreatedOnDcom-es-es-DA5sHgD_.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BuwNjNbC.js → CreatedOnDcom-fr-ca-CDv2SG1-.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DCQcsFPf.js → CreatedOnDcom-fr-fr-Cygc7c5d.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DD-oLAS1.js → CreatedOnDcom-pt-br-B_3RIzgF.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-D8qLq2wW.js → CreatedOnDcom-pt-pt-Bz5w-rrD.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +2 -5
- package/lib/src/brand-page/models/index.d.ts +26 -34
- package/lib/src/brand-page/utils/migration.util.js +0 -11
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +0 -32
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
- package/lib/src/brand-page/utils/view-model.util.js +20 -68
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +103 -24
- package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
- package/lib/src/brand-page-maker/utils/index.js +0 -2
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
- package/lib/src/css/library.brand-page-designCom.min.css +39 -39
- package/lib/src/css/library.brand-page-preview.min.css +39 -39
- package/lib/src/index.d.ts +0 -1
- package/lib/src/index.mjs +138 -143
- package/lib/src/{lib-Db6j9fQZ.js → lib-6_P6kXPu.js} +51709 -55194
- package/lib/src/lib.js +0 -2
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
- package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +11 -15
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
- package/lib/src/metadata-builder/metadata-executor.js +4 -0
- package/lib/src/metadata-builder/models.d.ts +2 -0
- package/lib/src/metadata-builder/models.js +2 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +19 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
- package/lib/src/utils/minify-brand-page-metadata.js +1 -4
- package/package.json +15 -17
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +2 -5
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
- package/src/brand-page/models/index.ts +26 -35
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration.util.ts +0 -12
- package/src/brand-page/utils/style.util.test.ts +0 -51
- package/src/brand-page/utils/text.util.ts +4 -43
- package/src/brand-page/utils/vcard.util.ts +4 -6
- package/src/brand-page/utils/view-model.util.ts +26 -94
- package/src/brand-page/view-models.ts +2 -3
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +3 -4
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
- package/src/brand-page-maker/utils/index.ts +0 -2
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
- package/src/brand-page-maker/validators/field.validator.ts +2 -10
- package/src/lib.ts +0 -9
- package/src/metadata-builder/ai-metadata-executor.ts +26 -0
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +13 -19
- package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
- package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
- package/src/metadata-builder/metadata-executor.ts +5 -0
- package/src/metadata-builder/models.ts +3 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +27 -0
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.ts +1 -4
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -90
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
- package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
- package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
- package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-20.ts +0 -87
- package/src/brand-page/utils/text.util.test.ts +0 -47
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -137
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
- package/src/brand-page-maker/utils/rte-features.test.ts +0 -351
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -157
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
- package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type BrandPageModel } from '../../brand-page/models';
|
|
2
|
+
import type { Logger } from '../logger';
|
|
3
|
+
import {
|
|
4
|
+
BaseMetadataBuilder,
|
|
5
|
+
BUILDER_TYPES,
|
|
6
|
+
type BuilderResult,
|
|
7
|
+
type MetadataBuilderRequest,
|
|
8
|
+
} from '../models';
|
|
9
|
+
import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
|
|
10
|
+
|
|
11
|
+
export interface UpdateTextPayload {
|
|
12
|
+
contentId: string;
|
|
13
|
+
fieldName: string;
|
|
14
|
+
newText: string;
|
|
15
|
+
maxCharacters?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const buildUpdateTextRequest = (
|
|
19
|
+
id: string,
|
|
20
|
+
payload: UpdateTextPayload,
|
|
21
|
+
): MetadataBuilderRequest<UpdateTextPayload> => {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
type: BUILDER_TYPES.UPDATE_CONTENT_TEXT,
|
|
25
|
+
payload,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const updateTextField = (target: any, fieldName: string, newText: string) => {
|
|
30
|
+
// Check if this is an array item path like "textItems.0.text"
|
|
31
|
+
const match = fieldName.match(/^(\w+)\.(\d+)\.(\w+)$/);
|
|
32
|
+
|
|
33
|
+
if (match) {
|
|
34
|
+
const [, arrayName, index, property] = match;
|
|
35
|
+
const array = target[arrayName];
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(array) && array[+index]) {
|
|
38
|
+
const existingText = array[+index][property];
|
|
39
|
+
const updatedValue =
|
|
40
|
+
existingText &&
|
|
41
|
+
typeof existingText === 'string' &&
|
|
42
|
+
hasRTEContent(existingText)
|
|
43
|
+
? replaceTextInRTE(existingText, newText)
|
|
44
|
+
: newText;
|
|
45
|
+
|
|
46
|
+
// Create a new array with the updated item to trigger reactivity
|
|
47
|
+
const newArray = [...array];
|
|
48
|
+
|
|
49
|
+
newArray[+index] = {
|
|
50
|
+
...newArray[+index],
|
|
51
|
+
[property]: updatedValue,
|
|
52
|
+
};
|
|
53
|
+
target[arrayName] = newArray;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// Simple field
|
|
57
|
+
const existingText = target[fieldName];
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
existingText &&
|
|
61
|
+
typeof existingText === 'string' &&
|
|
62
|
+
hasRTEContent(existingText)
|
|
63
|
+
) {
|
|
64
|
+
target[fieldName] = replaceTextInRTE(existingText, newText);
|
|
65
|
+
} else {
|
|
66
|
+
target[fieldName] = newText;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export class UpdateContentTextBuilder extends BaseMetadataBuilder<UpdateTextPayload> {
|
|
72
|
+
constructor(
|
|
73
|
+
request: MetadataBuilderRequest<UpdateTextPayload>,
|
|
74
|
+
logger?: Logger,
|
|
75
|
+
) {
|
|
76
|
+
super(request, logger);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
validateAsync(): Promise<{ errors: string[] }> {
|
|
80
|
+
const newText = this.request.payload?.newText ?? '';
|
|
81
|
+
|
|
82
|
+
const errors: string[] = [];
|
|
83
|
+
|
|
84
|
+
const maxChars = this.request.payload?.maxCharacters ?? 2000;
|
|
85
|
+
|
|
86
|
+
if (newText.length > maxChars) {
|
|
87
|
+
errors.push(`Text exceeds maximum allowed length of ${maxChars}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.log({
|
|
91
|
+
message: `Validation called`,
|
|
92
|
+
errors,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return Promise.resolve({ errors: errors });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
|
|
99
|
+
this.log(`Build called`);
|
|
100
|
+
|
|
101
|
+
const { contentId, fieldName, newText } = this.request.payload!;
|
|
102
|
+
|
|
103
|
+
const validation = await this.validateAsync();
|
|
104
|
+
|
|
105
|
+
if (validation.errors.length) {
|
|
106
|
+
return {
|
|
107
|
+
request: this.request,
|
|
108
|
+
model,
|
|
109
|
+
errors: validation.errors,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let updated = false;
|
|
114
|
+
|
|
115
|
+
for (const page of model.pages) {
|
|
116
|
+
for (const content of page.contents) {
|
|
117
|
+
// Use includes to handle potential ID format differences
|
|
118
|
+
if (content.id.includes(contentId) || contentId.includes(content.id)) {
|
|
119
|
+
this.log({
|
|
120
|
+
message: `Content found`,
|
|
121
|
+
model,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
updateTextField(content, fieldName, newText);
|
|
125
|
+
|
|
126
|
+
this.log({
|
|
127
|
+
message: `Successfully updated text content.`,
|
|
128
|
+
model,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
updated = true;
|
|
132
|
+
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
request: this.request,
|
|
140
|
+
model,
|
|
141
|
+
errors: updated
|
|
142
|
+
? undefined
|
|
143
|
+
: [
|
|
144
|
+
`Could not update the text because content with ID "${contentId}" not found.`,
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BrandPageModel,
|
|
3
|
+
type Content,
|
|
4
|
+
type TextStyleDeclaration,
|
|
5
|
+
type TextStyleOptions,
|
|
6
|
+
} from '../../brand-page/models';
|
|
7
|
+
import {
|
|
8
|
+
BaseMetadataBuilder,
|
|
9
|
+
BUILDER_TYPES,
|
|
10
|
+
type BuilderResult,
|
|
11
|
+
type MetadataBuilderRequest,
|
|
12
|
+
} from '../models';
|
|
13
|
+
import { hasRTEContent } from '../../brand-page-maker/utils';
|
|
14
|
+
import { useExternalRichTextEditor } from '../../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
|
|
15
|
+
import type { Logger } from '../logger';
|
|
16
|
+
import { applyStyleToRTEText } from '../helpers/rte.helpers';
|
|
17
|
+
import { letterSpacingValueConvertor } from '../../brand-page-maker/convertors/letter-spacing-value.convertor';
|
|
18
|
+
|
|
19
|
+
export interface UpdateTextStylePayload {
|
|
20
|
+
contentId: string;
|
|
21
|
+
fieldName: string;
|
|
22
|
+
newStyles: TextStyleDeclaration;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const buildUpdateTextStyleRequest = (
|
|
26
|
+
id: string,
|
|
27
|
+
payload: UpdateTextStylePayload,
|
|
28
|
+
): MetadataBuilderRequest<UpdateTextStylePayload> => {
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
type: BUILDER_TYPES.UPDATE_CONTENT_TEXT_STYLE,
|
|
32
|
+
payload,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const mergeStyles = (
|
|
37
|
+
currentStyle: Partial<TextStyleDeclaration>,
|
|
38
|
+
stylesToUpdate: TextStyleDeclaration,
|
|
39
|
+
textStyleOptions?: TextStyleOptions,
|
|
40
|
+
): Partial<TextStyleDeclaration> => {
|
|
41
|
+
let merged = { ...currentStyle };
|
|
42
|
+
|
|
43
|
+
// If content uses a textStyleType preset (e.g. "titles"), resolve the preset
|
|
44
|
+
// styles into the style object before clearing the preset reference.
|
|
45
|
+
// This ensures properties like fontSize, fontFamily etc. are preserved
|
|
46
|
+
// when the AI only changes a single property like color.
|
|
47
|
+
if (merged.textStyleType && textStyleOptions?.[merged.textStyleType]) {
|
|
48
|
+
const presetStyle = textStyleOptions[merged.textStyleType].style;
|
|
49
|
+
|
|
50
|
+
merged = { ...merged, ...presetStyle };
|
|
51
|
+
merged.textStyleType = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Object.entries(stylesToUpdate).forEach(([key, value]) => {
|
|
55
|
+
if (value === null) {
|
|
56
|
+
// backgroundColor: null means "remove background color"
|
|
57
|
+
if (key === 'backgroundColor') {
|
|
58
|
+
merged.backgroundColor = '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// All other null values mean "not changed" — skip to preserve existing values
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
(merged as Record<string, unknown>)[key] = value;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return merged;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const updateRTEText = (text: string, style: TextStyleDeclaration): string => {
|
|
72
|
+
if (!text || typeof text !== 'string' || !hasRTEContent(text)) {
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { externalEditor } = useExternalRichTextEditor();
|
|
77
|
+
const updatedText = applyStyleToRTEText(externalEditor, text, style);
|
|
78
|
+
|
|
79
|
+
externalEditor.destroy();
|
|
80
|
+
|
|
81
|
+
return updatedText;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const findStyleKey = (target: Content, property: string): string | null => {
|
|
85
|
+
// Try exact match first (e.g., 'testimonialsStyle')
|
|
86
|
+
const exactKey = `${property}Style`;
|
|
87
|
+
|
|
88
|
+
if (exactKey in target) return exactKey;
|
|
89
|
+
|
|
90
|
+
// Try singular form (e.g., 'testimonialStyle' for 'testimonials')
|
|
91
|
+
if (property.endsWith('s')) {
|
|
92
|
+
const singularKey = `${property.slice(0, -1)}Style`;
|
|
93
|
+
|
|
94
|
+
if (singularKey in target) return singularKey;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Try plural form (edge case)
|
|
98
|
+
const pluralKey = `${property}sStyle`;
|
|
99
|
+
|
|
100
|
+
if (pluralKey in target) return pluralKey;
|
|
101
|
+
|
|
102
|
+
return exactKey; // fallback to exact match
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const updateSharedArrayStyle = (
|
|
106
|
+
content: Content,
|
|
107
|
+
property: string,
|
|
108
|
+
stylesToUpdate: TextStyleDeclaration,
|
|
109
|
+
textStyleOptions?: TextStyleOptions,
|
|
110
|
+
) => {
|
|
111
|
+
const styleKey = findStyleKey(content, property);
|
|
112
|
+
|
|
113
|
+
if (!styleKey) return;
|
|
114
|
+
|
|
115
|
+
const currentStyle = content[styleKey] || {};
|
|
116
|
+
const mergedStyle = mergeStyles(
|
|
117
|
+
currentStyle,
|
|
118
|
+
stylesToUpdate,
|
|
119
|
+
textStyleOptions,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
content[styleKey] = mergedStyle;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const updateArrayItemStyle = (
|
|
126
|
+
content: Content,
|
|
127
|
+
fieldName: string,
|
|
128
|
+
index: number,
|
|
129
|
+
property: string,
|
|
130
|
+
stylesToUpdate: TextStyleDeclaration,
|
|
131
|
+
textStyleOptions?: TextStyleOptions,
|
|
132
|
+
) => {
|
|
133
|
+
const currentContentItems = content[fieldName];
|
|
134
|
+
|
|
135
|
+
if (!Array.isArray(currentContentItems) || !currentContentItems[+index])
|
|
136
|
+
return;
|
|
137
|
+
|
|
138
|
+
const contentItem = currentContentItems[+index];
|
|
139
|
+
const styleKey = `${property}Style`;
|
|
140
|
+
const currentStyle =
|
|
141
|
+
(contentItem[styleKey] as Partial<TextStyleDeclaration>) || {};
|
|
142
|
+
const mergedStyle = mergeStyles(
|
|
143
|
+
currentStyle,
|
|
144
|
+
stylesToUpdate,
|
|
145
|
+
textStyleOptions,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const newContentItems = [...currentContentItems];
|
|
149
|
+
|
|
150
|
+
newContentItems[+index] = {
|
|
151
|
+
...newContentItems[+index],
|
|
152
|
+
[styleKey]: mergedStyle,
|
|
153
|
+
[property]: updateRTEText(contentItem[property], mergedStyle),
|
|
154
|
+
};
|
|
155
|
+
content[fieldName] = newContentItems;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const updateTextStyle = (
|
|
159
|
+
content: Content,
|
|
160
|
+
fieldName: string,
|
|
161
|
+
stylesToUpdate: TextStyleDeclaration,
|
|
162
|
+
textStyleOptions?: TextStyleOptions,
|
|
163
|
+
) => {
|
|
164
|
+
// Check if fieldName is in the format of "fieldName.index.fieldItem"
|
|
165
|
+
const matchFieldItems = fieldName.match(/^(\w+)\.(\d+)\.(.+)$/);
|
|
166
|
+
|
|
167
|
+
if (matchFieldItems) {
|
|
168
|
+
const [, fieldName, index, fieldItem] = matchFieldItems;
|
|
169
|
+
const array = content[fieldName];
|
|
170
|
+
|
|
171
|
+
if (!Array.isArray(array) || !array[+index]) return;
|
|
172
|
+
|
|
173
|
+
const item = array[+index];
|
|
174
|
+
const itemStyleKey = `${fieldItem}Style`;
|
|
175
|
+
const hasItemStyle = itemStyleKey in item;
|
|
176
|
+
|
|
177
|
+
if (hasItemStyle) {
|
|
178
|
+
updateArrayItemStyle(
|
|
179
|
+
content,
|
|
180
|
+
fieldName,
|
|
181
|
+
+index,
|
|
182
|
+
fieldItem,
|
|
183
|
+
stylesToUpdate,
|
|
184
|
+
textStyleOptions,
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
updateSharedArrayStyle(
|
|
188
|
+
content,
|
|
189
|
+
fieldItem,
|
|
190
|
+
stylesToUpdate,
|
|
191
|
+
textStyleOptions,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const styleKey = `${fieldName}Style`;
|
|
196
|
+
const currentStyle =
|
|
197
|
+
(content[styleKey] as Partial<TextStyleDeclaration>) || {};
|
|
198
|
+
const mergedStyle = mergeStyles(
|
|
199
|
+
currentStyle,
|
|
200
|
+
stylesToUpdate,
|
|
201
|
+
textStyleOptions,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
content[styleKey] = mergedStyle;
|
|
205
|
+
content[fieldName] = updateRTEText(content[fieldName], mergedStyle);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export class UpdateContentTextStyleBuilder extends BaseMetadataBuilder<UpdateTextStylePayload> {
|
|
210
|
+
constructor(
|
|
211
|
+
request: MetadataBuilderRequest<UpdateTextStylePayload>,
|
|
212
|
+
logger?: Logger,
|
|
213
|
+
) {
|
|
214
|
+
super(request, logger);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
validateAsync(): Promise<{ errors: string[] }> {
|
|
218
|
+
const { newStyles } = this.request.payload!;
|
|
219
|
+
const errors: string[] = [];
|
|
220
|
+
|
|
221
|
+
// Validate lineHeight: must be between 0 and 2.5 inclusive, snap to 0.1 step
|
|
222
|
+
if (newStyles.lineHeight !== undefined && newStyles.lineHeight !== null) {
|
|
223
|
+
const lineHeightValue = parseFloat(String(newStyles.lineHeight));
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
isNaN(lineHeightValue) ||
|
|
227
|
+
lineHeightValue < 0 ||
|
|
228
|
+
lineHeightValue > 2.5
|
|
229
|
+
) {
|
|
230
|
+
errors.push('Line height must be a value between 0 and 2.5 inclusive.');
|
|
231
|
+
} else {
|
|
232
|
+
// Snap to 0.1 increments to match the UI slider step
|
|
233
|
+
newStyles.lineHeight = String(Math.floor(lineHeightValue * 10) / 10);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate and normalize letterSpacing: AI sends unitless 0-100, convert to em
|
|
238
|
+
if (
|
|
239
|
+
newStyles.letterSpacing !== undefined &&
|
|
240
|
+
newStyles.letterSpacing !== null
|
|
241
|
+
) {
|
|
242
|
+
const letterSpacingStr = String(newStyles.letterSpacing);
|
|
243
|
+
|
|
244
|
+
if (/[a-zA-Z]/.test(letterSpacingStr)) {
|
|
245
|
+
errors.push(
|
|
246
|
+
'Letter spacing must be a unitless value between 0 and 100.',
|
|
247
|
+
);
|
|
248
|
+
} else {
|
|
249
|
+
const letterSpacingValue = parseFloat(letterSpacingStr);
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
isNaN(letterSpacingValue) ||
|
|
253
|
+
letterSpacingValue < 0 ||
|
|
254
|
+
letterSpacingValue > 100
|
|
255
|
+
) {
|
|
256
|
+
errors.push('Letter spacing must be between 0 and 100 inclusive.');
|
|
257
|
+
} else {
|
|
258
|
+
newStyles.letterSpacing = letterSpacingValueConvertor.convertBack(
|
|
259
|
+
String(letterSpacingValue),
|
|
260
|
+
).letterSpacing;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Validate fontSize: must be in 'em' unit and between 0.75em and 8em inclusive
|
|
266
|
+
if (newStyles?.fontSize != null) {
|
|
267
|
+
const fontSizeStr = String(newStyles.fontSize);
|
|
268
|
+
const fontSizeMatch = fontSizeStr.match(/^(-?\d*\.?\d+)em$/);
|
|
269
|
+
|
|
270
|
+
if (!fontSizeMatch) {
|
|
271
|
+
errors.push('Font size must be in "em" unit (e.g., "1.5em").');
|
|
272
|
+
} else {
|
|
273
|
+
const fontSizeValue = parseFloat(fontSizeMatch[1]);
|
|
274
|
+
|
|
275
|
+
if (isNaN(fontSizeValue) || fontSizeValue < 0.75 || fontSizeValue > 8) {
|
|
276
|
+
errors.push(
|
|
277
|
+
'Font size must be between 0.75em and 8em inclusive. If unitless from user always treat as px and convert to em using 16px = 1em ratio.',
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return Promise.resolve({ errors });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
|
|
287
|
+
this.log(`Build called`);
|
|
288
|
+
|
|
289
|
+
const validation = await this.validateAsync();
|
|
290
|
+
|
|
291
|
+
if (validation.errors.length) {
|
|
292
|
+
return {
|
|
293
|
+
request: this.request,
|
|
294
|
+
model,
|
|
295
|
+
errors: validation.errors,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { contentId, fieldName, newStyles } = this.request.payload!;
|
|
300
|
+
|
|
301
|
+
// Filter out undefined values
|
|
302
|
+
const stylesToUpdate: TextStyleDeclaration = Object.entries(
|
|
303
|
+
newStyles,
|
|
304
|
+
).reduce((acc, [key, value]) => {
|
|
305
|
+
if (value !== undefined) {
|
|
306
|
+
acc[key as keyof TextStyleDeclaration] = value;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return acc;
|
|
310
|
+
}, {} as TextStyleDeclaration);
|
|
311
|
+
|
|
312
|
+
let updated = false;
|
|
313
|
+
|
|
314
|
+
for (const page of model.pages) {
|
|
315
|
+
for (const content of page.contents) {
|
|
316
|
+
// Use includes to handle potential ID format differences
|
|
317
|
+
if (content.id.includes(contentId) || contentId.includes(content.id)) {
|
|
318
|
+
this.log({
|
|
319
|
+
message: `Content found`,
|
|
320
|
+
model,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
updateTextStyle(
|
|
324
|
+
content,
|
|
325
|
+
fieldName,
|
|
326
|
+
stylesToUpdate,
|
|
327
|
+
model.styles?.textStyles?.textStyleOptions,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
this.log({
|
|
331
|
+
message: `Successfully updated text style content.`,
|
|
332
|
+
model,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
updated = true;
|
|
336
|
+
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
request: this.request,
|
|
344
|
+
model,
|
|
345
|
+
errors: updated
|
|
346
|
+
? undefined
|
|
347
|
+
: [
|
|
348
|
+
`Could not update the text style because content with ID "${contentId}" not found.`,
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -1,28 +1,114 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { TextStyleDeclaration } from '../../brand-page';
|
|
2
|
+
import { getRTEColor } from '../../brand-page-maker/utils';
|
|
3
|
+
import { useExternalRichTextEditor } from '../../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
|
|
4
|
+
import { getRenderText } from '../../brand-page/utils';
|
|
5
|
+
import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
|
|
6
|
+
import type { Editor } from '@tiptap/vue-3';
|
|
7
|
+
import {
|
|
8
|
+
isTiptapDocument,
|
|
9
|
+
type TiptapDocument,
|
|
10
|
+
} from '../parsers/section-tool-arguments';
|
|
11
|
+
|
|
12
|
+
export const applyStyleToRTEText = (
|
|
13
|
+
externalEditor: Editor,
|
|
14
|
+
contentText: string,
|
|
15
|
+
newContentStyle: TextStyleDeclaration,
|
|
16
|
+
) => {
|
|
17
|
+
if (!contentText) {
|
|
18
|
+
return contentText;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
22
|
+
? getRenderText(contentText)
|
|
23
|
+
: contentText;
|
|
24
|
+
|
|
25
|
+
externalEditor.commands.setContent(content);
|
|
26
|
+
|
|
27
|
+
let chain = externalEditor.chain().selectAll();
|
|
28
|
+
|
|
29
|
+
if (newContentStyle?.fontFamily) {
|
|
30
|
+
chain = chain.setFontFamily(newContentStyle.fontFamily);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (newContentStyle?.fontSize) {
|
|
34
|
+
chain = chain.setFontSize(newContentStyle.fontSize);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (newContentStyle?.letterSpacing) {
|
|
38
|
+
chain = chain.setLetterSpacing(newContentStyle.letterSpacing);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (newContentStyle?.lineHeight) {
|
|
42
|
+
chain = chain.setLineHeight(newContentStyle.lineHeight);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (newContentStyle?.color) {
|
|
46
|
+
chain = chain.setMark('textStyle', {
|
|
47
|
+
color: getRTEColor(newContentStyle.color, 'text'),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle backgroundColor
|
|
52
|
+
if ('backgroundColor' in (newContentStyle || {})) {
|
|
53
|
+
chain = newContentStyle.backgroundColor
|
|
54
|
+
? chain.setMark('highlight', {
|
|
55
|
+
color: getRTEColor(newContentStyle.backgroundColor, 'background'),
|
|
56
|
+
})
|
|
57
|
+
: chain.unsetHighlight();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (newContentStyle?.textAlign) {
|
|
61
|
+
chain = chain.setTextAlign(newContentStyle.textAlign);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const hasStrike = newContentStyle?.textDecoration?.includes('line-through');
|
|
65
|
+
const hasUnderline = newContentStyle?.textDecoration?.includes('underline');
|
|
66
|
+
|
|
67
|
+
chain = hasStrike ? chain.setStrike() : chain.unsetStrike();
|
|
68
|
+
chain = hasUnderline ? chain.setUnderline() : chain.unsetUnderline();
|
|
69
|
+
|
|
70
|
+
chain =
|
|
71
|
+
newContentStyle?.fontWeight === 'bold'
|
|
72
|
+
? chain.setBold()
|
|
73
|
+
: chain.unsetBold();
|
|
74
|
+
chain =
|
|
75
|
+
newContentStyle?.fontStyle === 'italic'
|
|
76
|
+
? chain.setItalic()
|
|
77
|
+
: chain.unsetItalic();
|
|
78
|
+
|
|
79
|
+
chain.run();
|
|
80
|
+
const html = externalEditor.getHTML();
|
|
81
|
+
|
|
82
|
+
return html;
|
|
83
|
+
};
|
|
4
84
|
|
|
5
85
|
/**
|
|
6
86
|
* Converts a Tiptap JSON document (ProseMirror document model) to an HTML
|
|
7
|
-
* string
|
|
8
|
-
* renderToHTMLString). DOM-less and editor-free, so it runs on the server
|
|
9
|
-
* (Nitro/Node), not just the browser — unlike the previous headless-Editor
|
|
10
|
-
* implementation (BP-5787: the AI section builders no longer need a `document`).
|
|
87
|
+
* string using a headless Tiptap Editor instance.
|
|
11
88
|
*
|
|
12
89
|
* Returns the value unchanged if it is already a string.
|
|
13
|
-
* Returns an empty string for null/undefined
|
|
90
|
+
* Returns an empty string for null/undefined.
|
|
14
91
|
*
|
|
15
|
-
* Note:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
92
|
+
* Note: Tiptap's `setContent` does NOT throw on malformed input — it
|
|
93
|
+
* silently drops content and returns `<p></p>`. To prevent saved copy
|
|
94
|
+
* being wiped (BP-5669), structural validity of any AI-emitted Tiptap
|
|
95
|
+
* doc is checked upstream by `rteSchemaCheck` in
|
|
96
|
+
* `section-args-validator.ts` using the editor's real ProseMirror
|
|
97
|
+
* schema. The validator surfaces a clear error into
|
|
98
|
+
* `BuilderResult.errors`, which the AI-chat retry loop reads via
|
|
99
|
+
* `tool_result.errorMessage` and uses to re-emit a corrected payload.
|
|
21
100
|
*/
|
|
22
101
|
export const tiptapJsonToHtml = (value: unknown): string => {
|
|
23
102
|
if (typeof value === 'string') return value;
|
|
24
103
|
|
|
25
104
|
if (!isTiptapDocument(value)) return '';
|
|
26
105
|
|
|
27
|
-
|
|
106
|
+
const { externalEditor } = useExternalRichTextEditor();
|
|
107
|
+
|
|
108
|
+
externalEditor.commands.setContent(value as TiptapDocument);
|
|
109
|
+
const html = externalEditor.getHTML();
|
|
110
|
+
|
|
111
|
+
externalEditor.destroy();
|
|
112
|
+
|
|
113
|
+
return html;
|
|
28
114
|
};
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
BUILDER_TYPES,
|
|
9
9
|
type BuilderResult,
|
|
10
10
|
} from './models';
|
|
11
|
+
import { UpdateContentTextBuilder } from './builders/update-content-text-builder';
|
|
12
|
+
import { UpdateContentTextStyleBuilder } from './builders/update-content-text-style-builder';
|
|
11
13
|
import {
|
|
12
14
|
BannerSectionBuilder,
|
|
13
15
|
BookingSectionBuilder,
|
|
@@ -46,6 +48,9 @@ type BuilderConstructor = new (
|
|
|
46
48
|
|
|
47
49
|
// Lazy initialization to avoid circular dependency issues
|
|
48
50
|
const getBuilderRegistry = (): Record<BuilderType, BuilderConstructor> => ({
|
|
51
|
+
[BUILDER_TYPES.UPDATE_CONTENT_TEXT]: UpdateContentTextBuilder,
|
|
52
|
+
[BUILDER_TYPES.UPDATE_CONTENT_TEXT_STYLE]: UpdateContentTextStyleBuilder,
|
|
53
|
+
|
|
49
54
|
// Section builders (create)
|
|
50
55
|
[BUILDER_TYPES.CREATE_BANNER_SECTION]: BannerSectionBuilder,
|
|
51
56
|
[BUILDER_TYPES.CREATE_BOOKING_SECTION]: BookingSectionBuilder,
|
|
@@ -3,6 +3,9 @@ import type { EventTracker } from '../brand-page-maker/composables/useEventTrack
|
|
|
3
3
|
import type { Logger } from './logger';
|
|
4
4
|
|
|
5
5
|
export const BUILDER_TYPES = {
|
|
6
|
+
UPDATE_CONTENT_TEXT: 'update-content-text',
|
|
7
|
+
UPDATE_CONTENT_TEXT_STYLE: 'update-content-text-style',
|
|
8
|
+
|
|
6
9
|
// Per-section builder types (create)
|
|
7
10
|
CREATE_BANNER_SECTION: 'create-banner-section',
|
|
8
11
|
CREATE_ABOUT_SECTION: 'create-about-section',
|
|
@@ -7,10 +7,15 @@ import type {
|
|
|
7
7
|
import type { SectionType, TextStyleArguments } from './section-tool-arguments';
|
|
8
8
|
|
|
9
9
|
// Tool names — must match the function names generated by the AI pipeline.
|
|
10
|
+
// Text element tools use camelCase names.
|
|
10
11
|
// Section tools follow the pattern: build_{sectionType}_section
|
|
11
12
|
// where sectionType matches the ContentType value (e.g. 'banner-section', 'profile').
|
|
12
13
|
// The mode ('create' | 'update') is a field in the tool arguments, not the tool name.
|
|
13
14
|
export const AI_ASSISTANT_TOOL_NAMES = {
|
|
15
|
+
// Text element agent tools
|
|
16
|
+
UPDATE_TEXT: 'updateText',
|
|
17
|
+
UPDATE_STYLE: 'updateStyle',
|
|
18
|
+
|
|
14
19
|
// Website builder agent tools — unified build
|
|
15
20
|
BUILD_PROFILE_SECTION: 'build_profile_section',
|
|
16
21
|
BUILD_TEXT_SECTION: 'build_text_section',
|
|
@@ -79,6 +84,28 @@ export interface ParsedToolCall {
|
|
|
79
84
|
usage_type?: 'free' | 'paid';
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
export interface BaseArguments {
|
|
88
|
+
message?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// updateText tool arguments
|
|
92
|
+
export interface UpdateTextToolArguments extends BaseArguments {
|
|
93
|
+
contentId: string;
|
|
94
|
+
fieldName: string;
|
|
95
|
+
metadata: {
|
|
96
|
+
text: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// updateStyle tool arguments
|
|
101
|
+
export interface UpdateStyleToolArguments extends BaseArguments {
|
|
102
|
+
contentId: string;
|
|
103
|
+
fieldName: string;
|
|
104
|
+
metadata: {
|
|
105
|
+
style: BrandPageCssStyle;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
// Maps each section tool name to its SectionType.
|
|
83
110
|
// Mode is now determined from the tool arguments, not the tool name.
|
|
84
111
|
export const SECTION_TOOL_MAP: Record<string, { sectionType: SectionType }> = {
|