πως να κάνω την εφαρμογή αυτή να λειτουργεί σωστά?   <script>
    class ImageCache {
      constructor() {
        this.cache = new Map();
      }

      async getImage(url) {
        if (this.cache.has(url)) {
          return this.cache.get(url);
        }

        try {
          const imageData = await this.loadImage(url);
          this.cache.set(url, imageData);
          return imageData;
        } catch (error) {
          throw new Error(`Failed to load image: ${error.message}`);
        }
      }

      async loadImage(url) {
        return new Promise((resolve, reject) => {
          const img = new Image();
          img.crossOrigin = 'anonymous';
          img.onload = () => {
            resolve({
              dataUrl: img.src,
              width: img.width,
              height: img.height,
              element: img
            });
          };
          img.onerror = () => reject(new Error('Image load failed'));
          img.src = url;
        });
      }

      clear() {
        this.cache.clear();
      }
    }

    class PollinationsAPI {
      static retryDelay = 1000; 
      static maxRetries = 5;
      static requestQueue = [];
      static isProcessingQueue = false;
      static rateLimitDelay = 1000; 

      static async fetchWithRetry(url, options = {}, retries = this.maxRetries) {
        try {
          const response = await fetch(url, options);
          if (response.status === 429) {
            throw new Error('RATE_LIMIT');
          }
          if (!response.ok) {
            throw new Error('FETCH_ERROR');
          }
          return response;
        } catch (error) {
          if (retries === 0) throw error;
          if (error.message === 'RATE_LIMIT') {
            await new Promise(res => setTimeout(res, this.rateLimitDelay));
          } else {
            await new Promise(res => setTimeout(res, this.retryDelay * (this.maxRetries - retries + 1)));
          }
          return this.fetchWithRetry(url, options, retries - 1);
        }
      }

      static async queueRequest(requestFn) {
        return new Promise((resolve, reject) => {
          this.requestQueue.push({ requestFn, resolve, reject });
          if (!this.isProcessingQueue) this.processQueue();
        });
      }

      static async processQueue() {
        if (this.requestQueue.length === 0) {
          this.isProcessingQueue = false;
          return;
        }
        this.isProcessingQueue = true;
        const { requestFn, resolve, reject } = this.requestQueue.shift();
        try {
          const result = await requestFn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
        await new Promise(res => setTimeout(res, this.rateLimitDelay));
        this.processQueue();
      }

      static async generateText(prompt) {
        return this.queueRequest(async () => {
          try {
            const url = `https://text.pollinations.ai/${encodeURIComponent(prompt)}`;
            const response = await this.fetchWithRetry(url);
            return response.text();
          } catch (error) {
            if (error.message === 'RATE_LIMIT') {
              throw new Error('Rate limit exceeded. Please try again later.');
            }
            throw new Error('Failed to generate text.');
          }
        });
      }

      static async generateImage(prompt, settings = {}) {
        return this.queueRequest(async () => {
          const baseUrl = 'https://image.pollinations.ai/prompt/';
          const params = new URLSearchParams({
            width: settings.width || 512, 
            height: settings.height || 512,
            seed: settings.seed || Math.floor(Math.random() * 1000000),
            nologo: 'true',
            model: settings.model || 'flux',
            private: 'true',
            t: Date.now()
          });

          const encodedPrompt = encodeURIComponent(prompt);
          const url = `${baseUrl}${encodedPrompt}?${params}`;

          try {
            const response = await this.fetchWithRetry(url);
            if (!response.url) {
              throw new Error('Image URL not found in response.');
            }
            return response.url;
          } catch (error) {
            if (error.message.includes('RATE_LIMIT')) {
              throw new Error('Rate limit exceeded. Please try again later.');
            }
            throw new Error('Failed to generate image.');
          }
        });
      }
    }

    class PersonalizedComicStripCreator {
      constructor() {
        this.currentPage = 0;
        this.pages = [];
        this.isGenerating = false;
        this.imageCache = new ImageCache();
        
        this.stylePrompts = {
          manga: {
            style: "Japanese manga art style with dynamic line work, expressive eyes, and clear black and white contrast",
            format: "Manga-style paneling with emphasis on action lines and emotional close-ups"
          },
          american: {
            style: "American comic book style with bold colors, strong outlines, and superhero-inspired aesthetics",
            format: "Traditional American comic book layout with varied panel sizes and dramatic angles"
          },
          european: {
            style: "European BD (Bande Dessinée) style with detailed backgrounds and realistic character proportions",
            format: "Clear panel layouts with emphasis on scenic composition and atmospheric storytelling"
          },
          webcomic: {
            style: "Modern webcomic style with clean lines, simplified designs, and contemporary aesthetics",
            format: "Vertical scrolling format optimized for digital reading with consistent panel sizes"
          },
          chibi: {
            style: "Cute chibi style with super-deformed characters, exaggerated expressions, and kawaii elements",
            format: "Simple panel layouts focusing on character interactions and emotional reactions"
          }
        };

        this.storyTitleInput = document.getElementById('storyTitle');
        this.comicStyleInput = document.getElementById('comicStyle');
        this.dialogueInput = document.getElementById('dialogue');
        this.textAssistantInput = document.getElementById('textAssistant');
        
        this.protagonistNameInput = document.getElementById('protagonistName');
        this.protagonistDescriptionInput = document.getElementById('protagonistDescription');
        this.protagonistPersonalityInput = document.getElementById('protagonistPersonality');
        
        this.antagonistNameInput = document.getElementById('antagonistName');
        this.antagonistDescriptionInput = document.getElementById('antagonistDescription');
        this.antagonistPersonalityInput = document.getElementById('antagonistPersonality');
        
        this.panelsContainer = document.getElementById('panelsContainer');

        this.generateComicBtn = document.getElementById('generateComicBtn');
        this.exportPdfBtn = document.getElementById('export-pdf-btn');
        this.exportImagePngBtn = document.getElementById('export-image-png-btn');
        this.exportImageJpgBtn = document.getElementById('export-image-jpg-btn');
        
        this.comicStrip = document.getElementById('comicStrip');
        this.previewSection = document.querySelector('.preview-section');
        
        this.messageBox = document.getElementById('messageBox');

        this.loadingSpinner = document.getElementById('loadingSpinner');

        this.themeToggle = document.getElementById('themeToggle');

        this.storyMode = document.getElementById('storyMode');
        this.aiStoryGen = document.getElementById('aiStoryGen');
        this.manualInputs = document.querySelectorAll('.input-section > *:not(h2):not(.mode-selector)');

        this.initializeTheme();

        this.bindEvents();
        
        this.panelCount = 3;
        this.maxPanels = 5;

        for (let i = 1; i <= this.panelCount; i++) {
          this.addPanel(false);
        }
      }

      bindEvents() {
        document.getElementById('saveCharacterProfiles').addEventListener('click', () => this.saveCharacterProfiles());
        document.querySelector('.add-panel-btn').addEventListener('click', () => this.addPanel());
        
        this.generateComicBtn.addEventListener('click', () => this.generateComicStrip());
        
        this.exportPdfBtn.addEventListener('click', () => this.exportAsPDF());
        this.exportImagePngBtn.addEventListener('click', () => this.exportAsImage('png'));
        this.exportImageJpgBtn.addEventListener('click', () => this.exportAsImage('jpg'));
        
        document.getElementById('refineStorylineBtn').addEventListener('click', () => this.refineStoryline());

        this.storyMode.addEventListener('change', () => this.toggleStoryMode());
        document.getElementById('generateFullStory').addEventListener('click', () => this.generateFullStory());

        this.themeToggle.addEventListener('click', () => this.toggleTheme());
      }

      initializeTheme() {
        const savedTheme = localStorage.getItem('theme') || 'light';
        document.documentElement.setAttribute('data-theme', savedTheme);
        this.updateThemeToggleButton(savedTheme);
      }

      updateThemeToggleButton(theme) {
        this.themeToggle.innerHTML = theme === 'dark' ? '☀️' : '🌙';
        this.themeToggle.title = theme === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode';
      }

      toggleTheme() {
        const currentTheme = document.documentElement.getAttribute('data-theme');
        const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
        
        document.documentElement.setAttribute('data-theme', newTheme);
        localStorage.setItem('theme', newTheme);
        this.updateThemeToggleButton(newTheme);
      }

      saveCharacterProfiles() {
        this.characters = {
          protagonist: {
            name: this.protagonistNameInput.value.trim(),
            description: this.protagonistDescriptionInput.value.trim(),
            personality: this.protagonistPersonalityInput.value.trim(),
          },
          antagonist: {
            name: this.antagonistNameInput.value.trim(),
            description: this.antagonistDescriptionInput.value.trim(),
            personality: this.antagonistPersonalityInput.value.trim(),
          }
        };

        this.showMessage('Character profiles saved successfully!', 'success');
      }

      addPanel(showMessage = true) {
        if (this.panelsContainer.querySelectorAll('.panel').length >= this.maxPanels) {
          if (showMessage) {
            this.showMessage('You can add up to 5 panels only.', 'error');
          }
          return;
        }

        const newPanelNumber = this.panelsContainer.querySelectorAll('.panel').length + 1;

        const panelDiv = document.createElement('div');
        panelDiv.classList.add('panel');
        panelDiv.innerHTML = `
          <div class="panel-number">${newPanelNumber}</div>
          <label for="dialoguePanel${newPanelNumber}">Dialogue for Panel ${newPanelNumber} (optional):
            <span class="tooltip" title="Leave empty if no dialogue is needed for this panel">ⓘ</span>
          </label>
          <textarea id="dialoguePanel${newPanelNumber}" rows="3" placeholder="Enter dialogue if needed..."></textarea>
          <label for="scenarioPanel${newPanelNumber}">Scenario for Panel ${newPanelNumber}:</label>
          <textarea id="scenarioPanel${newPanelNumber}" rows="3" placeholder="Describe what happens in this panel..."></textarea>
          <button class="remove-panel-btn">Remove Panel</button>
        `;

        panelDiv.querySelector('.remove-panel-btn').addEventListener('click', () => {
          this.removePanel(panelDiv);
        });
        
        this.panelsContainer.appendChild(panelDiv);
      }

      removePanel(panelDiv) {
        panelDiv.classList.add('removing');
        panelDiv.addEventListener('transitionend', () => {
          panelDiv.remove();
          this.panelCount--;
          const panels = this.panelsContainer.querySelectorAll('.panel');
          panels.forEach((panel, index) => {
            panel.querySelector('.panel-number').textContent = index + 1;
          });
        });
      }

      async refineStoryline() {
        const initialDialogue = this.dialogueInput.value.trim();
        const aiAssistantText = this.textAssistantInput.value.trim();

        if (!initialDialogue) {
          this.showMessage('Please enter some dialogues or plot points.', 'error');
          return;
        }

        let prompt = `Refine and expand the following storyline for a comic strip:\n\n${initialDialogue}`;
        if (aiAssistantText) {
          prompt += `\n\nAdditional instructions: ${aiAssistantText}`;
        }

        try {
          const refinedStory = await PollinationsAPI.generateText(prompt);
          this.dialogueInput.value = refinedStory.trim();
          this.showMessage('Storyline refined successfully!', 'success');
        } catch (error) {
          this.showMessage(error.message, 'error');
        }
      }

      toggleStoryMode() {
        const isAiMode = this.storyMode.value === 'ai';
        this.aiStoryGen.style.display = isAiMode ? 'block' : 'none';
        this.manualInputs.forEach(el => {
          el.style.display = isAiMode ? 'none' : 'block';
        });
      }

      async generateFullStory() {
        const prompt = document.getElementById('aiStoryPrompt').value.trim();
        const stylePreference = document.getElementById('aiStylePreference').value.trim();
        
        if (!prompt) {
          this.showMessage('Please enter a story idea first.', 'error');
          return;
        }

        this.loadingSpinner.style.display = 'flex';
        
        try {
          const storyPrompt = `Create a complete comic strip story with the following elements:
            Main Story: ${prompt}
            Style Preferences: ${stylePreference}
            Requirements:
            - Title
            - 3-5 main characters with descriptions
            - 4-6 comic panels with scenarios and dialogue
            Format as JSON with properties: title, characters, panels`;
          
          const responseText = await PollinationsAPI.generateText(storyPrompt);
          let storyData;
          try {
            storyData = JSON.parse(responseText);
          } catch (parseError) {
            throw new Error('Invalid JSON response from API.');
          }

          // Validate the JSON structure
          if (!storyData.title || !storyData.characters || !storyData.panels) {
            throw new Error('Incomplete story data received from API.');
          }

          this.storyTitleInput.value = storyData.title || '';

          if (storyData.characters && storyData.characters.length > 0) {
            this.protagonistNameInput.value = storyData.characters[0].name || '';
            this.protagonistDescriptionInput.value = storyData.characters[0].description || '';
            this.protagonistPersonalityInput.value = storyData.characters[0].personality || '';
          }

          if (storyData.characters && storyData.characters.length > 1) {
            this.antagonistNameInput.value = storyData.characters[1].name || '';
            this.antagonistDescriptionInput.value = storyData.characters[1].description || '';
            this.antagonistPersonalityInput.value = storyData.characters[1].personality || '';
          }

          // Remove existing panels
          this.panelsContainer.innerHTML = '';

          // Add panels from the story data
          if (storyData.panels && storyData.panels.length > 0) {
            storyData.panels.slice(0, this.maxPanels).forEach((panel, index) => {
              this.addPanel(false);
              const currentPanel = this.panelsContainer.querySelectorAll('.panel')[index];
              if (currentPanel) {
                currentPanel.querySelector(`[id^="dialoguePanel"]`).value = panel.dialogue || '';
                currentPanel.querySelector(`[id^="scenarioPanel"]`).value = panel.scenario || '';
              }
            });
          }

          this.showMessage('Story generated successfully! You can now edit the details or generate the comic.', 'success');
          this.storyMode.value = 'manual';
          this.toggleStoryMode();
          
        } catch (error) {
          this.showMessage(error.message, 'error');
          console.error('generateFullStory error:', error);
        } finally {
          this.loadingSpinner.style.display = 'none';
        }
      }

      async assembleComicStrip() {
        if (this.pages.length === 0) {
          this.showMessage('Please generate the comic strip first.', 'error');
          return;
        }
        
        this.comicStrip.innerHTML = '';
        
        for (const page of this.pages) {
          const panelDiv = document.createElement('div');
          panelDiv.classList.add('comic-panel');
          
          const img = document.createElement('img');
          img.src = page.imageUrl;
          img.alt = "Comic Panel Illustration";
          
          const dialogueP = document.createElement('p');
          dialogueP.classList.add('dialogue');
          dialogueP.textContent = page.dialogue;
          
          panelDiv.appendChild(img);
          panelDiv.appendChild(dialogueP);
          
          this.comicStrip.appendChild(panelDiv);
        }
        
        this.previewSection.style.display = 'block';
      }

      async generateComicStrip() {
        if (this.isGenerating) return;

        this.isGenerating = true;
        this.showMessage('Generating comic strip...', 'success');
        this.loadingSpinner.style.display = 'flex'; 

        try {
          this.pages = [];
          const styleConfig = this.getSelectedStyleConfig();
        
          const panels = this.panelsContainer.querySelectorAll('.panel');
          for (let i = 0; i < panels.length; i++) {
            const panel = panels[i];
            const dialogueTextarea = panel.querySelector(`textarea[id^="dialoguePanel"]`);
            const scenario = panel.querySelector(`textarea[id^="scenarioPanel"]`).value.trim();
        
            const dialogue = dialogueTextarea ? dialogueTextarea.value.trim() : '';
            
            if (!scenario) {
              this.showMessage(`Please fill in the scenario for Panel ${i + 1}.`, 'error');
              this.isGenerating = false;
              this.loadingSpinner.style.display = 'none';
              return;
            }
        
            const sceneDescription = await PollinationsAPI.generateText(`
              Create a detailed and stylistic description for a comic panel illustration.
              Style: ${styleConfig.style}
              Format: ${styleConfig.format}
              Characters:
                Protagonist: ${this.characters.protagonist.description} Personality: ${this.characters.protagonist.personality}
                Antagonist: ${this.characters.antagonist.description} Personality: ${this.characters.antagonist.personality}
              Scenario: ${scenario}
              Dialogue: "${dialogue}"
              Theme: ${this.storyTitleInput.value.trim() || 'General'}
              Consider:
                - Character continuity in ${this.comicStyleInput.value} style
                - Expressive emotions typical of ${this.comicStyleInput.value} comics
                - Dynamic angles and composition
                - Background details matching the style
                - Panel composition following ${this.comicStyleInput.value} conventions
            `);
        
            const imagePrompt = `
              ${sceneDescription}
              Protagonist Seed: ${this.characters.protagonist.seed}
              Antagonist Seed: ${this.characters.antagonist.seed}
            `;
        
            const imageUrl = await PollinationsAPI.generateImage(imagePrompt, { 
              width: 512, 
              height: 512,
              seed: this.characters.protagonist.seed 
            });
        
            this.pages.push({
              dialogue: dialogue,
              scenario: scenario,
              imageUrl: imageUrl
            });
        
            this.showMessage(`Panel ${i + 1} generated successfully!`, 'success');
          }
        
          await this.assembleComicStrip();
          this.showMessage('Comic strip generated successfully!', 'success');
        
        } catch (error) {
          this.showMessage(error.message, 'error');
        } finally {
          this.isGenerating = false;
          this.loadingSpinner.style.display = 'none'; 
        }
      }

      async exportAsPDF() {
        if (this.pages.length === 0) {
          this.showMessage('Please generate the comic strip first.', 'error');
          return;
        }

        try {
          const storyTitle = this.storyTitleInput.value.trim() || 'My Comic Strip';

          const docDefinition = {
            content: [
              { text: storyTitle, style: 'header', alignment: 'center', margin: [0, 0, 0, 20] }
            ],
            styles: {
              header: {
                fontSize: 22,
                bold: true
              },
              dialogue: {
                italics: true,
                alignment: 'center',
                margin: [0, 10, 0, 10]
              }
            }
          };

          for (const page of this.pages) {
            docDefinition.content.push({ image: page.imageUrl, width: 500 });
            docDefinition.content.push({ text: `"${page.dialogue}"`, style: 'dialogue' });
            docDefinition.content.push({ text: '\n' });
          }

          pdfMake.createPdf(docDefinition).download(`${storyTitle}.pdf`);
          this.showMessage('Comic strip exported as PDF successfully!', 'success');
        } catch (error) {
          this.showMessage('Failed to export PDF. Please try again.', 'error');
        }
      }

      async exportAsImage(format = 'png') {
        if (this.pages.length === 0) {
          this.showMessage('Please generate the comic strip first.', 'error');
          return;
        }

        try {
          const canvas = document.createElement('canvas');
          const context = canvas.getContext('2d');
          const panelWidth = 512;
          let panelHeightTotal = 0;

          const images = await Promise.all(this.pages.map(page => this.loadImage(page.imageUrl)));
          images.forEach(img => {
            panelHeightTotal += img.height;
          });

          canvas.width = panelWidth;
          canvas.height = panelHeightTotal + (40 * this.pages.length);

          let yOffset = 0;
          images.forEach((img, index) => {
            context.drawImage(img, 0, yOffset, panelWidth, img.height);
            yOffset += img.height;
            context.font = '16px Arial';
            context.fillStyle = '#2D3436';
            context.textAlign = 'center';
            context.fillText(`"${this.pages[index].dialogue}"`, panelWidth / 2, yOffset + 20);
            yOffset += 40; 
          });

          canvas.toBlob(blob => {
            const fileName = `${this.storyTitleInput.value.trim() || 'My_Comic_Strip'}.${format}`;
            saveAs(blob, fileName);
            this.showMessage('Comic strip exported as image successfully!', 'success');
          }, `image/${format}`);
        } catch (error) {
          this.showMessage('Failed to export image. Please try again.', 'error');
        }
      }

      loadImage(url) {
        return new Promise((resolve, reject) => {
          const img = new Image();
          img.crossOrigin = 'anonymous';
          img.onload = () => resolve(img);
          img.onerror = () => reject(new Error('Image failed to load.'));
          img.src = url;
        });
      }

      showMessage(message, type = 'success') {
        this.messageBox.textContent = message;
        this.messageBox.className = `message-box ${type}`;
        this.messageBox.style.display = 'block';
        this.messageBox.style.opacity = '1';
        
        setTimeout(() => {
          this.messageBox.style.opacity = '0';
          setTimeout(() => {
            this.messageBox.style.display = 'none';
          }, 500);
        }, 4000);
      }
    }

    document.addEventListener('DOMContentLoaded', () => {
      window.comicCreator = new PersonalizedComicStripCreator();
    });
  </script>