image: Refac B/F. #2
Browse files- assets/plugins/imageGenerator.js +229 -169
    	
        assets/plugins/imageGenerator.js
    CHANGED
    
    | @@ -22,8 +22,7 @@ | |
| 22 | 
             
                isConnecting = true;
         | 
| 23 | 
             
                var protocol = window.location.protocol === 'https:' 
         | 
| 24 | 
             
                  ? 'wss:' : 'ws:';
         | 
| 25 | 
            -
                var wsUrl = protocol + '//' + 
         | 
| 26 | 
            -
                  window.location.host;
         | 
| 27 |  | 
| 28 | 
             
                ws = new WebSocket(wsUrl);
         | 
| 29 |  | 
| @@ -76,36 +75,35 @@ | |
| 76 | 
             
              var handleWebSocketMessage = function(data) {
         | 
| 77 | 
             
                if (!data || !data.type) return;
         | 
| 78 |  | 
| 79 | 
            -
                 | 
| 80 | 
            -
                   | 
| 81 | 
            -
                    updateProgressUI(data.progress);
         | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                   | 
| 85 | 
            -
                     | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
                    break;
         | 
| 99 | 
            -
                    
         | 
| 100 | 
            -
                  case 'imageDeleted':
         | 
| 101 | 
            -
                    handleImageDeleted(data.images);
         | 
| 102 | 
            -
                    break;
         | 
| 103 | 
             
                }
         | 
| 104 | 
             
              };
         | 
| 105 |  | 
| 106 | 
             
              var updateProgressUI = function(progress) {
         | 
| 107 | 
            -
                var progressFill = document.querySelector( | 
| 108 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
| 109 |  | 
| 110 | 
             
                if (progressFill) {
         | 
| 111 | 
             
                  progressFill.style.width = progress + '%';
         | 
| @@ -117,46 +115,51 @@ | |
| 117 | 
             
                }
         | 
| 118 | 
             
              };
         | 
| 119 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 120 | 
             
              var showGeneratingUI = function() {
         | 
| 121 | 
             
                var outputSection = document.querySelector(
         | 
| 122 | 
             
                  '.image-output-section'
         | 
| 123 | 
             
                );
         | 
| 124 | 
            -
                var form = document.getElementById('generateForm');
         | 
| 125 | 
            -
                var inputs = form ? 
         | 
| 126 | 
            -
                  form.querySelectorAll('input, select, textarea') : [];
         | 
| 127 |  | 
| 128 | 
            -
                 | 
| 129 | 
            -
                  input.disabled = true;
         | 
| 130 | 
            -
                });
         | 
| 131 |  | 
| 132 | 
             
                if (outputSection) {
         | 
| 133 | 
             
                  outputSection.classList.remove('has-images');
         | 
| 134 | 
            -
                  outputSection.innerHTML = 
         | 
| 135 | 
            -
                    '<div class="loading-container">' | 
| 136 | 
            -
                    '<div class="loading-spinner" ' | 
| 137 | 
            -
                    'style="margin: 0 auto 20px;"></div>' | 
| 138 | 
            -
                    '<p class="loading-text"> | 
| 139 | 
            -
                    ' | 
| 140 | 
            -
                    '<div class="progress- | 
| 141 | 
            -
                    ' | 
| 142 | 
            -
                    ' | 
| 143 | 
            -
                    '</div>' | 
|  | |
|  | |
|  | |
| 144 | 
             
                }
         | 
| 145 |  | 
| 146 | 
             
                updateButtonsForGeneration(true);
         | 
| 147 | 
             
              };
         | 
| 148 |  | 
| 149 | 
             
              var hideGeneratingUI = function() {
         | 
| 150 | 
            -
                 | 
| 151 | 
            -
                var inputs = form ? 
         | 
| 152 | 
            -
                  form.querySelectorAll('input, select, textarea') : [];
         | 
| 153 | 
            -
                
         | 
| 154 | 
            -
                Array.prototype.forEach.call(inputs, function(input) {
         | 
| 155 | 
            -
                  input.disabled = false;
         | 
| 156 | 
            -
                });
         | 
| 157 | 
            -
                
         | 
| 158 | 
             
                updateButtonsForGeneration(false);
         | 
| 159 | 
            -
                 | 
|  | |
|  | |
|  | |
| 160 | 
             
              };
         | 
