@innovastudio/contentbuilder 1.5.190 → 1.5.192

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.
@@ -6471,6 +6471,8 @@ class Util {
6471
6471
  localStorage.removeItem('_command_lang');
6472
6472
  localStorage.removeItem('_ai_panel_open');
6473
6473
  localStorage.removeItem('_pagesize');
6474
+ localStorage.removeItem('chatPanelVisible');
6475
+ localStorage.removeItem('chatSettings');
6474
6476
 
6475
6477
  //NOT USED
6476
6478
  localStorage.removeItem('_scrollableeditor');
@@ -88119,6 +88121,10 @@ class CodeChat {
88119
88121
  this.builder = builder;
88120
88122
  const builderStuff = this.builder.builderStuff;
88121
88123
  this.builderStuff = builderStuff;
88124
+
88125
+ // Check if demo mode is enabled
88126
+ this.isDemoMode = this.builder.demoMode || false;
88127
+ this.demoConversations = this.builder.demoConversations || [];
88122
88128
  const out = s => this.out(s);
88123
88129
 
88124
88130
  // Load saved settings or use defaults
@@ -88458,6 +88464,12 @@ class CodeChat {
88458
88464
  // backward
88459
88465
  this.imageModels = this.builder.imageGenerationModels;
88460
88466
  }
88467
+ let inputPlaceholderText;
88468
+ if (this.builder.editor) {
88469
+ inputPlaceholderText = out('e.g., Create a landing page for a creative studio');
88470
+ } else {
88471
+ inputPlaceholderText = out('e.g., Create an article about a productive home workspace');
88472
+ }
88461
88473
  let html = `
88462
88474
  <style>
88463
88475
  #chatPanel {
@@ -88789,10 +88801,12 @@ class CodeChat {
88789
88801
  box-shadow: 6px 14px 20px 0px rgba(95, 95, 95, 0.11);
88790
88802
  z-index: 10005;
88791
88803
  display: none;
88804
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
88792
88805
  }
88793
88806
 
88794
88807
  #settingsDialog.open {
88795
88808
  display: block;
88809
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
88796
88810
  }
88797
88811
 
88798
88812
  #settingsDialog .settings-header {
@@ -89053,9 +89067,118 @@ class CodeChat {
89053
89067
  body.dark #chatPanel .copy-button:hover {
89054
89068
  background: rgba(255, 255, 255, 0.2);
89055
89069
  }
89070
+
89071
+
89072
+ /* Quick Image Size Button */
89073
+ #chatPanel .quick-image-size-button {
89074
+ width: 48px;
89075
+ height: 48px;
89076
+ border: none;
89077
+ border-radius: 8px;
89078
+ font-size: 13px;
89079
+ font-weight: 500;
89080
+ cursor: pointer;
89081
+ transition: background 0.2s;
89082
+ display: flex;
89083
+ align-items: center;
89084
+ justify-content: center;
89085
+ }
89086
+
89087
+ #chatPanel .quick-image-size-button:hover:not(:disabled) {
89088
+ background: rgba(0, 0, 0, 0.05);
89089
+ }
89090
+
89091
+ #chatPanel .quick-image-size-button:disabled {
89092
+ opacity: 0.6;
89093
+ cursor: not-allowed;
89094
+ }
89095
+
89096
+ body.dark #chatPanel .quick-image-size-button {
89097
+ background: #484848;
89098
+ }
89099
+
89100
+ body.dark #chatPanel .quick-image-size-button:hover:not(:disabled) {
89101
+ background: #4c4c4c;
89102
+ }
89103
+
89104
+ /* Image Size Popover */
89105
+ #chatPanel .image-size-popover {
89106
+ position: absolute;
89107
+ bottom: 72px;
89108
+ right: 68px;
89109
+ background: white;
89110
+ border: 1px solid #e5e5e5;
89111
+ border-radius: 8px;
89112
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
89113
+ z-index: 10006;
89114
+ min-width: 160px;
89115
+ max-height: 300px;
89116
+ overflow-y: auto;
89117
+ }
89118
+
89119
+ body.dark #chatPanel .image-size-popover {
89120
+ background: #3a3a3a;
89121
+ border-color: #555;
89122
+ }
89123
+
89124
+ #chatPanel .size-options {
89125
+ padding: 8px;
89126
+ }
89127
+
89128
+ #chatPanel .size-option {
89129
+ display: flex;
89130
+ align-items: center;
89131
+ padding: 10px 12px;
89132
+ cursor: pointer;
89133
+ border-radius: 6px;
89134
+ transition: background 0.2s;
89135
+ font-size: 14px;
89136
+ }
89137
+ #chatPanel .size-option label,
89138
+ #chatPanel .size-option input {
89139
+ cursor: pointer;
89140
+ }
89141
+
89142
+ #chatPanel .size-option:hover {
89143
+ background: rgba(0, 0, 0, 0.05);
89144
+ }
89145
+
89146
+ #chatPanel .size-option.selected {
89147
+ background: rgba(62, 147, 247, 0.1);
89148
+ font-weight: 500;
89149
+ }
89150
+
89151
+ #chatPanel .size-option input[type="radio"] {
89152
+ margin-right: 8px;
89153
+ }
89154
+
89155
+ body.dark #chatPanel .size-option:hover {
89156
+ background: rgba(255, 255, 255, 0.1);
89157
+ }
89158
+
89159
+ body.dark #chatPanel .size-option.selected {
89160
+ background: rgba(62, 147, 247, 0.2);
89161
+ }
89162
+
89163
+ #chatPanel .image-size-popover::-webkit-scrollbar {
89164
+ width: 6px;
89165
+ }
89166
+
89167
+ #chatPanel .image-size-popover::-webkit-scrollbar-track {
89168
+ background: transparent;
89169
+ }
89170
+
89171
+ #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
89172
+ background: #e5e5e5;
89173
+ border-radius: 3px;
89174
+ }
89175
+
89176
+ body.dark #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
89177
+ background: #555;
89178
+ }
89056
89179
  </style>
89057
89180
  <div
89058
- class="hidden"
89181
+ class="hidden keep-selection"
89059
89182
  id="chatPanel"
89060
89183
  role="dialog"
89061
89184
  aria-labelledby="chatPanelTitle"
@@ -89105,10 +89228,24 @@ class CodeChat {
89105
89228
  <label for="promptInput" class="sr-only">${out('Message input')}</label>
89106
89229
  <textarea
89107
89230
  id="promptInput"
89108
- placeholder="${out('e.g., Create a landing page and explain best practices for hero sections')}"
89231
+ placeholder="${inputPlaceholderText}"
89109
89232
  rows="1"
89110
89233
  aria-label="${out('Type your message')}"
89111
89234
  ></textarea>
89235
+
89236
+ <!-- NEW: Quick image size button -->
89237
+ <button
89238
+ id="quickImageSizeButton"
89239
+ type="button"
89240
+ class="quick-image-size-button"
89241
+ aria-label="${out('Select image size')}"
89242
+ aria-haspopup="true"
89243
+ aria-expanded="false"
89244
+ style="display: none;"
89245
+ >
89246
+
89247
+ </button>
89248
+
89112
89249
  <button
89113
89250
  id="sendButton"
89114
89251
  type="button"
@@ -89122,15 +89259,30 @@ class CodeChat {
89122
89259
  </svg>
89123
89260
  </button>
89124
89261
  </div>
89262
+
89263
+ <!-- NEW: Size selection popover -->
89264
+ <div
89265
+ id="imageSizePopover"
89266
+ class="image-size-popover"
89267
+ role="menu"
89268
+ aria-hidden="true"
89269
+ style="display: none;"
89270
+ >
89271
+ <div class="size-options" id="sizeOptionsContainer">
89272
+ <!-- Options populated dynamically -->
89273
+ </div>
89274
+ </div>
89275
+
89125
89276
  </div>
89126
89277
  </div>
89127
89278
 
89128
89279
  <!-- Settings Dialog Overlay -->
89129
- <div id="settingsOverlay" aria-hidden="true"></div>
89280
+ <div id="settingsOverlay" class="keep-selection" aria-hidden="true"></div>
89130
89281
 
89131
89282
  <!-- Settings Dialog -->
89132
89283
  <div
89133
89284
  id="settingsDialog"
89285
+ class="keep-selection"
89134
89286
  role="dialog"
89135
89287
  aria-labelledby="settingsTitle"
89136
89288
  aria-modal="true"
@@ -89264,9 +89416,21 @@ class CodeChat {
89264
89416
  this.chatModelSelect.value = this.settings.chatModel;
89265
89417
  this.imageModelSelect.value = this.settings.imageModel;
89266
89418
  this.imageSizeSelect.value = this.settings.imageSize;
89267
- let isChatVisible = localStorage.getItem('chatPanelVisible') !== 'false';
89419
+
89420
+ // Check saved state - default to closed if never set
89421
+ const savedState = localStorage.getItem('chatPanelVisible');
89422
+ let isChatVisible = savedState === 'true'; // Only open if explicitly set to 'true'
89268
89423
  if (!isChatVisible) {
89269
- modal.classList.add('hidden');
89424
+ if (this.builder.startCodeChat) {
89425
+ modal.classList.remove('hidden');
89426
+ modal.removeAttribute('aria-hidden');
89427
+ } else {
89428
+ modal.classList.add('hidden');
89429
+ modal.setAttribute('aria-hidden', 'true');
89430
+ }
89431
+ } else {
89432
+ modal.classList.remove('hidden');
89433
+ modal.removeAttribute('aria-hidden');
89270
89434
  }
89271
89435
  const btnClose = modal.querySelector('.close-button');
89272
89436
  btnClose.addEventListener('click', () => {
@@ -89349,7 +89513,45 @@ class CodeChat {
89349
89513
  this.closeChatButton = closeChatButton;
89350
89514
  this.sendButton = sendButton;
89351
89515
  this.messagesContainer = messagesContainer;
89516
+
89517
+ // NEW: Quick image size button elements
89518
+ this.quickImageSizeButton = builderStuff.querySelector('#quickImageSizeButton');
89519
+ this.imageSizePopover = builderStuff.querySelector('#imageSizePopover');
89520
+ this.sizeOptionsContainer = builderStuff.querySelector('#sizeOptionsContainer');
89521
+
89522
+ // Quick Image Size Button Handlers
89523
+ this.quickImageSizeButton.addEventListener('click', e => {
89524
+ e.stopPropagation();
89525
+ this.toggleImageSizePopover();
89526
+ });
89527
+
89528
+ // Close popover when clicking outside
89529
+ document.addEventListener('click', e => {
89530
+ if (!this.imageSizePopover.contains(e.target) && !this.quickImageSizeButton.contains(e.target)) {
89531
+ this.closeImageSizePopover();
89532
+ }
89533
+ });
89534
+
89535
+ // Close popover on Escape
89536
+ this.imageSizePopover.addEventListener('keydown', e => {
89537
+ if (e.key === 'Escape') {
89538
+ this.closeImageSizePopover();
89539
+ this.quickImageSizeButton.focus();
89540
+ }
89541
+ });
89352
89542
  this.renderImageOptions();
89543
+ if (this.demoConversations) {
89544
+ this.loadConversations();
89545
+ }
89546
+ if (this.isDemoMode) {
89547
+ this.addDemoBanner();
89548
+
89549
+ // Disable input in demo mode
89550
+ this.promptInput.disabled = true;
89551
+ // this.promptInput.placeholder = out('Demo mode - Chat is read-only');
89552
+ this.sendButton.disabled = true;
89553
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
89554
+ }
89353
89555
  }
89354
89556
  renderImageOptions() {
89355
89557
  const out = s => this.out(s);
@@ -89456,6 +89658,9 @@ class CodeChat {
89456
89658
  // Trigger initial render with defaults
89457
89659
  modelSelect.value = defaultModelId;
89458
89660
  renderSizes(defaultModelId, false);
89661
+
89662
+ // NEW: Initialize quick size button
89663
+ this.updateQuickImageSizeButton();
89459
89664
  }
89460
89665
 
89461
89666
  /**
@@ -89521,6 +89726,9 @@ class CodeChat {
89521
89726
  this.settingsOverlay.setAttribute('aria-hidden', 'true');
89522
89727
  this.settingsDialog.classList.remove('open');
89523
89728
  this.settingsDialog.setAttribute('aria-hidden', 'true');
89729
+
89730
+ // Reset cursor to default to prevent it from getting stuck
89731
+ document.body.style.cursor = '';
89524
89732
  if (this.settingsLastFocusedElement) {
89525
89733
  this.settingsLastFocusedElement.focus();
89526
89734
  }
@@ -89532,6 +89740,9 @@ class CodeChat {
89532
89740
  this.settings.imageModel = this.imageModelSelect.value;
89533
89741
  this.settings.imageSize = this.imageSizeSelect.value;
89534
89742
  this.saveSettingsToStorage();
89743
+
89744
+ // NEW: Update quick button when settings change
89745
+ this.updateQuickImageSizeButton();
89535
89746
  this.closeSettings();
89536
89747
  }
89537
89748
 
@@ -89565,6 +89776,106 @@ Your job:
89565
89776
  3. Determine what each image should depict based on surrounding content and context
89566
89777
  4. Create detailed image generation prompts for each image
89567
89778
 
89779
+ 📸 EDITORIAL STYLE GUIDE - Apply to all image prompts:
89780
+
89781
+ - Default aesthetic: Minimalist magazine-style (Kinfolk, Cereal, Vogue Living)
89782
+ - Composition: Clean, lots of negative space, well-balanced
89783
+
89784
+
89785
+ CREATIVE PROMPT ENHANCEMENT:
89786
+ When constructing image prompts, intelligently enrich basic descriptions using these proven cinematic stylers:
89787
+
89788
+ 1. **Golden Hour Cinematic**
89789
+ "Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
89790
+ → Best for: Landscapes, architecture, outdoor scenes, lifestyle content
89791
+
89792
+ 2. **Morning Halo Effect**
89793
+ "The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
89794
+ → Best for: Portraits, people in action, outdoor activities
89795
+
89796
+ 3. **Natural Forest Serenity**
89797
+ "in a lush, green forest. The sunlight should be filtering through the trees, creating a serene and natural atmosphere"
89798
+ → Best for: Nature scenes, outdoor activities, wellness content
89799
+
89800
+ 4. **Coastal Summer Drama**
89801
+ "on a dramatic windswept coastline. The overall atmosphere is cheerful, breezy, and full of summer warmth"
89802
+ → Best for: Beach/coastal scenes, adventure, travel content
89803
+
89804
+ 5. **Modern Interior Elegance**
89805
+ "illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
89806
+ → Best for: Indoor portraits, product shots, professional/business settings
89807
+
89808
+ ENHANCEMENT STRATEGY:
89809
+ - Analyze the subject matter and context from the HTML/request
89810
+ - Select the most appropriate styler that matches the scene type
89811
+ - Check if user has specified lighting/time → DO NOT apply contradicting styler elements
89812
+ * Example: User says "daylight" → DO NOT add "golden-hour" or "sunset"
89813
+ - Aim for creative elevation while maintaining the core subject integrity
89814
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
89815
+ - IF user is vague (no specific lighting/mood) → Apply full styler enhancement
89816
+ - User's explicit details ALWAYS take precedence over styler recommendations
89817
+ ` +
89818
+ /*
89819
+ ENHANCEMENT STRATEGY:
89820
+ - Analyze the subject matter and context from the HTML/request
89821
+ - Select the most appropriate styler that matches the scene type
89822
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
89823
+ - If the subject already has specific lighting/atmosphere details, complement rather than override
89824
+ - Aim for creative elevation while maintaining the core subject integrity
89825
+ */
89826
+ `
89827
+ Examples of enriched prompts:
89828
+ - Basic: "a house with mountain view"
89829
+ → Enhanced: "a house with beautiful mountain view. Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
89830
+
89831
+ - Basic: "person hiking"
89832
+ → Enhanced: "a person hiking in nature. The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
89833
+
89834
+ - Basic: "woman reading"
89835
+ → Enhanced: "a woman sitting comfortably and reading. Her face is illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
89836
+
89837
+ - Already detailed: "dramatic headshot with bokeh and rim lighting"
89838
+ → No enhancement needed (preserve user's vision)
89839
+
89840
+
89841
+ CONTEXT-SPECIFIC GUIDELINES:
89842
+
89843
+ Interior/Indoor scenes:
89844
+ - Lighting: Soft natural window light
89845
+ - Backdrop: white walls or neutral tones
89846
+ - Mood: Calm, serene, inviting, uncluttered
89847
+ - Reference: "Kinfolk interior photography"
89848
+
89849
+ People/Portraits/Activities:
89850
+ - Lighting: Natural diffused light, flattering and soft
89851
+ - Styling: Effortlessly elegant, contemporary casual
89852
+ - Pose: Candid, authentic moments, relaxed
89853
+ - Reference: "modern lifestyle editorial photography"
89854
+
89855
+ Products/Objects:
89856
+ - Lighting: Clean studio or soft natural light
89857
+ - Backdrop: Minimalist neutral, lots of breathing room
89858
+ - Mood: Refined simplicity, premium feel
89859
+ - Reference: "high-end editorial product photography"
89860
+
89861
+ Food/Culinary:
89862
+ - Lighting: Soft daylight, gentle overhead or 45° angle
89863
+ - Styling: Minimal props, artisan ceramics, linen
89864
+ - Mood: Fresh, appetizing, rustic-modern
89865
+ - Reference: "Bon Appétit editorial food styling"
89866
+
89867
+ Outdoor/Landscape:
89868
+ - Composition: Serene, contemplative, minimal
89869
+ - Mood: Calm atmosphere, breathing space, lifted brightness
89870
+ - Colors: Vibrant cinematic shot.
89871
+ - Reference: "editorial travel photography"
89872
+
89873
+ CRITICAL RULES:
89874
+ 1. If user specifies a style (cinematic, vintage, vibrant, etc.) → RESPECT IT, only add quality markers
89875
+ 2. If user is vague → APPLY FULL EDITORIAL TREATMENT based on subject category
89876
+ 3. Always maintain cohesive aesthetic across all images
89877
+ 4. Every prompt should feel like it belongs in a high-end lifestyle magazine
89878
+
89568
89879
  Respond with a JSON array with one entry per image to generate:
89569
89880
  [
89570
89881
  {
@@ -89675,6 +89986,11 @@ Response: [
89675
89986
  */
89676
89987
  async sendMessage() {
89677
89988
  const out = s => this.out(s);
89989
+
89990
+ // Prevent sending messages in demo mode
89991
+ if (this.isDemoMode) {
89992
+ return;
89993
+ }
89678
89994
  const prompt = this.promptInput.value.trim();
89679
89995
  if (!prompt) return;
89680
89996
  this.addMessage('user', prompt);
@@ -89742,14 +90058,42 @@ Response: [
89742
90058
  } else if (result.type === 'image') {
89743
90059
  if (result.imageDetails && result.imageDetails.length > 1) {
89744
90060
  this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`);
90061
+ // this.addMessage('assistant', `✓ ${out('Generated {count} images successfully').replace('{count}', result.imageDetails.length)}`);
89745
90062
  this.addImagePreview(result.imageDetails);
89746
90063
  } else {
89747
- this.addMessage('assistant', '✓ Generated image successfully');
90064
+ this.addMessage('assistant', '✓ Image generated successfully');
90065
+ // this.addMessage('assistant', `✓ ${out('Image generated successfully')}`);
89748
90066
  if (result.generatedUrls && result.generatedUrls[0]) {
89749
90067
  this.addImagePreview([{
89750
90068
  url: result.generatedUrls[0],
89751
90069
  context: result.description
89752
90070
  }]);
90071
+
90072
+ // If there is a selected image, replace with the new image
90073
+ const url = result.generatedUrls[0];
90074
+ if (this.builder.editor) {
90075
+ // ContentBox
90076
+ const img = this.builder.editor.activeImage;
90077
+ if (img) {
90078
+ this.builder.editor.saveForUndo();
90079
+ img.addEventListener('load', () => {
90080
+ this.builder.editor.element.image.repositionImageTool();
90081
+ this.builder.editor.elmTool.repositionElementTool();
90082
+ });
90083
+ this.builder.editor.activeImage.setAttribute('src', url);
90084
+ }
90085
+ } else {
90086
+ // ContentBuilder
90087
+ const img = this.builder.activeImage;
90088
+ if (img) {
90089
+ this.builder.uo.saveForUndo();
90090
+ img.addEventListener('load', () => {
90091
+ this.builder.element.image.repositionImageTool();
90092
+ this.builder.elmTool.repositionElementTool();
90093
+ });
90094
+ this.builder.activeImage.setAttribute('src', url);
90095
+ }
90096
+ }
89753
90097
  }
89754
90098
  }
