This project is deployed on Netlify. I want to make some changes to it. First I want to give the user an option for how many designs he want to generate. max 10 images and when the user clicks on an image it should be full screen like preview an user can check the image closely. 

functions/generate-image.js
const fetch = require('node-fetch');

exports.handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      body: JSON.stringify({ error: 'Method not allowed' })
    };
  }

  try {
    const { 
      accountId, 
      apiKey, 
      prompt, 
      steps, 
      guidance, 
      width, 
      height, 
      seed 
    } = JSON.parse(event.body);

    if (!accountId || !apiKey || !prompt) {
      return {
        statusCode: 400,
        body: JSON.stringify({ 
          success: false, 
          error: 'Missing required parameters' 
        })
      };
    }

    const response = await fetch(
       `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/@cf/black-forest-labs/flux-1-schnell`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify({
          prompt,
          steps: parseInt(steps) || 4,
          guidance_scale: parseFloat(guidance) || 7.5,
          width: parseInt(width) || 512,
          height: parseInt(height) || 512,
          seed: parseInt(seed) || Math.floor(10000 + Math.random() * 90000)
        })
      }
    );

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.errors?.[0]?.message || 'Failed to generate image');
    }

    return {
      statusCode: 200,
      body: JSON.stringify(data)
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ 
        success: false, 
        error: error.message 
      })
    };
  }
};