| 161 |  | 
| 162 | 
             
              var resetToInitialState = function() {
         | 
| @@ -177,20 +180,59 @@ | |
| 177 | 
             
                if (!outputSection) return;
         | 
| 178 |  | 
| 179 | 
             
                outputSection.classList.remove('has-images');
         | 
| 180 | 
            -
                outputSection.innerHTML = 
         | 
| 181 | 
            -
                  '<svg class="placeholder-icon"  | 
| 182 | 
            -
                  ' | 
| 183 | 
            -
                  ' | 
| 184 | 
            -
                  ' | 
| 185 | 
            -
                  ' | 
| 186 | 
            -
                  ' | 
| 187 | 
            -
                  ' | 
| 188 | 
            -
                  'stroke | 
| 189 | 
            -
                  ' | 
| 190 | 
            -
                  ' | 
| 191 | 
            -
                  ' | 
| 192 | 
            -
                  ' | 
| 193 | 
            -
                  ' | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 194 | 
             
              };
         | 
| 195 |  | 
| 196 | 
             
              var updateButtonsForGeneration = function(isGenerating) {
         | 
| @@ -200,29 +242,10 @@ | |
| 200 |  | 
| 201 | 
             
                if (!buttonsContainer) return;
         | 
| 202 |  | 
| 203 | 
            -
                 | 
| 204 | 
            -
                   | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                    '<svg class="button-icon" viewBox="0 0 24 24" ' +
         | 
| 208 | 
            -
                    'fill="none">' +
         | 
| 209 | 
            -
                    '<rect x="4" y="4" width="16" height="16" rx="3" ' +
         | 
| 210 | 
            -
                    'fill="currentColor"/>' +
         | 
| 211 | 
            -
                    '</svg>' +
         | 
| 212 | 
            -
                    'Stop Generation' +
         | 
| 213 | 
            -
                    '</button>';
         | 
| 214 | 
            -
                } else {
         | 
| 215 | 
            -
                  buttonsContainer.innerHTML = 
         | 
| 216 | 
            -
                    '<button type="submit" id="submitBtn" disabled ' +
         | 
| 217 | 
            -
                    'class="btn btn-primary">' +
         | 
| 218 | 
            -
                    '<svg class="button-icon" viewBox="0 0 24 24" ' +
         | 
| 219 | 
            -
                    'fill="none">' +
         | 
| 220 | 
            -
                    '<path d="M3 20V4L22 12L3 20ZM5 17L16.85 12L5 7V10.5' +
         | 
| 221 | 
            -
                    'L11 12L5 13.5V17Z" fill="currentColor"/>' +
         | 
| 222 | 
            -
                    '</svg>' +
         | 
| 223 | 
            -
                    'Generate Image' +
         | 
| 224 | 
            -
                    '</button>';
         | 
| 225 | 
            -
                }
         | 
| 226 | 
             
              };
         | 
| 227 |  | 
| 228 | 
             
              var handleGenerationComplete = function(images) {
         | 
| @@ -245,6 +268,66 @@ | |
| 245 | 
             
                displayImages(currentImages);
         | 
| 246 | 
             
              };
         | 
