@designcrowd/library.brand-page 5.16.3 → 5.16.4-abtest-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BVouwZJN.js → CreatedOnBrandCrowd-de-de-DQc3IjpR.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-Dtx-1uWO.js → CreatedOnBrandCrowd-en-us-CIgIJjzf.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-CmlnW8oC.js → CreatedOnBrandCrowd-es-es-C4JLISvP.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-C95ioWzr.js → CreatedOnBrandCrowd-fr-ca-DPpWX0aJ.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-C95ioWzr.js → CreatedOnBrandCrowd-fr-fr-DPpWX0aJ.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-C_5SiaiE.js → CreatedOnBrandCrowd-pt-br-DmKD5o2o.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-C_5SiaiE.js → CreatedOnBrandCrowd-pt-pt-DmKD5o2o.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BP4saQXn.js → CreatedOnDcom-de-de-DhALOqwz.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-B8Taru6f.js → CreatedOnDcom-en-us-LmejZqTn.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-Bo3JhWwd.js → CreatedOnDcom-es-es-D3ZH5JD5.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-CDk3ZAHU.js → CreatedOnDcom-fr-ca-BUOMh8kU.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-CD04W_hw.js → CreatedOnDcom-fr-fr-CNc-56QW.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-B2DGv1qj.js → CreatedOnDcom-pt-br-BGvy2ON7.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-TYqvgQ08.js → CreatedOnDcom-pt-pt-FaVxQHpN.js} +1 -1
  15. package/lib/src/brand-page/models/brand-page-context.model.d.ts +1 -1
  16. package/lib/src/brand-page/models/brand-page-context.model.js +5 -1
  17. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +13 -0
  18. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +30 -1
  19. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +0 -12
  20. package/lib/src/brand-page-maker/stores/index.d.ts +1 -0
  21. package/lib/src/brand-page-maker/stores/index.js +1 -0
  22. package/lib/src/brand-page-maker/stores/snapshot/snapshot-store.d.ts +14 -0
  23. package/lib/src/brand-page-maker/stores/snapshot/snapshot-store.js +21 -0
  24. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +2 -0
  25. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +117 -0
  26. package/lib/src/bundled-translations-Dw3XtNmX.js +163 -0
  27. package/lib/src/bundled-translations.de-DE-B82Ik9M7.js +163 -0
  28. package/lib/src/bundled-translations.es-ES-cf3pdNuN.js +163 -0
  29. package/lib/src/bundled-translations.fr-CA-DP44EcAZ.js +163 -0
  30. package/lib/src/bundled-translations.fr-FR-V0Zt25kg.js +163 -0
  31. package/lib/src/bundled-translations.pt-BR-Brv6rCss.js +163 -0
  32. package/lib/src/bundled-translations.pt-PT-BTgscCTG.js +163 -0
  33. package/lib/src/bundles/bundled-translations.de-DE.json +15 -1
  34. package/lib/src/bundles/bundled-translations.es-ES.json +15 -1
  35. package/lib/src/bundles/bundled-translations.fr-CA.json +15 -1
  36. package/lib/src/bundles/bundled-translations.fr-FR.json +15 -1
  37. package/lib/src/bundles/bundled-translations.json +15 -1
  38. package/lib/src/bundles/bundled-translations.pt-BR.json +15 -1
  39. package/lib/src/bundles/bundled-translations.pt-PT.json +15 -1
  40. package/lib/src/clients/brand-page-client.d.ts +0 -4
  41. package/lib/src/clients/design-template-client.d.ts +2 -0
  42. package/lib/src/clients/design-template-client.js +33 -0
  43. package/lib/src/composables/useSnapshot.d.ts +5 -0
  44. package/lib/src/composables/useSnapshot.js +74 -0
  45. package/lib/src/composables/useTranslate.d.ts +2 -1
  46. package/lib/src/composables/useTranslate.js +2 -1
  47. package/lib/src/css/library.brand-page-brandCrowd.min.css +149 -8
  48. package/lib/src/css/library.brand-page-brandPage.min.css +136 -8
  49. package/lib/src/css/library.brand-page-designCom.min.css +149 -8
  50. package/lib/src/css/library.brand-page-preview.min.css +133 -8
  51. package/lib/src/index.mjs +1 -1
  52. package/lib/src/{lib-3sE17Ltm.js → lib-CqA2Cr9V.js} +20353 -19606
  53. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -0
  54. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +3 -0
  55. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +2 -0
  56. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.js +6 -0
  57. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +8 -0
  58. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +44 -1
  59. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +1 -0
  60. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +3 -0
  61. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +21 -3
  62. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +39 -2
  63. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +20 -2
  64. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +41 -2
  65. package/lib/src/shared/content-settings/utils.d.ts +1 -0
  66. package/lib/src/shared/content-settings/utils.js +6 -0
  67. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +0 -1
  68. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.js +0 -4
  69. package/package.json +2 -2
  70. package/src/brand-page/models/brand-page-context.model.ts +13 -3
  71. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +31 -0
  72. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +2 -1
  73. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +0 -36
  74. package/src/brand-page-maker/stores/index.ts +1 -0
  75. package/src/brand-page-maker/stores/snapshot/snapshot-store.ts +31 -0
  76. package/src/brand-page-maker/utils/rich-text-editor.util.ts +154 -0
  77. package/src/bundles/bundled-translations.de-DE.json +15 -1
  78. package/src/bundles/bundled-translations.es-ES.json +15 -1
  79. package/src/bundles/bundled-translations.fr-CA.json +15 -1
  80. package/src/bundles/bundled-translations.fr-FR.json +15 -1
  81. package/src/bundles/bundled-translations.json +15 -1
  82. package/src/bundles/bundled-translations.pt-BR.json +15 -1
  83. package/src/bundles/bundled-translations.pt-PT.json +15 -1
  84. package/src/clients/brand-page-client.ts +0 -4
  85. package/src/clients/design-template-client.ts +57 -0
  86. package/src/composables/useSnapshot.ts +97 -0
  87. package/src/composables/useTranslate.ts +3 -0
  88. package/src/shared/common/i18n/common.de-DE.json +5 -1
  89. package/src/shared/common/i18n/common.es-ES.json +5 -1
  90. package/src/shared/common/i18n/common.fr-CA.json +5 -1
  91. package/src/shared/common/i18n/common.fr-FR.json +5 -1
  92. package/src/shared/common/i18n/common.json +5 -1
  93. package/src/shared/common/i18n/common.pt-BR.json +5 -1
  94. package/src/shared/common/i18n/common.pt-PT.json +5 -1
  95. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +3 -0
  96. package/src/shared/content-settings/components/content-settings/ContentSettings.vue +2 -0
  97. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.ts +8 -0
  98. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -0
  99. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +54 -0
  100. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.vue +20 -2
  101. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +3 -1
  102. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +1 -0
  103. package/src/shared/content-settings/components/generate-ai-button/GenerateAiButton.vue +95 -0
  104. package/src/shared/content-settings/components/generate-ai-modal/GenerateAiModal.vue +500 -0
  105. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.de-DE.json +12 -0
  106. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.es-ES.json +12 -0
  107. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.fr-CA.json +12 -0
  108. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.fr-FR.json +12 -0
  109. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.json +12 -0
  110. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.pt-BR.json +12 -0
  111. package/src/shared/content-settings/components/generate-ai-modal/i18n/generate-ai.pt-PT.json +12 -0
  112. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +44 -2
  113. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +15 -3
  114. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +52 -2
  115. package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +16 -3
  116. package/src/shared/content-settings/utils.ts +9 -0
  117. package/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.ts +0 -6
  118. package/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.vue +1 -1
  119. package/lib/src/bundled-translations-D9iHE5G9.js +0 -161
  120. package/lib/src/bundled-translations.de-DE-DW3iDx_G.js +0 -161
  121. package/lib/src/bundled-translations.es-ES-C9I8wDyq.js +0 -161
  122. package/lib/src/bundled-translations.fr-CA-C0NN8DsO.js +0 -161
  123. package/lib/src/bundled-translations.fr-FR-oKV5NyE1.js +0 -161
  124. package/lib/src/bundled-translations.pt-BR-CTvCfv1X.js +0 -161
  125. package/lib/src/bundled-translations.pt-PT-CbwxwRY1.js +0 -161
