@innovastudio/contentbox 1.6.176 → 1.6.178

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.
@@ -34244,6 +34244,8 @@ class Util$1 {
34244
34244
  localStorage.removeItem('_command_lang');
34245
34245
  localStorage.removeItem('_ai_panel_open');
34246
34246
  localStorage.removeItem('_pagesize');
34247
+ localStorage.removeItem('chatPanelVisible');
34248
+ localStorage.removeItem('chatSettings');
34247
34249
 
34248
34250
  //NOT USED
34249
34251
  localStorage.removeItem('_scrollableeditor');
@@ -115892,6 +115894,10 @@ class CodeChat$1 {
115892
115894
  this.builder = builder;
115893
115895
  const builderStuff = this.builder.builderStuff;
115894
115896
  this.builderStuff = builderStuff;
115897
+
115898
+ // Check if demo mode is enabled
115899
+ this.isDemoMode = this.builder.demoMode || false;
115900
+ this.demoConversations = this.builder.demoConversations || [];
115895
115901
  const out = s => this.out(s);
115896
115902
 
115897
115903
  // Load saved settings or use defaults
@@ -116231,6 +116237,12 @@ class CodeChat$1 {
116231
116237
  // backward
116232
116238
  this.imageModels = this.builder.imageGenerationModels;
116233
116239
  }
116240
+ let inputPlaceholderText;
116241
+ if (this.builder.editor) {
116242
+ inputPlaceholderText = out('e.g., Create a landing page for a creative studio');
116243
+ } else {
116244
+ inputPlaceholderText = out('e.g., Create an article about a productive home workspace');
116245
+ }
116234
116246
  let html = `
116235
116247
  <style>
116236
116248
  #chatPanel {
@@ -116562,10 +116574,12 @@ class CodeChat$1 {
116562
116574
  box-shadow: 6px 14px 20px 0px rgba(95, 95, 95, 0.11);
116563
116575
  z-index: 10005;
116564
116576
  display: none;
116577
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
116565
116578
  }
116566
116579
 
116567
116580
  #settingsDialog.open {
116568
116581
  display: block;
116582
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
116569
116583
  }
116570
116584
 
116571
116585
  #settingsDialog .settings-header {
@@ -116826,9 +116840,118 @@ class CodeChat$1 {
116826
116840
  body.dark #chatPanel .copy-button:hover {
116827
116841
  background: rgba(255, 255, 255, 0.2);
116828
116842
  }
116843
+
116844
+
116845
+ /* Quick Image Size Button */
116846
+ #chatPanel .quick-image-size-button {
116847
+ width: 48px;
116848
+ height: 48px;
116849
+ border: none;
116850
+ border-radius: 8px;
116851
+ font-size: 13px;
116852
+ font-weight: 500;
116853
+ cursor: pointer;
116854
+ transition: background 0.2s;
116855
+ display: flex;
116856
+ align-items: center;
116857
+ justify-content: center;
116858
+ }
116859
+
116860
+ #chatPanel .quick-image-size-button:hover:not(:disabled) {
116861
+ background: rgba(0, 0, 0, 0.05);
116862
+ }
116863
+
116864
+ #chatPanel .quick-image-size-button:disabled {
116865
+ opacity: 0.6;
116866
+ cursor: not-allowed;
116867
+ }
116868
+
116869
+ body.dark #chatPanel .quick-image-size-button {
116870
+ background: #484848;
116871
+ }
116872
+
116873
+ body.dark #chatPanel .quick-image-size-button:hover:not(:disabled) {
116874
+ background: #4c4c4c;
116875
+ }
116876
+
116877
+ /* Image Size Popover */
116878
+ #chatPanel .image-size-popover {
116879
+ position: absolute;
116880
+ bottom: 72px;
116881
+ right: 68px;
116882
+ background: white;
116883
+ border: 1px solid #e5e5e5;
116884
+ border-radius: 8px;
116885
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
116886
+ z-index: 10006;
116887
+ min-width: 160px;
116888
+ max-height: 300px;
116889
+ overflow-y: auto;
116890
+ }
116891
+
116892
+ body.dark #chatPanel .image-size-popover {
116893
+ background: #3a3a3a;
116894
+ border-color: #555;
116895
+ }
116896
+
116897
+ #chatPanel .size-options {
116898
+ padding: 8px;
116899
+ }
116900
+
116901
+ #chatPanel .size-option {
116902
+ display: flex;
116903
+ align-items: center;
116904
+ padding: 10px 12px;
116905
+ cursor: pointer;
116906
+ border-radius: 6px;
116907
+ transition: background 0.2s;
116908
+ font-size: 14px;
116909
+ }
116910
+ #chatPanel .size-option label,
116911
+ #chatPanel .size-option input {
116912
+ cursor: pointer;
116913
+ }
116914
+
116915
+ #chatPanel .size-option:hover {
116916
+ background: rgba(0, 0, 0, 0.05);
116917
+ }
116918
+
116919
+ #chatPanel .size-option.selected {
116920
+ background: rgba(62, 147, 247, 0.1);
116921
+ font-weight: 500;
116922
+ }
116923
+
116924
+ #chatPanel .size-option input[type="radio"] {
116925
+ margin-right: 8px;
116926
+ }
116927
+
116928
+ body.dark #chatPanel .size-option:hover {
116929
+ background: rgba(255, 255, 255, 0.1);
116930
+ }
116931
+
116932
+ body.dark #chatPanel .size-option.selected {
116933
+ background: rgba(62, 147, 247, 0.2);
116934
+ }
116935
+
116936
+ #chatPanel .image-size-popover::-webkit-scrollbar {
116937
+ width: 6px;
116938
+ }
116939
+
116940
+ #chatPanel .image-size-popover::-webkit-scrollbar-track {
116941
+ background: transparent;
116942
+ }
116943
+
116944
+ #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
116945
+ background: #e5e5e5;
116946
+ border-radius: 3px;
116947
+ }
116948
+
116949
+ body.dark #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
116950
+ background: #555;
116951
+ }
116829
116952
  </style>
116830
116953
  <div
116831
- class="hidden"
116954
+ class="hidden keep-selection"
116832
116955
  id="chatPanel"
116833
116956
  role="dialog"
116834
116957
  aria-labelledby="chatPanelTitle"
@@ -116878,10 +117001,24 @@ class CodeChat$1 {
116878
117001
  <label for="promptInput" class="sr-only">${out('Message input')}</label>
116879
117002
  <textarea
116880
117003
  id="promptInput"
116881
- placeholder="${out('e.g., Create a landing page and explain best practices for hero sections')}"
117004
+ placeholder="${inputPlaceholderText}"
116882
117005
  rows="1"
116883
117006
  aria-label="${out('Type your message')}"
116884
117007
  ></textarea>
117008
+
117009
+ <!-- NEW: Quick image size button -->
117010
+ <button
117011
+ id="quickImageSizeButton"
117012
+ type="button"
117013
+ class="quick-image-size-button"
117014
+ aria-label="${out('Select image size')}"
117015
+ aria-haspopup="true"
117016
+ aria-expanded="false"
117017
+ style="display: none;"
117018
+ >
117019
+
117020
+ </button>
117021
+
116885
117022
  <button
116886
117023
  id="sendButton"
116887
117024
  type="button"
@@ -116895,15 +117032,30 @@ class CodeChat$1 {
116895
117032
  </svg>
116896
117033
  </button>
116897
117034
  </div>
117035
+
117036
+ <!-- NEW: Size selection popover -->
117037
+ <div
117038
+ id="imageSizePopover"
117039
+ class="image-size-popover"
117040
+ role="menu"
117041
+ aria-hidden="true"
117042
+ style="display: none;"
117043
+ >
117044
+ <div class="size-options" id="sizeOptionsContainer">
117045
+ <!-- Options populated dynamically -->
117046
+ </div>
117047
+ </div>
117048
+
116898
117049
  </div>
116899
117050
  </div>
116900
117051
 
116901
117052
  <!-- Settings Dialog Overlay -->
116902
- <div id="settingsOverlay" aria-hidden="true"></div>
117053
+ <div id="settingsOverlay" class="keep-selection" aria-hidden="true"></div>
116903
117054
 
116904
117055
  <!-- Settings Dialog -->
116905
117056
  <div
116906
117057
  id="settingsDialog"
117058
+ class="keep-selection"
116907
117059
  role="dialog"
116908
117060
  aria-labelledby="settingsTitle"
116909
117061
  aria-modal="true"
@@ -117037,9 +117189,21 @@ class CodeChat$1 {
117037
117189
  this.chatModelSelect.value = this.settings.chatModel;
117038
117190
  this.imageModelSelect.value = this.settings.imageModel;
117039
117191
  this.imageSizeSelect.value = this.settings.imageSize;
117040
- let isChatVisible = localStorage.getItem('chatPanelVisible') !== 'false';
117192
+
117193
+ // Check saved state - default to closed if never set
117194
+ const savedState = localStorage.getItem('chatPanelVisible');
117195
+ let isChatVisible = savedState === 'true'; // Only open if explicitly set to 'true'
117041
117196
  if (!isChatVisible) {
117042
- modal.classList.add('hidden');
117197
+ if (this.builder.startCodeChat) {
117198
+ modal.classList.remove('hidden');
117199
+ modal.removeAttribute('aria-hidden');
117200
+ } else {
117201
+ modal.classList.add('hidden');
117202
+ modal.setAttribute('aria-hidden', 'true');
117203
+ }
117204
+ } else {
117205
+ modal.classList.remove('hidden');
117206
+ modal.removeAttribute('aria-hidden');
117043
117207
  }
117044
117208
  const btnClose = modal.querySelector('.close-button');
117045
117209
  btnClose.addEventListener('click', () => {
@@ -117122,7 +117286,45 @@ class CodeChat$1 {
117122
117286
  this.closeChatButton = closeChatButton;
117123
117287
  this.sendButton = sendButton;
117124
117288
  this.messagesContainer = messagesContainer;
117289
+
117290
+ // NEW: Quick image size button elements
117291
+ this.quickImageSizeButton = builderStuff.querySelector('#quickImageSizeButton');
117292
+ this.imageSizePopover = builderStuff.querySelector('#imageSizePopover');
117293
+ this.sizeOptionsContainer = builderStuff.querySelector('#sizeOptionsContainer');
117294
+
117295
+ // Quick Image Size Button Handlers
117296
+ this.quickImageSizeButton.addEventListener('click', e => {
117297
+ e.stopPropagation();
117298
+ this.toggleImageSizePopover();
117299
+ });
117300
+
117301
+ // Close popover when clicking outside
117302
+ document.addEventListener('click', e => {
117303
+ if (!this.imageSizePopover.contains(e.target) && !this.quickImageSizeButton.contains(e.target)) {
117304
+ this.closeImageSizePopover();
117305
+ }
117306
+ });
117307
+
117308
+ // Close popover on Escape
117309
+ this.imageSizePopover.addEventListener('keydown', e => {
117310
+ if (e.key === 'Escape') {
117311
+ this.closeImageSizePopover();
117312
+ this.quickImageSizeButton.focus();
117313
+ }
117314
+ });
117125
117315
  this.renderImageOptions();
117316
+ if (this.demoConversations) {
117317
+ this.loadConversations();
117318
+ }
117319
+ if (this.isDemoMode) {
117320
+ this.addDemoBanner();
117321
+
117322
+ // Disable input in demo mode
117323
+ this.promptInput.disabled = true;
117324
+ // this.promptInput.placeholder = out('Demo mode - Chat is read-only');
117325
+ this.sendButton.disabled = true;
117326
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
117327
+ }
117126
117328
  }
117127
117329
  renderImageOptions() {
117128
117330
  const out = s => this.out(s);
@@ -117229,6 +117431,9 @@ class CodeChat$1 {
117229
117431
  // Trigger initial render with defaults
117230
117432
  modelSelect.value = defaultModelId;
117231
117433
  renderSizes(defaultModelId, false);
117434
+
117435
+ // NEW: Initialize quick size button
117436
+ this.updateQuickImageSizeButton();
117232
117437
  }
117233
117438
 
117234
117439
  /**
@@ -117294,6 +117499,9 @@ class CodeChat$1 {
117294
117499
  this.settingsOverlay.setAttribute('aria-hidden', 'true');
117295
117500
  this.settingsDialog.classList.remove('open');
117296
117501
  this.settingsDialog.setAttribute('aria-hidden', 'true');
117502
+
117503
+ // Reset cursor to default to prevent it from getting stuck
117504
+ document.body.style.cursor = '';
117297
117505
  if (this.settingsLastFocusedElement) {
117298
117506
  this.settingsLastFocusedElement.focus();
117299
117507
  }
@@ -117305,6 +117513,9 @@ class CodeChat$1 {
117305
117513
  this.settings.imageModel = this.imageModelSelect.value;
117306
117514
  this.settings.imageSize = this.imageSizeSelect.value;
117307
117515
  this.saveSettingsToStorage();
117516
+
117517
+ // NEW: Update quick button when settings change
117518
+ this.updateQuickImageSizeButton();
117308
117519
  this.closeSettings();
117309
117520
  }
117310
117521
 
@@ -117338,6 +117549,106 @@ Your job:
117338
117549
  3. Determine what each image should depict based on surrounding content and context
117339
117550
  4. Create detailed image generation prompts for each image
117340
117551
 
117552
+ 📸 EDITORIAL STYLE GUIDE - Apply to all image prompts:
117553
+
117554
+ - Default aesthetic: Minimalist magazine-style (Kinfolk, Cereal, Vogue Living)
117555
+ - Composition: Clean, lots of negative space, well-balanced
117556
+
117557
+
117558
+ CREATIVE PROMPT ENHANCEMENT:
117559
+ When constructing image prompts, intelligently enrich basic descriptions using these proven cinematic stylers:
117560
+
117561
+ 1. **Golden Hour Cinematic**
117562
+ "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."
117563
+ → Best for: Landscapes, architecture, outdoor scenes, lifestyle content
117564
+
117565
+ 2. **Morning Halo Effect**
117566
+ "The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
117567
+ → Best for: Portraits, people in action, outdoor activities
117568
+
117569
+ 3. **Natural Forest Serenity**
117570
+ "in a lush, green forest. The sunlight should be filtering through the trees, creating a serene and natural atmosphere"
117571
+ → Best for: Nature scenes, outdoor activities, wellness content
117572
+
117573
+ 4. **Coastal Summer Drama**
117574
+ "on a dramatic windswept coastline. The overall atmosphere is cheerful, breezy, and full of summer warmth"
117575
+ → Best for: Beach/coastal scenes, adventure, travel content
117576
+
117577
+ 5. **Modern Interior Elegance**
117578
+ "illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
117579
+ → Best for: Indoor portraits, product shots, professional/business settings
117580
+
117581
+ ENHANCEMENT STRATEGY:
117582
+ - Analyze the subject matter and context from the HTML/request
117583
+ - Select the most appropriate styler that matches the scene type
117584
+ - Check if user has specified lighting/time → DO NOT apply contradicting styler elements
117585
+ * Example: User says "daylight" → DO NOT add "golden-hour" or "sunset"
117586
+ - Aim for creative elevation while maintaining the core subject integrity
117587
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
117588
+ - IF user is vague (no specific lighting/mood) → Apply full styler enhancement
117589
+ - User's explicit details ALWAYS take precedence over styler recommendations
117590
+ ` +
117591
+ /*
117592
+ ENHANCEMENT STRATEGY:
117593
+ - Analyze the subject matter and context from the HTML/request
117594
+ - Select the most appropriate styler that matches the scene type
117595
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
117596
+ - If the subject already has specific lighting/atmosphere details, complement rather than override
117597
+ - Aim for creative elevation while maintaining the core subject integrity
117598
+ */
117599
+ `
117600
+ Examples of enriched prompts:
117601
+ - Basic: "a house with mountain view"
117602
+ → 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."
117603
+
117604
+ - Basic: "person hiking"
117605
+ → Enhanced: "a person hiking in nature. The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
117606
+
117607
+ - Basic: "woman reading"
117608
+ → 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"
117609
+
117610
+ - Already detailed: "dramatic headshot with bokeh and rim lighting"
117611
+ → No enhancement needed (preserve user's vision)
117612
+
117613
+
117614
+ CONTEXT-SPECIFIC GUIDELINES:
117615
+
117616
+ Interior/Indoor scenes:
117617
+ - Lighting: Soft natural window light
117618
+ - Backdrop: white walls or neutral tones
117619
+ - Mood: Calm, serene, inviting, uncluttered
117620
+ - Reference: "Kinfolk interior photography"
117621
+
117622
+ People/Portraits/Activities:
117623
+ - Lighting: Natural diffused light, flattering and soft
117624
+ - Styling: Effortlessly elegant, contemporary casual
117625
+ - Pose: Candid, authentic moments, relaxed
117626
+ - Reference: "modern lifestyle editorial photography"
117627
+
117628
+ Products/Objects:
117629
+ - Lighting: Clean studio or soft natural light
117630
+ - Backdrop: Minimalist neutral, lots of breathing room
117631
+ - Mood: Refined simplicity, premium feel
117632
+ - Reference: "high-end editorial product photography"
117633
+
117634
+ Food/Culinary:
117635
+ - Lighting: Soft daylight, gentle overhead or 45° angle
117636
+ - Styling: Minimal props, artisan ceramics, linen
117637
+ - Mood: Fresh, appetizing, rustic-modern
117638
+ - Reference: "Bon Appétit editorial food styling"
117639
+
117640
+ Outdoor/Landscape:
117641
+ - Composition: Serene, contemplative, minimal
117642
+ - Mood: Calm atmosphere, breathing space, lifted brightness
117643
+ - Colors: Vibrant cinematic shot.
117644
+ - Reference: "editorial travel photography"
117645
+
117646
+ CRITICAL RULES:
117647
+ 1. If user specifies a style (cinematic, vintage, vibrant, etc.) → RESPECT IT, only add quality markers
117648
+ 2. If user is vague → APPLY FULL EDITORIAL TREATMENT based on subject category
117649
+ 3. Always maintain cohesive aesthetic across all images
117650
+ 4. Every prompt should feel like it belongs in a high-end lifestyle magazine
117651
+
117341
117652
  Respond with a JSON array with one entry per image to generate:
117342
117653
  [
117343
117654
  {
@@ -117448,6 +117759,11 @@ Response: [
117448
117759
  */
117449
117760
  async sendMessage() {
117450
117761
  const out = s => this.out(s);
117762
+
117763
+ // Prevent sending messages in demo mode
117764
+ if (this.isDemoMode) {
117765
+ return;
117766
+ }
117451
117767
  const prompt = this.promptInput.value.trim();
117452
117768
  if (!prompt) return;
117453
117769
  this.addMessage('user', prompt);
@@ -117515,14 +117831,42 @@ Response: [
117515
117831
  } else if (result.type === 'image') {
117516
117832
  if (result.imageDetails && result.imageDetails.length > 1) {
117517
117833
  this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`);
117834
+ // this.addMessage('assistant', `✓ ${out('Generated {count} images successfully').replace('{count}', result.imageDetails.length)}`);
117518
117835
  this.addImagePreview(result.imageDetails);
117519
117836
  } else {
117520
- this.addMessage('assistant', '✓ Generated image successfully');
117837
+ this.addMessage('assistant', '✓ Image generated successfully');
117838
+ // this.addMessage('assistant', `✓ ${out('Image generated successfully')}`);
117521
117839
  if (result.generatedUrls && result.generatedUrls[0]) {
117522
117840
  this.addImagePreview([{
117523
117841
  url: result.generatedUrls[0],
117524
117842
  context: result.description
117525
117843
  }]);
117844
+
117845
+ // If there is a selected image, replace with the new image
117846
+ const url = result.generatedUrls[0];
117847
+ if (this.builder.editor) {
117848
+ // ContentBox
117849
+ const img = this.builder.editor.activeImage;
117850
+ if (img) {
117851
+ this.builder.editor.saveForUndo();
117852
+ img.addEventListener('load', () => {
117853
+ this.builder.editor.element.image.repositionImageTool();
117854
+ this.builder.editor.elmTool.repositionElementTool();
117855
+ });
117856
+ this.builder.editor.activeImage.setAttribute('src', url);
117857
+ }
117858
+ } else {
117859
+ // ContentBuilder
117860
+ const img = this.builder.activeImage;
117861
+ if (img) {
117862
+ this.builder.uo.saveForUndo();
117863
+ img.addEventListener('load', () => {
117864
+ this.builder.element.image.repositionImageTool();
117865
+ this.builder.elmTool.repositionElementTool();
117866
+ });
117867
+ this.builder.activeImage.setAttribute('src', url);
117868
+ }
117869
+ }
117526
117870
  }
117527
117871
  }