public/app.js
document.addEventListener('DOMContentLoaded', () => {
    // Elements
    const accountIdInput = document.getElementById('accountId');
    const apiKeyInput = document.getElementById('apiKey');
    const promptInput = document.getElementById('prompt');
    const stepsInput = document.getElementById('steps');
    const stepsSlider = document.getElementById('stepsSlider');
    const guidanceInput = document.getElementById('guidance');
    const guidanceSlider = document.getElementById('guidanceSlider');
    const widthInput = document.getElementById('width');
    const heightInput = document.getElementById('height');
    const seedInput = document.getElementById('seed');
    const randomSeedBtn = document.getElementById('randomSeed');
    const generateBtn = document.getElementById('generateBtn');
    const loadingIndicator = document.getElementById('loadingIndicator');
    const result = document.getElementById('result');
    const generatedImage = document.getElementById('generatedImage');
    const downloadBtn = document.getElementById('downloadBtn');
    const error = document.getElementById('error');
    const seedInfo = document.getElementById('seedInfo');
    const copySeedBtn = document.getElementById('copySeed');
    const collapsibleHeaders = document.querySelectorAll('.collapsible-header');
    const loadingSpinner = document.querySelector('.spinner');
    

    // Load saved credentials from localStorage
    accountIdInput.value = localStorage.getItem('accountId') || '';
    apiKeyInput.value = localStorage.getItem('apiKey') || '';

    // Sync sliders with number inputs
    function syncSliderInput(slider, input) {
        slider.addEventListener('input', () => input.value = slider.value);
        input.addEventListener('input', () => slider.value = input.value);
    }

    syncSliderInput(stepsSlider, stepsInput);
    syncSliderInput(guidanceSlider, guidanceInput);

    // Random seed generator
    function generateRandomSeed() {
        return Math.floor(10000 + Math.random() * 90000);
    }

    randomSeedBtn.addEventListener('click', () => {
        seedInput.value = generateRandomSeed();
    });

    // Copy seed functionality
    copySeedBtn.addEventListener('click', () => {
        const seedText = seedInfo.textContent.split(': ')[1];
        navigator.clipboard.writeText(seedText).then(() => {
            const originalText = copySeedBtn.textContent;
            copySeedBtn.textContent = 'Copied!';
            setTimeout(() => {
                copySeedBtn.textContent = originalText;
            }, 2000);
        });
    });

    // Setup collapsible sections
    collapsibleHeaders.forEach(header => {
        header.addEventListener('click', () => {
            const content = header.nextElementSibling;
            header.classList.toggle('active');
            content.classList.toggle('active');
        });
    });

    // Modified showLoading function
    function showLoading() {
        loadingIndicator.classList.remove('hidden');
        result.classList.add('hidden');
        error.classList.add('hidden');
    }

    function showResult() {
        loadingIndicator.classList.add('hidden');
        result.classList.remove('hidden');
        error.classList.add('hidden');
    }

    function showError(message) {
        loadingIndicator.classList.add('hidden');
        result.classList.add('hidden');
        error.classList.remove('hidden');
        error.querySelector('p').textContent = message;
    }

    // Main generate image functionality
    generateBtn.addEventListener('click', async () => {
        // Store current scroll position
        const scrollPos = window.scrollY;

        // Get all input values
        const accountId = accountIdInput.value.trim();
        const apiKey = apiKeyInput.value.trim();
        const prompt = promptInput.value.trim();
        const steps = parseInt(stepsInput.value);
        const guidance = parseFloat(guidanceInput.value);
        const width = parseInt(widthInput.value);
        const height = parseInt(heightInput.value);
        let seed = seedInput.value ? parseInt(seedInput.value) : generateRandomSeed();

        // Save credentials to localStorage
        localStorage.setItem('accountId', accountId);
        localStorage.setItem('apiKey', apiKey);

        // Validate required fields
        if (!accountId || !apiKey) {
            showError('Please enter your Account ID and API Key');
            return;
        }

        if (!prompt) {
            showError('Please enter a prompt');
            return;
        }

        try {
            showLoading();

            const response = await fetch('/api/generate-image', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    accountId,
                    apiKey,
                    prompt,
                    steps,
                    guidance,
                    width,
                    height,
                    seed
                })
            });

            const data = await response.json();

            if (!response.ok || !data.success) {
                throw new Error(data.error || 'Failed to generate image');
            }

            const base64Image = data.result.image;
            const imageUrl = `data:image/jpeg;base64,${base64Image}`;

            generatedImage.src = imageUrl;
            seedInfo.textContent = `Seed: ${seed}`;
            showResult();

            // Setup download functionality
            downloadBtn.onclick = () => {
                const link = document.createElement('a');
                link.href = imageUrl;
                link.download = `generated-image-${seed}.jpg`;
                link.click();
            };

            // Restore scroll position on mobile
            if (window.innerWidth <= 768) {
                setTimeout(() => {
                    window.scrollTo({
                        top: scrollPos,
                        behavior: 'instant'
                    });
                }, 100);
            }

        } catch (err) {
            showError(err.message);
        }
    });

    // Initialize empty seed
    seedInfo.textContent = 'Seed: ';
    seedInput.value = '';

    // Set default dimensions
    widthInput.value = '1024';
    heightInput.value = '1024';

    // Initialize mobile view
    if (window.innerWidth <= 768) {
        document.querySelectorAll('.collapsible-content').forEach(content => {
            content.classList.remove('active');
        });
    }

    // Set initial values for inputs
    if (!seedInput.value) {
        // seedInput.value = generateRandomSeed();
    }
});