@@ -0,0 +1,500 @@
1
+ <template>
2
+ <div>
3
+ <Modal
4
+ full-screen-breakpoint="sm"
5
+ :visible="props.isVisible"
6
+ :close-on-esc="true"
7
+ :hide-scrollbar="false"
8
+ :show-modal-background-image="false"
9
+ @close-modal="onClose"
10
+ >
11
+ <template #header>
12
+ <h2
13
+ class="tw-font-bold tw-text-2xl sm:tw-text-3xl tw-mb-2 tw-text-black tw-bg-grayscale-200 tw-flex tw-items-center"
14
+ >
15
+ <Icon name="ai" size="lg" class="tw-mr-2 tw-text-primary-500" />
16
+ {{ generateAiModalTr('generateTitle') }}
17
+ </h2>
18
+ </template>
19
+ <template #default>
20
+ <div
21
+ class="tw-bg-white tw-rounded-lg tw-shadow-md tw-text-left tw-border-grayscale-500 tw-border tw-border-solid tw-flex tw-flex-col tw-h-full"
22
+ >
23
+ <div
24
+ class="tw-border-b tw-border-solid tw-border-grayscale-500 tw-p-4 tw-flex-1 tw-overflow-auto tw-flex tw-flex-col tw-justify-between"
25
+ >
26
+ <div
27
+ class="tw-p-2 tw-rounded-md tw-mb-4 tw-block"
28
+ :class="{
29
+ 'c-modal-dcom': isDesignCom,
30
+ 'c-modal-bc': !isDesignCom,
31
+ }"
32
+ >
33
+ <div
34
+ class="tw-flex tw-text-sm tw-items-start tw-text-grayscale-600"
35
+ >
36
+ <SparkleIcon
37
+ v-if="isProcessing"
38
+ color-class="tw-text-primary-500"
39
+ animated
40
+ class="tw-mr-2 tw-flex tw-flex-shrink-0 tw-mt-0.5"
41
+ />
42
+ <Icon
43
+ v-else
44
+ name="ai"
45
+ size="sm"
46
+ class="tw-mr-2 tw-text-primary-500 tw-flex tw-flex-shrink-0 tw-mt-0.5"
47
+ />
48
+ <p class="tw-flex-1 tw-max-h-64 tw-overflow-y-auto">
49
+ {{ response }}
50
+ <span v-if="data.isTyping" class="typing-cursor">|</span>
51
+ </p>
52
+ </div>
53
+ <div class="tw-flex tw-justify-end tw-gap-2">
54
+ <div
55
+ v-if="isDoneTyping"
56
+ class="tw-flex tw-gap-2 tw-items-center tw-mt-2"
57
+ >
58
+ <div class="tw-relative tw-group">
59
+ <Icon
60
+ v-if="!data.isCopied"
61
+ name="duplicate"
62
+ size="sm"
63
+ class="tw-cursor-pointer hover:tw-text-info-700"
64
+ @click="copyToClipboard"
65
+ />
66
+ <Icon v-else name="check" size="sm" class="text-success" />
67
+ <span
68
+ v-if="!data.isCopied"
69
+ class="tw-absolute tw-bottom-full tw-left-1/2 tw--translate-x-1/2 tw-mb-2 tw-px-2 tw-py-1 tw-text-xs tw-text-white tw-bg-grayscale-800 tw-rounded tw-whitespace-nowrap tw-opacity-0 group-hover:tw-opacity-100 tw-transition-opacity tw-pointer-events-none"
70
+ >
71
+ {{ commonTr('copy') }}
72
+ </span>
73
+ </div>
74
+ <Button
75
+ classes="tw-w-full tw-h-full"
76
+ :label="commonTr('regenerateText')"
77
+ variant="primary"
78
+ size="small"
79
+ :attrs="{ 'data-test-tag': 'regenerate-btn' }"
80
+ @click="onRegeneratePrompt"
81
+ />
82
+ <Button
83
+ classes="tw-w-full tw-h-full"
84
+ :label="generateAiModalTr('useText')"
85
+ variant="primary"
86
+ size="small"
87
+ :attrs="{ 'data-test-tag': 'use-text-btn' }"
88
+ @click="onUseText"
89
+ />
90
+ </div>
91
+ <div v-if="data.isApiRequesting">
92
+ <Button
93
+ classes="tw-w-full tw-h-full"
94
+ :label="generateAiModalTr('stopGeneration')"
95
+ variant="primary"
96
+ size="small"
97
+ icon-left="stop"
98
+ :attrs="{ 'data-test-tag': 'stop-btn' }"
99
+ @click="onStopGeneration"
100
+ />
101
+ </div>
102
+ </div>
103
+ </div>
104
+ <p class="tw-text-xs text-grayscale tw-flex tw-items-center">
105
+ <Icon name="callout-info" size="sm" class="tw-mr-1 tw-flex" />
106
+ {{ generateAiModalTr('generateDescription') }}
107
+ </p>
108
+ </div>
109
+ <div class="tw-p-4 tw-flex tw-flex-col sm:tw-flex-row tw-gap-2">
110
+ <div class="tw-flex tw-flex-col tw-flex-1 tw-gap-1">
111
+ <div class="tw-flex tw-items-center tw-flex-1">
112
+ <textarea
113
+ ref="textareaRef"
114
+ v-model="data.prompt"
115
+ :placeholder="generateAiModalTr('generateInputPlaceholder')"
116
+ :disabled="isProcessing"
117
+ rows="1"
118
+ class="prompt-textarea tw-font-sans tw-box-border tw-w-full tw-py-2 tw-text-sm tw-px-4 tw-rounded tw-border tw-border-solid tw-transition-colors tw-duration-300 tw-ease-out tw-shadow-inner tw-resize-none tw-overflow-y-auto sm:tw-min-h-10"
119
+ :class="{
120
+ 'tw-border-error-600 focus:tw-border-error-700':
121
+ isPromptTooLong,
122
+ 'tw-border-grayscale-500 focus:tw-border-grayscale-600':
123
+ !isPromptTooLong,
124
+ 'tw-bg-grayscale-300': isProcessing,
125
+ }"
126
+ @input="resizeTextarea"
127
+ />
128
+ </div>
129
+ <p
130
+ v-if="data.promptError"
131
+ class="tw-text-xs tw-text-error-600 tw-mt-1"
132
+ >
133
+ {{ data.promptError }}
134
+ </p>
135
+ </div>
136
+ <Button
137
+ classes="tw-w-full sm:tw-w-auto tw-h-full tw-max-h-10"
138
+ class="tw-flex tw-justify-center tw-items-center"
139
+ :label="commonTr('sendButtonText')"
140
+ variant="primary"
141
+ size="small-medium"
142
+ icon-left="send"
143
+ icon-left-size="sm"
144
+ :disabled="!data.prompt.trim() || isProcessing || isPromptTooLong"
145
+ :attrs="{
146
+ 'data-test-tag': 'send-btn',
147
+ }"
148
+ @click="onSendButtonClick"
149
+ />
150
+ </div>
151
+ </div>
152
+ </template>
153
+ </Modal>
154
+ </div>
155
+ </template>
156
+
157
+ <script setup lang="ts">
158
+ import { Modal, Icon, Button, SparkleIcon } from '@designcrowd/fe-shared-lib';
159
+ import { computed, reactive, onUnmounted, watch, ref, nextTick } from 'vue';
160
+ import {
161
+ generateAiModalTr,
162
+ commonTr,
163
+ } from '../../../../composables/useTranslate';
164
+ import { getInstance as getDesignTemplateClient } from '../../../../clients/design-template-client';
165
+
166
+ interface Props {
167
+ isVisible: boolean;
168
+ existingText?: string;
169
+ snapshotBase64?: string | null;
170
+ isDesignCom?: boolean;
171
+ fieldType?: string;
172
+ }
173
+
174
+ const props = withDefaults(defineProps<Props>(), {
175
+ isVisible: true,
176
+ existingText: '',
177
+ snapshotBase64: null,
178
+ fieldType: 'text',
179
+ });
180
+
181
+ const data = reactive({
182
+ prompt: '',
183
+ response: null as string | null,
184
+ lastPromptSent: '',
185
+ isApiRequesting: false,
186
+ isTyping: false,
187
+ isStopped: false,
188
+ typingIntervalId: null as number | null,
189
+ isCopied: false,
190
+ promptError: '',
191
+ });
192
+
193
+ const textareaRef = ref<HTMLTextAreaElement | null>(null);
194
+ const MAX_PROMPT_LENGTH = 500;
195
+
196
+ // Auto-resize textarea
197
+ const resizeTextarea = () => {
198
+ if (textareaRef.value) {
199
+ textareaRef.value.style.height = 'auto';
200
+ textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`;
201
+ }
202
+ };
203
+
204
+ const isProcessing = computed(() => data.isApiRequesting || data.isTyping);
205
+
206
+ const response = computed(() => {
207
+ if (isProcessing.value && data.response === null) {
208
+ return '';
209
+ }
210
+ return data.response ?? generateAiModalTr('generatePlaceholder');
211
+ });
212
+
213
+ const isDoneTyping = computed(
214
+ () => !isProcessing.value && data.response !== null,
215
+ );
216
+
217
+ const isPromptTooLong = computed(() => data.prompt.length > MAX_PROMPT_LENGTH);
218
+
219
+ // Watch prompt length and set error
220
+ watch(
221
+ () => data.prompt,
222
+ (newPrompt) => {
223
+ if (newPrompt.length > MAX_PROMPT_LENGTH) {
224
+ data.promptError = generateAiModalTr('promptTooLong', {
225
+ maxLength: MAX_PROMPT_LENGTH,
226
+ currentLength: newPrompt.length,
227
+ });
228
+ } else {
229
+ data.promptError = '';
230
+ }
231
+
232
+ // Resize textarea on next tick to ensure DOM is updated
233
+ nextTick(() => {
234
+ resizeTextarea();
235
+ });
236
+ },
237
+ );
238
+
239
+ const emit = defineEmits(['on-close', 'on-use-text']);
240
+
241
+ const clearTypingInterval = () => {
242
+ if (data.typingIntervalId) {
243
+ clearInterval(data.typingIntervalId);
244
+ data.typingIntervalId = null;
245
+ }
246
+ };
247
+
248
+ const stopGeneration = () => {
249
+ data.isStopped = true;
250
+ data.isApiRequesting = false;
251
+ data.isTyping = false;
252
+ clearTypingInterval();
253
+ };
254
+
255
+ const resetModalState = () => {
256
+ data.response = null;
257
+ data.prompt = '';
258
+ data.isCopied = false;
259
+ data.promptError = '';
260
+ data.isApiRequesting = false;
261
+ data.isTyping = false;
262
+ };
263
+
264
+ const onClose = () => {
265
+ // Mark as stopped so API response is ignored
266
+ data.isStopped = true;
267
+ clearTypingInterval();
268
+
269
+ // Reset UI state
270
+ resetModalState();
271
+
272
+ emit('on-close');
273
+ };
274
+
275
+ const typeText = (fullText: string) => {
276
+ clearTypingInterval();
277
+
278
+ data.response = '';
279
+ data.isTyping = true;
280
+ let index = 0;
281
+ const charsPerTick = fullText.length > 150 ? 2 : 1;
282
+
283
+ data.typingIntervalId = window.setInterval(() => {
284
+ // Check if generation was stopped
285
+ if (data.isStopped) {
286
+ clearTypingInterval();
287
+ data.isTyping = false;
288
+ return;
289
+ }
290
+
291
+ if (index < fullText.length) {
292
+ index = Math.min(index + charsPerTick, fullText.length);
293
+ data.response = fullText.substring(0, index);
294
+ } else {
295
+ clearTypingInterval();
296
+ data.isTyping = false;
297
+ }
298
+ }, 20);
299
+ };
300
+
301
+ const copyToClipboard = async () => {
302
+ if (!data.response) return;
303
+
304
+ try {
305
+ await navigator.clipboard.writeText(data.response);
306
+ data.isCopied = true;
307
+
308
+ // Reset the icon back to duplicate after 2 seconds
309
+ setTimeout(() => {
310
+ data.isCopied = false;
311
+ }, 2000);
312
+ } catch (error) {
313
+ console.error('Failed to copy text:', error);
314
+ }
315
+ };
316
+
317
+ const onUseText = () => {
318
+ if (!data.response) return;
319
+
320
+ emit('on-use-text', data.response);
321
+ onClose();
322
+ };
323
+
324
+ const onStopGeneration = () => {
325
+ stopGeneration();
326
+ data.response = generateAiModalTr('generationStopped');
327
+ };
328
+ const onRegeneratePrompt = () => {
329
+ if (data.lastPromptSent) {
330
+ onSendPrompt(data.lastPromptSent);
331
+ }
332
+ };
333
+
334
+ const onSendButtonClick = () => {
335
+ onSendPrompt();
336
+ };
337
+
338
+ const onSendPrompt = async (customPrompt?: string) => {
339
+ const promptToSend = customPrompt || data.prompt.trim();
340
+
341
+ if (
342
+ !promptToSend ||
343
+ (!customPrompt && promptToSend.length > MAX_PROMPT_LENGTH)
344
+ ) {
345
+ return;
346
+ }
347
+
348
+ data.lastPromptSent = promptToSend;
349
+ data.isStopped = false;
350
+ data.isApiRequesting = true;
351
+ data.isTyping = false;
352
+ data.response = null;
353
+ data.prompt = '';
354
+ clearTypingInterval();
355
+
356
+ try {
357
+ const client = getDesignTemplateClient();
358
+ const result = await client.generateText(
359
+ promptToSend,
360
+ props.existingText ?? '',
361
+ props.snapshotBase64 || undefined,
362
+ );
363
+
364
+ // Check if generation was stopped before setting response
365
+ if (data.isStopped) {
366
+ data.isApiRequesting = false;
367
+ return;
368
+ }
369
+
370
+ data.isApiRequesting = false;
371
+
372
+ if (result) {
373
+ typeText(result);
374
+ } else {
375
+ data.response = 'Failed to generate text. Please try again.';
376
+ }
377
+ } catch (error: any) {
378
+ // Check if generation was stopped before setting error
379
+ if (data.isStopped) {
380
+ data.isApiRequesting = false;
381
+ return;
382
+ }
383
+
384
+ data.isApiRequesting = false;
385
+ data.response =
386
+ error?.response?.data?.error || 'An error occurred. Please try again.';
387
+ }
388
+ };
389
+
390
+ // Generate default prompt based on field type
391
+ const getDefaultPrompt = (): string => {
392
+ const fieldType = props.fieldType || 'text';
393
+ return `Improve this ${fieldType}`;
394
+ };
395
+
396
+ // Auto-generate text when modal opens if there's existing text
397
+ watch(
398
+ () => props.isVisible,
399
+ (newValue, oldValue) => {
400
+ if (newValue && !oldValue) {
401
+ // Only reset when transitioning from closed to open
402
+ resetModalState();
403
+ data.isStopped = false;
404
+ data.lastPromptSent = '';
405
+
406
+ if (props.existingText && props.existingText.trim()) {
407
+ const defaultPrompt = getDefaultPrompt();
408
+ onSendPrompt(defaultPrompt);
409
+ }
410
+ } else if (!newValue && oldValue) {
411
+ // Modal is closing - stop everything immediately
412
+ data.isStopped = true;
413
+ clearTypingInterval();
414
+ data.isApiRequesting = false;
415
+ data.isTyping = false;
416
+ }
417
+ },
418
+ { flush: 'sync' }, // Execute synchronously to prevent race conditions
419
+ );
420
+
421
+ onUnmounted(clearTypingInterval);
422
+ </script>
423
+ <style scoped>
424
+ .c-modal-dcom {
425
+ background-color: #eceefe;
426
+ border: 1px solid #3247c5;
427
+ }
428
+
429
+ .c-modal-bc {
430
+ background-color: #cceaf7;
431
+ border: 1px solid #0097d7;
432
+ }
433
+
434
+ .typing-cursor {
435
+ display: inline-block;
436
+ animation: blink 1s step-end infinite;
437
+ color: #3247c5;
438
+ font-weight: bold;
439
+ margin-left: 2px;
440
+ }
441
+
442
+ .prompt-textarea {
443
+ line-height: 1.5;
444
+ min-height: 4.5rem; /* Minimum 3 rows on mobile (1.5em * 3) */
445
+ max-height: calc(7.5em + 1rem); /* Maximum 5 rows */
446
+ color: rgb(96 96 96);
447
+ scrollbar-gutter: stable;
448
+ overflow-y: auto;
449
+ }
450
+
451
+ /* Desktop: smaller minimum height */
452
+ @media (min-width: 640px) {
453
+ .prompt-textarea {
454
+ min-height: 2.5rem; /* Single line on desktop */
455
+ }
456
+ }
457
+
458
+ .prompt-textarea::placeholder {
459
+ color: rgb(96 96 96 / 50%);
460
+ }
461
+
462
+ /* Ensure border is visible when scrollbar appears */
463
+ .prompt-textarea::-webkit-scrollbar {
464
+ width: 8px;
465
+ }
466
+
467
+ .prompt-textarea::-webkit-scrollbar-track {
468
+ background: transparent;
469
+ margin: 2px;
470
+ }
471
+
472
+ .prompt-textarea::-webkit-scrollbar-thumb {
473
+ background: rgb(96 96 96 / 30%);
474
+ border-radius: 4px;
475
+ }
476
+
477
+ .prompt-textarea::-webkit-scrollbar-thumb:hover {
478
+ background: rgb(96 96 96 / 50%);
479
+ }
480
+
481
+ .text-grayscale {
482
+ color: rgb(96 96 96 / 50%);
483
+ }
484
+
485
+ .text-success {
486
+ --tw-text-opacity: 1;
487
+ color: rgb(29 134 13 / var(--tw-text-opacity));
488
+ }
489
+
490
+ @keyframes blink {
491
+ 0%,
492
+ 50% {
493
+ opacity: 1;
494
+ }
495
+ 51%,
496
+ 100% {
497
+ opacity: 0;
498
+ }
499
+ }
500
+ </style>
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Text mit KI generieren",
4
+ "generateDescription" : "Der Inhalt wurde von einer KI generiert und muss möglicherweise zur Verbesserung der Genauigkeit bearbeitet werden",
5
+ "generatePlaceholder" : "Beschreiben Sie Ihren Text.",
6
+ "generationStopped" : "Die Textgenerierung wurde gestoppt. Starten Sie eine neue Eingabeaufforderung, um fortzufahren.",
7
+ "generateInputPlaceholder" : "Geben Sie Ihre Eingabeaufforderung ein, um den Text zu beschreiben ...",
8
+ "stopGeneration" : "Generierung anhalten",
9
+ "useText" : "Text verwenden",
10
+ "promptTooLong" : "Der Prompt darf maximal {{maxLength}} Zeichen lang sein ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Generar texto con IA",
4
+ "generateDescription" : "El contenido se genera mediante IA y podría necesitar una revisión para garantizar su precisión",
5
+ "generatePlaceholder" : "Describe tu texto.",
6
+ "generationStopped" : "El proceso de generación se ha detenido. Envía un nuevo mensaje para continuar.",
7
+ "generateInputPlaceholder" : "Introduce tu mensaje para describir el texto...",
8
+ "stopGeneration" : "Detén la generación",
9
+ "useText" : "Usa texto",
10
+ "promptTooLong" : "El mensaje debe tener {{maxLength}} caracteres como máximo ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Générer du texte avec l'IA",
4
+ "generateDescription" : "Le contenu est généré par l'IA et peut nécessiter des modifications pour plus de précision.",
5
+ "generatePlaceholder" : "Décrivez votre texte.",
6
+ "generationStopped" : "La génération de texte s'est arrêtée. Lancez une nouvelle requête pour continuer.",
7
+ "generateInputPlaceholder" : "Entrez votre requête pour décrire le contenu...",
8
+ "stopGeneration" : "Arrêter la génération",
9
+ "useText" : "Utiliser le texte",
10
+ "promptTooLong" : "La requête doit comporter {{maxLength}} caractères ou moins ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Générer du texte avec l'IA",
4
+ "generateDescription" : "Le contenu est généré par l'IA et peut nécessiter des modifications pour plus de précision.",
5
+ "generatePlaceholder" : "Décrivez votre texte.",
6
+ "generationStopped" : "La génération de texte s'est arrêtée. Démarrez une nouvelle invite pour continuer.",
7
+ "generateInputPlaceholder" : "Entrez votre prompt pour décrire le contenu...",
8
+ "stopGeneration" : "Arrêter la génération",
9
+ "useText" : "Utiliser le texte",
10
+ "promptTooLong" : "L'invite doit comporter {{maxLength}} caractères ou moins ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal": {
3
+ "generateTitle": "Generate Text with AI",
4
+ "generateDescription": "Content is AI-generated and may require editing for accuracy",
5
+ "generatePlaceholder": "Describe your text.",
6
+ "generationStopped": "Text generation stopped. Start a new prompt to continue.",
7
+ "generateInputPlaceholder": "Enter your prompt to describe the text...",
8
+ "stopGeneration": "Stop generation",
9
+ "useText": "Use text",
10
+ "promptTooLong": "Prompt must be {{maxLength}} characters or less ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Gerar texto com IA",
4
+ "generateDescription" : "O conteúdo é gerado por IA, pode precisar de alterações para melhorar a precisão",
5
+ "generatePlaceholder" : "Descreva seu texto.",
6
+ "generationStopped" : "A geração de texto parou. Inicie um novo prompt para continuar.",
7
+ "generateInputPlaceholder" : "Digite o prompt para descrever o texto...",
8
+ "stopGeneration" : "Parar de gerar",
9
+ "useText" : "Usar texto",
10
+ "promptTooLong" : "O prompt deve ter {{maxLength}} caracteres ou menos ({{currentLength}})"
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "aiModal" : {
3
+ "generateTitle" : "Gerar Texto com IA",
4
+ "generateDescription" : "O conteúdo é gerado por IA e pode precisar de edição para ser preciso",
5
+ "generatePlaceholder" : "Descreva o seu texto.",
6
+ "generationStopped" : "A geração de texto parou. Inicie um novo prompt para continuar.",
7
+ "generateInputPlaceholder" : "Introduza o seu comando para descrever o texto...",
8
+ "stopGeneration" : "Parar a geração",
9
+ "useText" : "Utilizar texto",
10
+ "promptTooLong" : "A mensagem tem de ter {{maxLength}} carateres ou menos ({{currentLength}})"
11
+ }
12
+ }
@@ -1,4 +1,4 @@
1
- import { defineComponent } from 'vue';
1
+ import { defineComponent, computed } from 'vue';
2
2
  import type { PropType } from 'vue';
3
3
  import type {
4
4
  ColorReplacement,
@@ -16,6 +16,9 @@ import type { DebounceHandle } from 'lodash.debounce';
16
16
  import debounce from 'lodash.debounce';
17
17
  import type { UpdateItemValuePayload } from '../../models';
18
18
  import cloneDeep from 'lodash.clonedeep';
19
+ import { useSnapshotStore } from '../../../../brand-page-maker/stores';
20
+ import { getPlainTextFromRTE } from '../../../../brand-page-maker/utils';
21
+ import { isDefaultContentText } from '../../utils';
19
22
 
20
23
  const DEBOUNCE_IN_MS = 250;
21
24
 
@@ -45,8 +48,24 @@ export default defineComponent({
45
48
  type: Object as PropType<Content>,
46
49
  required: true,
47
50
  },
51
+ isDesignCom: {
52
+ type: Boolean,
53
+ required: false,
54
+ default: false,
55
+ },
56
+ },
57
+ setup() {
58
+ const snapshotStore = useSnapshotStore();
59
+
60
+ return {
61
+ snapshotBase64: computed(() => snapshotStore.snapshotBase64),
62
+ };
48
63
  },
49
- emits: ['update:modelValue', 'on-intent-image-replace'],
64
+ emits: [
65
+ 'update:modelValue',
66
+ 'on-intent-image-replace',
67
+ 'capture-preview-snapshot',
68
+ ],
50
69
  data: () => ({
51
70
  changeDebounce: undefined as DebounceHandle | undefined,
52
71
  options: [] as TestimonialItems[],
@@ -185,5 +204,28 @@ export default defineComponent({
185
204
  this.options[index][payload.name] = payload.value;
186
205
  this.emitChange();
187
206
  },
207
+ getPlainTextValue(index: number): string {
208
+ const text = this.options[index]?.testimonials || '';
209
+ const plainText = getPlainTextFromRTE(text);
210
+
211
+ if (isDefaultContentText(plainText)) {
212
+ return '';
213
+ }
214
+
215
+ return plainText;
216
+ },
217
+ onUseGeneratedText(generatedText: string, index: number) {
218
+ // Update the testimonial text
219
+ this.options[index].testimonials = generatedText;
220
+ this.isModified[index] = true;
221
+ this.isEmpty[index] = false;
222
+
223
+ // Emit the change
224
+ this.emitChange();
225
+ },
226
+
227
+ onCaptureSnapshot() {
228
+ this.$emit('capture-preview-snapshot');
229
+ },
188
230
  },
189
231
  });