89755
90099
  }
@@ -89803,15 +90147,16 @@ Response: [
89803
90147
  `;
89804
90148
  images.forEach(img => {
89805
90149
  previewHTML += `
89806
- <div style="border-radius: 8px; overflow: hidden; background: rgba(255,255,255,0.05);">
89807
- <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%; height: 150px; object-fit: cover;" />
90150
+ <div style="border-radius: 6px; overflow: hidden; background: rgba(255,255,255,0.05);">
90151
+ <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%;" />
89808
90152
  <div style="padding: 8px; font-size: 11px; opacity: 0.8;">
89809
90153
  ${img.context || ''}
89810
90154
  <a href="${img.url}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 4px;">View</a>
89811
90155
  </div>
89812
90156
  </div>
89813
- `;
90157
+ `; // height: 150px; object-fit: cover;
89814
90158
  });
90159
+
89815
90160
  previewHTML += '</div>';
89816
90161
  previewDiv.innerHTML = previewHTML;
89817
90162
  this.messagesContainer.appendChild(previewDiv);
@@ -89832,6 +90177,32 @@ Response: [
89832
90177
 
89833
90178
  // Check if image generation is enabled
89834
90179
  const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
90180
+
90181
+ // Build context about previously generated images
90182
+ let imageHistoryContext = '';
90183
+ const previousImageResults = this.conversationResults.filter(r => r.type === 'image' && r.generatedUrls && r.generatedUrls.length > 0);
90184
+ if (previousImageResults.length > 0) {
90185
+ imageHistoryContext = '\n\n=== PREVIOUSLY GENERATED IMAGES IN THIS CONVERSATION ===\n';
90186
+ imageHistoryContext += 'The following images were generated earlier in this conversation:\n\n';
90187
+ previousImageResults.forEach((result, idx) => {
90188
+ if (result.imageDetails && result.imageDetails.length > 0) {
90189
+ result.imageDetails.forEach((img, imgIdx) => {
90190
+ imageHistoryContext += `Generated Image ${idx + 1}.${imgIdx + 1}:\n`;
90191
+ imageHistoryContext += ` URL: ${img.url}\n`;
90192
+ imageHistoryContext += ` Context: ${img.context}\n`;
90193
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
90194
+ });
90195
+ } else {
90196
+ result.generatedUrls.forEach((url, urlIdx) => {
90197
+ imageHistoryContext += `Generated Image ${idx + 1}.${urlIdx + 1}:\n`;
90198
+ imageHistoryContext += ` URL: ${url}\n`;
90199
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
90200
+ });
90201
+ }
90202
+ });
90203
+ imageHistoryContext += '⚠️ IMPORTANT: If the user refers to "the generated image", "this image", "the image", or "previous image", they are referring to these images above.\n';
90204
+ imageHistoryContext += '⚠️ DO NOT create a new image task. Instead, create a CODE task that uses these existing URLs.\n\n';
90205
+ }
89835
90206
  const classificationPrompt = `Analyze this user message and break it down into tasks.
89836
90207
 
89837
90208
  CURRENT HTML CONTEXT (use this to understand the page structure):
@@ -89839,11 +90210,19 @@ CURRENT HTML CONTEXT (use this to understand the page structure):
89839
90210
  ${this.builder.html()}
89840
90211
  \`\`\`
89841
90212
 
90213
+ ${imageHistoryContext}
90214
+
89842
90215
  IMPORTANT: This is a WEB BUILDER tool with chat capabilities. Valid requests are:
89843
90216
  - Creating/editing/removing HTML content (code tasks)
89844
90217
  ${imageGenEnabled ? '- Generating images for the webpage (image tasks)' : ''}
89845
90218
  - Asking questions or having conversations (chat tasks) - THIS CAN BE ABOUT ANYTHING
89846
90219
 
90220
+ CRITICAL CONTEXT RULE:
90221
+ - In a web builder, "create an article/blog/story/content" means CREATE A WEBPAGE (CODE task)
90222
+ - Only use CHAT task for questions, explanations, or requests for advice
90223
+ - If user says "create", "write", "make", "build" followed by content → CODE task
90224
+ - If user says "explain", "how do I", "what is", "tell me about" → CHAT task
90225
+
89847
90226
  ${imageGenEnabled ? '' : `
89848
90227
  ⚠️ AI IMAGE GENERATION IS CURRENTLY DISABLED
89849
90228
  - If user requests AI image generation/creation, explain that it's disabled
@@ -89913,6 +90292,18 @@ IMAGE TASK RULES:
89913
90292
  - IMPORTANT: Look at the HTML context to understand if multiple images are involved
89914
90293
  - If the target section has multiple images, indicate this in targetElement (e.g., "all 3 images in Culinary Delights section")
89915
90294
  - Image tasks should come BEFORE code tasks that depend on them
90295
+
90296
+ - CRITICAL: DO NOT create image task for these phrases (they mean REUSE existing):
90297
+ * "use the generated image" / "use the image"
90298
+ * "use this image" / "use existing image"
90299
+ * "with the generated image" / "with this image"
90300
+ * "with the previous image" / "with the last image"
90301
+ * "using the image I just created/generated"
90302
+ * Any reference to a PREVIOUS image from conversation history
90303
+ - Decision logic:
90304
+ * "Generate a new image of X" → CREATE image task (new generation)
90305
+ * "Create article using the generated image" → NO image task (reuse existing)
90306
+
89916
90307
  ` : ''}
89917
90308
 
89918
90309
  Examples:
@@ -89950,6 +90341,17 @@ Input: "Create a landing page about wood furniture workshop"
89950
90341
  Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create a landing page for a wood furniture workshop", "order": 1}], "is_mixed": false}
89951
90342
  Note: No image task created because user did not explicitly request AI image generation
89952
90343
 
90344
+ Input: "Create an inspiring article and use the generated image"
90345
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create article using previously generated image from conversation history", "order": 1}], "is_mixed": false}
90346
+ Note: No image task because user wants to REUSE existing image
90347
+
90348
+ Input: "Write a blog post about productivity and use this image"
90349
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create blog post using previously generated image", "order": 1}], "is_mixed": false}
90350
+
90351
+ Input: "Generate another mountain image and create a landing page"
90352
+ Output: {"tasks": [{"type": "image", "description": "Generate new mountain image", "imagePrompt": "...", "order": 1}, {"type": "code", "description": "Create landing page with new image", "order": 2}], "is_mixed": true}
90353
+ Note: "another" and "generate" indicate NEW image generation
90354
+
89953
90355
  ` : `
89954
90356
  Input: "Generate an AI image of a mountain"
89955
90357
  Output: {"is_valid": false, "reason": "AI image generation is currently disabled.", "tasks": [], "is_mixed": false}
@@ -90033,7 +90435,9 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
90033
90435
  if (imageTasksFromThisRequest.length > 0) {
90034
90436
  hasGeneratedImages = true;
90035
90437
  imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n';
90036
- imageTasksFromThisRequest.forEach(imageTask => {
90438
+
90439
+ // ⭐ REVERSE to show most recent first
90440
+ imageTasksFromThisRequest.reverse().forEach(imageTask => {
90037
90441
  if (imageTask.imageDetails && imageTask.imageDetails.length > 0) {
90038
90442
  // Multiple images with context
90039
90443
  imageTask.imageDetails.forEach((img, idx) => {
@@ -90048,7 +90452,8 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
90048
90452
  });
90049
90453
  }
90050
90454
  });
90051
- imageContext += '\n⚠️ IMPORTANT: Use these generated image URLs (not placehold.co or other sources) for the specific images mentioned in your task.\n';
90455
+ imageContext += '\n⚠️ IMPORTANT: Use the FIRST image URL listed above (most recent) unless the task specifically asks for a different image.\n';
90456
+ imageContext += '⚠️ The first image is the MOST RECENTLY GENERATED and should be used by default.\n';
90052
90457
  imageContext += '⚠️ Your task description specifies WHICH images to update - follow it precisely.\n';
90053
90458
  }
90054
90459
 
@@ -90601,6 +91006,319 @@ ${this.builder.html()}
90601
91006
  document.body.removeChild(announcement);
90602
91007
  }, 1000);
90603
91008
  }
91009
+
91010
+ /**
91011
+ * ============================================================================
91012
+ * DEMO MODE
91013
+ * ============================================================================
91014
+ */
91015
+ loadConversations() {
91016
+ // Load demo conversations
91017
+ if (this.demoConversations && this.demoConversations.length > 0) {
91018
+ this.demoConversations.forEach(msg => {
91019
+ if (msg.role === 'user') {
91020
+ this.addMessage('user', msg.content);
91021
+ } else if (msg.role === 'assistant') {
91022
+ this.addMessage('assistant', msg.content, true);
91023
+
91024
+ // Add image preview if available
91025
+ if (msg.imagePreview && msg.imagePreview.length > 0) {
91026
+ this.addImagePreview(msg.imagePreview);
91027
+
91028
+ // Include in conversationResults with correct structure
91029
+ this.conversationResults.push({
91030
+ type: 'image',
91031
+ description: 'Previously generated image from demo',
91032
+ content: `Generated ${msg.imagePreview.length} images`,
91033
+ generatedUrls: msg.imagePreview.map(img => img.url),
91034
+ // Array of URLs
91035
+ imageDetails: msg.imagePreview.map(img => ({
91036
+ // Array of objects
91037
+ url: img.url,
91038
+ context: img.context,
91039
+ prompt: img.context || '' // Use context as prompt if no prompt available
91040
+ })),
91041
+
91042
+ targetElement: 'demo images'
91043
+ });
91044
+ }
91045
+ }
91046
+ });
91047
+ }
91048
+ }
91049
+ addDemoBanner() {
91050
+ const out = s => this.out(s);
91051
+ const banner = document.createElement('div');
91052
+ banner.className = 'demo-banner';
91053
+ banner.innerHTML = `
91054
+ <div style="
91055
+ background: linear-gradient(135deg, #527cff 0%, #5563db 100%);
91056
+ color: white;
91057
+ padding: 12px 16px;
91058
+ border-radius: 8px;
91059
+ font-size: 13px;
91060
+ display: flex;
91061
+ align-items: center;
91062
+ gap: 8px;
91063
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
91064
+ ">
91065
+ <span style="font-size: 18px;">📖</span>
91066
+ <div>
91067
+ <strong>${out('Read-Only Demo')}</strong>
91068
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">
91069
+ ${out('Full AI chat available in the complete version.')}
91070
+ </div>
91071
+ </div>
91072
+ </div>
91073
+ `;
91074
+
91075
+ // Insert banner at the top of messages container
91076
+ // this.messagesContainer.insertBefore(banner, this.messagesContainer.firstChild);
91077
+ this.messagesContainer.appendChild(banner);
91078
+ }
91079
+
91080
+ /**
91081
+ * Update the quick image size button based on current model and settings
91082
+ */
91083
+ updateQuickImageSizeButton() {
91084
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
91085
+ if (!imageGenEnabled) {
91086
+ this.quickImageSizeButton.style.display = 'none';
91087
+ return;
91088
+ }
91089
+ const currentModelId = this.settings.imageModel;
91090
+ const availableSizes = this.getSizesForModel(currentModelId);
91091
+
91092
+ // Hide button if model has no size options
91093
+ if (!availableSizes || availableSizes.length === 0) {
91094
+ this.quickImageSizeButton.style.display = 'none';
91095
+ return;
91096
+ }
91097
+
91098
+ // Show button
91099
+ this.quickImageSizeButton.style.display = 'flex';
91100
+
91101
+ // Smart selection: check if current size is available
91102
+ let selectedSize = this.settings.imageSize;
91103
+ if (!availableSizes.includes(selectedSize)) {
91104
+ // Current size not available, find best fallback
91105
+ selectedSize = this.findBestFallback(selectedSize, availableSizes);
91106
+ this.settings.imageSize = selectedSize;
91107
+
91108
+ // Sync with settings dialog
91109
+ this.imageSizeSelect.value = selectedSize;
91110
+
91111
+ // Save to storage
91112
+ this.saveSettingsToStorage();
91113
+ }
91114
+
91115
+ // Update button label
91116
+ this.updateButtonLabel(selectedSize);
91117
+
91118
+ // Populate popover options
91119
+ this.populateSizePopover(availableSizes, selectedSize);
91120
+ }
91121
+
91122
+ /**
91123
+ * Get available sizes for a model (reuse existing logic)
91124
+ */
91125
+ getSizesForModel(modelId) {
91126
+ const model = this.imageModels.find(m => m.id === modelId);
91127
+ if (!model) return null;
91128
+
91129
+ // if sizes explicitly empty array → means no size options
91130
+ if (Array.isArray(model.sizes) && model.sizes.length === 0) {
91131
+ return null;
91132
+ }
91133
+ const defaultSizes = ['square', 'square_hd', 'landscape_4_3', 'landscape_16_9', 'portrait_4_3', 'portrait_16_9'];
91134
+ return model.sizes || defaultSizes;
91135
+ }
91136
+
91137
+ /**
91138
+ * Find best fallback size when current selection is not available
91139
+ */
91140
+ findBestFallback(preferredSize, availableSizes) {
91141
+ // Strategy 1: Try to match orientation
91142
+ const orientation = this.getOrientation(preferredSize);
91143
+ const matchingOrientation = availableSizes.find(size => this.getOrientation(size) === orientation);
91144
+ if (matchingOrientation) return matchingOrientation;
91145
+
91146
+ // Strategy 2: Common defaults
91147
+ if (availableSizes.includes('16:9')) return '16:9';
91148
+ if (availableSizes.includes('landscape_16_9')) return 'landscape_16_9';
91149
+ if (availableSizes.includes('1:1')) return '1:1';
91150
+ if (availableSizes.includes('square')) return 'square';
91151
+
91152
+ // Strategy 3: First available
91153
+ return availableSizes[0];
91154
+ }
91155
+
91156
+ /**
91157
+ * Determine orientation from size value
91158
+ */
91159
+ getOrientation(size) {
91160
+ const landscape = ['16:9', '21:9', '4:3', '3:2', '5:4', 'landscape_16_9', 'landscape_4_3'];
91161
+ const portrait = ['9:16', '9:21', '3:4', '2:3', '4:5', 'portrait_16_9', 'portrait_4_3'];
91162
+ if (landscape.includes(size)) return 'landscape';
91163
+ if (portrait.includes(size)) return 'portrait';
91164
+ return 'square';
91165
+ }
91166
+
91167
+ /**
91168
+ * Update button label to show current size
91169
+ */
91170
+ updateButtonLabel(sizeValue) {
91171
+ if (!sizeValue) {
91172
+ this.quickImageSizeButton.textContent = '□';
91173
+ this.quickImageSizeButton.setAttribute('aria-label', this.out('Select image size'));
91174
+ return;
91175
+ }
91176
+
91177
+ // Convert verbose labels to short form
91178
+ const labelMap = {
91179
+ 'landscape_16_9': '16:9',
91180
+ 'landscape_4_3': '4:3',
91181
+ 'portrait_16_9': '9:16',
91182
+ 'portrait_4_3': '3:4',
91183
+ 'square': '1:1',
91184
+ 'square_hd': '1:1 HD'
91185
+ };
91186
+ const displayLabel = labelMap[sizeValue] || sizeValue;
91187
+ this.quickImageSizeButton.textContent = displayLabel;
91188
+ this.quickImageSizeButton.setAttribute('aria-label', `${this.out('Image size')}: ${displayLabel}`);
91189
+ }
91190
+
91191
+ /**
91192
+ * Populate the size popover with available options
91193
+ */
91194
+ populateSizePopover(sizes, selectedSize) {
91195
+ const out = s => this.out(s);
91196
+ const sizeDefs = {
91197
+ 'square_hd': out('Square HD'),
91198
+ 'square': out('Square'),
91199
+ 'landscape_4_3': out('Landscape 4x3'),
91200
+ 'landscape_16_9': out('Landscape 16x9'),
91201
+ 'portrait_4_3': out('Portrait 3x4'),
91202
+ 'portrait_16_9': out('Portrait 9x16'),
91203
+ '1:1': out('Square'),
91204
+ '3:2': out('Landscape 3x2'),
91205
+ '4:3': out('Landscape 4x3'),
91206
+ '5:4': out('Landscape 5x4'),
91207
+ '16:9': out('Landscape 16x9'),
91208
+ '21:9': out('Landscape 21:9'),
91209
+ '2:3': out('Portrait 2x3'),
91210
+ '3:4': out('Portrait 3x4'),
91211
+ '4:5': out('Portrait 4x5'),
91212
+ '9:16': out('Portrait 9x16'),
91213
+ '9:21': out('Portrait 9:21')
91214
+ };
91215
+ this.sizeOptionsContainer.innerHTML = '';
91216
+ sizes.forEach(size => {
91217
+ const option = document.createElement('div');
91218
+ option.className = 'size-option' + (size === selectedSize ? ' selected' : '');
91219
+ option.setAttribute('role', 'menuitem');
91220
+ option.setAttribute('tabindex', '0');
91221
+ const radio = document.createElement('input');
91222
+ radio.type = 'radio';
91223
+ radio.name = 'quickImageSize';
91224
+ radio.value = size;
91225
+ radio.checked = size === selectedSize;
91226
+ radio.id = `quick-size-${size}`;
91227
+ const label = document.createElement('label');
91228
+ label.htmlFor = `quick-size-${size}`;
91229
+ label.textContent = sizeDefs[size] || size;
91230
+ // label.style.cursor = 'pointer';
91231
+ label.style.flex = '1';
91232
+ option.appendChild(radio);
91233
+ option.appendChild(label);
91234
+
91235
+ // Click handler
91236
+ option.addEventListener('click', () => {
91237
+ this.selectImageSize(size);
91238
+ });
91239
+
91240
+ // Keyboard handler
91241
+ option.addEventListener('keydown', e => {
91242
+ if (e.key === 'Enter' || e.key === ' ') {
91243
+ e.preventDefault();
91244
+ this.selectImageSize(size);
91245
+ }
91246
+ });
91247
+ this.sizeOptionsContainer.appendChild(option);
91248
+ });
91249
+ }
91250
+
91251
+ /**
91252
+ * Handle size selection
91253
+ */
91254
+ selectImageSize(size) {
91255
+ // Update settings
91256
+ this.settings.imageSize = size;
91257
+
91258
+ // Sync with settings dialog
91259
+ this.imageSizeSelect.value = size;
91260
+
91261
+ // Update button label
91262
+ this.updateButtonLabel(size);
91263
+
91264
+ // Update popover selection
91265
+ this.sizeOptionsContainer.querySelectorAll('.size-option').forEach(opt => {
91266
+ opt.classList.remove('selected');
91267
+ opt.querySelector('input[type="radio"]').checked = false;
91268
+ });
91269
+
91270
+ // const selectedOption = this.sizeOptionsContainer.querySelector(`#quick-size-${size}`);
91271
+ const selectedOption = Array.from(this.sizeOptionsContainer.querySelectorAll('input[type="radio"]')).find(radio => radio.value === size);
91272
+ if (selectedOption) {
91273
+ selectedOption.closest('.size-option').classList.add('selected');
91274
+ selectedOption.checked = true;
91275
+ }
91276
+
91277
+ // Save to storage
91278
+ this.saveSettingsToStorage();
91279
+
91280
+ // Close popover
91281
+ this.closeImageSizePopover();
91282
+
91283
+ // Focus back to input
91284
+ this.promptInput.focus();
91285
+ }
91286
+
91287
+ /**
91288
+ * Toggle popover visibility
91289
+ */
91290
+ toggleImageSizePopover() {
91291
+ const isVisible = this.imageSizePopover.style.display !== 'none';
91292
+ if (isVisible) {
91293
+ this.closeImageSizePopover();
91294
+ } else {
91295
+ this.openImageSizePopover();
91296
+ }
91297
+ }
91298
+
91299
+ /**
91300
+ * Open the size popover
91301
+ */
91302
+ openImageSizePopover() {
91303
+ this.imageSizePopover.style.display = 'block';
91304
+ this.imageSizePopover.setAttribute('aria-hidden', 'false');
91305
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'true');
91306
+
91307
+ // Focus first option
91308
+ const firstOption = this.sizeOptionsContainer.querySelector('.size-option');
91309
+ if (firstOption) {
91310
+ firstOption.focus();
91311
+ }
91312
+ }
91313
+
91314
+ /**
91315
+ * Close the size popover
91316
+ */
91317
+ closeImageSizePopover() {
91318
+ this.imageSizePopover.style.display = 'none';
91319
+ this.imageSizePopover.setAttribute('aria-hidden', 'true');
91320
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'false');
91321
+ }
90604
91322
  out(s) {
90605
91323
  let result = this.builder.lang[s];
90606
91324
  if (result) return result;else return s;
@@ -91026,7 +91744,7 @@ Classes: transition-none, transition, transition-colors, transition-opacity, tra
91026
91744
 
91027
91745
  **Duration**
91028
91746
 
91029
- Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000
91747
+ Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000, duration-1500
91030
91748
 
91031
91749
  **Timing**
91032
91750
 
@@ -91040,6 +91758,10 @@ Classes: delay-75, delay-100, delay-150, delay-200, delay-300, delay-500
91040
91758
 
91041
91759
  Classes: scale-0, scale-50, scale-75, scale-90, scale-95, scale-100, scale-105, scale-110, scale-125, scale-150
91042
91760
 
91761
+ **Hover Effect**
91762
+
91763
+ Classes: hover:scale-105
91764
+
91043
91765
  **Rotate**
91044
91766
 
91045
91767
  Classes: rotate-0, rotate-45, rotate-90, rotate-180
@@ -91373,6 +92095,31 @@ When creating icon-based features, benefits, or service sections: use Bootstrap
91373
92095
  <h4 class="size-18 font-medium text-center pb-3">Lightning Fast</h4>
91374
92096
  <p class="size-14 leading-16 text-gray-600 text-center">Description...</p>
91375
92097
 
92098
+ ### 9. Image Layout with Hover Effect
92099
+
92100
+ Use this pattern for all images (galleries, featured images, portfolio items):
92101
+
92102
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
92103
+ <img src="..." alt="..." class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
92104
+ </div>
92105
+
92106
+ **Key elements:**
92107
+ - 'aspect-ratio:16/9' - maintains proportions (adjust as needed: 1/1, 4/3, 3/2, 16/9, etc.)
92108
+ - 'overflow-hidden' - keeps scaled image contained
92109
+ - 'bg-gray-50' - placeholder while image loads
92110
+ - 'transition-transform duration-1000' - smooth 1-second animation
92111
+ - 'hover:scale-105' - subtle 5% scale on hover
92112
+ - 'object-cover' - ensures image fills container
92113
+
92114
+ **Common aspect ratios:**
92115
+ - Square: 'aspect-ratio:1/1'
92116
+ - Portrait: 'aspect-ratio:3/4' or 'aspect-ratio:2/3'
92117
+ - Landscape: 'aspect-ratio:4/3' or 'aspect-ratio:3/2'
92118
+ - Wide: 'aspect-ratio:16/9' or 'aspect-ratio:21/9'
92119
+ - Tall portrait: 'aspect-ratio:9/16'
92120
+
92121
+ Always include this hover effect for a polished, interactive feel.
92122
+
91376
92123
  `;
91377
92124
 
91378
92125
  const contextCodeBlock = `
@@ -91572,6 +92319,17 @@ The frameworks provide utility classes for structure and typography. To create r
91572
92319
 
91573
92320
  **Example <style>: When needed for animations, transitions, or reusable patterns**
91574
92321
 
92322
+ <div class="row">
92323
+ <div class="column">
92324
+
92325
+ <!-- GUIDANCE: Use this layout to show images at any aspect ratio with a hover scale effect -->
92326
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
92327
+ <img src="https://placehold.co/1200x1200/f5f5f5/999?text=Featured" alt="Featured Project" class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
92328
+ </div>
92329
+
92330
+ </div>
92331
+ </div>
92332
+
91575
92333
  <div class="row">
91576
92334
  <div class="column">
91577
92335
 
@@ -91746,6 +92504,33 @@ class ContentBuilder {
91746
92504
  'snippets': []
91747
92505
  },
91748
92506
  screenMode: 'desktop',
92507
+ demoMode: false,
92508
+ /*
92509
+ demoConversations: [
92510
+ {
92511
+ role: 'user',
92512
+ content: 'Generate an image: "a house with beautiful mountain view. warm sunlight, golden-hour lighting, filmic teal-orange color grade".',
92513
+ // timestamp: Date.now() - 180000 // 3 minutes ago
92514
+ },
92515
+ {
92516
+ role: 'assistant',
92517
+ content: '✓ Image generated successfully',
92518
+ imagePreview: [
92519
+ { url: 'uploads/ai-8vnqv.png', context: 'Generate image of a house with a beautiful mountain view' },
92520
+ ]
92521
+ },
92522
+ {
92523
+ role: 'user',
92524
+ content: `Create a thoughtful article about finding home in extraordinary landscapes. Write vivid prose about mountain dwellings and how our environments shape the way we live.
92525
+ Use the generated image for this article, and present it as a premium design magazine feature.`,
92526
+ },
92527
+ {
92528
+ role: 'assistant',
92529
+ content: '✓ Create a thoughtful article about finding home in extraordinary landscapes with vivid prose about mountain dwellings and their impact on our lives, formatted as a premium design magazine feature.',
92530
+ }
92531
+ ],
92532
+ */
92533
+
91749
92534
  // Live Preview
91750
92535
  // previewURL: 'preview.html',
91751
92536
  onPreviewOpen: () => {