public/index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Imagly AI Image Generator</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <div class="app-container">
        <header>
            <h1>IMAGELY</h1>
        </header>

        <main class="split-layout">
            <section class="output-section card">
            <div id="loadingIndicator" class="loading hidden">
                <div class="spinner"></div>
                <p>Generating image...</p>
            </div>

                <div id="result">
                    <img id="generatedImage" class="generated-image" alt="Generated image" src="https://upload.wikimedia.org/wikipedia/commons/3/3f/Placeholder_view_vector.svg" />

                </div>
                <div>
                     <div class="result-controls">
                        <button id="downloadBtn" class="primary-button">Download Image</button>
                    </div>
                    <div class="result-controls">
                        <div id="seedInfo" class="seed-info">Seed: </div>
                        <button id="copySeed" class="secondary-button">Copy Seed</button>
                    </div>                   
                </div>

                <div id="error" class="error hidden">
                    <p></p>
                </div>
            </section>

            <section class="controls-section">
                <div class="prompt-section card">
                    <h2>Image Prompt</h2>
                    <div class="input-group">
                        <textarea id="prompt" class="input-field"
                            placeholder="Describe the image you want to generate..."></textarea>
                    </div>
                    <button id="generateBtn" class="primary-button">Generate Image</button>
                </div>

                <div class="parameters-section card collapsible-section">
                    <div class="collapsible-header">
                        <h2>Advanced Parameters</h2>
                    </div>
                    <div class="collapsible-content">
                        <div class="parameter-row">
                            <label for="steps">Inference Steps (1-8):</label>
                            <div class="slider-with-input">
                                <input type="range" id="stepsSlider" min="1" max="8" value="4" class="input-field" />
                                <input type="number" id="steps" min="1" max="8" value="4" class="input-field" />
                            </div>
                        </div>

                        <div class="parameter-row">
                            <label for="guidance">Guidance Scale (0-20):</label>
                            <div class="slider-with-input">
                                <input type="range" id="guidanceSlider" min="0" max="20" value="7.5" step="0.1"
                                    class="input-field" />
                                <input type="number" id="guidance" min="0" max="20" value="7.5" step="0.1"
                                    class="input-field" />
                            </div>
                        </div>

                        <div class="parameter-row">
                            <div class="dimensions-group">
                                <div class="dimension-input">
                                    <label for="width">Width:</label>
                                    <input type="number" id="width" value="1024" min="128" max="1024" step="64"
                                        class="input-field" />
                                </div>
                                <div class="dimension-input">
                                    <label for="height">Height:</label>
                                    <input type="number" id="height" value="1024" min="128" max="1024" step="64"
                                        class="input-field" />
                                </div>
                            </div>
                        </div>

                        <div class="parameter-row seed-row">
                            <label for="seed">Seed (Optional):</label>
                            <div class="button-group">
                                <input type="number" id="seed" class="input-field" placeholder="Random seed" />
                                <button id="randomSeed" class="secondary-button">🎲</button>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="config-section card collapsible-section">
                    <div class="collapsible-header">
                        <h2>API Configuration</h2>
                    </div>
                    <div class="collapsible-content">
                        <div class="input-group">
                            <label for="accountId">Account ID:</label>
                            <input type="text" id="accountId" class="input-field" placeholder="Enter your Account ID" />
                        </div>
                        <div class="input-group">
                            <label for="apiKey">API Key:</label>
                            <input type="password" id="apiKey" class="input-field" placeholder="Enter your API Key" />
                        </div>
                    </div>
                </div>
            </section>
        </main>
    </div>
    <script src="app.js"></script>
</body>

</html>


public/styles.css
:root {
    --primary-color: #4f46e5;
    --primary-hover: #4338ca;
    --secondary-color: #6366f1;
    --background-color: #f8fafc;
    --card-background: #ffffff;
    --text-color: #1f2937;
    --border-color: #e2e8f0;
    --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
    --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    --radius: 0.75rem;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    background-color: var(--background-color);
    color: var(--text-color);
    line-height: 1.6;
    min-height: 100vh;
}

.app-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 2rem;
    min-height: 100vh;
}

header {
    text-align: center;
    margin-bottom: 2rem;
}

h1 {
    color: var(--primary-color);
    font-size: 2.5rem;
    font-weight: 700;
}

h2 {
    font-size: 1.25rem;
    color: var(--primary-color);
    font-weight: 600;
}

.split-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
}

.card {
    background-color: var(--card-background);
    border-radius: var(--radius);
    padding: 1.5rem;
    box-shadow: var(--shadow);
    border: 1px solid var(--border-color);
}

.output-section {
    height: calc(100vh - 130px);
    position: sticky;
    top: 2rem;
}

.controls-section {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}

.input-group {
    margin-top: 1rem;
    margin-bottom: 0.7rem;
}

.input-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: var(--text-color);
}

.input-field {
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1px solid var(--border-color);
    border-radius: 0.5rem;
    font-size: 1rem;
    transition: border-color 0.2s;
    background-color: white;
}

.input-field:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}

textarea.input-field {
    height: 120px;
    resize: vertical;
}

.button-group {
    display: flex;
    gap: 0.75rem;
    align-items: center;
}

.primary-button {
    background-color: var(--primary-color);
    color: white;
    border: none;
    padding: 0.75rem 1.5rem;
    border-radius: 0.5rem;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
}