| 247 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 248 | 
             
              var displayImages = function(images) {
         | 
| 249 | 
             
                var outputSection = document.querySelector(
         | 
| 250 | 
             
                  '.image-output-section'
         | 
| @@ -256,73 +339,38 @@ | |
| 256 | 
             
                  showPlaceholder();
         | 
| 257 | 
             
                } else {
         | 
| 258 | 
             
                  outputSection.classList.add('has-images');
         | 
| 259 | 
            -
                  var html = '<div class="image-grid">';
         | 
| 260 |  | 
| 261 | 
             
                  images.forEach(function(image, index) {
         | 
| 262 | 
            -
                    html  | 
| 263 | 
            -
                      '<div class="image-card">' +
         | 
| 264 | 
            -
                      '<img src="data:image/png;base64,' + image.base64 + 
         | 
| 265 | 
            -
                      '" alt="' + image.prompt + '">' +
         | 
| 266 | 
            -
                      '<div class="image-actions">' +
         | 
| 267 | 
            -
                      '<a href="data:image/png;base64,' + image.base64 + 
         | 
| 268 | 
            -
                      '" download="generated-' + image.id + '.png" ' +
         | 
| 269 | 
            -
                      'class="action-btn">' +
         | 
| 270 | 
            -
                      '<svg class="action-icon" viewBox="0 0 24 24" ' +
         | 
| 271 | 
            -
                      'fill="none">' +
         | 
| 272 | 
            -
                      '<path d="M12 16L7 11L8.4 9.55L11 12.15V4H13V12.15' +
         | 
| 273 | 
            -
                      'L15.6 9.55L17 11L12 16Z" fill="currentColor"/>' +
         | 
| 274 | 
            -
                      '<path d="M4 20C3.45 20 2.98 19.8 2.59 19.41C2.2 ' +
         | 
| 275 | 
            -
                      '19.02 2 18.55 2 18V15H4V18H20V15H22V18C22 18.55 ' +
         | 
| 276 | 
            -
                      '21.8 19.02 21.41 19.41C21.02 19.8 20.55 20 20 20H4Z" ' +
         | 
| 277 | 
            -
                      'fill="currentColor"/>' +
         | 
| 278 | 
            -
                      '</svg>' +
         | 
| 279 | 
            -
                      '</a>' +
         | 
| 280 | 
            -
                      '<button type="button" onclick="deleteImage(' + 
         | 
| 281 | 
            -
                      index + ')" class="action-btn">' +
         | 
| 282 | 
            -
                      '<svg class="action-icon" viewBox="0 0 24 24" ' +
         | 
| 283 | 
            -
                      'fill="none">' +
         | 
| 284 | 
            -
                      '<path d="M18.3 5.71C17.91 5.32 17.28 5.32 16.89 ' +
         | 
| 285 | 
            -
                      '5.71L12 10.59L7.11 5.7C6.72 5.31 6.09 5.31 5.7 ' +
         | 
| 286 | 
            -
                      '5.7C5.31 6.09 5.31 6.72 5.7 7.11L10.59 12L5.7 ' +
         | 
| 287 | 
            -
                      '16.89C5.31 17.28 5.31 17.91 5.7 18.3C6.09 18.69 ' +
         | 
| 288 | 
            -
                      '6.72 18.69 7.11 18.3L12 13.41L16.89 18.3C17.28 ' +
         | 
| 289 | 
            -
                      '18.69 17.91 18.69 18.3 18.3C18.69 17.91 18.69 ' +
         | 
| 290 | 
            -
                      '17.28 18.3 16.89L13.41 12L18.3 7.11C18.68 6.73 ' +
         | 
| 291 | 
            -
                      '18.68 6.09 18.3 5.71Z" fill="currentColor"/>' +
         | 
| 292 | 
            -
                      '</svg>' +
         | 
| 293 | 
            -
                      '</button>' +
         | 
| 294 | 
            -
                      '</div>' +
         | 
| 295 | 
            -
                      '<div class="image-info">' +
         | 
| 296 | 
            -
                      '<p class="image-prompt">' + image.prompt + '</p>' +
         | 
| 297 | 
            -
                      '<p class="image-meta">' +
         | 
| 298 | 
            -
                      '<span class="image-model">' + 
         | 
| 299 | 
            -
                      image.model.toUpperCase() + '</span> | ' + 
         | 
| 300 | 
            -
                      image.size + '</p>' +
         | 
| 301 | 
            -
                      '</div>' +
         | 
| 302 | 
            -
                      '</div>';
         | 
| 303 | 
             
                  });
         | 
| 304 |  | 
| 305 | 
            -
                  html | 
| 306 | 
            -
                  outputSection.innerHTML = html;
         | 
| 307 | 
             
                }
         | 
| 308 | 
             
              };
         | 
| 309 |  | 
| 310 | 
             
              var showErrorModal = function(error) {
         | 
| 311 | 
            -
                var existingModal = document.getElementById( | 
|  | |
|  | |
|  | |
| 312 | 
             
                if (existingModal) existingModal.remove();
         | 
| 313 |  | 
| 314 | 
             
                var modal = document.createElement('div');
         | 
| 315 | 
             
                modal.id = 'errorModal';
         | 
| 316 | 
             
                modal.className = 'modal-overlay';
         | 
| 317 | 
            -
                modal.innerHTML = 
         | 
| 318 | 
            -
                  '<div class="modal-content  | 
| 319 | 
            -
                  ' | 
| 320 | 
            -
                  '< | 
| 321 | 
            -
                  '< | 
| 322 | 
            -
                  '< | 
| 323 | 
            -
                  ' | 
| 324 | 
            -
                  '</ | 
| 325 | 
            -
                  '</div>' | 
|  | |
|  | |
| 326 |  | 
| 327 | 
             
                document.body.appendChild(modal);
         | 
| 328 | 
             
              };
         | 
| @@ -338,9 +386,8 @@ | |
| 338 | 
             
                xhr.onload = function() {
         | 
| 339 | 
             
                  try {
         | 
| 340 | 
             
                    var response = JSON.parse(xhr.responseText);
         | 
| 341 | 
            -
                    if (response.success) {
         | 
| 342 | 
            -
                       | 
| 343 | 
            -
                      displayImages(currentImages);
         | 
| 344 | 
             
                    }
         | 
| 345 | 
             
                  } catch (e) {}
         | 
| 346 | 
             
                };
         | 
| @@ -357,30 +404,43 @@ | |
| 357 | 
             
                  '.image-output-section'
         | 
| 358 | 
             
                );
         | 
