// OpenAI Image generation API docs: https://platform.openai.com/docs/guides/images?context=node// Open AI Rate Limits: https://platform.openai.com/account/limits//Tweaker's Box// ===============================================// Cost Saving Mode will prevent the user from selecting "premium" options by mistake to prevent exhausting OpenAI creditsconst COST_SAVING_MODE =true;// ===============================================//HTML Templates// ===============================================// Base page template
document.write (`<h1>OpenAI DALL·E API Demo</h1><p>This is a demo application showing off OpenAI's DALL·E models (2&3).</p><div id="modeSelectors"></div><div id="inputSection">PleaseEneter a valid OpenAI API Keywith credits.<div><div><label for="apikeyfield"> API Key:</label><input id="apikeyfield" name="apikey" type="text"/></div><button id="apikeybtn" disabled=true>Set API Key</button></div></div><div id="resultsSection"></div>`);// Cost Saving Mode Warning messageif(COST_SAVING_MODE){
document.write (`<p id="warningMessage"> WARNING:You are running in "Cost Saving Mode" premium features are disabled</p>`);}// Mode selection buttonsconst modeSelectBtns =`<button id="generationModeBtn" disabled>GenerationMode</button><button id="editModeBtn">EditMode</button><button id="variationModeBtn">VariationMode</button>`;// Form template for generation mode// https://platform.openai.com/docs/api-reference/images/createconst generationModeForm =`<h2>GenerationMode</h2><p>Please select the required parameters and then enter your prompt.Once you are satisfied click "generate".</p><p>Image generation may take a few seconds.</p><p><a href="https://platform.openai.com/docs/api-reference/images/create">Please refer to OpenAI documentation for more details</a></p><ul><li><b>Model:</b> Set which version of DALL-E to use for generation </li><li><b>Resolution:</b> Set the resolution of the generated image. The diffrent models have diffrent supported sizes </li><li><b>Quality:</b> Sets image quality. HD is more expensive but produces better results. Only works for DALL-E 3 </li><li><b>Quantity:</b> How many images the model produces. Only n=1 supported for DALL-E 3. Additionally limitations are applied to prevent cost overspend ("Cost saving mode" max=2, else max=4)</li><li><b>Prompt:</b> What image do you want the AI to create </li></ul><div><div><label for="modelSelector">Model:</label><select id="modelSelector" name="model"><option value="">SelectModel</option><option value="dall-e-2">DALL·E 2</option><option value="dall-e-3">DALL·E 3</option></select></div><div><label for="sizeSelector">Resolution:</label><select id="sizeSelector" name="size" disabled><option value="">SelectResolution</option></select></div><div><label for="qualitySelector">Quality:</label><select id="qualitySelector" name="quality" disabled><option value="">SelectQuality</option></select></div><div><label for="quantityField">Quantity:</label><input id="quantityField" name="n" type="number" min=1 max=4 disabled /></div><div><label for="promptField">Prompt:</label><input id="promptField" name="prompt" type="text"disabled placeholder="Enter a Prompt..."/></div><button id="generateBtn" disabled=true>Generate</button></div>`;// Dropdown resolution options for dall-e-3//Smallest size only when in Cost Saving Modeconst dalle3ResOptions =`<option value="">SelectResolution</option><option value="1024x1024">1024x1024</option>
${!COST_SAVING_MODE ?'<option value="1024x1792">1024x1792</option>':'<option value="1024x1792" disabled>1024x1792</option>'}
${!COST_SAVING_MODE ?'<option value="1792x1024">1792x1024</option>':'<option value="1792x1024" disabled>1792x1024</option>'}`;// Dropdown resolution options for dall-e-2//Smallest size only when in Cost Saving Modeconst dalle2ResOptions =`<option value="">SelectResolution</option><option value="256x256">256x256</option>
${!COST_SAVING_MODE ?'<option value="512x512">512x512</option>':'<option value="512x512" disabled>512x512</option>'}
${!COST_SAVING_MODE ?'<option value="1024x1024">1024x1024</option>':'<option value="1024x1024" disabled>1024x1024</option>'}`;// Template for edit mode feature form// https://platform.openai.com/docs/api-reference/images/createEditconst editModeForm =`<h2>EditMode</h2><p>Please select the required parameters and then enter your prompt.Once you are satisfied click "generate".</p><p>This feature is only supported by DALL-E 2.Image generation may take a few seconds.</p><p><a href="https://platform.openai.com/docs/api-reference/images/createEdit">Please refer to OpenAI documentation for more details</a></p><ul><li><b>Image:</b> The base image you want the AI to edit. Must be png and less than 4MB </li><li><b>Mask:</b> The base image with the "area of intrest" set to be transparent. Must be png and less than 4MB </li><li><b>Resolution:</b> Set the resolution of the generated image. </li><li><b>Quantity:</b> How many images the model produces. True max value is 10. In this app limitations are applied to prevent cost overspend ("Cost saving mode" max=2, else max=4)</li><li><b>Prompt:</b> What image do you want the AI to create in the specified area </li></ul><div><div><label for="imageSelector">Image:</label><input id="imageSelector" name="image" type="file"/></div><div><label for="maskSelector">Image:</label><input id="maskSelector" name="mask" type="file" disabled/></div><div><label for="sizeSelector">Resolution:</label><select id="sizeSelector" name="size" disabled><option value="">SelectResolution</option></select></div><div><label for="quantityField">Quantity:</label><input id="quantityField" name="n" type="number" min=1 max=4 disabled /></div><div><label for="promptField">Prompt:</label><input id="promptField" name="prompt" type="text"disabled placeholder="Enter a Prompt..."/></div><button id="generateBtn" disabled=true>Generate</button></div>`;// Template for variation mode feature form// https://platform.openai.com/docs/api-reference/images/createVariationconst variationModeForm =`<h2>VariationMode</h2><p>Please select the image you want to create a variation of and the size of the output image.Once you are satisfied click "generate".</p><p>This feature is only supported by DALL-E 2.Image generation may take a few seconds.</p><p><a href="https://platform.openai.com/docs/api-reference/images/createVariation">Please refer to OpenAI documentation for more details</a></p><ul><li><b>Image:</b> The base image you want the AI to edit. Must be png and less than 4MB </li><li><b>Resolution:</b> Set the resolution of the generated image </li></ul><div><div><label for="imageSelector">Image:</label><input id="imageSelector" name="image" type="file"/></div><div><label for="sizeSelector">Resolution:</label><select id="sizeSelector" name="size" disabled><option value="">SelectResolution</option></select></div><button id="generateBtn" disabled=true>Generate</button></div>`;// ===============================================//CSS// ===============================================//TODO: Load Css through JQuery
$('body').css("margin","20px");
$('body').css("padding","20px");
$('#warningMessage').css("color","red");// ===============================================//JavaScript
let APIKey="";
let selectedMode ="generation";const apiKeyBtn = document.getElementById("apikeybtn");const apiKeyField = document.getElementById("apikeyfield");const inputSection = document.getElementById("inputSection");const resultsSection = document.getElementById("resultsSection");
let generationBtn =null;
let editBtn =null;
let variationBtn =null;
let modelSelector =null;
let resolutionSelector =null;
let qualitySelector =null;
let quantityField =null;
let promptField =null;//Make request to one of the three OpenAI endpoints// Generation: https://api.openai.com/v1/images/generations// Edit: https://api.openai.com/v1/images/edits// Variation: https://api.openai.com/v1/images/variations// Ancient Brain seems to give warning for async arrow functions but code runs fineconst makeOpenAIRequest = async (url, data, contentType)=>{const headers ={"Authorization":`Bearer ${APIKey}`,};// Adding content type for multipart forms causes 400 error if(contentType) headers["Content-Type"]= contentType;
console.log("Payload", data)// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetchconst response = await fetch(url,{
method:"POST",
mode:"cors",
cache:"no-cache",
headers: headers,
body: data
});
console.log(response);return response.json();}// Create JSON request from form dataconst createRequestBodyJson =()=>{const inputs =Array.from(inputSection.getElementsByTagName('input')).concat(Array.from(inputSection.getElementsByTagName('select')));const requestBody ={};for(let input of inputs){if(input.type =="number") requestBody[input.name]= parseInt(input.value);else requestBody[input.name]= input.value;}return JSON.stringify(requestBody);};// Create Multipart form request from form dataconst createRequestBodyForm =()=>{const inputs =Array.from(inputSection.getElementsByTagName('input')).concat(Array.from(inputSection.getElementsByTagName('select')));const requestBody =newFormData();for(let input of inputs){if(input.type =="file") requestBody.append(input.name, input.files[0]);elseif(input.type =="number") requestBody.append(input.name, parseInt(input.value));else requestBody.append(input.name, input.value);}return requestBody;};const submitForm =(url, type)=>{
let requestBody;// If the api endpoint requires the body to be mutlipart form create one else use jsonif(type ==='multipart') requestBody = createRequestBodyForm();else requestBody = createRequestBodyJson();
console.log(requestBody);
console.log(resultsSection);// Add laoding message
resultsSection.innerHTML ="Loading. Please wait.";//Make API request
makeOpenAIRequest(url, requestBody, type !=='multipart'?"application/json":null).then((res)=>{
console.log("response", res);
let displayContent ="";const data = res.data;// Iterate through array of returned imagesfor(let imageData of data){// If using DALL-E 3 there will be an enhanced prompt returned as part of the responseif(imageData.revised_prompt){
displayContent +=`<h4>EnhancedPrompt:</h4> <p>${imageData.revised_prompt}</p>`;}// Create the display image
displayContent +=`<img src="${imageData.url}"/><br/>`;}//Render images
resultsSection.innerHTML = displayContent;}).catch((err)=>{
console.log("error", err);
resultsSection.innerHTML ="An error occured while making the request. Check console for details.";});};// Clears value and disables input elementsconst clearInputs =(inputs)=>{for(let input of inputs){
input.value ="";
input.disabled =true;}};// Add the mode selection buttons and their respective functionalityconst createModeSelectButtons =()=>{//Create the buttons
document.getElementById("modeSelectors").innerHTML = modeSelectBtns;
generationBtn = document.getElementById("generationModeBtn");
editBtn = document.getElementById("editModeBtn");
variationBtn = document.getElementById("variationModeBtn");
generationBtn.addEventListener('click',()=>{//Disable selected button and enable the non-selected ones to create 'tab-like' functionality
generationBtn.disabled =true;
editBtn.disabled =false;
variationBtn.disabled =false;//Clear and currently disabled results
resultsSection.innerHTML =null;
createGenerationForm();
console.log("generation");});
editBtn.addEventListener('click',()=>{//Disable selected button and enable the non-selected ones to create 'tab-like' functionality
generationBtn.disabled =false;
editBtn.disabled =true;
variationBtn.disabled =false;//Clear and currently disabled results
resultsSection.innerHTML =null;
createEditForm();
console.log("edit");});
variationBtn.addEventListener('click',()=>{//Disable selected button and enable the non-selected ones to create 'tab-like' functionality
generationBtn.disabled =false;
editBtn.disabled =false;
variationBtn.disabled =true;//Clear and currently disabled results
resultsSection.innerHTML =null;
createVaritationForm();
console.log("variation");});};// Render and add functionality to the generation mode form const createGenerationForm =()=>{//Render the form
inputSection.innerHTML = generationModeForm;
modelSelector = document.getElementById("modelSelector");
resolutionSelector = document.getElementById("sizeSelector");
qualitySelector = document.getElementById("qualitySelector");
quantityField = document.getElementById("quantityField");
promptField = document.getElementById("promptField");
generateBtn = document.getElementById("generateBtn");
modelSelector.addEventListener('input',(e)=>{
let model = e.target.value;
console.log("Model: ", model);// If a non null value is input enable the next form item (Resolution)if(model){// Clear all proceeding inputs if the value is changed as the diffrent models have diffrent allowed values in other fields
clearInputs([resolutionSelector, qualitySelector, quantityField, promptField, generateBtn]);
resolutionSelector.disabled =false;}// If the value input is null clear all proceeding fields as they cannot be set until the model is selectedelse clearInputs([resolutionSelector, qualitySelector, quantityField, promptField, generateBtn]);// Render the correct resolution dropdown menu items based on selected modelif(model ==="dall-e-3") resolutionSelector.innerHTML = dalle3ResOptions;elseif(model ==="dall-e-2") resolutionSelector.innerHTML = dalle2ResOptions;});
resolutionSelector.addEventListener('input',(e)=>{
let resolution = e.target.value;
console.log("Resolution: ", resolution);// If non-null value is selected enable the next form item (Quality)if(resolution) qualitySelector.disabled =false;//If the field is set to null clear and disable all proceeding fieldselse clearInputs([qualitySelector, quantityField, promptField, generateBtn]);//Set Quality dropdown inputs.
let options =`<option value="">SelectQuality</option><option value="standard">Standard</option>`;// If model is dall-e 3 add quality selector (Unsupported by Dall-e 2)if(modelSelector.value ==="dall-e-3"){//If in cost saving mode grey out 'hd' optionif(!COST_SAVING_MODE) options +=`<option value="hd">HD</option>`;else options +=`<option value="hd" disabled>HD</option>`;}
console.log("quality ", qualitySelector, options, modelSelector.value, modelSelector.value ==="dalle-e-3")
qualitySelector.innerHTML = options;});
qualitySelector.addEventListener('input',(e)=>{
let quality = e.target.value;
console.log("Quality: ", quality);// If non-null value is selected enable the next form item (Quantity)if(quality) quantityField.disabled =false;//If the field is set to null clear and disable all proceeding fieldselse clearInputs([quantityField, promptField, generateBtn]);});
quantityField.addEventListener('input',(e)=>{
let quantity = e.target.value;// Limit the amount of images that can be generated at once// DALL-E-2 Cost saving mode: Max = 2// DALL-E-2 Normal: Max = 4. <-- The true API limit is greater but this is imposed to prevent overspending// DALL-E-3 Normal: Max = 1 <-- This is a true limitation of the model and this model can only output 1 image at a timeif(quantity <1|| modelSelector.value ==="dall-e-3"){
quantity =1;
quantityField.value =1;}if(COST_SAVING_MODE &&(quantity >2)){
quantity =2;
quantityField.value =2;}elseif(quantity >4){
quantity =4;
quantityField.value =4;}
console.log("Quantity: ", quantity);// If non-null value is selected enable the next form item (Prompt)if(quantity) promptField.disabled =false;//If the field is set to null clear and disable all proceeding fieldselse clearInputs([promptField, generateBtn]);});
promptField.addEventListener('input',(e)=>{
let prompt = e.target.value;// If non-null value is selected enable the "Generate" button allowing the user to submit the requestif(prompt) generateBtn.disabled =false;else clearInputs([generateBtn]);});
generateBtn.addEventListener('click',(e)=>{// Submit form inputs
submitForm('https://api.openai.com/v1/images/generations','json');});};// Render and add functionality to edit mode form const createEditForm =()=>{//Render the edit form
inputSection.innerHTML = editModeForm;
imageSelector = document.getElementById("imageSelector");
maskSelector = document.getElementById("maskSelector");
resolutionSelector = document.getElementById("sizeSelector");
quantityField = document.getElementById("quantityField");
promptField = document.getElementById("promptField");
generateBtn = document.getElementById("generateBtn");
imageSelector.addEventListener('input',(e)=>{
let image = e.target.files[0];
console.log("Image: ", image);// If non-null value is selected enable the next form item (Mask)if(image){
clearInputs([resolutionSelector, maskSelector, quantityField, promptField, generateBtn]);
maskSelector.disabled =false;}else clearInputs([resolutionSelector, maskSelector, quantityField, promptField, generateBtn]);});
maskSelector.addEventListener('input',(e)=>{
let mask = e.target.files[0];
console.log("Mask: ", mask);// If non-null value is selected enable the next form item (Resolution)if(mask){
clearInputs([resolutionSelector, quantityField, promptField, generateBtn]);
resolutionSelector.disabled =false;}else clearInputs([resolutionSelector, quantityField, promptField, generateBtn]);
resolutionSelector.innerHTML = dalle2ResOptions;});
resolutionSelector.addEventListener('input',(e)=>{
let resolution = e.target.value;
console.log("Resolution: ", resolution);// If non-null value is selected enable the next form item (Quantity)if(resolution) quantityField.disabled =false;else clearInputs([quantityField, promptField, generateBtn]);});
quantityField.addEventListener('input',(e)=>{// Limit the amount of images that can be generated at once// Cost saving mode: Max = 2// Normal: Max = 4. <-- The true API limit is greater but this is imposed to prevent overspending
let quantity = e.target.value;if(quantity <1){
quantity =1;
quantityField.value =1;}if(COST_SAVING_MODE &&(quantity >2)){
quantity =2;
quantityField.value =2;}elseif(quantity >4){
quantity =4;
quantityField.value =4;}
console.log("Quantity: ", quantity);// If non-null value is selected enable the next form item (Prompt)if(quantity) promptField.disabled =false;else clearInputs([promptField, generateBtn]);});
promptField.addEventListener('input',(e)=>{
let prompt = e.target.value;// If non-null value is selected enable the "Generate" button allowing the user to submit the requestif(prompt) generateBtn.disabled =false;else clearInputs([generateBtn]);});
generateBtn.addEventListener('click',(e)=>{// Submit form inputs
submitForm('https://api.openai.com/v1/images/edits','multipart');});};// Render and add functionality to edit mode form const createVaritationForm =()=>{// Render the variations form
inputSection.innerHTML = variationModeForm;
imageSelector = document.getElementById("imageSelector");
resolutionSelector = document.getElementById("sizeSelector");
generateBtn = document.getElementById("generateBtn");
imageSelector.addEventListener('input',(e)=>{
let image = e.target.files[0];
console.log("Image: ", image);// If non-null value is selected enable the next form item (Resolution)if(image){
clearInputs([resolutionSelector, generateBtn]);
resolutionSelector.disabled =false;}else clearInputs([resolutionSelector, generateBtn]);
resolutionSelector.innerHTML = dalle2ResOptions;});
resolutionSelector.addEventListener('input',(e)=>{
let resolution = e.target.value;
console.log("Resolution: ", resolution);// If non-null value is selected enable the "Generate" button allowing the user to submit the requestif(resolution) generateBtn.disabled =false;else clearInputs([generateBtn]);});
generateBtn.addEventListener('click',(e)=>{// Submit form inputs
submitForm('https://api.openai.com/v1/images/variations','multipart');});};// Save the user submitted API Key for use in requestsconst setAPIKey =(e)=>{
e.preventDefault();const apiKeyField = document.getElementById("apikeyfield");APIKey= apiKeyField.value;
console.log("Set API Key: ", apiKeyField.value);
createModeSelectButtons();
createGenerationForm();};//"Set Api Key" button event listener
apiKeyBtn.addEventListener('click', setAPIKey);//Onlt enable the "Set Api Key" button when the user has input something in the field
apiKeyField.addEventListener('input',(e)=>{if(e.target.value) apiKeyBtn.disabled =false;else apiKeyBtn.disabled =true;});