@designcrowd/library.brand-page 5.19.1-id-based-items-2 → 5.19.2
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-Ruu5Az32.js → CreatedOnBrandCrowd-de-de-Ekmn0O3s.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DGUJtvld.js → CreatedOnBrandCrowd-en-us-BjeXlKTS.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Dg-1uOfV.js → CreatedOnBrandCrowd-es-es-rpNINAMY.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CRKraZl8.js → CreatedOnBrandCrowd-fr-ca-BL4nSaLj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CRKraZl8.js → CreatedOnBrandCrowd-fr-fr-BL4nSaLj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-bZ9VlEAu.js → CreatedOnBrandCrowd-pt-br-K0r4FyYq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-bZ9VlEAu.js → CreatedOnBrandCrowd-pt-pt-K0r4FyYq.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CJYfwZx8.js → CreatedOnDcom-de-de-0XYyEKYc.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-rzA1lVxH.js → CreatedOnDcom-en-us-E8hvwiMd.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-CNwGPjik.js → CreatedOnDcom-es-es-pRuCd7xX.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DKw7OGIz.js → CreatedOnDcom-fr-ca-Bu4V3jpn.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-WzOEaAZG.js → CreatedOnDcom-fr-fr-CCcrGNz2.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DkualVhm.js → CreatedOnDcom-pt-br-1GTvaLKd.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-w4Nnsdzq.js → CreatedOnDcom-pt-pt-CTIYW10X.js} +1 -1
- package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
- package/lib/src/brand-page/models/index.d.ts +1 -12
- package/lib/src/brand-page/models/index.js +3 -0
- package/lib/src/brand-page/utils/personalisation.util.js +72 -14
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +6 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +2 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +1 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +2 -3
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +16 -5
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +0 -5
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +6 -18
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +1 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +3 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +3 -1
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +5 -1
- package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +6 -0
- package/lib/src/brand-page-maker/stores/ui/ui-store.js +8 -0
- package/lib/src/clients/design-template-client.d.ts +3 -0
- package/lib/src/clients/models.d.ts +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +19 -7
- package/lib/src/css/library.brand-page-brandPage.min.css +19 -7
- package/lib/src/css/library.brand-page-designCom.min.css +19 -7
- package/lib/src/css/library.brand-page-preview.min.css +19 -7
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-Dy7MYyqz.js → lib-BfgUqu0B.js} +25060 -24791
- package/lib/src/metadata-builder/builders/footer-builder.js +44 -34
- package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +68 -57
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +93 -58
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +99 -66
- package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +17 -57
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +85 -51
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +74 -49
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +4 -13
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +48 -66
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +7 -1
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +9 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
- package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +6 -0
- package/lib/src/shared/models.d.ts +2 -0
- package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -0
- package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +6 -0
- package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +6 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +7 -3
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +7 -3
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +7 -1
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +10 -439
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
- package/lib/src/shared/template-settings/models.d.ts +1 -0
- package/lib/src/shared/template-settings/store/template-settings-store.d.ts +4 -0
- package/lib/src/shared/template-settings/store/template-settings-store.js +79 -11
- package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +6 -0
- package/lib/src/utils/minify-brand-page-metadata.d.ts +0 -2
- package/lib/src/utils/minify-brand-page-metadata.js +1 -8
- package/package.json +1 -1
- package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +1 -2
- package/src/brand-page/models/index.ts +4 -12
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -0
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +1 -0
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +2 -0
- package/src/brand-page/utils/personalisation.util.test.ts +35 -0
- package/src/brand-page/utils/personalisation.util.ts +119 -23
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +4 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +1 -0
- package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +22 -8
- package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +5 -10
- package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +12 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +0 -6
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +10 -0
- package/src/brand-page-maker/stores/ui/ui-store.ts +16 -0
- package/src/clients/design-template-client.ts +3 -0
- package/src/clients/models.ts +2 -0
- package/src/metadata-builder/builders/footer-builder.test.ts +0 -298
- package/src/metadata-builder/builders/footer-builder.ts +65 -56
- package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +88 -62
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +129 -66
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +160 -105
- package/src/metadata-builder/builders/section-builders/social-links-builder.ts +20 -65
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +146 -83
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +122 -70
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +4 -13
- package/src/metadata-builder/parsers/section-tool-arguments.ts +48 -66
- package/src/shared/models.ts +6 -0
- package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
- package/src/shared/template-settings/models.ts +1 -0
- package/src/shared/template-settings/store/template-settings-store.ts +105 -18
- package/src/utils/minify-brand-page-metadata.ts +2 -14
- package/lib/src/metadata-builder/builders/item-id-utils.d.ts +0 -54
- package/lib/src/metadata-builder/builders/item-id-utils.js +0 -96
- package/src/metadata-builder/builders/item-id-utils.test.ts +0 -184
- package/src/metadata-builder/builders/item-id-utils.ts +0 -117
|
@@ -38,6 +38,12 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
38
38
|
setUserPersonalisationData({ personalisationData, }: {
|
|
39
39
|
personalisationData: import("../../brand-page").UserPersonalisationContent[];
|
|
40
40
|
}): void;
|
|
41
|
+
setUserPromptContentKey({ userPromptContentKey, }: {
|
|
42
|
+
userPromptContentKey: string | undefined;
|
|
43
|
+
}): void;
|
|
44
|
+
setShowAiTemplatesTab({ showAiTemplatesTab, }: {
|
|
45
|
+
showAiTemplatesTab: boolean;
|
|
46
|
+
}): void;
|
|
41
47
|
onBackgroundChanged(): void;
|
|
42
48
|
onContentChanged(): void;
|
|
43
49
|
onStyleChanged(): void;
|
|
@@ -2,8 +2,6 @@ import { type BrandPageModel, type BrandPageType, type NavModel, type PageStyles
|
|
|
2
2
|
import type { ContentType } from '../brand-page/models/content.model';
|
|
3
3
|
import { type AssetEntry } from './build-asset-manifest';
|
|
4
4
|
type MinifiedFooterSocialLink = {
|
|
5
|
-
position: number;
|
|
6
|
-
id?: string;
|
|
7
5
|
imageUrl: string;
|
|
8
6
|
url: string;
|
|
9
7
|
};
|
|
@@ -164,7 +164,6 @@ const minifyContent = (content, editor, assets) => {
|
|
|
164
164
|
...(Array.isArray(c.items) && {
|
|
165
165
|
items: c.items.map((item, index) => ({
|
|
166
166
|
position: index + 1,
|
|
167
|
-
...(item.id && { id: item.id }),
|
|
168
167
|
...st('name', item.name, editor),
|
|
169
168
|
...st('jobTitle', item.position, editor),
|
|
170
169
|
...st('testimonials', item.testimonials, editor),
|
|
@@ -192,7 +191,6 @@ const minifyContent = (content, editor, assets) => {
|
|
|
192
191
|
...(Array.isArray(c.items) && {
|
|
193
192
|
items: c.items.map((item, index) => ({
|
|
194
193
|
position: index + 1,
|
|
195
|
-
...(item.id && { id: item.id }),
|
|
196
194
|
...includeImageWithKey(item.imageUrl),
|
|
197
195
|
...st('title', item.title, editor),
|
|
198
196
|
...st('paragraph', item.paragraph, editor),
|
|
@@ -222,7 +220,6 @@ const minifyContent = (content, editor, assets) => {
|
|
|
222
220
|
...(Array.isArray(c.textItems) && {
|
|
223
221
|
textItems: c.textItems.map((item, index) => ({
|
|
224
222
|
position: index + 1,
|
|
225
|
-
...(item.id && { id: item.id }),
|
|
226
223
|
...st('text', item.text, editor),
|
|
227
224
|
...(includeStyle(item.textStyle) && { textStyle: item.textStyle }),
|
|
228
225
|
})),
|
|
@@ -303,7 +300,6 @@ const minifyContent = (content, editor, assets) => {
|
|
|
303
300
|
...(Array.isArray(c.links) && {
|
|
304
301
|
links: c.links.map((link, index) => ({
|
|
305
302
|
position: index + 1,
|
|
306
|
-
...(link.id && { id: link.id }),
|
|
307
303
|
...(includeString(link.url) && { url: link.url }),
|
|
308
304
|
...includeImageWithKey(link.imageUrl),
|
|
309
305
|
})),
|
|
@@ -343,7 +339,6 @@ const minifyContent = (content, editor, assets) => {
|
|
|
343
339
|
...(Array.isArray(c.images) && {
|
|
344
340
|
images: c.images.map((img, index) => ({
|
|
345
341
|
position: index + 1,
|
|
346
|
-
...(img.id && { id: img.id }),
|
|
347
342
|
...includeImageWithKey(img.imageUrl),
|
|
348
343
|
...st('imageTitle', img.imageTitle, editor),
|
|
349
344
|
})),
|
|
@@ -384,9 +379,7 @@ const minifyFooter = (footer, logoAssetKey) => {
|
|
|
384
379
|
logoImageWidth: footer.logoImageWidth || undefined,
|
|
385
380
|
...(logoAssetKey && { logoAssetKey }),
|
|
386
381
|
socialLinks: footer.socialLinks?.length
|
|
387
|
-
? footer.socialLinks.map((l
|
|
388
|
-
position: index + 1,
|
|
389
|
-
...(l.id && { id: l.id }),
|
|
382
|
+
? footer.socialLinks.map((l) => ({
|
|
390
383
|
imageUrl: l.imageUrl || '',
|
|
391
384
|
url: l.url || '',
|
|
392
385
|
}))
|
package/package.json
CHANGED
|
@@ -7,8 +7,7 @@ import type { ImageGalleryImageClickPayload } from '../models';
|
|
|
7
7
|
import type { BrandPageContext, ImageGalleryItem } from '../../../models';
|
|
8
8
|
import { getImageAltText } from '../../../utils';
|
|
9
9
|
|
|
10
|
-
interface IndexedImageGalleryItem extends
|
|
11
|
-
// Local rendering index, distinct from the stable string `id` on ImageItem.
|
|
10
|
+
interface IndexedImageGalleryItem extends ImageGalleryItem {
|
|
12
11
|
id: number;
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -234,13 +234,6 @@ export interface ImageContent extends Content {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
export interface ImageItem {
|
|
237
|
-
/**
|
|
238
|
-
* Stable per-item identifier. Mirrors the section-level `Content.id` one
|
|
239
|
-
* level deeper so AI tool calls can reference items by id rather than by
|
|
240
|
-
* positional index. Optional for backward compatibility with persisted data
|
|
241
|
-
* that predates item ids — backfilled on load (see ensureItemIds).
|
|
242
|
-
*/
|
|
243
|
-
id?: string;
|
|
244
237
|
imageUrl: string;
|
|
245
238
|
imageTitle: string;
|
|
246
239
|
width: number;
|
|
@@ -473,8 +466,6 @@ export interface ShapeItem extends ImageItem {
|
|
|
473
466
|
}
|
|
474
467
|
|
|
475
468
|
export interface TestimonialItems {
|
|
476
|
-
/** Stable per-item identifier. See ImageItem.id. */
|
|
477
|
-
id?: string;
|
|
478
469
|
imageUrl: string;
|
|
479
470
|
name: string;
|
|
480
471
|
position: string;
|
|
@@ -492,8 +483,6 @@ export interface TestimonialItems {
|
|
|
492
483
|
}
|
|
493
484
|
|
|
494
485
|
export interface TextItem {
|
|
495
|
-
/** Stable per-item identifier. See ImageItem.id. */
|
|
496
|
-
id?: string;
|
|
497
486
|
text: string;
|
|
498
487
|
textStyle: TextStyleDeclaration;
|
|
499
488
|
|
|
@@ -563,7 +552,6 @@ export interface AboutSectionContent extends Content {
|
|
|
563
552
|
}
|
|
564
553
|
|
|
565
554
|
export interface LinksItem {
|
|
566
|
-
id?: string;
|
|
567
555
|
imageUrl: string;
|
|
568
556
|
url: string;
|
|
569
557
|
size?: string;
|
|
@@ -941,6 +929,10 @@ export class BrandPageModel {
|
|
|
941
929
|
// Model version
|
|
942
930
|
v: number;
|
|
943
931
|
|
|
932
|
+
// AI personalisation key persisted with the draft so previews survive login/checkout-back/
|
|
933
|
+
// refresh - content is retrieved by this token (user-agnostic), not regenerated (bp-5742).
|
|
934
|
+
userPromptContentKey?: string;
|
|
935
|
+
|
|
944
936
|
styles: PageStyles;
|
|
945
937
|
|
|
946
938
|
/** @deprecated */
|
|
@@ -456,6 +456,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
|
|
|
456
456
|
},
|
|
457
457
|
},
|
|
458
458
|
},
|
|
459
|
+
"userPromptContentKey": undefined,
|
|
459
460
|
"v": 17,
|
|
460
461
|
},
|
|
461
462
|
"useOldLogoRenderSize": true,
|
|
@@ -901,6 +902,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
|
|
|
901
902
|
},
|
|
902
903
|
},
|
|
903
904
|
},
|
|
905
|
+
"userPromptContentKey": undefined,
|
|
904
906
|
"v": 17,
|
|
905
907
|
},
|
|
906
908
|
"useOldLogoRenderSize": true,
|
|
@@ -1353,6 +1355,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
|
|
|
1353
1355
|
},
|
|
1354
1356
|
},
|
|
1355
1357
|
},
|
|
1358
|
+
"userPromptContentKey": undefined,
|
|
1356
1359
|
"v": 17,
|
|
1357
1360
|
},
|
|
1358
1361
|
"useOldLogoRenderSize": true,
|
|
@@ -701,6 +701,41 @@ describe('personalisation.util', async () => {
|
|
|
701
701
|
).toBe(title);
|
|
702
702
|
});
|
|
703
703
|
|
|
704
|
+
test('applyUserPersonalisationToBrandPageModelAsync should fall back to a populated section when the block section was not generated', async () => {
|
|
705
|
+
// Arrange - simulate userPrompt AI content that only generated the core sections
|
|
706
|
+
// (profile/about/information/media); email/website/phone are never generated.
|
|
707
|
+
const personalisation = {
|
|
708
|
+
...PERSONALISATION,
|
|
709
|
+
websiteSection: undefined,
|
|
710
|
+
emailSection: undefined,
|
|
711
|
+
phoneSection: undefined,
|
|
712
|
+
} as unknown as typeof PERSONALISATION;
|
|
713
|
+
|
|
714
|
+
// A block whose role points at the ungenerated website section - without the fallback
|
|
715
|
+
// this renders the designer's placeholder instead of personalised content.
|
|
716
|
+
const model = new BrandPageModel({
|
|
717
|
+
brandPageType: BRAND_PAGE_TYPES.brandSite,
|
|
718
|
+
styles: {
|
|
719
|
+
backgroundStyles: {
|
|
720
|
+
backgroundImageUrl: faker.internet.url(),
|
|
721
|
+
personalisationRole: 'website',
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Act
|
|
727
|
+
const result = await applyUserPersonalisationToBrandPageModelAsync(
|
|
728
|
+
personalisation,
|
|
729
|
+
model,
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
// Assert - it borrowed the first populated fallback section (profile).
|
|
733
|
+
expect(result.isModified).toBe(true);
|
|
734
|
+
expect(result.model.styles.backgroundStyles.backgroundImageUrl).toBe(
|
|
735
|
+
PERSONALISATION.profileSection.imageUrls?.[0].imageUrl,
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
704
739
|
test('applyUserPersonalisationToBrandPageModelAsync should return modified text items when input has text with multiple items', async () => {
|
|
705
740
|
// Arrange
|
|
706
741
|
const textContent = {
|
|
@@ -326,6 +326,84 @@ const getPersonalisationValueAsync = async (
|
|
|
326
326
|
return { value: propertyValue, section };
|
|
327
327
|
};
|
|
328
328
|
|
|
329
|
+
// The userPrompt AI content generation only populates these content-bearing sections
|
|
330
|
+
// (see GenerateAiDesignMakerContentService / GenerateWebsiteContentService - About,
|
|
331
|
+
// Information, Profile, Media). Sections like email/website/phone are never generated, so
|
|
332
|
+
// blocks whose role points at an ungenerated section fall back to this pool.
|
|
333
|
+
const PERSONALISATION_FALLBACK_ROLES: PersonalisationRole[] = [
|
|
334
|
+
'profile',
|
|
335
|
+
'about',
|
|
336
|
+
'information',
|
|
337
|
+
'media',
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
// "Effectively empty" = no renderable content (ungenerated userPrompt sections like
|
|
341
|
+
// email/website/phone). A section that has data but lacks one property is NOT empty - it
|
|
342
|
+
// intentionally keeps its original content.
|
|
343
|
+
const isSectionEffectivelyEmpty = (
|
|
344
|
+
section: UserPersonalisationSectionWithIndices | undefined,
|
|
345
|
+
): boolean => {
|
|
346
|
+
if (!section) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
!section.title &&
|
|
352
|
+
!section.name &&
|
|
353
|
+
!section.url &&
|
|
354
|
+
!section.description &&
|
|
355
|
+
!section.descriptionShort &&
|
|
356
|
+
!section.ctaTitle &&
|
|
357
|
+
!section.imageUrls?.length &&
|
|
358
|
+
!section.shapes?.length &&
|
|
359
|
+
!section.items?.length &&
|
|
360
|
+
!section.mainShapesCollection?.tall?.length &&
|
|
361
|
+
!section.mainShapesCollection?.square?.length &&
|
|
362
|
+
!section.mainShapesCollection?.wide?.length
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Resolve a property from a specific role's section. Returns null when that section is
|
|
367
|
+
// ungenerated (effectively empty) or cannot supply the property, so the caller can try the
|
|
368
|
+
// next role.
|
|
369
|
+
const resolvePersonalisationValueForRoleAsync = async (
|
|
370
|
+
role: PersonalisationRole,
|
|
371
|
+
property: keyof UserPersonalisationSection | keyof UserPersonalisationItem,
|
|
372
|
+
personalisation: UserPersonalisationWithIndices,
|
|
373
|
+
): Promise<{
|
|
374
|
+
value: any;
|
|
375
|
+
personalisation: UserPersonalisationWithIndices;
|
|
376
|
+
valuesToUpdate?: Record<string, any>;
|
|
377
|
+
} | null> => {
|
|
378
|
+
const key = `${role}Section` as keyof UserPersonalisationWithIndices;
|
|
379
|
+
const section = personalisation[key] as
|
|
380
|
+
| UserPersonalisationSectionWithIndices
|
|
381
|
+
| undefined;
|
|
382
|
+
|
|
383
|
+
if (isSectionEffectivelyEmpty(section)) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const {
|
|
388
|
+
value,
|
|
389
|
+
section: updatedSection,
|
|
390
|
+
valuesToUpdate,
|
|
391
|
+
} = await getPersonalisationValueAsync(
|
|
392
|
+
section as UserPersonalisationSectionWithIndices,
|
|
393
|
+
property,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (value === undefined || value === null) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
value,
|
|
402
|
+
personalisation: { ...personalisation, [key]: updatedSection },
|
|
403
|
+
valuesToUpdate,
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
|
|
329
407
|
const getPersonalisationValueFromPropertyAsync = async (
|
|
330
408
|
propertyKey: string,
|
|
331
409
|
propertyValue: any,
|
|
@@ -342,16 +420,6 @@ const getPersonalisationValueFromPropertyAsync = async (
|
|
|
342
420
|
return { value: undefined, personalisation };
|
|
343
421
|
}
|
|
344
422
|
|
|
345
|
-
const matchingValue = Object.entries(personalisation).find(
|
|
346
|
-
([key]) => key === `${personalisationRole}Section`,
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
if (!matchingValue) {
|
|
350
|
-
return { value: undefined, personalisation };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const section = matchingValue[1] as UserPersonalisationSectionWithIndices;
|
|
354
|
-
|
|
355
423
|
const mapping = getPersonalisationMappingForContentType(contentType);
|
|
356
424
|
|
|
357
425
|
const matchingProperty = Object.entries(mapping).find(([, value]) =>
|
|
@@ -372,20 +440,48 @@ const getPersonalisationValueFromPropertyAsync = async (
|
|
|
372
440
|
typeof propertyValue === 'string' && propertyValue.endsWith('.svg'),
|
|
373
441
|
});
|
|
374
442
|
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
443
|
+
const primaryKey =
|
|
444
|
+
`${personalisationRole}Section` as keyof UserPersonalisationWithIndices;
|
|
445
|
+
const primarySection = personalisation[primaryKey] as
|
|
446
|
+
| UserPersonalisationSectionWithIndices
|
|
447
|
+
| undefined;
|
|
448
|
+
|
|
449
|
+
// When the block's own section has content, use it as-is - this preserves existing
|
|
450
|
+
// behaviour, including legitimately keeping the original when that section lacks a
|
|
451
|
+
// specific property (e.g. no items, or an invalid shape).
|
|
452
|
+
if (!isSectionEffectivelyEmpty(primarySection)) {
|
|
453
|
+
const {
|
|
454
|
+
value,
|
|
455
|
+
section: updatedSection,
|
|
456
|
+
valuesToUpdate,
|
|
457
|
+
} = await getPersonalisationValueAsync(
|
|
458
|
+
primarySection as UserPersonalisationSectionWithIndices,
|
|
459
|
+
property,
|
|
460
|
+
);
|
|
380
461
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
462
|
+
return {
|
|
463
|
+
value,
|
|
464
|
+
personalisation: { ...personalisation, [primaryKey]: updatedSection },
|
|
465
|
+
valuesToUpdate,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// The block's section was never generated - fall back to the first populated section that
|
|
470
|
+
// can supply this property, so it doesn't render the "should be AI generated" placeholder.
|
|
471
|
+
// Never reached when the section exists (above) or all are populated (AI Website Builder).
|
|
472
|
+
for (const role of PERSONALISATION_FALLBACK_ROLES) {
|
|
473
|
+
const resolved = await resolvePersonalisationValueForRoleAsync(
|
|
474
|
+
role,
|
|
475
|
+
property,
|
|
476
|
+
personalisation,
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
if (resolved) {
|
|
480
|
+
return resolved;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return { value: undefined, personalisation };
|
|
389
485
|
};
|
|
390
486
|
|
|
391
487
|
type ContentProperty = Record<string, string | any>;
|
|
@@ -70,6 +70,8 @@ export interface BrandPageMakerViewModel {
|
|
|
70
70
|
initialCategories: InitialCategories;
|
|
71
71
|
personalisation?: UserPersonalisationContent[];
|
|
72
72
|
isPersonalisationEnabled?: boolean;
|
|
73
|
+
userPromptContentKey?: string;
|
|
74
|
+
showAiTemplatesTab?: boolean;
|
|
73
75
|
brandPageType?: BrandPageType;
|
|
74
76
|
businessName?: string;
|
|
75
77
|
industry?: string;
|
|
@@ -227,6 +229,8 @@ export default defineComponent({
|
|
|
227
229
|
personalisationData: this.viewModel.personalisation,
|
|
228
230
|
isPersonalisationEnabled:
|
|
229
231
|
this.viewModel.isPersonalisationEnabled || false,
|
|
232
|
+
userPromptContentKey: this.viewModel.userPromptContentKey,
|
|
233
|
+
showAiTemplatesTab: this.viewModel.showAiTemplatesTab || false,
|
|
230
234
|
brandPageType: this.viewModel.brandPageType,
|
|
231
235
|
businessName: this.viewModel.businessName,
|
|
232
236
|
industry: this.viewModel.industry,
|
|
@@ -15,10 +15,7 @@ import {
|
|
|
15
15
|
getBrandPageViewModelFromBrandPageModel,
|
|
16
16
|
} from '@brand-page/utils';
|
|
17
17
|
import { STYLE_THEME_KEYS } from '@brand-page/constants';
|
|
18
|
-
import {
|
|
19
|
-
TEMPLATE_TABS,
|
|
20
|
-
type TemplateSettingsTemplate,
|
|
21
|
-
} from '@shared/template-settings/models';
|
|
18
|
+
import type { TemplateSettingsTemplate } from '@shared/template-settings/models';
|
|
22
19
|
import type { DebounceHandle } from 'lodash.debounce';
|
|
23
20
|
import debounce from 'lodash.debounce';
|
|
24
21
|
import { MOBILE_HEIGHT, MOBILE_WIDTH } from '@brand-page-maker/constants';
|
|
@@ -74,7 +71,6 @@ export default defineComponent({
|
|
|
74
71
|
isBrandSite: computed(
|
|
75
72
|
() => brandPageMakerStore.brandPageType === BRAND_PAGE_TYPES.brandSite,
|
|
76
73
|
),
|
|
77
|
-
selectedTab: computed(() => templateSettingsStore.selectedTab.key),
|
|
78
74
|
renderAsImage: computed(() => templateSettingsStore.renderAsImage),
|
|
79
75
|
brandPageMakerUserStore,
|
|
80
76
|
brandPageContainerStore,
|
|
@@ -241,10 +237,28 @@ export default defineComponent({
|
|
|
241
237
|
return '';
|
|
242
238
|
}
|
|
243
239
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
const isAlreadyPersonalised =
|
|
241
|
+
previewImageUrl.includes('contentKey=') ||
|
|
242
|
+
previewImageUrl.includes('userPromptContentKey=');
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
this.isPersonalisable &&
|
|
246
|
+
this.template.contentKey &&
|
|
247
|
+
!isAlreadyPersonalised
|
|
248
|
+
) {
|
|
249
|
+
if (this.userToken && !this.template.usesUserPromptContentKey) {
|
|
250
|
+
previewImageUrl += `&contentKey=${this.template.contentKey}`;
|
|
251
|
+
|
|
252
|
+
// The backend already bakes userToken into brand-site preview URLs. Only add
|
|
253
|
+
// it when missing - two userToken params parse as an array and the preview
|
|
254
|
+
// renderer rejects it ("userToken must be a string type") (bp-5765).
|
|
255
|
+
if (!previewImageUrl.includes('userToken=')) {
|
|
256
|
+
previewImageUrl += `&userToken=${this.userToken}`;
|
|
257
|
+
}
|
|
247
258
|
} else {
|
|
259
|
+
// userPromptContentKey resolves from the userPrompt S3 bucket (real AI images).
|
|
260
|
+
// Post-purchase designs with a userPrompt key route here too, NOT through the
|
|
261
|
+
// userToken/regular-bucket branch above which holds placeholders (bp-5454).
|
|
248
262
|
previewImageUrl += `&userPromptContentKey=${this.template.contentKey}`;
|
|
249
263
|
}
|
|
250
264
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div
|
|
3
3
|
ref="container"
|
|
4
4
|
data-component="template-preview"
|
|
5
|
-
class="template-preview-container hover:!tw-shadow-[0_0_0_3px] hover:!tw-shadow-info-500 tw-overflow-hidden"
|
|
5
|
+
class="template-preview-container hover:!tw-shadow-[0_0_0_3px] hover:!tw-shadow-info-500 tw-overflow-hidden tw-relative tw-isolate"
|
|
6
6
|
:style="containerStyle"
|
|
7
7
|
:class="{
|
|
8
8
|
'!tw-shadow-[0_0_0_3px] !tw-shadow-info-500':
|
|
@@ -55,17 +55,10 @@
|
|
|
55
55
|
/>
|
|
56
56
|
</div>
|
|
57
57
|
<!-- End Preview Image -->
|
|
58
|
-
<!-- Start personalisation tag -->
|
|
59
|
-
<div
|
|
60
|
-
v-if="isPersonalisable"
|
|
61
|
-
class="tw-absolute tw-bottom-0 tw-right-0 tw-p-2 tw-z-50 tw-text-white"
|
|
62
|
-
style="background: lightgreen"
|
|
63
|
-
>
|
|
64
|
-
<span>Personalisable</span>
|
|
65
|
-
</div>
|
|
66
|
-
<!-- End personalisation tag -->
|
|
67
58
|
</div>
|
|
68
59
|
</div>
|
|
60
|
+
<!-- AI badge outside scaled container so it renders at natural size -->
|
|
61
|
+
<TemplatePreviewAiBadge v-if="isPersonalisable" />
|
|
69
62
|
</div>
|
|
70
63
|
</template>
|
|
71
64
|
<script>
|
|
@@ -73,11 +66,13 @@ import { defineComponent } from 'vue';
|
|
|
73
66
|
import TemplatePreviewMixin from './TemplatePreview.mixin.ts';
|
|
74
67
|
import Page from '@brand-page/components/page/Page.vue';
|
|
75
68
|
import { Loader } from '@designcrowd/fe-shared-lib';
|
|
69
|
+
import TemplatePreviewAiBadge from './TemplatePreviewAiBadge.vue';
|
|
76
70
|
|
|
77
71
|
export default defineComponent({
|
|
78
72
|
components: {
|
|
79
73
|
Page,
|
|
80
74
|
Loader,
|
|
75
|
+
TemplatePreviewAiBadge,
|
|
81
76
|
},
|
|
82
77
|
mixins: [TemplatePreviewMixin],
|
|
83
78
|
setup(props, context) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
data-test-ai-design
|
|
4
|
+
class="tw-flex tw-flex-row tw-absolute tw-top-2.5 tw-left-2.5 tw-rounded-full tw-py-1 tw-px-2 tw-text-white tw-text-xs tw-font-bold tw-z-[25] tw-bg-gradient-to-t tw-from-[#d946ef] tw-to-[#8b5cf6]"
|
|
5
|
+
>
|
|
6
|
+
<Icon name="ai" class="tw-text-white tw-pr-1" size="sm" />
|
|
7
|
+
<span>AI</span>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { Icon } from '@designcrowd/fe-shared-lib';
|
|
12
|
+
</script>
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
type PlanItem,
|
|
19
19
|
} from '../../../clients/websocket-gateway-client';
|
|
20
20
|
import { minifyBrandPageMetadata } from '../../../utils/minify-brand-page-metadata';
|
|
21
|
-
import { ensureBrandPageItemIds } from '../../../metadata-builder/builders/item-id-utils';
|
|
22
21
|
import { buildAssetManifest } from '../../../utils/build-asset-manifest';
|
|
23
22
|
import { usePageNavigationStore } from '../../../shared/page-navigation/store/page-navigation-store';
|
|
24
23
|
import { navigateToPageByPageId } from '../../routing';
|
|
@@ -257,11 +256,6 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
|
|
|
257
256
|
|
|
258
257
|
const buildMessagePayload = async (userPrompt: string) => {
|
|
259
258
|
const brandPageData = brandPageMakerStore.brandPageData;
|
|
260
|
-
|
|
261
|
-
// BP-5603: stamp stable uuid ids onto every nested item BEFORE minifying,
|
|
262
|
-
// so the id the AI is shown (and returns as `item_id`) is the exact id the
|
|
263
|
-
// executor matches when applying tool calls to this same live model.
|
|
264
|
-
ensureBrandPageItemIds(brandPageData);
|
|
265
259
|
const headerLogoDraftToken =
|
|
266
260
|
brandPageData.pageHeaderDesktop?.logoImage?.logoDraftToken ??
|
|
267
261
|
uiStore.logoDraftToken;
|
|
@@ -123,6 +123,7 @@ export type InitialisePayload = Omit<
|
|
|
123
123
|
logoDraftAssets: LogoDraftAsset[];
|
|
124
124
|
gettyVideos: GettyVideoEditorViewModelResponse[];
|
|
125
125
|
templateSearchTerm: string | undefined;
|
|
126
|
+
aiTemplateKeywords: string | undefined;
|
|
126
127
|
onSearchTermChanged?: OnSearchTermChangedCallback;
|
|
127
128
|
};
|
|
128
129
|
|
|
@@ -266,6 +267,10 @@ export const useBrandPageContainerStore = defineStore('brandPageContainer', {
|
|
|
266
267
|
onSearchTermChanged: payload.onSearchTermChanged,
|
|
267
268
|
});
|
|
268
269
|
|
|
270
|
+
templateSettingsStore.setAiTemplateKeywords({
|
|
271
|
+
aiTemplateKeywords: payload.aiTemplateKeywords ?? '',
|
|
272
|
+
});
|
|
273
|
+
|
|
269
274
|
brandPageMakerStore.setSelectedTemplateId({
|
|
270
275
|
templateId: this.designTemplateToken,
|
|
271
276
|
});
|
|
@@ -125,6 +125,8 @@ export interface InitialisePayload {
|
|
|
125
125
|
templateCategories: TemplateCategory[];
|
|
126
126
|
personalisationData?: UserPersonalisationContent[];
|
|
127
127
|
isPersonalisationEnabled: boolean;
|
|
128
|
+
userPromptContentKey?: string;
|
|
129
|
+
showAiTemplatesTab?: boolean;
|
|
128
130
|
businessName: string | undefined;
|
|
129
131
|
industry: string | undefined;
|
|
130
132
|
layout?: string;
|
|
@@ -1230,6 +1232,8 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
1230
1232
|
templateCategories,
|
|
1231
1233
|
personalisationData,
|
|
1232
1234
|
isPersonalisationEnabled,
|
|
1235
|
+
userPromptContentKey,
|
|
1236
|
+
showAiTemplatesTab = false,
|
|
1233
1237
|
businessName,
|
|
1234
1238
|
industry,
|
|
1235
1239
|
layout,
|
|
@@ -1353,6 +1357,10 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
1353
1357
|
});
|
|
1354
1358
|
}
|
|
1355
1359
|
|
|
1360
|
+
uiStore.setUserPromptContentKey({ userPromptContentKey });
|
|
1361
|
+
|
|
1362
|
+
uiStore.setShowAiTemplatesTab({ showAiTemplatesTab });
|
|
1363
|
+
|
|
1356
1364
|
templateSettingsStore.setTemplateCategories({
|
|
1357
1365
|
categories: templateCategories,
|
|
1358
1366
|
});
|
|
@@ -1800,6 +1808,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
1800
1808
|
const navigationStyleSettingsStore = useUiNavigationStyleSettingsStore();
|
|
1801
1809
|
const shopStore = useShopConfigStore();
|
|
1802
1810
|
const pageNavigationStore = usePageNavigationStore();
|
|
1811
|
+
const uiStore = useUiStore();
|
|
1803
1812
|
|
|
1804
1813
|
const pages = pageSettingsStore.pages.map((p) => ({
|
|
1805
1814
|
...p,
|
|
@@ -1817,6 +1826,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
1817
1826
|
|
|
1818
1827
|
return {
|
|
1819
1828
|
v: GetLatestModelVersion(),
|
|
1829
|
+
userPromptContentKey: uiStore.userPromptContentKey,
|
|
1820
1830
|
pages,
|
|
1821
1831
|
styles: {
|
|
1822
1832
|
logoStyles,
|
|
@@ -48,6 +48,8 @@ export const useUiStore = defineStore('ui', {
|
|
|
48
48
|
},
|
|
49
49
|
personalisationData: undefined,
|
|
50
50
|
isPersonalisationEnabled: false,
|
|
51
|
+
userPromptContentKey: undefined,
|
|
52
|
+
showAiTemplatesTab: false,
|
|
51
53
|
}),
|
|
52
54
|
actions: {
|
|
53
55
|
setLogoTheme(payload: { logoTheme: LogoTheme }) {
|
|
@@ -78,6 +80,20 @@ export const useUiStore = defineStore('ui', {
|
|
|
78
80
|
}) {
|
|
79
81
|
this.personalisationData = personalisationData;
|
|
80
82
|
},
|
|
83
|
+
setUserPromptContentKey({
|
|
84
|
+
userPromptContentKey,
|
|
85
|
+
}: {
|
|
86
|
+
userPromptContentKey: string | undefined;
|
|
87
|
+
}) {
|
|
88
|
+
this.userPromptContentKey = userPromptContentKey;
|
|
89
|
+
},
|
|
90
|
+
setShowAiTemplatesTab({
|
|
91
|
+
showAiTemplatesTab,
|
|
92
|
+
}: {
|
|
93
|
+
showAiTemplatesTab: boolean;
|
|
94
|
+
}) {
|
|
95
|
+
this.showAiTemplatesTab = showAiTemplatesTab;
|
|
96
|
+
},
|
|
81
97
|
onBackgroundChanged() {
|
|
82
98
|
const brandPageMakerStore = useBrandPageMakerStore();
|
|
83
99
|
|
|
@@ -11,6 +11,7 @@ interface DesignTemplateClientOptions {}
|
|
|
11
11
|
interface GetTemplatesAsyncOptions {
|
|
12
12
|
categoryName?: string;
|
|
13
13
|
searchTerm?: string;
|
|
14
|
+
keywords?: string;
|
|
14
15
|
side?: string;
|
|
15
16
|
logoDraftToken?: string;
|
|
16
17
|
logoVariation?: number;
|
|
@@ -22,6 +23,8 @@ interface GetTemplatesAsyncOptions {
|
|
|
22
23
|
locale?: string;
|
|
23
24
|
logoS3Token?: string;
|
|
24
25
|
logoTemplateToken?: string;
|
|
26
|
+
pageSection?: string;
|
|
27
|
+
pageName?: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export const getTemplatesEndpoint = (templateTypeName: string) =>
|