| 359 |  | 
| 360 | 
            -
                if (outputSection  | 
| 361 | 
            -
             | 
| 362 | 
            -
                   | 
| 363 | 
            -
             | 
| 364 | 
            -
             | 
| 365 | 
            -
             | 
| 366 | 
            -
             | 
| 367 | 
            -
             | 
| 368 | 
            -
             | 
| 369 | 
            -
             | 
| 370 | 
            -
             | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 374 | 
            -
             | 
| 375 | 
            -
             | 
| 376 | 
            -
             | 
| 377 | 
            -
             | 
| 378 | 
            -
             | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 381 | 
             
                    });
         | 
| 382 | 
             
                  }
         | 
| 383 | 
            -
                }
         | 
| 384 | 
             
              };
         | 
| 385 |  | 
| 386 | 
             
              connectWebSocket();
         | 
|  | |
| 22 | 
             
                isConnecting = true;
         | 
| 23 | 
             
                var protocol = window.location.protocol === 'https:' 
         | 
| 24 | 
             
                  ? 'wss:' : 'ws:';
         | 
| 25 | 
            +
                var wsUrl = protocol + '//' + window.location.host;
         | 
|  | |
| 26 |  | 
| 27 | 
             
                ws = new WebSocket(wsUrl);
         | 
| 28 |  | 
|  | |
| 75 | 
             
              var handleWebSocketMessage = function(data) {
         | 
| 76 | 
             
                if (!data || !data.type) return;
         | 
| 77 |  | 
| 78 | 
            +
                var handlers = {
         | 
| 79 | 
            +
                  'progressUpdate': function() { 
         | 
| 80 | 
            +
                    updateProgressUI(data.progress); 
         | 
| 81 | 
            +
                  },
         | 
| 82 | 
            +
                  'generationStarted': showGeneratingUI,
         | 
| 83 | 
            +
                  'generationComplete': function() { 
         | 
| 84 | 
            +
                    handleGenerationComplete(data.images); 
         | 
| 85 | 
            +
                  },
         | 
| 86 | 
            +
                  'generationError': function() { 
         | 
| 87 | 
            +
                    handleGenerationError(data.error); 
         | 
| 88 | 
            +
                  },
         | 
| 89 | 
            +
                  'generationCancelled': handleGenerationCancelled,
         | 
| 90 | 
            +
                  'imageDeleted': function() { 
         | 
| 91 | 
            +
                    handleImageDeleted(data.images); 
         | 
| 92 | 
            +
                  }
         | 
| 93 | 
            +
                };
         | 
| 94 | 
            +
                
         | 
| 95 | 
            +
                if (handlers[data.type]) {
         | 
| 96 | 
            +
                  handlers[data.type]();
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 97 | 
             
                }
         | 
| 98 | 
             
              };
         | 
| 99 |  | 
| 100 | 
             
              var updateProgressUI = function(progress) {
         | 
| 101 | 
            +
                var progressFill = document.querySelector(
         | 
| 102 | 
            +
                  '.progress-fill'
         | 
| 103 | 
            +
                );
         | 
| 104 | 
            +
                var progressText = document.querySelector(
         | 
| 105 | 
            +
                  '.progress-text'
         | 
| 106 | 
            +
                );
         | 
| 107 |  | 
| 108 | 
             
                if (progressFill) {
         | 
| 109 | 
             
                  progressFill.style.width = progress + '%';
         | 
|  | |
| 115 | 
             
                }
         | 
| 116 | 
             
              };
         | 
| 117 |  | 
| 118 | 
            +
              var toggleFormInputs = function(disabled) {
         | 
| 119 | 
            +
                var form = document.getElementById('generateForm');
         | 
| 120 | 
            +
                var inputs = form ? form.querySelectorAll(
         | 
| 121 | 
            +
                  'input, select, textarea'
         | 
| 122 | 
            +
                ) : [];
         | 
| 123 | 
            +
                
         | 
| 124 | 
            +
                Array.prototype.forEach.call(inputs, function(input) {
         | 
| 125 | 
            +
                  input.disabled = disabled;
         | 
| 126 | 
            +
                });
         | 
| 127 | 
            +
              };
         | 
