@designcrowd/library.brand-page 5.18.2 → 5.18.3-actionable-buttons-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-DB2oJ5vl.js → CreatedOnBrandCrowd-de-de-HPO35Atc.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-fbk0Gf2G.js → CreatedOnBrandCrowd-en-us-Cl4aaalq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Ch4Q9__I.js → CreatedOnBrandCrowd-es-es-B_2laZxX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DaHJ6OMH.js → CreatedOnBrandCrowd-fr-ca-BTauzkB5.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DaHJ6OMH.js → CreatedOnBrandCrowd-fr-fr-BTauzkB5.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BgF8UCqy.js → CreatedOnBrandCrowd-pt-br-B0V4UZv3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BgF8UCqy.js → CreatedOnBrandCrowd-pt-pt-B0V4UZv3.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-j3rcGaGp.js → CreatedOnDcom-de-de-DvX7MlWL.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Cx4eQPRU.js → CreatedOnDcom-en-us-BnXBQ4jL.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-D-1APCL9.js → CreatedOnDcom-es-es-lKzARICU.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DAHdBw0D.js → CreatedOnDcom-fr-ca-BXBuLMp3.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BYAw2kXr.js → CreatedOnDcom-fr-fr-wUZo-ppX.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CdSknjZg.js → CreatedOnDcom-pt-br-Dcpf3PTV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DzyZ2j80.js → CreatedOnDcom-pt-pt-BUN3ynDi.js} +1 -1
- package/lib/src/brand-page-maker/composables/ai-chat/types.d.ts +3 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.d.ts +29 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.js +32 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +201 -172
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.js +1 -0
- package/lib/src/{bundled-translations-CnareLWC.js → bundled-translations-B5Fkt3qd.js} +1 -1
- package/lib/src/{bundled-translations.de-DE-2Ys7epLO.js → bundled-translations.de-DE-DpdVePM9.js} +1 -1
- package/lib/src/bundled-translations.es-ES-BsIcrzZF.js +175 -0
- package/lib/src/bundled-translations.fr-CA-G1XCp2Qh.js +175 -0
- package/lib/src/bundled-translations.fr-FR-BdwLXOnB.js +175 -0
- package/lib/src/bundled-translations.pt-BR-Bvn5ePe7.js +175 -0
- package/lib/src/bundled-translations.pt-PT-CDtkb2k1.js +175 -0
- package/lib/src/bundles/bundled-translations.de-DE.json +2 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/lib/src/bundles/bundled-translations.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +102 -19
- package/lib/src/css/library.brand-page-brandPage.min.css +102 -19
- package/lib/src/css/library.brand-page-designCom.min.css +102 -19
- package/lib/src/css/library.brand-page-preview.min.css +102 -19
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-mcvVokSM.js → lib-BP3rUcsW.js} +17328 -17189
- package/lib/src/plugins/google-maps-plugin/google-maps-plugin.d.ts +14 -2
- package/lib/src/plugins/google-maps-plugin/google-maps-plugin.js +43 -13
- package/package.json +2 -2
- package/src/brand-page-maker/components/ai-text-edit/AiActionButtons.vue +34 -7
- package/src/brand-page-maker/components/ai-text-edit/AiChatbox.vue +3 -0
- package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +7 -1
- package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +21 -1
- package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.vue +8 -0
- package/src/brand-page-maker/composables/ai-chat/types.ts +6 -0
- package/src/brand-page-maker/composables/ai-chat/ui-actions.test.ts +80 -0
- package/src/brand-page-maker/composables/ai-chat/ui-actions.ts +58 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +305 -256
- package/src/brand-page-maker/composables/useEventTracker.ts +1 -0
- package/src/brand-page-maker/constants/i18n/constants.de-DE.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.es-ES.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.fr-CA.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.fr-FR.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.pt-BR.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.pt-PT.json +2 -0
- package/src/bundles/bundled-translations.de-DE.json +2 -0
- package/src/bundles/bundled-translations.es-ES.json +2 -0
- package/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/src/bundles/bundled-translations.json +2 -0
- package/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/src/plugins/google-maps-plugin/google-maps-plugin.ts +58 -18
- package/src/shared/content-settings/components/google-address-field/GoogleAddressField.vue +148 -15
- package/src/shared/font-family-selector/FontPicker.vue +91 -4
- package/lib/src/bundled-translations.es-ES-Cjp-G8rT.js +0 -175
- package/lib/src/bundled-translations.fr-CA-CsaW4Oae.js +0 -175
- package/lib/src/bundled-translations.fr-FR-DIT2dd8J.js +0 -175
- package/lib/src/bundled-translations.pt-BR-BamoBpDW.js +0 -175
- package/lib/src/bundled-translations.pt-PT-C9A4hY_e.js +0 -175
- package/lib/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.d.ts +0 -54
- package/lib/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.js +0 -67
- package/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.ts +0 -84
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare const PLACE_CHANGED = "place_changed";
|
|
2
1
|
export declare const PLACE_ID_QUERY_PREFIX = "place_id:";
|
|
3
2
|
export interface GoogleMapsPluginOptions {
|
|
4
3
|
readonly apiKey: string;
|
|
@@ -9,13 +8,26 @@ export interface GetEmbedGoogleMapsOptions {
|
|
|
9
8
|
readonly query: string;
|
|
10
9
|
readonly locale?: string;
|
|
11
10
|
}
|
|
11
|
+
export interface AddressDetails {
|
|
12
|
+
readonly formattedAddress: string;
|
|
13
|
+
readonly placeId: string;
|
|
14
|
+
}
|
|
12
15
|
export declare class GoogleMapsPlugin {
|
|
13
16
|
private initialised;
|
|
14
17
|
private initialising;
|
|
15
18
|
private readonly _options;
|
|
16
19
|
constructor(options: GoogleMapsPluginOptions);
|
|
17
20
|
private initialise;
|
|
18
|
-
|
|
21
|
+
private getPlacesLibrary;
|
|
22
|
+
/**
|
|
23
|
+
* Creates Google's official Places Autocomplete widget (new Places API).
|
|
24
|
+
* It renders its own input + dropdown with built-in icons, formatting,
|
|
25
|
+
* keyboard/a11y support and the required "Powered by Google" attribution,
|
|
26
|
+
* and manages session tokens internally. Listen for `gmp-select` to read the
|
|
27
|
+
* chosen prediction, then pass it to {@link fetchAddressDetails}.
|
|
28
|
+
*/
|
|
29
|
+
createPlaceAutocompleteElement(options?: google.maps.places.PlaceAutocompleteElementOptions): Promise<google.maps.places.PlaceAutocompleteElement>;
|
|
30
|
+
fetchAddressDetails(prediction: google.maps.places.PlacePrediction): Promise<AddressDetails | undefined>;
|
|
19
31
|
private createGoogleMapsScriptElement;
|
|
20
32
|
private getGoogleScriptUrl;
|
|
21
33
|
getEmbedGoogleMapsSrc(options: GetEmbedGoogleMapsOptions): string;
|
|
@@ -8,7 +8,6 @@ const delayInMS = (delay) => {
|
|
|
8
8
|
const DELAY_MS = 100;
|
|
9
9
|
const MAP_MODE_PLACE = 'place';
|
|
10
10
|
const SCRIPT_TAG_NAME = 'SCRIPT';
|
|
11
|
-
export const PLACE_CHANGED = 'place_changed';
|
|
12
11
|
export const PLACE_ID_QUERY_PREFIX = 'place_id:';
|
|
13
12
|
export class GoogleMapsPlugin {
|
|
14
13
|
initialised = false;
|
|
@@ -30,16 +29,23 @@ export class GoogleMapsPlugin {
|
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
const url = this.getGoogleScriptUrl();
|
|
33
|
-
|
|
34
|
-
googleMapsScript = Array.from(document.head.children).find((c) => c.tagName.toUpperCase() === SCRIPT_TAG_NAME &&
|
|
32
|
+
const existingScript = Array.from(document.head.children).find((c) => c.tagName.toUpperCase() === SCRIPT_TAG_NAME &&
|
|
35
33
|
c.hasAttribute('src') &&
|
|
36
34
|
c.getAttribute('src') === url);
|
|
37
|
-
if (
|
|
38
|
-
|
|
35
|
+
if (!existingScript) {
|
|
36
|
+
document.head.appendChild(this.createGoogleMapsScriptElement(url));
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
// Wait for the specific class we use. The `places` library loads eagerly
|
|
39
|
+
// via the `libraries=places` script param, so the new Places classes are
|
|
40
|
+
// on the `google.maps.places` namespace once ready. We avoid
|
|
41
|
+
// `importLibrary` (absent in some hosts that already loaded Maps).
|
|
42
|
+
//
|
|
43
|
+
// `typeof google === 'undefined'` must be checked first and on its own:
|
|
44
|
+
// `typeof` only suppresses ReferenceError for a bare identifier, so
|
|
45
|
+
// touching `google.maps` while `google` is still undeclared (before the
|
|
46
|
+
// script executes) throws "google is not defined".
|
|
47
|
+
while (typeof google === 'undefined' ||
|
|
48
|
+
typeof google.maps?.places?.PlaceAutocompleteElement !== 'function') {
|
|
43
49
|
await delayInMS(DELAY_MS);
|
|
44
50
|
}
|
|
45
51
|
this.initialised = true;
|
|
@@ -48,16 +54,40 @@ export class GoogleMapsPlugin {
|
|
|
48
54
|
this.initialising = false;
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
|
-
async
|
|
57
|
+
async getPlacesLibrary() {
|
|
52
58
|
await this.initialise();
|
|
53
59
|
while (!this.initialised) {
|
|
54
60
|
await delayInMS(DELAY_MS);
|
|
55
61
|
}
|
|
56
|
-
|
|
62
|
+
return google.maps.places;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Creates Google's official Places Autocomplete widget (new Places API).
|
|
66
|
+
* It renders its own input + dropdown with built-in icons, formatting,
|
|
67
|
+
* keyboard/a11y support and the required "Powered by Google" attribution,
|
|
68
|
+
* and manages session tokens internally. Listen for `gmp-select` to read the
|
|
69
|
+
* chosen prediction, then pass it to {@link fetchAddressDetails}.
|
|
70
|
+
*/
|
|
71
|
+
async createPlaceAutocompleteElement(options) {
|
|
72
|
+
const { PlaceAutocompleteElement } = await this.getPlacesLibrary();
|
|
73
|
+
return new PlaceAutocompleteElement({
|
|
74
|
+
requestedLanguage: this._options.locale,
|
|
75
|
+
...options,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async fetchAddressDetails(prediction) {
|
|
79
|
+
const place = prediction.toPlace();
|
|
57
80
|
// NOTE: additional fields may incur a charge, check before adding a new field
|
|
58
81
|
// See https://developers.google.com/maps/billing-and-pricing/pricing#basic-data for more information
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
// The widget's session token is included automatically on this first fetch.
|
|
83
|
+
await place.fetchFields({ fields: ['formattedAddress', 'id'] });
|
|
84
|
+
if (!place.formattedAddress || !place.id) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
formattedAddress: place.formattedAddress,
|
|
89
|
+
placeId: place.id,
|
|
90
|
+
};
|
|
61
91
|
}
|
|
62
92
|
createGoogleMapsScriptElement(url) {
|
|
63
93
|
const scriptElement = document.createElement(SCRIPT_TAG_NAME);
|
|
@@ -67,7 +97,7 @@ export class GoogleMapsPlugin {
|
|
|
67
97
|
return scriptElement;
|
|
68
98
|
}
|
|
69
99
|
getGoogleScriptUrl() {
|
|
70
|
-
const baseUrl = `https://maps.googleapis.com/maps/api/js?key=${this._options.apiKey}&libraries=places`;
|
|
100
|
+
const baseUrl = `https://maps.googleapis.com/maps/api/js?key=${this._options.apiKey}&libraries=places&loading=async`;
|
|
71
101
|
return this._options.locale
|
|
72
102
|
? `${baseUrl}&language=${this._options.locale}`
|
|
73
103
|
: baseUrl;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designcrowd/library.brand-page",
|
|
3
3
|
"description": "A collection of Maker/Designer applications",
|
|
4
|
-
"version": "5.18.2",
|
|
4
|
+
"version": "5.18.3-actionable-buttons-2",
|
|
5
5
|
"author": "Design.com Eng",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"@types/dompurify": "3.2.0",
|
|
142
142
|
"@types/express": "4.17.21",
|
|
143
143
|
"@types/faker": "6.6.8",
|
|
144
|
-
"@types/google.maps": "3.
|
|
144
|
+
"@types/google.maps": "3.65.1",
|
|
145
145
|
"@types/istanbul-lib-report": "3.0.3",
|
|
146
146
|
"@types/jquery": "3.5.30",
|
|
147
147
|
"@types/lodash.clonedeep": "4.5.9",
|
|
@@ -9,13 +9,25 @@
|
|
|
9
9
|
icon-left="stop"
|
|
10
10
|
@click="$emit('stop-generation')"
|
|
11
11
|
/>
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
<template v-else>
|
|
13
|
+
<Button
|
|
14
|
+
v-for="action in actions"
|
|
15
|
+
:key="action.type"
|
|
16
|
+
:label="action.label"
|
|
17
|
+
variant="primary"
|
|
18
|
+
size="small"
|
|
19
|
+
:icon-left="action.icon || undefined"
|
|
20
|
+
classes="ai-cta-button"
|
|
21
|
+
@click="$emit('action-click', action.type)"
|
|
22
|
+
/>
|
|
23
|
+
<Button
|
|
24
|
+
v-if="showUseButton"
|
|
25
|
+
:label="useButtonLabel"
|
|
26
|
+
variant="primary"
|
|
27
|
+
size="small"
|
|
28
|
+
@click="$emit('use-content')"
|
|
29
|
+
/>
|
|
30
|
+
</template>
|
|
19
31
|
<Button
|
|
20
32
|
v-if="showRegenerateButton && !isTyping"
|
|
21
33
|
label="Regenerate"
|
|
@@ -31,12 +43,14 @@
|
|
|
31
43
|
<script setup lang="ts">
|
|
32
44
|
import { Button } from '@designcrowd/fe-shared-lib';
|
|
33
45
|
import { generateAiModalTr } from '../../../composables/useTranslate';
|
|
46
|
+
import type { AgentAction } from '../../composables/ai-chat/types';
|
|
34
47
|
|
|
35
48
|
interface Props {
|
|
36
49
|
isTyping?: boolean;
|
|
37
50
|
showUseButton?: boolean;
|
|
38
51
|
showRegenerateButton?: boolean;
|
|
39
52
|
useButtonLabel?: string;
|
|
53
|
+
actions?: AgentAction[];
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
withDefaults(defineProps<Props>(), {
|
|
@@ -44,11 +58,24 @@ withDefaults(defineProps<Props>(), {
|
|
|
44
58
|
showUseButton: false,
|
|
45
59
|
showRegenerateButton: false,
|
|
46
60
|
useButtonLabel: 'Use text',
|
|
61
|
+
actions: () => [],
|
|
47
62
|
});
|
|
48
63
|
|
|
49
64
|
defineEmits<{
|
|
50
65
|
'use-content': [];
|
|
51
66
|
'stop-generation': [];
|
|
52
67
|
regenerate: [];
|
|
68
|
+
'action-click': [type: AgentAction['type']];
|
|
53
69
|
}>();
|
|
54
70
|
</script>
|
|
71
|
+
|
|
72
|
+
<style scoped>
|
|
73
|
+
/* The upgrade glyph (`upgrade-alt`) is a hairline faceted gem that barely reads
|
|
74
|
+
at button size. Give it a stroke in the current text colour so the icon is
|
|
75
|
+
distinguishable from afar. Scoped to the CTA button via the `ai-cta-button`
|
|
76
|
+
class passed to the design-system Button. */
|
|
77
|
+
:deep(.ai-cta-button svg path) {
|
|
78
|
+
stroke: currentColor;
|
|
79
|
+
stroke-width: 1;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
@use-text="$emit('use-text', message.props.text)"
|
|
12
12
|
@stop-generation="$emit('stop-generation')"
|
|
13
13
|
@use-image="$emit('use-image', $event)"
|
|
14
|
+
@action-click="$emit('action-click', $event)"
|
|
14
15
|
>
|
|
15
16
|
<template v-if="message.content" #default>
|
|
16
17
|
{{ message.content }}
|
|
@@ -49,6 +50,7 @@ import { ref, watch, nextTick, onMounted } from 'vue';
|
|
|
49
50
|
import { Button } from '@designcrowd/fe-shared-lib';
|
|
50
51
|
import TextBubble from './TextBubble.vue';
|
|
51
52
|
import ImageBubble from './ImageBubble.vue';
|
|
53
|
+
import type { AgentAction } from '../../composables/ai-chat/types';
|
|
52
54
|
|
|
53
55
|
interface Message {
|
|
54
56
|
type: 'text' | 'image';
|
|
@@ -86,6 +88,7 @@ const emit = defineEmits<{
|
|
|
86
88
|
'stop-generation': [];
|
|
87
89
|
'use-image': [images: any[]];
|
|
88
90
|
'prompt-click': [prompt: SuggestedPrompt];
|
|
91
|
+
'action-click': [type: AgentAction['type']];
|
|
89
92
|
}>();
|
|
90
93
|
|
|
91
94
|
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
:is-typing="isTyping || isAnimating"
|
|
33
33
|
:show-use-button="showUseButton"
|
|
34
34
|
:show-regenerate-button="showRegenerateButton"
|
|
35
|
+
:actions="actions"
|
|
35
36
|
use-button-label="Use text"
|
|
36
37
|
@use-content="$emit('use-text')"
|
|
37
38
|
@stop-generation="$emit('stop-generation')"
|
|
38
39
|
@regenerate="$emit('regenerate')"
|
|
40
|
+
@action-click="$emit('action-click', $event)"
|
|
39
41
|
>
|
|
40
42
|
<slot name="actions" />
|
|
41
43
|
</AiActionButtons>
|
|
@@ -59,6 +61,7 @@ import AiActionButtons from './AiActionButtons.vue';
|
|
|
59
61
|
import AiProgressFeed from './AiProgressFeed.vue';
|
|
60
62
|
import { generateAiModalTr } from '../../../composables/useTranslate';
|
|
61
63
|
import { type PlanItem } from '../../../clients/websocket-gateway-client';
|
|
64
|
+
import type { AgentAction } from '../../composables/ai-chat/types';
|
|
62
65
|
|
|
63
66
|
interface Props {
|
|
64
67
|
text?: string;
|
|
@@ -75,6 +78,7 @@ interface Props {
|
|
|
75
78
|
initialMessage?: string;
|
|
76
79
|
planItems?: PlanItem[];
|
|
77
80
|
completedItemIds?: string[];
|
|
81
|
+
actions?: AgentAction[];
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -92,12 +96,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
92
96
|
initialMessage: undefined,
|
|
93
97
|
planItems: () => [],
|
|
94
98
|
completedItemIds: () => [],
|
|
99
|
+
actions: () => [],
|
|
95
100
|
});
|
|
96
101
|
|
|
97
102
|
defineEmits<{
|
|
98
103
|
'use-text': [];
|
|
99
104
|
'stop-generation': [];
|
|
100
105
|
regenerate: [];
|
|
106
|
+
'action-click': [type: AgentAction['type']];
|
|
101
107
|
}>();
|
|
102
108
|
|
|
103
109
|
const copiedFeedback = ref(false);
|
|
@@ -220,7 +226,7 @@ const showActions = computed(() => {
|
|
|
220
226
|
props.isAi &&
|
|
221
227
|
!props.isTyping &&
|
|
222
228
|
!isAnimating.value &&
|
|
223
|
-
props.showUseButton
|
|
229
|
+
(props.showUseButton || (props.actions?.length ?? 0) > 0)
|
|
224
230
|
);
|
|
225
231
|
});
|
|
226
232
|
</script>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
class="tw-flex-1 tw-overflow-hidden tw-px-5"
|
|
15
15
|
@send="onSendMessage"
|
|
16
16
|
@stop-generation="onStopGeneration"
|
|
17
|
+
@action-click="onActionClick"
|
|
17
18
|
/>
|
|
18
19
|
</div>
|
|
19
20
|
</template>
|
|
@@ -24,12 +25,22 @@ import { storeToRefs } from 'pinia';
|
|
|
24
25
|
import SideBarSettingsHeader from '../../../shared/common/SideBarSettingsHeader.vue';
|
|
25
26
|
import AiChatbox from '../ai-text-edit/AiChatbox.vue';
|
|
26
27
|
import { useUiSideBarStore } from '../../../shared/side-bar/store';
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
useSnapshotStore,
|
|
30
|
+
useAiWebsiteBuilderThreadStore,
|
|
31
|
+
useBrandPageContainerStore,
|
|
32
|
+
} from '../../stores';
|
|
28
33
|
import { SIDE_BAR_TAB_NAMES } from '../../constants';
|
|
29
34
|
import { useAiWebsiteBuilderChat } from '../../composables/ai-chat/useAiWebsiteBuilderChat';
|
|
35
|
+
import {
|
|
36
|
+
UI_ACTIONS,
|
|
37
|
+
type AgentActionType,
|
|
38
|
+
} from '../../composables/ai-chat/ui-actions';
|
|
39
|
+
import { AI_CTA_LOCATIONS } from '../../composables/useEventTracker';
|
|
30
40
|
|
|
31
41
|
const sideBarStore = useUiSideBarStore();
|
|
32
42
|
const snapshotStore = useSnapshotStore();
|
|
43
|
+
const brandPageContainerStore = useBrandPageContainerStore();
|
|
33
44
|
const aiWebsiteBuilderThreadStore = useAiWebsiteBuilderThreadStore();
|
|
34
45
|
const { promptText } = storeToRefs(aiWebsiteBuilderThreadStore);
|
|
35
46
|
|
|
@@ -60,6 +71,15 @@ const onStopGeneration = async () => {
|
|
|
60
71
|
await stopGenerationAsync();
|
|
61
72
|
};
|
|
62
73
|
|
|
74
|
+
const onActionClick = async (type: AgentActionType) => {
|
|
75
|
+
if (type === UI_ACTIONS.UPGRADE) {
|
|
76
|
+
await brandPageContainerStore.showUpgradeModalAndSaveAsync(
|
|
77
|
+
undefined,
|
|
78
|
+
AI_CTA_LOCATIONS.AI_BUILDER_CHAT_UPSELL,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
63
83
|
defineExpose({
|
|
64
84
|
stopGenerationAsync,
|
|
65
85
|
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
v-if="isPublished"
|
|
15
15
|
data-component="collapsible-panel"
|
|
16
16
|
:title="commonTr('domains')"
|
|
17
|
+
:is-collapsed="true"
|
|
17
18
|
>
|
|
18
19
|
<!-- Start URL Section -->
|
|
19
20
|
<UrlSettings
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
<CollapsiblePanel
|
|
28
29
|
data-component="collapsible-panel"
|
|
29
30
|
:title="brandPageSettingsTr('seoSettings')"
|
|
31
|
+
:is-collapsed="true"
|
|
30
32
|
>
|
|
31
33
|
<PageSettings
|
|
32
34
|
v-if="designDraftToken"
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
<CollapsiblePanel
|
|
61
63
|
data-component="collapsible-panel"
|
|
62
64
|
:title="brandPageSettingsTr('integrationSettings')"
|
|
65
|
+
:is-collapsed="true"
|
|
63
66
|
>
|
|
64
67
|
<GoogleAnalytics
|
|
65
68
|
:value="measurementID"
|
|
@@ -72,6 +75,7 @@
|
|
|
72
75
|
<CollapsiblePanel
|
|
73
76
|
data-component="collapsible-panel"
|
|
74
77
|
:title="brandPageSettingsTr('socialShareTitle')"
|
|
78
|
+
:is-collapsed="true"
|
|
75
79
|
>
|
|
76
80
|
<SocialShare
|
|
77
81
|
:value="ogImageUrl"
|
|
@@ -86,6 +90,7 @@
|
|
|
86
90
|
v-if="isPaymentProvidersSectionEnabled && !isPrePurchase"
|
|
87
91
|
data-component="collapsible-panel"
|
|
88
92
|
:title="brandPageSettingsTr('paymentProvidersTitle')"
|
|
93
|
+
:is-collapsed="true"
|
|
89
94
|
>
|
|
90
95
|
<div class="tw-mt-5">
|
|
91
96
|
<SideBarSectionTitle
|
|
@@ -115,6 +120,7 @@
|
|
|
115
120
|
v-if="designDraftToken"
|
|
116
121
|
data-component="collapsible-panel"
|
|
117
122
|
:title="commonTr('sensitiveContent')"
|
|
123
|
+
:is-collapsed="true"
|
|
118
124
|
>
|
|
119
125
|
<div class="tw-mt-2">
|
|
120
126
|
<SensitiveContent
|
|
@@ -130,6 +136,7 @@
|
|
|
130
136
|
ref="saveContactPanel"
|
|
131
137
|
data-component="collapsible-panel"
|
|
132
138
|
:title="saveContactSettingsTr('saveContactLabel')"
|
|
139
|
+
:is-collapsed="true"
|
|
133
140
|
>
|
|
134
141
|
<div class="tw-mt-2">
|
|
135
142
|
<SaveContact
|
|
@@ -142,6 +149,7 @@
|
|
|
142
149
|
<CollapsiblePanel
|
|
143
150
|
data-component="collapsible-panel"
|
|
144
151
|
:title="brandPageSettingsTr('cookieCollection')"
|
|
152
|
+
:is-collapsed="true"
|
|
145
153
|
>
|
|
146
154
|
<CookieSettings :view-model="brandPageViewModel.cookieOptions" />
|
|
147
155
|
</CollapsiblePanel>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ComputedRef, Ref } from 'vue';
|
|
2
2
|
import { type PlanItem } from '../../../clients/websocket-gateway-client';
|
|
3
|
+
import type { AgentAction } from './ui-actions';
|
|
4
|
+
|
|
5
|
+
// Re-exported for the chat components that consume it; defined alongside the
|
|
6
|
+
// UI-action registry in ./ui-actions.
|
|
7
|
+
export type { AgentAction };
|
|
3
8
|
|
|
4
9
|
export interface BaseThreadMessage {
|
|
5
10
|
id: string;
|
|
@@ -16,6 +21,7 @@ export interface TextThreadMessage extends BaseThreadMessage {
|
|
|
16
21
|
initialMessage?: string;
|
|
17
22
|
planItems?: PlanItem[];
|
|
18
23
|
completedItemIds?: string[];
|
|
24
|
+
actions?: AgentAction[];
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export interface ImageThreadMessage extends BaseThreadMessage {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
partitionAiToolCalls,
|
|
4
|
+
UI_ACTIONS,
|
|
5
|
+
AI_CHAT_UI_ACTIONS,
|
|
6
|
+
} from './ui-actions';
|
|
7
|
+
import type { AiAssistantToolCall } from '../../../clients/ai-assistant-client';
|
|
8
|
+
|
|
9
|
+
const tc = (fn: string, toolId = fn, args = '{}'): AiAssistantToolCall => ({
|
|
10
|
+
toolId,
|
|
11
|
+
function: fn,
|
|
12
|
+
arguments: args,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('partitionAiToolCalls', () => {
|
|
16
|
+
// The core invariant: a UI-action CTA must never land in mutationToolCalls,
|
|
17
|
+
// because those feed applyToolCallsAsync -> toolResults -> submitToolResults.
|
|
18
|
+
// A UI action has no tool result, so leaking one would leave an unresolved
|
|
19
|
+
// tool call in the thread and break the next LLM turn.
|
|
20
|
+
it('keeps an upgrade CTA out of the mutation set', () => {
|
|
21
|
+
const mutation = tc('build_banner_section', 'm1');
|
|
22
|
+
const upgrade = tc(UI_ACTIONS.UPGRADE, 'u1');
|
|
23
|
+
|
|
24
|
+
const { mutationToolCalls, actions } = partitionAiToolCalls([
|
|
25
|
+
mutation,
|
|
26
|
+
upgrade,
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
expect(mutationToolCalls).toEqual([mutation]);
|
|
30
|
+
expect(
|
|
31
|
+
mutationToolCalls.some((t) => t.function === UI_ACTIONS.UPGRADE),
|
|
32
|
+
).toBe(false);
|
|
33
|
+
expect(actions).toEqual([
|
|
34
|
+
{ type: UI_ACTIONS.UPGRADE, ...AI_CHAT_UI_ACTIONS[UI_ACTIONS.UPGRADE] },
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('routes a standalone upgrade to actions with zero mutations', () => {
|
|
39
|
+
const { mutationToolCalls, actions } = partitionAiToolCalls([
|
|
40
|
+
tc(UI_ACTIONS.UPGRADE),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
expect(mutationToolCalls).toEqual([]);
|
|
44
|
+
expect(actions.map((a) => a.type)).toEqual([UI_ACTIONS.UPGRADE]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('treats unknown tool names as mutations (never swallows a real tool call)', () => {
|
|
48
|
+
const unknown = tc('some_future_tool');
|
|
49
|
+
|
|
50
|
+
const { mutationToolCalls, actions } = partitionAiToolCalls([unknown]);
|
|
51
|
+
|
|
52
|
+
expect(mutationToolCalls).toEqual([unknown]);
|
|
53
|
+
expect(actions).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not treat inherited Object keys (e.g. "constructor") as UI actions', () => {
|
|
57
|
+
const ctor = tc('constructor');
|
|
58
|
+
|
|
59
|
+
const { mutationToolCalls, actions } = partitionAiToolCalls([ctor]);
|
|
60
|
+
|
|
61
|
+
expect(mutationToolCalls).toEqual([ctor]);
|
|
62
|
+
expect(actions).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('resolves label/icon from the registry, not the wire arguments', () => {
|
|
66
|
+
const { actions } = partitionAiToolCalls([
|
|
67
|
+
tc(UI_ACTIONS.UPGRADE, 'u1', '{"label":"WRONG","icon":"WRONG"}'),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(actions[0].label).toBe('Upgrade');
|
|
71
|
+
expect(actions[0].icon).toBe('upgrade-alt');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns empty partitions for no tool calls', () => {
|
|
75
|
+
expect(partitionAiToolCalls([])).toEqual({
|
|
76
|
+
mutationToolCalls: [],
|
|
77
|
+
actions: [],
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Single source of truth for the AI-chat actionable buttons (the Upgrade CTA, etc.).
|
|
2
|
+
//
|
|
3
|
+
// The AI pipeline decides WHICH action to surface and WHETHER the user is
|
|
4
|
+
// eligible, then emits only the action identity as a tool call. How each action
|
|
5
|
+
// looks — its button label and icon — is a frontend concern and lives here.
|
|
6
|
+
// Click behaviour is wired in AiWebsiteBuilder, where the relevant stores live.
|
|
7
|
+
import type { AiAssistantToolCall } from '../../../clients/ai-assistant-client';
|
|
8
|
+
|
|
9
|
+
// Action identities — must match the tool names emitted by the AI pipeline.
|
|
10
|
+
export const UI_ACTIONS = {
|
|
11
|
+
UPGRADE: 'upgrade',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const AI_CHAT_UI_ACTIONS = {
|
|
15
|
+
[UI_ACTIONS.UPGRADE]: { label: 'Upgrade', icon: 'upgrade-alt' },
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export type AgentActionType = keyof typeof AI_CHAT_UI_ACTIONS;
|
|
19
|
+
|
|
20
|
+
// A resolved actionable button shown under an AI message. `type` identifies the
|
|
21
|
+
// action; `label`/`icon` are resolved from AI_CHAT_UI_ACTIONS, never from the wire.
|
|
22
|
+
export type AgentAction = {
|
|
23
|
+
type: AgentActionType;
|
|
24
|
+
label: string;
|
|
25
|
+
icon?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// `hasOwnProperty` (not `in`) so inherited Object keys like "constructor" are never
|
|
29
|
+
// mistaken for a UI action.
|
|
30
|
+
const isUiActionFn = (fn: string | undefined): fn is AgentActionType =>
|
|
31
|
+
!!fn && Object.prototype.hasOwnProperty.call(AI_CHAT_UI_ACTIONS, fn);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Split server tool calls into page mutations and UI-action CTAs.
|
|
35
|
+
*
|
|
36
|
+
* Mutations are executed by the metadata-builder and answered with tool results;
|
|
37
|
+
* UI actions (e.g. the Upgrade button) are rendered only. UI actions MUST stay out
|
|
38
|
+
* of `mutationToolCalls`: they are never executed and never get a tool result, so
|
|
39
|
+
* leaking one into the mutation set would leave an unresolved tool call and break
|
|
40
|
+
* the next LLM turn on the same thread.
|
|
41
|
+
*/
|
|
42
|
+
export function partitionAiToolCalls(toolCalls: AiAssistantToolCall[]): {
|
|
43
|
+
mutationToolCalls: AiAssistantToolCall[];
|
|
44
|
+
actions: AgentAction[];
|
|
45
|
+
} {
|
|
46
|
+
const mutationToolCalls: AiAssistantToolCall[] = [];
|
|
47
|
+
const actions: AgentAction[] = [];
|
|
48
|
+
|
|
49
|
+
for (const tc of toolCalls) {
|
|
50
|
+
if (isUiActionFn(tc.function)) {
|
|
51
|
+
actions.push({ type: tc.function, ...AI_CHAT_UI_ACTIONS[tc.function] });
|
|
52
|
+
} else {
|
|
53
|
+
mutationToolCalls.push(tc);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { mutationToolCalls, actions };
|
|
58
|
+
}
|