117528
117872
  }
@@ -117576,15 +117920,16 @@ Response: [
117576
117920
  `;
117577
117921
  images.forEach(img => {
117578
117922
  previewHTML += `
117579
- <div style="border-radius: 8px; overflow: hidden; background: rgba(255,255,255,0.05);">
117580
- <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%; height: 150px; object-fit: cover;" />
117923
+ <div style="border-radius: 6px; overflow: hidden; background: rgba(255,255,255,0.05);">
117924
+ <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%;" />
117581
117925
  <div style="padding: 8px; font-size: 11px; opacity: 0.8;">
117582
117926
  ${img.context || ''}
117583
117927
  <a href="${img.url}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 4px;">View</a>
117584
117928
  </div>
117585
117929
  </div>
117586
- `;
117930
+ `; // height: 150px; object-fit: cover;
117587
117931
  });
117932
+
117588
117933
  previewHTML += '</div>';
117589
117934
  previewDiv.innerHTML = previewHTML;
117590
117935
  this.messagesContainer.appendChild(previewDiv);
@@ -117605,6 +117950,32 @@ Response: [
117605
117950
 
117606
117951
  // Check if image generation is enabled
117607
117952
  const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
117953
+
117954
+ // Build context about previously generated images
117955
+ let imageHistoryContext = '';
117956
+ const previousImageResults = this.conversationResults.filter(r => r.type === 'image' && r.generatedUrls && r.generatedUrls.length > 0);
117957
+ if (previousImageResults.length > 0) {
117958
+ imageHistoryContext = '\n\n=== PREVIOUSLY GENERATED IMAGES IN THIS CONVERSATION ===\n';
117959
+ imageHistoryContext += 'The following images were generated earlier in this conversation:\n\n';
117960
+ previousImageResults.forEach((result, idx) => {
117961
+ if (result.imageDetails && result.imageDetails.length > 0) {
117962
+ result.imageDetails.forEach((img, imgIdx) => {
117963
+ imageHistoryContext += `Generated Image ${idx + 1}.${imgIdx + 1}:\n`;
117964
+ imageHistoryContext += ` URL: ${img.url}\n`;
117965
+ imageHistoryContext += ` Context: ${img.context}\n`;
117966
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
117967
+ });
117968
+ } else {
117969
+ result.generatedUrls.forEach((url, urlIdx) => {
117970
+ imageHistoryContext += `Generated Image ${idx + 1}.${urlIdx + 1}:\n`;
117971
+ imageHistoryContext += ` URL: ${url}\n`;
117972
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
117973
+ });
117974
+ }
117975
+ });
117976
+ 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';
117977
+ imageHistoryContext += '⚠️ DO NOT create a new image task. Instead, create a CODE task that uses these existing URLs.\n\n';
117978
+ }
117608
117979
  const classificationPrompt = `Analyze this user message and break it down into tasks.
117609
117980
 
117610
117981
  CURRENT HTML CONTEXT (use this to understand the page structure):
@@ -117612,11 +117983,19 @@ CURRENT HTML CONTEXT (use this to understand the page structure):
117612
117983
  ${this.builder.html()}
117613
117984
  \`\`\`
117614
117985
 
117986
+ ${imageHistoryContext}
117987
+
117615
117988
  IMPORTANT: This is a WEB BUILDER tool with chat capabilities. Valid requests are:
117616
117989
  - Creating/editing/removing HTML content (code tasks)
117617
117990
  ${imageGenEnabled ? '- Generating images for the webpage (image tasks)' : ''}
117618
117991
  - Asking questions or having conversations (chat tasks) - THIS CAN BE ABOUT ANYTHING
117619
117992
 
117993
+ CRITICAL CONTEXT RULE:
117994
+ - In a web builder, "create an article/blog/story/content" means CREATE A WEBPAGE (CODE task)
117995
+ - Only use CHAT task for questions, explanations, or requests for advice
117996
+ - If user says "create", "write", "make", "build" followed by content → CODE task
117997
+ - If user says "explain", "how do I", "what is", "tell me about" → CHAT task
117998
+
117620
117999
  ${imageGenEnabled ? '' : `
117621
118000
  ⚠️ AI IMAGE GENERATION IS CURRENTLY DISABLED
117622
118001
  - If user requests AI image generation/creation, explain that it's disabled
@@ -117686,6 +118065,18 @@ IMAGE TASK RULES:
117686
118065
  - IMPORTANT: Look at the HTML context to understand if multiple images are involved
117687
118066
  - If the target section has multiple images, indicate this in targetElement (e.g., "all 3 images in Culinary Delights section")
117688
118067
  - Image tasks should come BEFORE code tasks that depend on them
118068
+
118069
+ - CRITICAL: DO NOT create image task for these phrases (they mean REUSE existing):
118070
+ * "use the generated image" / "use the image"
118071
+ * "use this image" / "use existing image"
118072
+ * "with the generated image" / "with this image"
118073
+ * "with the previous image" / "with the last image"
118074
+ * "using the image I just created/generated"
118075
+ * Any reference to a PREVIOUS image from conversation history
118076
+ - Decision logic:
118077
+ * "Generate a new image of X" → CREATE image task (new generation)
118078
+ * "Create article using the generated image" → NO image task (reuse existing)
118079
+
117689
118080
  ` : ''}
117690
118081
 
117691
118082
  Examples:
@@ -117723,6 +118114,17 @@ Input: "Create a landing page about wood furniture workshop"
117723
118114
  Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create a landing page for a wood furniture workshop", "order": 1}], "is_mixed": false}
117724
118115
  Note: No image task created because user did not explicitly request AI image generation
117725
118116
 
118117
+ Input: "Create an inspiring article and use the generated image"
118118
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create article using previously generated image from conversation history", "order": 1}], "is_mixed": false}
118119
+ Note: No image task because user wants to REUSE existing image
118120
+
118121
+ Input: "Write a blog post about productivity and use this image"
118122
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create blog post using previously generated image", "order": 1}], "is_mixed": false}
118123
+
118124
+ Input: "Generate another mountain image and create a landing page"
118125
+ 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}
118126
+ Note: "another" and "generate" indicate NEW image generation
118127
+
117726
118128
  ` : `
117727
118129
  Input: "Generate an AI image of a mountain"
117728
118130
  Output: {"is_valid": false, "reason": "AI image generation is currently disabled.", "tasks": [], "is_mixed": false}
@@ -117806,7 +118208,9 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
117806
118208
  if (imageTasksFromThisRequest.length > 0) {
117807
118209
  hasGeneratedImages = true;
117808
118210
  imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n';
117809
- imageTasksFromThisRequest.forEach(imageTask => {
118211
+
118212
+ // ⭐ REVERSE to show most recent first
118213
+ imageTasksFromThisRequest.reverse().forEach(imageTask => {
117810
118214
  if (imageTask.imageDetails && imageTask.imageDetails.length > 0) {
117811
118215
  // Multiple images with context
117812
118216
  imageTask.imageDetails.forEach((img, idx) => {
@@ -117821,7 +118225,8 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
117821
118225
  });
117822
118226
  }
117823
118227
  });
117824
- imageContext += '\n⚠️ IMPORTANT: Use these generated image URLs (not placehold.co or other sources) for the specific images mentioned in your task.\n';
118228
+ imageContext += '\n⚠️ IMPORTANT: Use the FIRST image URL listed above (most recent) unless the task specifically asks for a different image.\n';
118229
+ imageContext += '⚠️ The first image is the MOST RECENTLY GENERATED and should be used by default.\n';
117825
118230
  imageContext += '⚠️ Your task description specifies WHICH images to update - follow it precisely.\n';
117826
118231
  }
117827
118232
 
@@ -118374,6 +118779,319 @@ ${this.builder.html()}
118374
118779
  document.body.removeChild(announcement);
118375
118780
  }, 1000);
118376
118781
  }
118782
+
118783
+ /**
118784
+ * ============================================================================
118785
+ * DEMO MODE
118786
+ * ============================================================================
118787
+ */
118788
+ loadConversations() {
118789
+ // Load demo conversations
118790
+ if (this.demoConversations && this.demoConversations.length > 0) {
118791
+ this.demoConversations.forEach(msg => {
118792
+ if (msg.role === 'user') {
118793
+ this.addMessage('user', msg.content);
118794
+ } else if (msg.role === 'assistant') {
118795
+ this.addMessage('assistant', msg.content, true);
118796
+
118797
+ // Add image preview if available
118798
+ if (msg.imagePreview && msg.imagePreview.length > 0) {
118799
+ this.addImagePreview(msg.imagePreview);
118800
+
118801
+ // Include in conversationResults with correct structure
118802
+ this.conversationResults.push({
118803
+ type: 'image',
118804
+ description: 'Previously generated image from demo',
118805
+ content: `Generated ${msg.imagePreview.length} images`,
118806
+ generatedUrls: msg.imagePreview.map(img => img.url),
118807
+ // Array of URLs
118808
+ imageDetails: msg.imagePreview.map(img => ({
118809
+ // Array of objects
118810
+ url: img.url,
118811
+ context: img.context,
118812
+ prompt: img.context || '' // Use context as prompt if no prompt available
118813
+ })),
118814
+
118815
+ targetElement: 'demo images'
118816
+ });
118817
+ }
118818
+ }
118819
+ });
118820
+ }
118821
+ }
118822
+ addDemoBanner() {
118823
+ const out = s => this.out(s);
118824
+ const banner = document.createElement('div');
118825
+ banner.className = 'demo-banner';
118826
+ banner.innerHTML = `
118827
+ <div style="
118828
+ background: linear-gradient(135deg, #527cff 0%, #5563db 100%);
118829
+ color: white;
118830
+ padding: 12px 16px;
118831
+ border-radius: 8px;
118832
+ font-size: 13px;
118833
+ display: flex;
118834
+ align-items: center;
118835
+ gap: 8px;
118836
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
118837
+ ">
118838
+ <span style="font-size: 18px;">📖</span>
118839
+ <div>
118840
+ <strong>${out('Read-Only Demo')}</strong>
118841
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">
118842
+ ${out('Full AI chat available in the complete version.')}
118843
+ </div>
118844
+ </div>
118845
+ </div>
118846
+ `;
118847
+
118848
+ // Insert banner at the top of messages container
118849
+ // this.messagesContainer.insertBefore(banner, this.messagesContainer.firstChild);
118850
+ this.messagesContainer.appendChild(banner);
118851
+ }
118852
+
118853
+ /**
118854
+ * Update the quick image size button based on current model and settings
118855
+ */
118856
+ updateQuickImageSizeButton() {
118857
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
118858
+ if (!imageGenEnabled) {
118859
+ this.quickImageSizeButton.style.display = 'none';
118860
+ return;
118861
+ }
118862
+ const currentModelId = this.settings.imageModel;
118863
+ const availableSizes = this.getSizesForModel(currentModelId);
118864
+
118865
+ // Hide button if model has no size options
118866
+ if (!availableSizes || availableSizes.length === 0) {
118867
+ this.quickImageSizeButton.style.display = 'none';
118868
+ return;
118869
+ }
118870
+
118871
+ // Show button
118872
+ this.quickImageSizeButton.style.display = 'flex';
118873
+
118874
+ // Smart selection: check if current size is available
118875
+ let selectedSize = this.settings.imageSize;
118876
+ if (!availableSizes.includes(selectedSize)) {
118877
+ // Current size not available, find best fallback
118878
+ selectedSize = this.findBestFallback(selectedSize, availableSizes);
118879
+ this.settings.imageSize = selectedSize;
118880
+
118881
+ // Sync with settings dialog
118882
+ this.imageSizeSelect.value = selectedSize;
118883
+
118884
+ // Save to storage
118885
+ this.saveSettingsToStorage();
118886
+ }
118887
+
118888
+ // Update button label
118889
+ this.updateButtonLabel(selectedSize);
118890
+
118891
+ // Populate popover options
118892
+ this.populateSizePopover(availableSizes, selectedSize);
118893
+ }
118894
+
118895
+ /**
118896
+ * Get available sizes for a model (reuse existing logic)
118897
+ */
118898
+ getSizesForModel(modelId) {
118899
+ const model = this.imageModels.find(m => m.id === modelId);
118900
+ if (!model) return null;
118901
+
118902
+ // if sizes explicitly empty array → means no size options
118903
+ if (Array.isArray(model.sizes) && model.sizes.length === 0) {
118904
+ return null;
118905
+ }
118906
+ const defaultSizes = ['square', 'square_hd', 'landscape_4_3', 'landscape_16_9', 'portrait_4_3', 'portrait_16_9'];
118907
+ return model.sizes || defaultSizes;
118908
+ }
118909
+
118910
+ /**
118911
+ * Find best fallback size when current selection is not available
118912
+ */
118913
+ findBestFallback(preferredSize, availableSizes) {
118914
+ // Strategy 1: Try to match orientation
118915
+ const orientation = this.getOrientation(preferredSize);
118916
+ const matchingOrientation = availableSizes.find(size => this.getOrientation(size) === orientation);
118917
+ if (matchingOrientation) return matchingOrientation;
118918
+
118919
+ // Strategy 2: Common defaults
118920
+ if (availableSizes.includes('16:9')) return '16:9';
118921
+ if (availableSizes.includes('landscape_16_9')) return 'landscape_16_9';
118922
+ if (availableSizes.includes('1:1')) return '1:1';
118923
+ if (availableSizes.includes('square')) return 'square';
118924
+
118925
+ // Strategy 3: First available
118926
+ return availableSizes[0];
118927
+ }
118928
+
118929
+ /**
118930
+ * Determine orientation from size value
118931
+ */
118932
+ getOrientation(size) {
118933
+ const landscape = ['16:9', '21:9', '4:3', '3:2', '5:4', 'landscape_16_9', 'landscape_4_3'];
118934
+ const portrait = ['9:16', '9:21', '3:4', '2:3', '4:5', 'portrait_16_9', 'portrait_4_3'];
118935
+ if (landscape.includes(size)) return 'landscape';
118936
+ if (portrait.includes(size)) return 'portrait';
118937
+ return 'square';
118938
+ }
118939
+
118940
+ /**
118941
+ * Update button label to show current size
118942
+ */
118943
+ updateButtonLabel(sizeValue) {
118944
+ if (!sizeValue) {
118945
+ this.quickImageSizeButton.textContent = '□';
118946
+ this.quickImageSizeButton.setAttribute('aria-label', this.out('Select image size'));
118947
+ return;
118948
+ }
118949
+
118950
+ // Convert verbose labels to short form
118951
+ const labelMap = {
118952
+ 'landscape_16_9': '16:9',
118953
+ 'landscape_4_3': '4:3',
118954
+ 'portrait_16_9': '9:16',
118955
+ 'portrait_4_3': '3:4',
118956
+ 'square': '1:1',
118957
+ 'square_hd': '1:1 HD'
118958
+ };
118959
+ const displayLabel = labelMap[sizeValue] || sizeValue;
118960
+ this.quickImageSizeButton.textContent = displayLabel;
118961
+ this.quickImageSizeButton.setAttribute('aria-label', `${this.out('Image size')}: ${displayLabel}`);
118962
+ }
118963
+
118964
+ /**
118965
+ * Populate the size popover with available options
118966
+ */
118967
+ populateSizePopover(sizes, selectedSize) {
118968
+ const out = s => this.out(s);
118969
+ const sizeDefs = {
118970
+ 'square_hd': out('Square HD'),
118971
+ 'square': out('Square'),
118972
+ 'landscape_4_3': out('Landscape 4x3'),
118973
+ 'landscape_16_9': out('Landscape 16x9'),
118974
+ 'portrait_4_3': out('Portrait 3x4'),
118975
+ 'portrait_16_9': out('Portrait 9x16'),
118976
+ '1:1': out('Square'),
118977
+ '3:2': out('Landscape 3x2'),
118978
+ '4:3': out('Landscape 4x3'),
118979
+ '5:4': out('Landscape 5x4'),
118980
+ '16:9': out('Landscape 16x9'),
118981
+ '21:9': out('Landscape 21:9'),
118982
+ '2:3': out('Portrait 2x3'),
118983
+ '3:4': out('Portrait 3x4'),
118984
+ '4:5': out('Portrait 4x5'),
118985
+ '9:16': out('Portrait 9x16'),
118986
+ '9:21': out('Portrait 9:21')
118987
+ };
118988
+ this.sizeOptionsContainer.innerHTML = '';
118989
+ sizes.forEach(size => {
118990
+ const option = document.createElement('div');
118991
+ option.className = 'size-option' + (size === selectedSize ? ' selected' : '');
118992
+ option.setAttribute('role', 'menuitem');
118993
+ option.setAttribute('tabindex', '0');
118994
+ const radio = document.createElement('input');
118995
+ radio.type = 'radio';
118996
+ radio.name = 'quickImageSize';
118997
+ radio.value = size;
118998
+ radio.checked = size === selectedSize;
118999
+ radio.id = `quick-size-${size}`;
119000
+ const label = document.createElement('label');
119001
+ label.htmlFor = `quick-size-${size}`;
119002
+ label.textContent = sizeDefs[size] || size;
119003
+ // label.style.cursor = 'pointer';
119004
+ label.style.flex = '1';
119005
+ option.appendChild(radio);
119006
+ option.appendChild(label);
119007
+
119008
+ // Click handler
119009
+ option.addEventListener('click', () => {
119010
+ this.selectImageSize(size);
119011
+ });
119012
+
119013
+ // Keyboard handler
119014
+ option.addEventListener('keydown', e => {
119015
+ if (e.key === 'Enter' || e.key === ' ') {
119016
+ e.preventDefault();
119017
+ this.selectImageSize(size);
119018
+ }
119019
+ });
119020
+ this.sizeOptionsContainer.appendChild(option);
119021
+ });
119022
+ }
119023
+
119024
+ /**
119025
+ * Handle size selection
119026
+ */
119027
+ selectImageSize(size) {
119028
+ // Update settings
119029
+ this.settings.imageSize = size;
119030
+
119031
+ // Sync with settings dialog
119032
+ this.imageSizeSelect.value = size;
119033
+
119034
+ // Update button label
119035
+ this.updateButtonLabel(size);
119036
+
119037
+ // Update popover selection
119038
+ this.sizeOptionsContainer.querySelectorAll('.size-option').forEach(opt => {
119039
+ opt.classList.remove('selected');
119040
+ opt.querySelector('input[type="radio"]').checked = false;
119041
+ });
119042
+
119043
+ // const selectedOption = this.sizeOptionsContainer.querySelector(`#quick-size-${size}`);
119044
+ const selectedOption = Array.from(this.sizeOptionsContainer.querySelectorAll('input[type="radio"]')).find(radio => radio.value === size);
119045
+ if (selectedOption) {
119046
+ selectedOption.closest('.size-option').classList.add('selected');
119047
+ selectedOption.checked = true;
119048
+ }
119049
+
119050
+ // Save to storage
119051
+ this.saveSettingsToStorage();
119052
+
119053
+ // Close popover
119054
+ this.closeImageSizePopover();
119055
+
119056
+ // Focus back to input
119057
+ this.promptInput.focus();
119058
+ }
119059
+
119060
+ /**
119061
+ * Toggle popover visibility
119062
+ */
119063
+ toggleImageSizePopover() {
119064
+ const isVisible = this.imageSizePopover.style.display !== 'none';
119065
+ if (isVisible) {
119066
+ this.closeImageSizePopover();
119067
+ } else {
119068
+ this.openImageSizePopover();
119069
+ }
119070
+ }
119071
+
119072
+ /**
119073
+ * Open the size popover
119074
+ */
119075
+ openImageSizePopover() {
119076
+ this.imageSizePopover.style.display = 'block';
119077
+ this.imageSizePopover.setAttribute('aria-hidden', 'false');
119078
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'true');
119079
+
119080
+ // Focus first option
119081
+ const firstOption = this.sizeOptionsContainer.querySelector('.size-option');
119082
+ if (firstOption) {
119083
+ firstOption.focus();
119084
+ }
119085
+ }
119086
+
119087
+ /**
119088
+ * Close the size popover
119089
+ */
119090
+ closeImageSizePopover() {
119091
+ this.imageSizePopover.style.display = 'none';
119092
+ this.imageSizePopover.setAttribute('aria-hidden', 'true');
119093
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'false');
119094
+ }
118377
119095
  out(s) {
118378
119096
  let result = this.builder.lang[s];
118379
119097
  if (result) return result;else return s;
@@ -118799,7 +119517,7 @@ Classes: transition-none, transition, transition-colors, transition-opacity, tra
118799
119517
 
118800
119518
  **Duration**
118801
119519
 
118802
- Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000
119520
+ Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000, duration-1500
118803
119521
 
118804
119522
  **Timing**
118805
119523
 
@@ -118813,6 +119531,10 @@ Classes: delay-75, delay-100, delay-150, delay-200, delay-300, delay-500
118813
119531
 
118814
119532
  Classes: scale-0, scale-50, scale-75, scale-90, scale-95, scale-100, scale-105, scale-110, scale-125, scale-150
118815
119533
 
119534
+ **Hover Effect**
119535
+
119536
+ Classes: hover:scale-105
119537
+
118816
119538
  **Rotate**
118817
119539
 
118818
119540
  Classes: rotate-0, rotate-45, rotate-90, rotate-180
@@ -119146,6 +119868,31 @@ When creating icon-based features, benefits, or service sections: use Bootstrap
119146
119868
  <h4 class="size-18 font-medium text-center pb-3">Lightning Fast</h4>
119147
119869
  <p class="size-14 leading-16 text-gray-600 text-center">Description...</p>
119148
119870
 
119871
+ ### 9. Image Layout with Hover Effect
119872
+
119873
+ Use this pattern for all images (galleries, featured images, portfolio items):
119874
+
119875
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
119876
+ <img src="..." alt="..." class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
119877
+ </div>
119878
+
119879
+ **Key elements:**
119880
+ - 'aspect-ratio:16/9' - maintains proportions (adjust as needed: 1/1, 4/3, 3/2, 16/9, etc.)
119881
+ - 'overflow-hidden' - keeps scaled image contained
119882
+ - 'bg-gray-50' - placeholder while image loads
119883
+ - 'transition-transform duration-1000' - smooth 1-second animation
119884
+ - 'hover:scale-105' - subtle 5% scale on hover
119885
+ - 'object-cover' - ensures image fills container
119886
+
119887
+ **Common aspect ratios:**
119888
+ - Square: 'aspect-ratio:1/1'
119889
+ - Portrait: 'aspect-ratio:3/4' or 'aspect-ratio:2/3'
119890
+ - Landscape: 'aspect-ratio:4/3' or 'aspect-ratio:3/2'
119891
+ - Wide: 'aspect-ratio:16/9' or 'aspect-ratio:21/9'
119892
+ - Tall portrait: 'aspect-ratio:9/16'
119893
+
119894
+ Always include this hover effect for a polished, interactive feel.
119895
+
119149
119896
  `;
119150
119897
 
119151
119898
  const contextCodeBlock$1 = `
@@ -119345,6 +120092,17 @@ The frameworks provide utility classes for structure and typography. To create r
119345
120092
 
119346
120093
  **Example <style>: When needed for animations, transitions, or reusable patterns**
119347
120094
 
120095
+ <div class="row">
120096
+ <div class="column">
120097
+
120098
+ <!-- GUIDANCE: Use this layout to show images at any aspect ratio with a hover scale effect -->
120099
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
120100
+ <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">
120101
+ </div>
120102
+
120103
+ </div>
120104
+ </div>
120105
+
119348
120106
  <div class="row">
119349
120107
  <div class="column">
119350
120108
 
@@ -119519,6 +120277,33 @@ class ContentBuilder {
119519
120277
  'snippets': []
119520
120278
  },
119521
120279
  screenMode: 'desktop',
120280
+ demoMode: false,
120281
+ /*
120282
+ demoConversations: [
120283
+ {
120284
+ role: 'user',
120285
+ content: 'Generate an image: "a house with beautiful mountain view. warm sunlight, golden-hour lighting, filmic teal-orange color grade".',
120286
+ // timestamp: Date.now() - 180000 // 3 minutes ago
120287
+ },
120288
+ {
120289
+ role: 'assistant',
120290
+ content: '✓ Image generated successfully',
120291
+ imagePreview: [
120292
+ { url: 'uploads/ai-8vnqv.png', context: 'Generate image of a house with a beautiful mountain view' },
120293
+ ]
120294
+ },
120295
+ {
120296
+ role: 'user',
120297
+ 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.
120298
+ Use the generated image for this article, and present it as a premium design magazine feature.`,
120299
+ },
120300
+ {
120301
+ role: 'assistant',
120302
+ 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.',
120303
+ }
120304
+ ],
120305
+ */
120306
+
119522
120307
  // Live Preview
119523
120308
  // previewURL: 'preview.html',
119524
120309
  onPreviewOpen: () => {
@@ -143636,7 +144421,10 @@ class CodeChat {
143636
144421
  };
143637
144422
  this.builder = builder;
143638
144423
  const builderStuff = this.builder.builderStuff;
143639
- this.builderStuff = builderStuff;
144424
+ this.builderStuff = builderStuff; // Check if demo mode is enabled
144425
+
144426
+ this.isDemoMode = this.builder.demoMode || false;
144427
+ this.demoConversations = this.builder.demoConversations || [];
143640
144428
 
143641
144429
  const out = s => this.out(s); // Load saved settings or use defaults
143642
144430
 
@@ -143983,6 +144771,14 @@ class CodeChat {
143983
144771
  this.imageModels = this.builder.imageGenerationModels;
143984
144772
  }
143985
144773
 
144774
+ let inputPlaceholderText;
144775
+
144776
+ if (this.builder.editor) {
144777
+ inputPlaceholderText = out('e.g., Create a landing page for a creative studio');
144778
+ } else {
144779
+ inputPlaceholderText = out('e.g., Create an article about a productive home workspace');
144780
+ }
144781
+
143986
144782
  let html = `
143987
144783
  <style>
143988
144784
  #chatPanel {
@@ -144314,10 +145110,12 @@ class CodeChat {
144314
145110
  box-shadow: 6px 14px 20px 0px rgba(95, 95, 95, 0.11);
144315
145111
  z-index: 10005;
144316
145112
  display: none;
145113
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
144317
145114
  }
144318
145115
 
144319
145116
  #settingsDialog.open {
144320
145117
  display: block;
145118
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
144321
145119
  }
144322
145120
 
144323
145121
  #settingsDialog .settings-header {
@@ -144578,9 +145376,118 @@ class CodeChat {
144578
145376
  body.dark #chatPanel .copy-button:hover {
144579
145377
  background: rgba(255, 255, 255, 0.2);
144580
145378
  }
145379
+
145380
+
145381
+ /* Quick Image Size Button */
145382
+ #chatPanel .quick-image-size-button {
145383
+ width: 48px;
145384
+ height: 48px;
145385
+ border: none;
145386
+ border-radius: 8px;
145387
+ font-size: 13px;
145388
+ font-weight: 500;
145389
+ cursor: pointer;
145390
+ transition: background 0.2s;
145391
+ display: flex;
145392
+ align-items: center;
145393
+ justify-content: center;
145394
+ }
145395
+
145396
+ #chatPanel .quick-image-size-button:hover:not(:disabled) {
145397
+ background: rgba(0, 0, 0, 0.05);
145398
+ }
145399
+
145400
+ #chatPanel .quick-image-size-button:disabled {
145401
+ opacity: 0.6;
145402
+ cursor: not-allowed;
145403
+ }
145404
+
145405
+ body.dark #chatPanel .quick-image-size-button {
145406
+ background: #484848;
145407
+ }
145408
+
145409
+ body.dark #chatPanel .quick-image-size-button:hover:not(:disabled) {
145410
+ background: #4c4c4c;
145411
+ }
145412
+
145413
+ /* Image Size Popover */
145414
+ #chatPanel .image-size-popover {
145415
+ position: absolute;
145416
+ bottom: 72px;
145417
+ right: 68px;
145418
+ background: white;
145419
+ border: 1px solid #e5e5e5;
145420
+ border-radius: 8px;
145421
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
145422
+ z-index: 10006;
145423
+ min-width: 160px;
145424
+ max-height: 300px;
145425
+ overflow-y: auto;
145426
+ }
145427
+
145428
+ body.dark #chatPanel .image-size-popover {
145429
+ background: #3a3a3a;
145430
+ border-color: #555;
145431
+ }
145432
+
145433
+ #chatPanel .size-options {
145434
+ padding: 8px;
145435
+ }
145436
+
145437
+ #chatPanel .size-option {
145438
+ display: flex;
145439
+ align-items: center;
145440
+ padding: 10px 12px;
145441
+ cursor: pointer;
145442
+ border-radius: 6px;
145443
+ transition: background 0.2s;
145444
+ font-size: 14px;
145445
+ }
145446
+ #chatPanel .size-option label,
145447
+ #chatPanel .size-option input {
145448
+ cursor: pointer;
145449
+ }
145450
+
145451
+ #chatPanel .size-option:hover {
145452
+ background: rgba(0, 0, 0, 0.05);
145453
+ }
145454
+
145455
+ #chatPanel .size-option.selected {
145456
+ background: rgba(62, 147, 247, 0.1);
145457
+ font-weight: 500;
145458
+ }
145459
+
145460
+ #chatPanel .size-option input[type="radio"] {
145461
+ margin-right: 8px;
145462
+ }
145463
+
145464
+ body.dark #chatPanel .size-option:hover {
145465
+ background: rgba(255, 255, 255, 0.1);
145466
+ }
145467
+
145468
+ body.dark #chatPanel .size-option.selected {
145469
+ background: rgba(62, 147, 247, 0.2);
145470
+ }
145471
+
145472
+ #chatPanel .image-size-popover::-webkit-scrollbar {
145473
+ width: 6px;
145474
+ }
145475
+
145476
+ #chatPanel .image-size-popover::-webkit-scrollbar-track {
145477
+ background: transparent;
145478
+ }
145479
+
145480
+ #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
145481
+ background: #e5e5e5;
145482
+ border-radius: 3px;
145483
+ }
145484
+
145485
+ body.dark #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
145486
+ background: #555;
145487
+ }
144581
145488
  </style>
144582
145489
  <div
144583
- class="hidden"
145490
+ class="hidden keep-selection"
144584
145491
  id="chatPanel"
144585
145492
  role="dialog"
144586
145493
  aria-labelledby="chatPanelTitle"
@@ -144630,10 +145537,24 @@ class CodeChat {
144630
145537
  <label for="promptInput" class="sr-only">${out('Message input')}</label>
144631
145538
  <textarea
144632
145539
  id="promptInput"
144633
- placeholder="${out('e.g., Create a landing page and explain best practices for hero sections')}"
145540
+ placeholder="${inputPlaceholderText}"
144634
145541
  rows="1"
144635
145542
  aria-label="${out('Type your message')}"
144636
145543
  ></textarea>
145544
+
145545
+ <!-- NEW: Quick image size button -->
145546
+ <button
145547
+ id="quickImageSizeButton"
145548
+ type="button"
145549
+ class="quick-image-size-button"
145550
+ aria-label="${out('Select image size')}"
145551
+ aria-haspopup="true"
145552
+ aria-expanded="false"
145553
+ style="display: none;"
145554
+ >
145555
+
145556
+ </button>
145557
+
144637
145558
  <button
144638
145559
  id="sendButton"
144639
145560
  type="button"
@@ -144647,15 +145568,30 @@ class CodeChat {
144647
145568
  </svg>
144648
145569
  </button>
144649
145570
  </div>
145571
+
145572
+ <!-- NEW: Size selection popover -->
145573
+ <div
145574
+ id="imageSizePopover"
145575
+ class="image-size-popover"
145576
+ role="menu"
145577
+ aria-hidden="true"
145578
+ style="display: none;"
145579
+ >
145580
+ <div class="size-options" id="sizeOptionsContainer">
145581
+ <!-- Options populated dynamically -->
145582
+ </div>
145583
+ </div>
145584
+
144650
145585
  </div>
144651
145586
  </div>
144652
145587
 
144653
145588
  <!-- Settings Dialog Overlay -->
144654
- <div id="settingsOverlay" aria-hidden="true"></div>
145589
+ <div id="settingsOverlay" class="keep-selection" aria-hidden="true"></div>
144655
145590
 
144656
145591
  <!-- Settings Dialog -->
144657
145592
  <div
144658
145593
  id="settingsDialog"
145594
+ class="keep-selection"
144659
145595
  role="dialog"
144660
145596
  aria-labelledby="settingsTitle"
144661
145597
  aria-modal="true"
@@ -144785,11 +145721,17 @@ class CodeChat {
144785
145721
  this.codeModelSelect.value = this.settings.codeModel;
144786
145722
  this.chatModelSelect.value = this.settings.chatModel;
144787
145723
  this.imageModelSelect.value = this.settings.imageModel;
144788
- this.imageSizeSelect.value = this.settings.imageSize;
144789
- let isChatVisible = localStorage.getItem('chatPanelVisible') !== 'false';
145724
+ this.imageSizeSelect.value = this.settings.imageSize; // Check saved state - default to closed if never set
145725
+
145726
+ const savedState = localStorage.getItem('chatPanelVisible');
145727
+ let isChatVisible = savedState === 'true'; // Only open if explicitly set to 'true'
144790
145728
 
144791
145729
  if (!isChatVisible) {
144792
145730
  modal.classList.add('hidden');
145731
+ modal.setAttribute('aria-hidden', 'true');
145732
+ } else {
145733
+ modal.classList.remove('hidden');
145734
+ modal.removeAttribute('aria-hidden');
144793
145735
  }
144794
145736
 
144795
145737
  const btnClose = modal.querySelector('.close-button');
@@ -144873,8 +145815,43 @@ class CodeChat {
144873
145815
  this.promptInput = promptInput;
144874
145816
  this.closeChatButton = closeChatButton;
144875
145817
  this.sendButton = sendButton;
144876
- this.messagesContainer = messagesContainer;
145818
+ this.messagesContainer = messagesContainer; // NEW: Quick image size button elements
145819
+
145820
+ this.quickImageSizeButton = builderStuff.querySelector('#quickImageSizeButton');
145821
+ this.imageSizePopover = builderStuff.querySelector('#imageSizePopover');
145822
+ this.sizeOptionsContainer = builderStuff.querySelector('#sizeOptionsContainer'); // Quick Image Size Button Handlers
145823
+
145824
+ this.quickImageSizeButton.addEventListener('click', e => {
145825
+ e.stopPropagation();
145826
+ this.toggleImageSizePopover();
145827
+ }); // Close popover when clicking outside
145828
+
145829
+ document.addEventListener('click', e => {
145830
+ if (!this.imageSizePopover.contains(e.target) && !this.quickImageSizeButton.contains(e.target)) {
145831
+ this.closeImageSizePopover();
145832
+ }
145833
+ }); // Close popover on Escape
145834
+
145835
+ this.imageSizePopover.addEventListener('keydown', e => {
145836
+ if (e.key === 'Escape') {
145837
+ this.closeImageSizePopover();
145838
+ this.quickImageSizeButton.focus();
145839
+ }
145840
+ });
144877
145841
  this.renderImageOptions();
145842
+
145843
+ if (this.demoConversations) {
145844
+ this.loadConversations();
145845
+ }
145846
+
145847
+ if (this.isDemoMode) {
145848
+ this.addDemoBanner(); // Disable input in demo mode
145849
+
145850
+ this.promptInput.disabled = true; // this.promptInput.placeholder = out('Demo mode - Chat is read-only');
145851
+
145852
+ this.sendButton.disabled = true;
145853
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
145854
+ }
144878
145855
  }
144879
145856
 
144880
145857
  renderImageOptions() {
@@ -144977,7 +145954,9 @@ class CodeChat {
144977
145954
  }); // Trigger initial render with defaults
144978
145955
 
144979
145956
  modelSelect.value = defaultModelId;
144980
- renderSizes(defaultModelId, false);
145957
+ renderSizes(defaultModelId, false); // NEW: Initialize quick size button
145958
+
145959
+ this.updateQuickImageSizeButton();
144981
145960
  }
144982
145961
  /**
144983
145962
  * ============================================================================
@@ -145049,7 +146028,9 @@ class CodeChat {
145049
146028
  this.settingsOverlay.classList.remove('open');
145050
146029
  this.settingsOverlay.setAttribute('aria-hidden', 'true');
145051
146030
  this.settingsDialog.classList.remove('open');
145052
- this.settingsDialog.setAttribute('aria-hidden', 'true');
146031
+ this.settingsDialog.setAttribute('aria-hidden', 'true'); // Reset cursor to default to prevent it from getting stuck
146032
+
146033
+ document.body.style.cursor = '';
145053
146034
 
145054
146035
  if (this.settingsLastFocusedElement) {
145055
146036
  this.settingsLastFocusedElement.focus();
@@ -145063,7 +146044,9 @@ class CodeChat {
145063
146044
  this.settings.chatModel = this.chatModelSelect.value;
145064
146045
  this.settings.imageModel = this.imageModelSelect.value;
145065
146046
  this.settings.imageSize = this.imageSizeSelect.value;
145066
- this.saveSettingsToStorage();
146047
+ this.saveSettingsToStorage(); // NEW: Update quick button when settings change
146048
+
146049
+ this.updateQuickImageSizeButton();
145067
146050
  this.closeSettings();
145068
146051
  }
145069
146052
  /**
@@ -145098,6 +146081,106 @@ Your job:
145098
146081
  3. Determine what each image should depict based on surrounding content and context
145099
146082
  4. Create detailed image generation prompts for each image
145100
146083
 
146084
+ 📸 EDITORIAL STYLE GUIDE - Apply to all image prompts:
146085
+
146086
+ - Default aesthetic: Minimalist magazine-style (Kinfolk, Cereal, Vogue Living)
146087
+ - Composition: Clean, lots of negative space, well-balanced
146088
+
146089
+
146090
+ CREATIVE PROMPT ENHANCEMENT:
146091
+ When constructing image prompts, intelligently enrich basic descriptions using these proven cinematic stylers:
146092
+
146093
+ 1. **Golden Hour Cinematic**
146094
+ "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."
146095
+ → Best for: Landscapes, architecture, outdoor scenes, lifestyle content
146096
+
146097
+ 2. **Morning Halo Effect**
146098
+ "The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
146099
+ → Best for: Portraits, people in action, outdoor activities
146100
+
146101
+ 3. **Natural Forest Serenity**
146102
+ "in a lush, green forest. The sunlight should be filtering through the trees, creating a serene and natural atmosphere"
146103
+ → Best for: Nature scenes, outdoor activities, wellness content
146104
+
146105
+ 4. **Coastal Summer Drama**
146106
+ "on a dramatic windswept coastline. The overall atmosphere is cheerful, breezy, and full of summer warmth"
146107
+ → Best for: Beach/coastal scenes, adventure, travel content
146108
+
146109
+ 5. **Modern Interior Elegance**
146110
+ "illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
146111
+ → Best for: Indoor portraits, product shots, professional/business settings
146112
+
146113
+ ENHANCEMENT STRATEGY:
146114
+ - Analyze the subject matter and context from the HTML/request
146115
+ - Select the most appropriate styler that matches the scene type
146116
+ - Check if user has specified lighting/time → DO NOT apply contradicting styler elements
146117
+ * Example: User says "daylight" → DO NOT add "golden-hour" or "sunset"
146118
+ - Aim for creative elevation while maintaining the core subject integrity
146119
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
146120
+ - IF user is vague (no specific lighting/mood) → Apply full styler enhancement
146121
+ - User's explicit details ALWAYS take precedence over styler recommendations
146122
+ ` +
146123
+ /*
146124
+ ENHANCEMENT STRATEGY:
146125
+ - Analyze the subject matter and context from the HTML/request
146126
+ - Select the most appropriate styler that matches the scene type
146127
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
146128
+ - If the subject already has specific lighting/atmosphere details, complement rather than override
146129
+ - Aim for creative elevation while maintaining the core subject integrity
146130
+ */
146131
+ `
146132
+ Examples of enriched prompts:
146133
+ - Basic: "a house with mountain view"
146134
+ → 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."
146135
+
146136
+ - Basic: "person hiking"
146137
+ → Enhanced: "a person hiking in nature. The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
146138
+
146139
+ - Basic: "woman reading"
146140
+ → 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"
146141
+
146142
+ - Already detailed: "dramatic headshot with bokeh and rim lighting"
146143
+ → No enhancement needed (preserve user's vision)
146144
+
146145
+
146146
+ CONTEXT-SPECIFIC GUIDELINES:
146147
+
146148
+ Interior/Indoor scenes:
146149
+ - Lighting: Soft natural window light
146150
+ - Backdrop: white walls or neutral tones
146151
+ - Mood: Calm, serene, inviting, uncluttered
146152
+ - Reference: "Kinfolk interior photography"
146153
+
146154
+ People/Portraits/Activities:
146155
+ - Lighting: Natural diffused light, flattering and soft
146156
+ - Styling: Effortlessly elegant, contemporary casual
146157
+ - Pose: Candid, authentic moments, relaxed
146158
+ - Reference: "modern lifestyle editorial photography"
146159
+
146160
+ Products/Objects:
146161
+ - Lighting: Clean studio or soft natural light
146162
+ - Backdrop: Minimalist neutral, lots of breathing room
146163
+ - Mood: Refined simplicity, premium feel
146164
+ - Reference: "high-end editorial product photography"
146165
+
146166
+ Food/Culinary:
146167
+ - Lighting: Soft daylight, gentle overhead or 45° angle
146168
+ - Styling: Minimal props, artisan ceramics, linen
146169
+ - Mood: Fresh, appetizing, rustic-modern
146170
+ - Reference: "Bon Appétit editorial food styling"
146171
+
146172
+ Outdoor/Landscape:
146173
+ - Composition: Serene, contemplative, minimal
146174
+ - Mood: Calm atmosphere, breathing space, lifted brightness
146175
+ - Colors: Vibrant cinematic shot.
146176
+ - Reference: "editorial travel photography"
146177
+
146178
+ CRITICAL RULES:
146179
+ 1. If user specifies a style (cinematic, vintage, vibrant, etc.) → RESPECT IT, only add quality markers
146180
+ 2. If user is vague → APPLY FULL EDITORIAL TREATMENT based on subject category
146181
+ 3. Always maintain cohesive aesthetic across all images
146182
+ 4. Every prompt should feel like it belongs in a high-end lifestyle magazine
146183
+
145101
146184
  Respond with a JSON array with one entry per image to generate:
145102
146185
  [
145103
146186
  {
@@ -145202,7 +146285,12 @@ Response: [
145202
146285
 
145203
146286
 
145204
146287
  async sendMessage() {
145205
- const out = s => this.out(s);
146288
+ const out = s => this.out(s); // Prevent sending messages in demo mode
146289
+
146290
+
146291
+ if (this.isDemoMode) {
146292
+ return;
146293
+ }
145206
146294
 
145207
146295
  const prompt = this.promptInput.value.trim();
145208
146296
  if (!prompt) return;
@@ -145269,16 +146357,45 @@ Response: [
145269
146357
  this.addMessage('assistant', `✓ ${result.description}`);
145270
146358
  } else if (result.type === 'image') {
145271
146359
  if (result.imageDetails && result.imageDetails.length > 1) {
145272
- this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`);
146360
+ this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`); // this.addMessage('assistant', `✓ ${out('Generated {count} images successfully').replace('{count}', result.imageDetails.length)}`);
146361
+
145273
146362
  this.addImagePreview(result.imageDetails);
145274
146363
  } else {
145275
- this.addMessage('assistant', '✓ Generated image successfully');
146364
+ this.addMessage('assistant', '✓ Image generated successfully'); // this.addMessage('assistant', `✓ ${out('Image generated successfully')}`);
145276
146365
 
145277
146366
  if (result.generatedUrls && result.generatedUrls[0]) {
145278
146367
  this.addImagePreview([{
145279
146368
  url: result.generatedUrls[0],
145280
146369
  context: result.description
145281
- }]);
146370
+ }]); // If there is a selected image, replace with the new image
146371
+
146372
+ const url = result.generatedUrls[0];
146373
+
146374
+ if (this.builder.editor) {
146375
+ // ContentBox
146376
+ const img = this.builder.editor.activeImage;
146377
+
146378
+ if (img) {
146379
+ this.builder.editor.saveForUndo();
146380
+ img.addEventListener('load', () => {
146381
+ this.builder.editor.element.image.repositionImageTool();
146382
+ this.builder.editor.elmTool.repositionElementTool();
146383
+ });
146384
+ this.builder.editor.activeImage.setAttribute('src', url);
146385
+ }
146386
+ } else {
146387
+ // ContentBuilder
146388
+ const img = this.builder.activeImage;
146389
+
146390
+ if (img) {
146391
+ this.builder.uo.saveForUndo();
146392
+ img.addEventListener('load', () => {
146393
+ this.builder.element.image.repositionImageTool();
146394
+ this.builder.elmTool.repositionElementTool();
146395
+ });
146396
+ this.builder.activeImage.setAttribute('src', url);
146397
+ }
146398
+ }
145282
146399
  }
145283
146400
  }
145284
146401
  }
@@ -145336,14 +146453,14 @@ Response: [
145336
146453
  `;
145337
146454
  images.forEach(img => {
145338
146455
  previewHTML += `
145339
- <div style="border-radius: 8px; overflow: hidden; background: rgba(255,255,255,0.05);">
145340
- <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%; height: 150px; object-fit: cover;" />
146456
+ <div style="border-radius: 6px; overflow: hidden; background: rgba(255,255,255,0.05);">
146457
+ <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%;" />
145341
146458
  <div style="padding: 8px; font-size: 11px; opacity: 0.8;">
145342
146459
  ${img.context || ''}
145343
146460
  <a href="${img.url}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 4px;">View</a>
145344
146461
  </div>
145345
146462
  </div>
145346
- `;
146463
+ `; // height: 150px; object-fit: cover;
145347
146464
  });
145348
146465
  previewHTML += '</div>';
145349
146466
  previewDiv.innerHTML = previewHTML;
@@ -145363,7 +146480,34 @@ Response: [
145363
146480
  ...this.builder.defaultHeaders
145364
146481
  }; // Check if image generation is enabled
145365
146482
 
145366
- const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
146483
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false; // Build context about previously generated images
146484
+
146485
+ let imageHistoryContext = '';
146486
+ const previousImageResults = this.conversationResults.filter(r => r.type === 'image' && r.generatedUrls && r.generatedUrls.length > 0);
146487
+
146488
+ if (previousImageResults.length > 0) {
146489
+ imageHistoryContext = '\n\n=== PREVIOUSLY GENERATED IMAGES IN THIS CONVERSATION ===\n';
146490
+ imageHistoryContext += 'The following images were generated earlier in this conversation:\n\n';
146491
+ previousImageResults.forEach((result, idx) => {
146492
+ if (result.imageDetails && result.imageDetails.length > 0) {
146493
+ result.imageDetails.forEach((img, imgIdx) => {
146494
+ imageHistoryContext += `Generated Image ${idx + 1}.${imgIdx + 1}:\n`;
146495
+ imageHistoryContext += ` URL: ${img.url}\n`;
146496
+ imageHistoryContext += ` Context: ${img.context}\n`;
146497
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
146498
+ });
146499
+ } else {
146500
+ result.generatedUrls.forEach((url, urlIdx) => {
146501
+ imageHistoryContext += `Generated Image ${idx + 1}.${urlIdx + 1}:\n`;
146502
+ imageHistoryContext += ` URL: ${url}\n`;
146503
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
146504
+ });
146505
+ }
146506
+ });
146507
+ 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';
146508
+ imageHistoryContext += '⚠️ DO NOT create a new image task. Instead, create a CODE task that uses these existing URLs.\n\n';
146509
+ }
146510
+
145367
146511
  const classificationPrompt = `Analyze this user message and break it down into tasks.
145368
146512
 
145369
146513
  CURRENT HTML CONTEXT (use this to understand the page structure):
@@ -145371,11 +146515,19 @@ CURRENT HTML CONTEXT (use this to understand the page structure):
145371
146515
  ${this.builder.html()}
145372
146516
  \`\`\`
145373
146517
 
146518
+ ${imageHistoryContext}
146519
+
145374
146520
  IMPORTANT: This is a WEB BUILDER tool with chat capabilities. Valid requests are:
145375
146521
  - Creating/editing/removing HTML content (code tasks)
145376
146522
  ${imageGenEnabled ? '- Generating images for the webpage (image tasks)' : ''}
145377
146523
  - Asking questions or having conversations (chat tasks) - THIS CAN BE ABOUT ANYTHING
145378
146524
 
146525
+ CRITICAL CONTEXT RULE:
146526
+ - In a web builder, "create an article/blog/story/content" means CREATE A WEBPAGE (CODE task)
146527
+ - Only use CHAT task for questions, explanations, or requests for advice
146528
+ - If user says "create", "write", "make", "build" followed by content → CODE task
146529
+ - If user says "explain", "how do I", "what is", "tell me about" → CHAT task
146530
+
145379
146531
  ${imageGenEnabled ? '' : `
145380
146532
  ⚠️ AI IMAGE GENERATION IS CURRENTLY DISABLED
145381
146533
  - If user requests AI image generation/creation, explain that it's disabled
@@ -145445,6 +146597,18 @@ IMAGE TASK RULES:
145445
146597
  - IMPORTANT: Look at the HTML context to understand if multiple images are involved
145446
146598
  - If the target section has multiple images, indicate this in targetElement (e.g., "all 3 images in Culinary Delights section")
145447
146599
  - Image tasks should come BEFORE code tasks that depend on them
146600
+
146601
+ - CRITICAL: DO NOT create image task for these phrases (they mean REUSE existing):
146602
+ * "use the generated image" / "use the image"
146603
+ * "use this image" / "use existing image"
146604
+ * "with the generated image" / "with this image"
146605
+ * "with the previous image" / "with the last image"
146606
+ * "using the image I just created/generated"
146607
+ * Any reference to a PREVIOUS image from conversation history
146608
+ - Decision logic:
146609
+ * "Generate a new image of X" → CREATE image task (new generation)
146610
+ * "Create article using the generated image" → NO image task (reuse existing)
146611
+
145448
146612
  ` : ''}
145449
146613
 
145450
146614
  Examples:
@@ -145482,6 +146646,17 @@ Input: "Create a landing page about wood furniture workshop"
145482
146646
  Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create a landing page for a wood furniture workshop", "order": 1}], "is_mixed": false}
145483
146647
  Note: No image task created because user did not explicitly request AI image generation
145484
146648
 
146649
+ Input: "Create an inspiring article and use the generated image"
146650
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create article using previously generated image from conversation history", "order": 1}], "is_mixed": false}
146651
+ Note: No image task because user wants to REUSE existing image
146652
+
146653
+ Input: "Write a blog post about productivity and use this image"
146654
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create blog post using previously generated image", "order": 1}], "is_mixed": false}
146655
+
146656
+ Input: "Generate another mountain image and create a landing page"
146657
+ 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}
146658
+ Note: "another" and "generate" indicate NEW image generation
146659
+
145485
146660
  ` : `
145486
146661
  Input: "Generate an AI image of a mountain"
145487
146662
  Output: {"is_valid": false, "reason": "AI image generation is currently disabled.", "tasks": [], "is_mixed": false}
@@ -145564,8 +146739,9 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
145564
146739
 
145565
146740
  if (imageTasksFromThisRequest.length > 0) {
145566
146741
  hasGeneratedImages = true;
145567
- imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n';
145568
- imageTasksFromThisRequest.forEach(imageTask => {
146742
+ imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n'; // ⭐ REVERSE to show most recent first
146743
+
146744
+ imageTasksFromThisRequest.reverse().forEach(imageTask => {
145569
146745
  if (imageTask.imageDetails && imageTask.imageDetails.length > 0) {
145570
146746
  // Multiple images with context
145571
146747
  imageTask.imageDetails.forEach((img, idx) => {
@@ -145580,7 +146756,8 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
145580
146756
  });
145581
146757
  }
145582
146758
  });
145583
- imageContext += '\n⚠️ IMPORTANT: Use these generated image URLs (not placehold.co or other sources) for the specific images mentioned in your task.\n';
146759
+ imageContext += '\n⚠️ IMPORTANT: Use the FIRST image URL listed above (most recent) unless the task specifically asks for a different image.\n';
146760
+ imageContext += '⚠️ The first image is the MOST RECENTLY GENERATED and should be used by default.\n';
145584
146761
  imageContext += '⚠️ Your task description specifies WHICH images to update - follow it precisely.\n';
145585
146762
  } // ⭐ ALSO CHECK FOR CHAT RESULTS FROM PREVIOUS TASKS
145586
146763
 
@@ -146163,6 +147340,320 @@ ${this.builder.html()}
146163
147340
  document.body.removeChild(announcement);
146164
147341
  }, 1000);
146165
147342
  }
147343
+ /**
147344
+ * ============================================================================
147345
+ * DEMO MODE
147346
+ * ============================================================================
147347
+ */
147348
+
147349
+
147350
+ loadConversations() {
147351
+ // Load demo conversations
147352
+ if (this.demoConversations && this.demoConversations.length > 0) {
147353
+ this.demoConversations.forEach(msg => {
147354
+ if (msg.role === 'user') {
147355
+ this.addMessage('user', msg.content);
147356
+ } else if (msg.role === 'assistant') {
147357
+ this.addMessage('assistant', msg.content, true); // Add image preview if available
147358
+
147359
+ if (msg.imagePreview && msg.imagePreview.length > 0) {
147360
+ this.addImagePreview(msg.imagePreview); // Include in conversationResults with correct structure
147361
+
147362
+ this.conversationResults.push({
147363
+ type: 'image',
147364
+ description: 'Previously generated image from demo',
147365
+ content: `Generated ${msg.imagePreview.length} images`,
147366
+ generatedUrls: msg.imagePreview.map(img => img.url),
147367
+ // Array of URLs
147368
+ imageDetails: msg.imagePreview.map(img => ({
147369
+ // Array of objects
147370
+ url: img.url,
147371
+ context: img.context,
147372
+ prompt: img.context || '' // Use context as prompt if no prompt available
147373
+
147374
+ })),
147375
+ targetElement: 'demo images'
147376
+ });
147377
+ }
147378
+ }
147379
+ });
147380
+ }
147381
+ }
147382
+
147383
+ addDemoBanner() {
147384
+ const out = s => this.out(s);
147385
+
147386
+ const banner = document.createElement('div');
147387
+ banner.className = 'demo-banner';
147388
+ banner.innerHTML = `
147389
+ <div style="
147390
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147391
+ color: white;
147392
+ padding: 12px 16px;
147393
+ border-radius: 8px;
147394
+ font-size: 13px;
147395
+ display: flex;
147396
+ align-items: center;
147397
+ gap: 8px;
147398
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
147399
+ ">
147400
+ <span style="font-size: 18px;">📖</span>
147401
+ <div>
147402
+ <strong>${out('Read-Only Demo')}</strong>
147403
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">
147404
+ ${out('Full AI chat available in the complete version.')}
147405
+ </div>
147406
+ </div>
147407
+ </div>
147408
+ `; // Insert banner at the top of messages container
147409
+ // this.messagesContainer.insertBefore(banner, this.messagesContainer.firstChild);
147410
+
147411
+ this.messagesContainer.appendChild(banner);
147412
+ }
147413
+ /**
147414
+ * Update the quick image size button based on current model and settings
147415
+ */
147416
+
147417
+
147418
+ updateQuickImageSizeButton() {
147419
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
147420
+
147421
+ if (!imageGenEnabled) {
147422
+ this.quickImageSizeButton.style.display = 'none';
147423
+ return;
147424
+ }
147425
+
147426
+ const currentModelId = this.settings.imageModel;
147427
+ const availableSizes = this.getSizesForModel(currentModelId); // Hide button if model has no size options
147428
+
147429
+ if (!availableSizes || availableSizes.length === 0) {
147430
+ this.quickImageSizeButton.style.display = 'none';
147431
+ return;
147432
+ } // Show button
147433
+
147434
+
147435
+ this.quickImageSizeButton.style.display = 'flex'; // Smart selection: check if current size is available
147436
+
147437
+ let selectedSize = this.settings.imageSize;
147438
+
147439
+ if (!availableSizes.includes(selectedSize)) {
147440
+ // Current size not available, find best fallback
147441
+ selectedSize = this.findBestFallback(selectedSize, availableSizes);
147442
+ this.settings.imageSize = selectedSize; // Sync with settings dialog
147443
+
147444
+ this.imageSizeSelect.value = selectedSize; // Save to storage
147445
+
147446
+ this.saveSettingsToStorage();
147447
+ } // Update button label
147448
+
147449
+
147450
+ this.updateButtonLabel(selectedSize); // Populate popover options
147451
+
147452
+ this.populateSizePopover(availableSizes, selectedSize);
147453
+ }
147454
+ /**
147455
+ * Get available sizes for a model (reuse existing logic)
147456
+ */
147457
+
147458
+
147459
+ getSizesForModel(modelId) {
147460
+ const model = this.imageModels.find(m => m.id === modelId);
147461
+ if (!model) return null; // if sizes explicitly empty array → means no size options
147462
+
147463
+ if (Array.isArray(model.sizes) && model.sizes.length === 0) {
147464
+ return null;
147465
+ }
147466
+
147467
+ const defaultSizes = ['square', 'square_hd', 'landscape_4_3', 'landscape_16_9', 'portrait_4_3', 'portrait_16_9'];
147468
+ return model.sizes || defaultSizes;
147469
+ }
147470
+ /**
147471
+ * Find best fallback size when current selection is not available
147472
+ */
147473
+
147474
+
147475
+ findBestFallback(preferredSize, availableSizes) {
147476
+ // Strategy 1: Try to match orientation
147477
+ const orientation = this.getOrientation(preferredSize);
147478
+ const matchingOrientation = availableSizes.find(size => this.getOrientation(size) === orientation);
147479
+ if (matchingOrientation) return matchingOrientation; // Strategy 2: Common defaults
147480
+
147481
+ if (availableSizes.includes('16:9')) return '16:9';
147482
+ if (availableSizes.includes('landscape_16_9')) return 'landscape_16_9';
147483
+ if (availableSizes.includes('1:1')) return '1:1';
147484
+ if (availableSizes.includes('square')) return 'square'; // Strategy 3: First available
147485
+
147486
+ return availableSizes[0];
147487
+ }
147488
+ /**
147489
+ * Determine orientation from size value
147490
+ */
147491
+
147492
+
147493
+ getOrientation(size) {
147494
+ const landscape = ['16:9', '21:9', '4:3', '3:2', '5:4', 'landscape_16_9', 'landscape_4_3'];
147495
+ const portrait = ['9:16', '9:21', '3:4', '2:3', '4:5', 'portrait_16_9', 'portrait_4_3'];
147496
+ if (landscape.includes(size)) return 'landscape';
147497
+ if (portrait.includes(size)) return 'portrait';
147498
+ return 'square';
147499
+ }
147500
+ /**
147501
+ * Update button label to show current size
147502
+ */
147503
+
147504
+
147505
+ updateButtonLabel(sizeValue) {
147506
+ if (!sizeValue) {
147507
+ this.quickImageSizeButton.textContent = '□';
147508
+ this.quickImageSizeButton.setAttribute('aria-label', this.out('Select image size'));
147509
+ return;
147510
+ } // Convert verbose labels to short form
147511
+
147512
+
147513
+ const labelMap = {
147514
+ 'landscape_16_9': '16:9',
147515
+ 'landscape_4_3': '4:3',
147516
+ 'portrait_16_9': '9:16',
147517
+ 'portrait_4_3': '3:4',
147518
+ 'square': '1:1',
147519
+ 'square_hd': '1:1 HD'
147520
+ };
147521
+ const displayLabel = labelMap[sizeValue] || sizeValue;
147522
+ this.quickImageSizeButton.textContent = displayLabel;
147523
+ this.quickImageSizeButton.setAttribute('aria-label', `${this.out('Image size')}: ${displayLabel}`);
147524
+ }
147525
+ /**
147526
+ * Populate the size popover with available options
147527
+ */
147528
+
147529
+
147530
+ populateSizePopover(sizes, selectedSize) {
147531
+ const out = s => this.out(s);
147532
+
147533
+ const sizeDefs = {
147534
+ 'square_hd': out('Square HD'),
147535
+ 'square': out('Square'),
147536
+ 'landscape_4_3': out('Landscape 4x3'),
147537
+ 'landscape_16_9': out('Landscape 16x9'),
147538
+ 'portrait_4_3': out('Portrait 3x4'),
147539
+ 'portrait_16_9': out('Portrait 9x16'),
147540
+ '1:1': out('Square'),
147541
+ '3:2': out('Landscape 3x2'),
147542
+ '4:3': out('Landscape 4x3'),
147543
+ '5:4': out('Landscape 5x4'),
147544
+ '16:9': out('Landscape 16x9'),
147545
+ '21:9': out('Landscape 21:9'),
147546
+ '2:3': out('Portrait 2x3'),
147547
+ '3:4': out('Portrait 3x4'),
147548
+ '4:5': out('Portrait 4x5'),
147549
+ '9:16': out('Portrait 9x16'),
147550
+ '9:21': out('Portrait 9:21')
147551
+ };
147552
+ this.sizeOptionsContainer.innerHTML = '';
147553
+ sizes.forEach(size => {
147554
+ const option = document.createElement('div');
147555
+ option.className = 'size-option' + (size === selectedSize ? ' selected' : '');
147556
+ option.setAttribute('role', 'menuitem');
147557
+ option.setAttribute('tabindex', '0');
147558
+ const radio = document.createElement('input');
147559
+ radio.type = 'radio';
147560
+ radio.name = 'quickImageSize';
147561
+ radio.value = size;
147562
+ radio.checked = size === selectedSize;
147563
+ radio.id = `quick-size-${size}`;
147564
+ const label = document.createElement('label');
147565
+ label.htmlFor = `quick-size-${size}`;
147566
+ label.textContent = sizeDefs[size] || size; // label.style.cursor = 'pointer';
147567
+
147568
+ label.style.flex = '1';
147569
+ option.appendChild(radio);
147570
+ option.appendChild(label); // Click handler
147571
+
147572
+ option.addEventListener('click', () => {
147573
+ this.selectImageSize(size);
147574
+ }); // Keyboard handler
147575
+
147576
+ option.addEventListener('keydown', e => {
147577
+ if (e.key === 'Enter' || e.key === ' ') {
147578
+ e.preventDefault();
147579
+ this.selectImageSize(size);
147580
+ }
147581
+ });
147582
+ this.sizeOptionsContainer.appendChild(option);
147583
+ });
147584
+ }
147585
+ /**
147586
+ * Handle size selection
147587
+ */
147588
+
147589
+
147590
+ selectImageSize(size) {
147591
+ // Update settings
147592
+ this.settings.imageSize = size; // Sync with settings dialog
147593
+
147594
+ this.imageSizeSelect.value = size; // Update button label
147595
+
147596
+ this.updateButtonLabel(size); // Update popover selection
147597
+
147598
+ this.sizeOptionsContainer.querySelectorAll('.size-option').forEach(opt => {
147599
+ opt.classList.remove('selected');
147600
+ opt.querySelector('input[type="radio"]').checked = false;
147601
+ }); // const selectedOption = this.sizeOptionsContainer.querySelector(`#quick-size-${size}`);
147602
+
147603
+ const selectedOption = Array.from(this.sizeOptionsContainer.querySelectorAll('input[type="radio"]')).find(radio => radio.value === size);
147604
+
147605
+ if (selectedOption) {
147606
+ selectedOption.closest('.size-option').classList.add('selected');
147607
+ selectedOption.checked = true;
147608
+ } // Save to storage
147609
+
147610
+
147611
+ this.saveSettingsToStorage(); // Close popover
147612
+
147613
+ this.closeImageSizePopover(); // Focus back to input
147614
+
147615
+ this.promptInput.focus();
147616
+ }
147617
+ /**
147618
+ * Toggle popover visibility
147619
+ */
147620
+
147621
+
147622
+ toggleImageSizePopover() {
147623
+ const isVisible = this.imageSizePopover.style.display !== 'none';
147624
+
147625
+ if (isVisible) {
147626
+ this.closeImageSizePopover();
147627
+ } else {
147628
+ this.openImageSizePopover();
147629
+ }
147630
+ }
147631
+ /**
147632
+ * Open the size popover
147633
+ */
147634
+
147635
+
147636
+ openImageSizePopover() {
147637
+ this.imageSizePopover.style.display = 'block';
147638
+ this.imageSizePopover.setAttribute('aria-hidden', 'false');
147639
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'true'); // Focus first option
147640
+
147641
+ const firstOption = this.sizeOptionsContainer.querySelector('.size-option');
147642
+
147643
+ if (firstOption) {
147644
+ firstOption.focus();
147645
+ }
147646
+ }
147647
+ /**
147648
+ * Close the size popover
147649
+ */
147650
+
147651
+
147652
+ closeImageSizePopover() {
147653
+ this.imageSizePopover.style.display = 'none';
147654
+ this.imageSizePopover.setAttribute('aria-hidden', 'true');
147655
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'false');
147656
+ }
146166
147657
 