| 128 | 
            +
              
         | 
| 129 | 
             
              var showGeneratingUI = function() {
         | 
| 130 | 
             
                var outputSection = document.querySelector(
         | 
| 131 | 
             
                  '.image-output-section'
         | 
| 132 | 
             
                );
         | 
|  | |
|  | |
|  | |
| 133 |  | 
| 134 | 
            +
                toggleFormInputs(true);
         | 
|  | |
|  | |
| 135 |  | 
| 136 | 
             
                if (outputSection) {
         | 
| 137 | 
             
                  outputSection.classList.remove('has-images');
         | 
| 138 | 
            +
                  outputSection.innerHTML = [
         | 
| 139 | 
            +
                    '<div class="loading-container">',
         | 
| 140 | 
            +
                    '<div class="loading-spinner" ',
         | 
| 141 | 
            +
                    'style="margin: 0 auto 20px;"></div>',
         | 
| 142 | 
            +
                    '<p class="loading-text">',
         | 
| 143 | 
            +
                    'Generating your image...</p>',
         | 
| 144 | 
            +
                    '<div class="progress-bar">',
         | 
| 145 | 
            +
                    '<div class="progress-fill" ',
         | 
| 146 | 
            +
                    'style="width: 0%;"></div>',
         | 
| 147 | 
            +
                    '</div>',
         | 
| 148 | 
            +
                    '<p class="progress-text">0% Complete</p>',
         | 
| 149 | 
            +
                    '</div>'
         | 
| 150 | 
            +
                  ].join('');
         | 
| 151 | 
             
                }
         | 
| 152 |  | 
| 153 | 
             
                updateButtonsForGeneration(true);
         | 
| 154 | 
             
              };
         | 
| 155 |  | 
| 156 | 
             
              var hideGeneratingUI = function() {
         | 
| 157 | 
            +
                toggleFormInputs(false);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 158 | 
             
                updateButtonsForGeneration(false);
         | 
| 159 | 
            +
                
         | 
| 160 | 
            +
                if (window.validateInputs) {
         | 
| 161 | 
            +
                  window.validateInputs();
         | 
| 162 | 
            +
                }
         | 
| 163 | 
             
              };
         | 
| 164 |  | 
| 165 | 
             
              var resetToInitialState = function() {
         | 
|  | |
| 180 | 
             
                if (!outputSection) return;
         | 
| 181 |  | 
| 182 | 
             
                outputSection.classList.remove('has-images');
         | 
| 183 | 
            +
                outputSection.innerHTML = [
         | 
| 184 | 
            +
                  '<svg class="placeholder-icon" ',
         | 
| 185 | 
            +
                  'width="80" height="80" ',
         | 
| 186 | 
            +
                  'viewBox="0 0 24 24" fill="none">',
         | 
| 187 | 
            +
                  '<path d="M21 3H3C2 3 1 4 1 5V19C1 20 2 21 ',
         | 
| 188 | 
            +
                  '3 21H21C22 21 23 20 23 19V5C23 4 22 3 21 3Z',
         | 
| 189 | 
            +
                  'M21 19H3V5H21V19Z" fill="currentColor"/>',
         | 
| 190 | 
            +
                  '<path d="M4.5 16.5L9 12L11.5 14.5L16 10L',
         | 
| 191 | 
            +
                  '19.5 13.5" stroke="currentColor" ',
         | 
| 192 | 
            +
                  'stroke-width="1.5" stroke-linecap="round"/>',
         | 
| 193 | 
            +
                  '<circle cx="8" cy="8.5" r="1.5" ',
         | 
| 194 | 
            +
                  'fill="currentColor"/>',
         | 
| 195 | 
            +
                  '</svg>',
         | 
| 196 | 
            +
                  '<p class="placeholder-text">',
         | 
| 197 | 
            +
                  'No images generated yet. ',
         | 
| 198 | 
            +
                  'Start creating amazing visuals!',
         | 
| 199 | 
            +
                  '</p>'
         | 
| 200 | 
            +
                ].join('');
         | 
| 201 | 
            +
              };
         | 
| 202 | 
            +
              
         | 
