| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Image Generator bfl api by mahmoudshokri</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| margin: 0; |
| padding: 0; |
| background-color: #f9f9f9; |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| overflow: hidden; |
| } |
| |
| .header { |
| background-color: #007bff; |
| color: white; |
| padding: 15px; |
| text-align: center; |
| font-size: 24px; |
| position: sticky; |
| top: 0; |
| z-index: 2; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .header img { |
| max-height: 50px; |
| margin-right: 10px; |
| cursor: pointer; |
| } |
| |
| .header img:hover + .tooltip { |
| display: block; |
| } |
| |
| .tooltip { |
| display: none; |
| position: absolute; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.7); |
| padding: 5px; |
| border-radius: 5px; |
| font-size: 14px; |
| margin-left: -30px; |
| margin-top: -30px; |
| } |
| |
| .container { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| flex: 1; |
| overflow-y: auto; |
| } |
| |
| .api-info { |
| margin: 19px; |
| text-align: center; |
| font-size: 16px; |
| color: #333; |
| } |
| |
| .api-info a { |
| color: #007bff; |
| text-decoration: none; |
| } |
| |
| .api-info a:hover { |
| text-decoration: underline; |
| } |
| |
| |
| .controls { |
| width: 80%; |
| margin: 20px auto; |
| display: flex; |
| flex-direction: column; |
| align-items: stretch; |
| } |
| |
| .controls label { |
| margin-bottom: 5px; |
| font-size: 14px; |
| color: #333; |
| } |
| |
| .controls input[type="password"], |
| .controls input[type="text"], |
| .controls textarea { |
| width: 100%; |
| padding: 10px; |
| margin-bottom: 10px; |
| border: 1px solid #ccc; |
| border-radius: 5px; |
| font-size: 14px; |
| box-sizing: border-box; |
| transition: border-color 0.3s; |
| } |
| |
| .controls input[type="password"]:focus, |
| .controls input[type="text"]:focus, |
| .controls textarea:focus { |
| border-color: #007bff; |
| } |
| |
| .controls button { |
| padding: 10px; |
| background-color: #007bff; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| font-size: 16px; |
| transition: background-color 0.3s; |
| } |
| |
| .controls button:hover { |
| background-color: #0056b3; |
| } |
| |
| .sliders { |
| display: flex; |
| flex-direction: column; |
| margin: 10px 0; |
| } |
| |
| .slider-container { |
| display: flex; |
| align-items: center; |
| margin-bottom: 15px; |
| flex-wrap: nowrap; |
| } |
| |
| .slider-container label { |
| margin-right: 10px; |
| font-size: 14px; |
| color: #333; |
| width: 100px; |
| } |
| |
| .slider-value { |
| width: 40px; |
| text-align: center; |
| font-size: 14px; |
| margin-left: 10px; |
| } |
| |
| .output { |
| width: 80%; |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); |
| gap: 20px; |
| margin: 20px auto; |
| } |
| |
| .output img { |
| width: 100%; |
| height: auto; |
| border-radius: 10px; |
| cursor: pointer; |
| max-height: 620px; |
| object-fit: contain; |
| transition: transform 0.3s; |
| } |
| |
| .output img:hover { |
| transform: scale(1.05); |
| } |
| |
| .image-container { |
| position: relative; |
| } |
| |
| .delete-button, .download-button { |
| position: absolute; |
| top: 10px; |
| background-color: red; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| padding: 5px 10px; |
| |
| } |
| |
| .delete-button { |
| left: 25px; |
| background: border-box; |
| width: 55px; |
| } |
| |
| .download-button { |
| left: 75px; |
| width: 52px; |
| background: border-box; |
| } |
| |
| .image-container:hover .delete-button, |
| .image-container:hover .download-button { |
| display: block; |
| } |
| |
| #status { |
| text-align: center; |
| color: #333; |
| margin-top: 10px; |
| font-size: 14px; |
| } |
| |
| .modal { |
| display: none; |
| position: fixed; |
| z-index: 1000; |
| left: 0; |
| top: 0; |
| width: 100%; |
| height: 100%; |
| overflow: auto; |
| background-color: rgba(0, 0, 0, 0.8); |
| justify-content: center; |
| align-items: center; |
| padding: 20px; |
| } |
| |
| .modal-content { |
| background-color: white; |
| padding: 50px; |
| border-radius: 10px; |
| max-width: 800px; |
| width: 100%; |
| display: flex; |
| flex-direction: row; |
| align-items: center; |
| } |
| |
| .modal img { |
| max-width: 50%; |
| height: auto; |
| border-radius: 10px; |
| margin-right: 20px; |
| object-fit: contain; |
| } |
| |
| .prompt-info { |
| margin-top: 0px; |
| font-size: 14px; |
| max-width: 300px; |
| } |
| |
| .copy-prompt { |
| background-color: #007bff; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| padding: 5px 10px; |
| margin-top: 10px; |
| } |
| |
| .copy-prompt:hover { |
| background-color: #0056b3; |
| } |
| |
| @media (max-width: 600px) { |
| .slider-container label { |
| width: auto; |
| } |
| |
| .slider-container { |
| flex-direction: column; |
| align-items: stretch; |
| } |
| |
| .slider-value { |
| margin-left: 0; |
| margin-top: 5px; |
| } |
| |
| .modal img { |
| max-width: 100%; |
| } |
| } |
| |
| input[type="range"] { |
| width: 100%; |
| } |
| |
| .toggle-button { |
| background-color: #007bff; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| padding: 5px 10px; |
| margin-left: 10px; |
| font-size: 14px; |
| } |
| |
| .toggle-button:hover { |
| background-color: #0056b3; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="header"> |
| <img src="palestine.png" alt="Logo" id="logo"> |
| <span class="tooltip">Free Palestine</span> |
| Pray for Palestine |
| </div> |
|
|
| <div class="api-info"> |
| <p>Get the API key and 50 free credits to generate images and purchase from here<a href="https://api.bfl.ml/auth/login#" target="_blank">BFL API Portal</a>.</p> |
| </div> |
|
|
| <div class="container"> |
| <div class="controls"> |
| <label for="api-key">API Key:</label> |
| <div style="display: flex; align-items: center;"> |
| <input type="password" id="api-key" placeholder="Enter your API Key"> |
| <button class="toggle-button" id="toggle-api-key">Show</button> |
| </div> |
|
|
| <label for="prompt">Image Description:</label> |
| <textarea id="prompt" placeholder="Describe the image you want to generate" rows="4"></textarea> |
|
|
| <div class="sliders"> |
| <div class="slider-container"> |
| <label for="width">Width:</label> |
| <input type="range" id="width" min="512" max="1440" step="64" value="1024"> |
| <input class="slider-value" type="text" id="width-value" value="1024" readonly> |
| </div> |
|
|
| <div class="slider-container"> |
| <label for="height">Height:</label> |
| <input type="range" id="height" min="512" max="1440" step="64" value="768"> |
| <input class="slider-value" type="text" id="height-value" value="768" readonly> |
| </div> |
|
|
| <div class="slider-container"> |
| <label for="steps">Steps:</label> |
| <input type="range" id="steps" min="1" max="100" step="1" value="8"> |
| <input class="slider-value" type="text" id="steps-value" value="8" readonly> |
| </div> |
| </div> |
|
|
| <button id="generate-btn">Generate Image</button> |
| <p id="status"></p> |
| </div> |
|
|
| <div class="output" id="output-container"></div> |
| </div> |
|
|
| <div class="modal" id="modal"> |
| <div class="modal-content"> |
| <img id="modal-image" alt="Enlarged Image"> |
| <div> |
| <div class="prompt-info" id="prompt-info"></div> |
| <div class="settings-info" id="settings-info"></div> |
| <button id="copy-prompt" class="copy-prompt">Copy Prompt</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const generateBtn = document.getElementById('generate-btn'); |
| const promptInput = document.getElementById('prompt'); |
| const apiKeyInput = document.getElementById('api-key'); |
| const widthSlider = document.getElementById('width'); |
| const heightSlider = document.getElementById('height'); |
| const stepsSlider = document.getElementById('steps'); |
| const widthValue = document.getElementById('width-value'); |
| const heightValue = document.getElementById('height-value'); |
| const stepsValue = document.getElementById('steps-value'); |
| const statusElement = document.getElementById('status'); |
| const outputContainer = document.getElementById('output-container'); |
| const modal = document.getElementById('modal'); |
| const modalImage = document.getElementById('modal-image'); |
| const promptInfo = document.getElementById('prompt-info'); |
| const settingsInfo = document.getElementById('settings-info'); |
| const copyPromptBtn = document.getElementById('copy-prompt'); |
| const toggleApiKeyBtn = document.getElementById('toggle-api-key'); |
| |
| let isApiKeyVisible = false; |
| |
| window.onload = () => { |
| const savedPrompt = localStorage.getItem('lastPrompt'); |
| const savedApiKey = localStorage.getItem('apiKey'); |
| if (savedPrompt) { |
| promptInput.value = savedPrompt; |
| } |
| if (savedApiKey) { |
| apiKeyInput.value = savedApiKey; |
| } |
| |
| loadImagesFromStorage(); |
| }; |
| |
| function loadImagesFromStorage() { |
| const savedImages = JSON.parse(localStorage.getItem('images')) || []; |
| savedImages.forEach(imgObj => addImageToOutput(imgObj.url, imgObj.prompt, imgObj.width, imgObj.height, imgObj.steps)); |
| } |
| |
| widthSlider.addEventListener('input', () => { |
| widthValue.value = widthSlider.value; |
| localStorage.setItem('lastWidth', widthSlider.value); |
| }); |
| |
| heightSlider.addEventListener('input', () => { |
| heightValue.value = heightSlider.value; |
| localStorage.setItem('lastHeight', heightSlider.value); |
| }); |
| |
| stepsSlider.addEventListener('input', () => { |
| stepsValue.value = stepsSlider.value; |
| localStorage.setItem('lastSteps', stepsSlider.value); |
| }); |
| |
| generateBtn.addEventListener('click', async () => { |
| const prompt = promptInput.value; |
| const apiKey = apiKeyInput.value; |
| const width = widthSlider.value; |
| const height = heightSlider.value; |
| const steps = stepsSlider.value; |
| |
| if (!prompt || !apiKey) { |
| alert('Please enter both a prompt and API key!'); |
| return; |
| } |
| |
| localStorage.setItem('lastPrompt', prompt); |
| localStorage.setItem('apiKey', apiKey); |
| |
| statusElement.textContent = 'Generating image...'; |
| |
| try { |
| const requestId = await sendImageGenerationRequest(prompt, width, height, steps, apiKey); |
| const imageUrl = await pollResult(requestId, apiKey); |
| addImageToOutput(imageUrl, prompt, width, height, steps); |
| statusElement.textContent = 'Image generated successfully!'; |
| } catch (error) { |
| statusElement.textContent = `Error: ${error.message}`; |
| } |
| }); |
| |
| toggleApiKeyBtn.addEventListener('click', () => { |
| isApiKeyVisible = !isApiKeyVisible; |
| apiKeyInput.type = isApiKeyVisible ? 'text' : 'password'; |
| toggleApiKeyBtn.textContent = isApiKeyVisible ? 'Hide' : 'Show'; |
| }); |
| |
| function addImageToOutput(imageUrl, prompt, width, height, steps) { |
| const container = document.createElement('div'); |
| container.className = 'image-container'; |
| |
| const img = document.createElement('img'); |
| img.src = imageUrl; |
| img.alt = "Generated Image"; |
| img.onclick = () => openModal(imageUrl, prompt, width, height, steps); |
| |
| |
| const deleteButton = document.createElement('button'); |
| deleteButton.className = 'delete-button'; |
| const deleteIcon = document.createElement('img'); |
| deleteIcon.src = 'delete.png'; |
| |
| deleteButton.appendChild(deleteIcon); |
| deleteButton.onclick = (e) => { |
| e.stopPropagation(); |
| container.remove(); |
| removeImageFromStorage(imageUrl); |
| }; |
| |
| |
| const downloadButton = document.createElement('button'); |
| downloadButton.className = 'download-button'; |
| const downloadIcon = document.createElement('img'); |
| downloadIcon.src = 'download.png'; |
| |
| downloadButton.appendChild(downloadIcon); |
| downloadButton.onclick = (e) => { |
| e.stopPropagation(); |
| downloadImage(imageUrl); |
| }; |
| |
| container.appendChild(img); |
| container.appendChild(deleteButton); |
| container.appendChild(downloadButton); |
| outputContainer.prepend(container); |
| |
| let savedImages = JSON.parse(localStorage.getItem('images')) || []; |
| if (!savedImages.some(imgObj => imgObj.url === imageUrl)) { |
| savedImages.push({ url: imageUrl, prompt: prompt, width: width, height: height, steps: steps }); |
| localStorage.setItem('images', JSON.stringify(savedImages)); |
| } |
| } |
| |
| function removeImageFromStorage(imageUrl) { |
| let savedImages = JSON.parse(localStorage.getItem('images')) || []; |
| savedImages = savedImages.filter(imgObj => imgObj.url !== imageUrl); |
| localStorage.setItem('images', JSON.stringify(savedImages)); |
| } |
| |
| function openModal(imageUrl, prompt, width, height, steps) { |
| modal.style.display = 'flex'; |
| modalImage.src = imageUrl; |
| promptInfo.innerText = `Prompt: ${prompt}`; |
| settingsInfo.innerText = `Width: ${width}, Height: ${height}, Steps: ${steps}`; |
| } |
| |
| function downloadImage(imageUrl) { |
| const a = document.createElement('a'); |
| a.href = imageUrl; |
| a.download = 'generated-image.png'; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| } |
| |
| copyPromptBtn.onclick = () => { |
| const promptText = promptInfo.innerText.replace('Prompt: ', ''); |
| navigator.clipboard.writeText(promptText); |
| }; |
| |
| modal.onclick = (event) => { |
| if (event.target === modal) { |
| modal.style.display = 'none'; |
| } |
| }; |
| |
| async function sendImageGenerationRequest(prompt, width, height, steps, apiKey) { |
| const response = await fetch('https://api.bfl.ml/v1/image', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'x-key': apiKey, |
| }, |
| body: JSON.stringify({ |
| prompt: prompt, |
| width: parseInt(width), |
| height: parseInt(height), |
| steps: parseInt(steps), |
| }), |
| }); |
| |
| const data = await response.json(); |
| return data.id; |
| } |
| |
| async function pollResult(requestId, apiKey) { |
| while (true) { |
| const result = await getResult(requestId, apiKey); |
| const status = result.status; |
| |
| if (status === 'Ready') { |
| return result.result.sample; |
| } else if (['Error', 'Content Moderated', 'Request Moderated', 'Task not found'].includes(status)) { |
| throw new Error(`Image generation failed. Status: ${status}`); |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, 1000)); |
| } |
| } |
| |
| async function getResult(requestId, apiKey) { |
| const response = await fetch(`https://api.bfl.ml/v1/get_result?id=${requestId}`, { |
| method: 'GET', |
| headers: { |
| 'x-key': apiKey, |
| }, |
| }); |
| |
| const data = await response.json(); |
| return data; |
| } |
| </script> |
|
|
|
|
| </body> |
| </html> |