@innovastudio/contentbox 1.6.176 → 1.6.177

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