| 203 | 
            +
              var createButton = function(type, isGenerating) {
         | 
| 204 | 
            +
                var icons = {
         | 
| 205 | 
            +
                  stop: '<rect x="4" y="4" width="16" height="16" ' +
         | 
| 206 | 
            +
                        'rx="3" fill="currentColor"/>',
         | 
| 207 | 
            +
                  play: '<path d="M3 20V4L22 12L3 20ZM5 17L16.85 ' +
         | 
| 208 | 
            +
                        '12L5 7V10.5L11 12L5 13.5V17Z" ' +
         | 
| 209 | 
            +
                        'fill="currentColor"/>'
         | 
| 210 | 
            +
                };
         | 
| 211 | 
            +
                
         | 
| 212 | 
            +
                if (isGenerating) {
         | 
| 213 | 
            +
                  return [
         | 
| 214 | 
            +
                    '<button type="button" ',
         | 
| 215 | 
            +
                    'onclick="cancelGeneration()" ',
         | 
| 216 | 
            +
                    'class="btn btn-danger">',
         | 
| 217 | 
            +
                    '<svg class="button-icon" viewBox="0 0 24 24" ',
         | 
| 218 | 
            +
                    'fill="none">',
         | 
| 219 | 
            +
                    icons.stop,
         | 
| 220 | 
            +
                    '</svg>',
         | 
| 221 | 
            +
                    'Stop Generation',
         | 
| 222 | 
            +
                    '</button>'
         | 
| 223 | 
            +
                  ].join('');
         | 
| 224 | 
            +
                }
         | 
| 225 | 
            +
                
         | 
| 226 | 
            +
                return [
         | 
| 227 | 
            +
                  '<button type="submit" id="submitBtn" disabled ',
         | 
| 228 | 
            +
                  'class="btn btn-primary">',
         | 
| 229 | 
            +
                  '<svg class="button-icon" viewBox="0 0 24 24" ',
         | 
| 230 | 
            +
                  'fill="none">',
         | 
| 231 | 
            +
                  icons.play,
         | 
| 232 | 
            +
                  '</svg>',
         | 
| 233 | 
            +
                  'Generate Image',
         | 
| 234 | 
            +
                  '</button>'
         | 
| 235 | 
            +
                ].join('');
         | 
| 236 | 
             
              };
         | 
| 237 |  | 
| 238 | 
             
              var updateButtonsForGeneration = function(isGenerating) {
         | 
|  | |
| 242 |  | 
| 243 | 
             
                if (!buttonsContainer) return;
         | 
| 244 |  | 
| 245 | 
            +
                buttonsContainer.innerHTML = createButton(
         | 
| 246 | 
            +
                  isGenerating ? 'stop' : 'play', 
         | 
| 247 | 
            +
                  isGenerating
         | 
| 248 | 
            +
                );
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 249 | 
             
              };
         | 
| 250 |  | 
| 251 | 
             
              var handleGenerationComplete = function(images) {
         | 
|  | |
| 268 | 
             
                displayImages(currentImages);
         | 
| 269 | 
             
              };
         | 