146167
147658
  out(s) {
146168
147659
  let result = this.builder.lang[s];
@@ -146590,7 +148081,7 @@ Classes: transition-none, transition, transition-colors, transition-opacity, tra
146590
148081
 
146591
148082
  **Duration**
146592
148083
 
146593
- Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000
148084
+ Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000, duration-1500
146594
148085
 
146595
148086
  **Timing**
146596
148087
 
@@ -146604,6 +148095,10 @@ Classes: delay-75, delay-100, delay-150, delay-200, delay-300, delay-500
146604
148095
 
146605
148096
  Classes: scale-0, scale-50, scale-75, scale-90, scale-95, scale-100, scale-105, scale-110, scale-125, scale-150
146606
148097
 
148098
+ **Hover Effect**
148099
+
148100
+ Classes: hover:scale-105
148101
+
146607
148102
  **Rotate**
146608
148103
 
146609
148104
  Classes: rotate-0, rotate-45, rotate-90, rotate-180
@@ -146937,6 +148432,31 @@ When creating icon-based features, benefits, or service sections: use Bootstrap
146937
148432
  <h4 class="size-18 font-medium text-center pb-3">Lightning Fast</h4>
146938
148433
  <p class="size-14 leading-16 text-gray-600 text-center">Description...</p>
146939
148434
 
148435
+ ### 9. Image Layout with Hover Effect
148436
+
148437
+ Use this pattern for all images (galleries, featured images, portfolio items):
148438
+
148439
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
148440
+ <img src="..." alt="..." class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
148441
+ </div>
148442
+
148443
+ **Key elements:**
148444
+ - 'aspect-ratio:16/9' - maintains proportions (adjust as needed: 1/1, 4/3, 3/2, 16/9, etc.)
148445
+ - 'overflow-hidden' - keeps scaled image contained
148446
+ - 'bg-gray-50' - placeholder while image loads
148447
+ - 'transition-transform duration-1000' - smooth 1-second animation
148448
+ - 'hover:scale-105' - subtle 5% scale on hover
148449
+ - 'object-cover' - ensures image fills container
148450
+
148451
+ **Common aspect ratios:**
148452
+ - Square: 'aspect-ratio:1/1'
148453
+ - Portrait: 'aspect-ratio:3/4' or 'aspect-ratio:2/3'
148454
+ - Landscape: 'aspect-ratio:4/3' or 'aspect-ratio:3/2'
148455
+ - Wide: 'aspect-ratio:16/9' or 'aspect-ratio:21/9'
148456
+ - Tall portrait: 'aspect-ratio:9/16'
148457
+
148458
+ Always include this hover effect for a polished, interactive feel.
148459
+
146940
148460
  `;
146941
148461
 
146942
148462
  const contextBoxFramework = `
@@ -147710,6 +149230,17 @@ The frameworks provide utility classes for structure and typography. To create r
147710
149230
 
147711
149231
  <div class="is-container leading-14 size-18 is-content-1100">
147712
149232
 
149233
+ <div class="row">
149234
+ <div class="column">
149235
+
149236
+ <!-- GUIDANCE: Use this layout to show images at any aspect ratio with a hover scale effect -->
149237
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
149238
+ <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">
149239
+ </div>
149240
+
149241
+ </div>
149242
+ </div>
149243
+
147713
149244
  <div class="row">
147714
149245
  <div class="column">
147715
149246
 
@@ -154480,6 +156011,33 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
154480
156011
  */
154481
156012
 
154482
156013
  },
156014
+ demoMode: false,
156015
+
156016
+ /*
156017
+ demoConversations: [
156018
+ {
156019
+ role: 'user',
156020
+ content: 'Generate an image: "a house with beautiful mountain view. warm sunlight, golden-hour lighting, filmic teal-orange color grade".',
156021
+ // timestamp: Date.now() - 180000 // 3 minutes ago
156022
+ },
156023
+ {
156024
+ role: 'assistant',
156025
+ content: '✓ Image generated successfully',
156026
+ imagePreview: [
156027
+ { url: 'uploads/ai-8vnqv.png', context: 'Generate image of a house with a beautiful mountain view' },
156028
+ ]
156029
+ },
156030
+ {
156031
+ role: 'user',
156032
+ 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.
156033
+ Use the generated image for this article, and present it as a premium design magazine feature.`,
156034
+ },
156035
+ {
156036
+ role: 'assistant',
156037
+ 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.',
156038
+ }
156039
+ ],
156040
+ */
154483
156041
 
154484
156042
  /* Old Version (backward compatible) */
154485
156043
  onAddSectionOpen: function () {},