.primary-button:hover {
    background-color: var(--primary-hover);
    transform: translateY(-1px);
}

.secondary-button {
    background-color: white;
    color: var(--primary-color);
    border: 1px solid var(--border-color);
    padding: 0.75rem 1.5rem;
    border-radius: 0.5rem;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
}

.secondary-button:hover {
    border-color: var(--primary-color);
    background-color: rgba(79, 70, 229, 0.05);
}

.result-controls {
    display: flex;
    gap: 1rem;
    margin-top: 1rem;
    align-items: center;
    justify-content: center;
}

.seed-info {
    font-size: 0.875rem;
    color: var(--text-color);
    padding: 0.5rem 1rem;
    background-color: var(--background-color);
    border-radius: 0.5rem;
    border: 1px solid var(--border-color);
}

.generated-image {
    max-width: 100%;
    max-height: calc(100vh - 300px);
    object-fit: contain;
    border-radius: var(--radius);
}

@media (max-width: 768px) {
    .split-layout {
        grid-template-columns: 1fr;
        gap: 1.5rem;
    }

    .output-section {
        order: 1;
        position: static;
        height: auto;
        min-height: 200px;
    }

    .controls-section {
        order: 2;
    }

    .generated-image {
        max-height: 400px;
    }

    .app-container {
        padding: 1rem;
    }
}

/* Add these fixes to your existing styles.css */

.app-container {
    padding: 2rem;
}

/* .output-section {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 400px;
} */

.generated-image {
    width: 100%;
    height: auto;
    object-fit: contain;
}

.loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

.spinner {
    display: none;
}

.loading .spinner.active {
    display: block;
}

/* Mobile specific styles */
@media (max-width: 768px) {
    .app-container {
        padding: 1rem;
    }

    #prompt {
        height: 60px;
        /* Single line height for mobile */
    }

    .collapsible-section {
        border: 1px solid var(--border-color);
        border-radius: var(--radius);
    }

    .collapsible-header {
        background-color: var(--card-background);
        cursor: pointer;
        display: flex;
        justify-content: space-between;
        align-items: center;
    }

    .collapsible-content {
        display: none;
    }

    .collapsible-content.active {
        display: block;
    }

    .collapsible-header:after {
        content: '▼';
        font-size: 0.8em;
    }

    .collapsible-header.active:after {
        content: '▲';
    }
}

.slider-with-input {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex: 1;
}

/* .slider-with-input input[type="range"] {
    flex: 1;
} */

.slider-with-input input[type="number"] {
    width: 80px;
}

.dimensions-group {
    display: flex;
    gap: 1rem;
}

.dimension-input {
    flex: 1;
}

.dimension-input label {
    display: block;
    margin-bottom: 0.5rem;
}

.dimension-input input {
    width: 100%;
}

.seed-row {
    margin-top: 0.6rem;
}

.hidden {
    display: none;
}


.spinner {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  width: 30px;
  height: 30px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.loading {
  text-align: center;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.loading .spinner {
  display: block;
}

.hidden {
  display: none;
}



netlify.toml
[build]
  publish = "public"
  command = "npm install"

[functions]
  directory = "functions"

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200


package.json
{
  "name": "ai-image-generator",
  "version": "1.0.0",
  "description": "AI Image Generator using Cloudflare API",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "dotenv": "^16.3.1",
    "node-fetch": "^2.7.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}


server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());
app.use(express.static('public'));

app.post('/api/generate-image', async (req, res) => {
  try {
    const { accountId, apiKey, prompt, steps } = req.body;

    if (!accountId || !apiKey || !prompt) {
      return res.status(400).json({ 
        success: false, 
        error: 'Missing required parameters' 
      });
    }

    const response = await fetch(
      `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/@cf/black-forest-labs/flux-1-schnell`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify({ prompt, steps: parseInt(steps) || 4 })
      }
    );

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.errors?.[0]?.message || 'Failed to generate image');
    }

    res.json(data);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ 
      success: false, 
      error: error.message 
    });
  }
});

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});