| 270 |  | 
| 271 | 
            +
              var createImageCard = function(image, index) {
         | 
| 272 | 
            +
                var downloadIcon = [
         | 
| 273 | 
            +
                  '<path d="M12 16L7 11L8.4 9.55L11 12.15V4H13',
         | 
| 274 | 
            +
                  'V12.15L15.6 9.55L17 11L12 16Z" ',
         | 
| 275 | 
            +
                  'fill="currentColor"/>',
         | 
| 276 | 
            +
                  '<path d="M4 20C3.45 20 2.98 19.8 2.59 19.41',
         | 
| 277 | 
            +
                  'C2.2 19.02 2 18.55 2 18V15H4V18H20V15H22V18',
         | 
| 278 | 
            +
                  'C22 18.55 21.8 19.02 21.41 19.41C21.02 19.8 ',
         | 
| 279 | 
            +
                  '20.55 20 20 20H4Z" fill="currentColor"/>'
         | 
| 280 | 
            +
                ].join('');
         | 
| 281 | 
            +
                
         | 
| 282 | 
            +
                var deleteIcon = [
         | 
| 283 | 
            +
                  '<path d="M18.3 5.71C17.91 5.32 17.28 5.32 ',
         | 
| 284 | 
            +
                  '16.89 5.71L12 10.59L7.11 5.7C6.72 5.31 6.09 ',
         | 
| 285 | 
            +
                  '5.31 5.7 5.7C5.31 6.09 5.31 6.72 5.7 7.11',
         | 
| 286 | 
            +
                  'L10.59 12L5.7 16.89C5.31 17.28 5.31 17.91 ',
         | 
| 287 | 
            +
                  '5.7 18.3C6.09 18.69 6.72 18.69 7.11 18.3L12 ',
         | 
| 288 | 
            +
                  '13.41L16.89 18.3C17.28 18.69 17.91 18.69 ',
         | 
| 289 | 
            +
                  '18.3 18.3C18.69 17.91 18.69 17.28 18.3 ',
         | 
| 290 | 
            +
                  '16.89L13.41 12L18.3 7.11C18.68 6.73 18.68 ',
         | 
| 291 | 
            +
                  '6.09 18.3 5.71Z" fill="currentColor"/>'
         | 
| 292 | 
            +
                ].join('');
         | 
| 293 | 
            +
                
         | 
| 294 | 
            +
                return [
         | 
| 295 | 
            +
                  '<div class="image-card">',
         | 
| 296 | 
            +
                  '<img src="data:image/png;base64,',
         | 
| 297 | 
            +
                  image.base64,
         | 
| 298 | 
            +
                  '" alt="', image.prompt, '">',
         | 
| 299 | 
            +
                  '<div class="image-actions">',
         | 
| 300 | 
            +
                  '<a href="data:image/png;base64,',
         | 
| 301 | 
            +
                  image.base64,
         | 
| 302 | 
            +
                  '" download="generated-', image.id, '.png" ',
         | 
| 303 | 
            +
                  'class="action-btn">',
         | 
| 304 | 
            +
                  '<svg class="action-icon" viewBox="0 0 24 24" ',
         | 
| 305 | 
            +
                  'fill="none">',
         | 
| 306 | 
            +
                  downloadIcon,
         | 
| 307 | 
            +
                  '</svg>',
         | 
| 308 | 
            +
                  '</a>',
         | 
| 309 | 
            +
                  '<button type="button" onclick="deleteImage(',
         | 
| 310 | 
            +
                  index,
         | 
| 311 | 
            +
                  ')" class="action-btn">',
         | 
| 312 | 
            +
                  '<svg class="action-icon" viewBox="0 0 24 24" ',
         | 
| 313 | 
            +
                  'fill="none">',
         | 
| 314 | 
            +
                  deleteIcon,
         | 
| 315 | 
            +
                  '</svg>',
         | 
| 316 | 
            +
                  '</button>',
         | 
| 317 | 
            +
                  '</div>',
         | 
| 318 | 
            +
                  '<div class="image-info">',
         | 
| 319 | 
            +
                  '<p class="image-prompt">', image.prompt, '</p>',
         | 
| 320 | 
            +
                  '<p class="image-meta">',
         | 
| 321 | 
            +
                  '<span class="image-model">',
         | 
| 322 | 
            +
                  image.model.toUpperCase(),
         | 
| 323 | 
            +
                  '</span> | ',
         | 
| 324 | 
            +
                  image.size,
         | 
| 325 | 
            +
                  '</p>',
         | 
| 326 | 
            +
                  '</div>',
         | 
| 327 | 
            +
                  '</div>'
         | 
| 328 | 
            +
                ].join('');
         | 
| 329 | 
            +
              };
         | 
| 330 | 
            +
              
         | 
| 331 | 
             
              var displayImages = function(images) {
         | 
| 332 | 
             
                var outputSection = document.querySelector(
         | 
| 333 | 
             
                  '.image-output-section'
         | 
|  | |
| 339 | 
             
                  showPlaceholder();
         | 
| 340 | 
             
                } else {
         | 
| 341 | 
             
                  outputSection.classList.add('has-images');
         | 
| 342 | 
            +
                  var html = ['<div class="image-grid">'];
         | 
| 343 |  | 
| 344 | 
             
                  images.forEach(function(image, index) {
         | 
| 345 | 
            +
                    html.push(createImageCard(image, index));
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 346 | 
             
                  });
         | 
| 347 |  | 
| 348 | 
            +
                  html.push('</div>');
         | 
| 349 | 
            +
                  outputSection.innerHTML = html.join('');
         | 
| 350 | 
             
                }
         | 
| 351 | 
             
              };
         | 
| 352 |  | 
| 353 | 
             
              var showErrorModal = function(error) {
         | 
| 354 | 
            +
                var existingModal = document.getElementById(
         | 
| 355 | 
            +
                  'errorModal'
         | 
| 356 | 
            +
                );
         | 
| 357 | 
            +
                
         | 
| 358 | 
             
                if (existingModal) existingModal.remove();
         | 
| 359 |  | 
| 360 | 
             
                var modal = document.createElement('div');
         | 
| 361 | 
             
                modal.id = 'errorModal';
         | 
| 362 | 
             
                modal.className = 'modal-overlay';
         | 
| 363 | 
            +
                modal.innerHTML = [
         | 
| 364 | 
            +
                  '<div class="modal-content ',
         | 
| 365 | 
            +
                  'modal-error-content">',
         | 
| 366 | 
            +
                  '<div class="modal-inner">',
         | 
| 367 | 
            +
                  '<h3 class="modal-error-title">Error</h3>',
         | 
| 368 | 
            +
                  '<p class="modal-error-text">', error, '</p>',
         | 
| 369 | 
            +
                  '<button onclick="closeErrorModal()" ',
         | 
| 370 | 
            +
                  'class="btn btn-primary w-full">OK</button>',
         | 
| 371 | 
            +
                  '</div>',
         | 
| 372 | 
            +
                  '</div>'
         | 
| 373 | 
            +
                ].join('');
         | 
| 374 |  | 
| 375 | 
             
                document.body.appendChild(modal);
         | 
| 376 | 
             
              };
         | 
|  | |
| 386 | 
             
                xhr.onload = function() {
         | 
| 387 | 
             
                  try {
         | 
| 388 | 
             
                    var response = JSON.parse(xhr.responseText);
         | 
| 389 | 
            +
                    if (!response.success && response.error) {
         | 
| 390 | 
            +
                      showErrorModal(response.error);
         | 
|  | |
| 391 | 
             
                    }
         | 
| 392 | 
             
                  } catch (e) {}
         | 
| 393 | 
             
                };
         | 
|  | |
| 404 | 
             
                  '.image-output-section'
         | 
| 405 | 
             
                );
         | 
| 406 |  | 
| 407 | 
            +
                if (!outputSection || 
         | 
| 408 | 
            +
                    !outputSection.classList.contains('has-images')) {
         | 
| 409 | 
            +
                  return;
         | 
| 410 | 
            +
                }
         | 
| 411 | 
            +
                
         | 
| 412 | 
            +
                var imageCards = outputSection.querySelectorAll(
         | 
| 413 | 
            +
                  '.image-card'
         | 
| 414 | 
            +
                );
         | 
| 415 | 
            +
                
         | 
| 416 | 
            +
                if (!imageCards || imageCards.length === 0) return;
         | 
| 417 | 
            +
                
         | 
| 418 | 
            +
                currentImages = [];
         | 
| 419 | 
            +
                
         | 
| 420 | 
            +
                imageCards.forEach(function(card) {
         | 
| 421 | 
            +
                  var img = card.querySelector('img');
         | 
| 422 | 
            +
                  var prompt = card.querySelector('.image-prompt');
         | 
| 423 | 
            +
                  var model = card.querySelector('.image-model');
         | 
| 424 | 
            +
                  var meta = card.querySelector('.image-meta');
         | 
| 425 | 
            +
                  
         | 
| 426 | 
            +
                  if (img && img.src && img.src.includes('base64,')) {
         | 
| 427 | 
            +
                    var base64 = img.src.split('base64,')[1];
         | 
| 428 | 
            +
                    var size = meta 
         | 
| 429 | 
            +
                      ? meta.textContent.split('|')[1] 
         | 
| 430 | 
            +
                      : '';
         | 
| 431 | 
            +
                    
         | 
| 432 | 
            +
                    currentImages.push({
         | 
| 433 | 
            +
                      id: 'existing-' + Math.random()
         | 
| 434 | 
            +
                        .toString(36).substring(2, 15),
         | 
| 435 | 
            +
                      base64: base64,
         | 
| 436 | 
            +
                      prompt: prompt ? prompt.textContent : '',
         | 
| 437 | 
            +
                      model: model 
         | 
| 438 | 
            +
                        ? model.textContent.toLowerCase() 
         | 
| 439 | 
            +
                        : '',
         | 
| 440 | 
            +
                      size: size ? size.trim() : ''
         | 
| 441 | 
             
                    });
         | 
| 442 | 
             
                  }
         | 
| 443 | 
            +
                });
         | 
| 444 | 
             
              };
         | 
| 445 |  | 
| 446 | 
             
              connectWebSocket();
         | 
