Code viewer for World: Showcase AI API

// Cloned by Juraj Kuruc on 30 Nov 2023 from World "Chat with GPT model" by Starter user 
// Please leave this clone trail here.
 


// talk to OpenAI GPT model (ChatGPT)
// adapted from:
//  https://platform.openai.com/docs/api-reference/making-requests



let sandboxApikey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiOTE3ZDRiODYtNmExZi00Mjk2LWJmOGUtODI2ODA3NDIzOWMyIiwidHlwZSI6InNhbmRib3hfYXBpX3Rva2VuIn0.QJseruzX_KLVWbckHOJoXm8vQ5ZGzMnPMckxk2c2Ovo";
let realApikey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiOTE3ZDRiODYtNmExZi00Mjk2LWJmOGUtODI2ODA3NDIzOWMyIiwidHlwZSI6ImFwaV90b2tlbiJ9.LFMtO0qoMmncAwcP8uL82p-h2zZBFbL9-JP0n6pGmes";
let apiKey=sandboxApikey;
let question="What's in the picture?";


let painting = false;
let penSize = 10;
let penColor;
let prevX, prevY;
let uploadedImage;
let rubberMode = false;
let pictureDescriptionInput;
let statusPanelText;

let resolution='256x256';
let temperature=0,maxTokens=64;
let describeProvider="alephalpha";
let generatorProvider="deepai";


let panelStyle=`
      background-color: #f1f1f1;
      padding: 20px;
      border-radius: 10px;
      width: 500px;
      height: 500px;
      overflow-y: scroll;
      overflow-x: hidden;
      font-size: 14px;
      font-family: monospace;
      line-height: 1.5;
      color: #000000;
      font-weight: bold;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
      border: 1px solid rgba(0, 0, 0, 0.2);
      position: absolute;
      left: 10px;
      z-index: 1;
      cursor: pointer;
    `;


    $('body').css( "margin", "20px" );
    $('body').css( "padding", "20px" );
    
    $('head').append( `
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
      <title>UI API Showcase</title>
      <style>
        body {
          margin: 0;
          overflow: hidden; /* Prevent scrolling when canvas is larger than window */
        }
    
        .statusText{
            font-weight: normal;
        }
        
        .defaultAllign {
          margin-top: 10px;
          margin-bottom: 10px;
          margin-left: 5px;
          color: black;
        }
        .penButton {
          margin-top: 10px;
          margin-bottom: 10px;
          margin-left: 5px;
          cursor: pointer;
        }
        .aiButton {
          margin-bottom: 10px;
          margin-top: 10px;
          margin-left: 5px;
          cursor: pointer;
        }
        .canvas{
          border: 2px solid #333;
          left: 100px;
        }
    
        .wrappingContainer {
          display: flex;
          align-items: center;
        }
    
        .defaultLabel {
          font-size: 14px;
          margin-right: 5px;
        }
       
      </style>
    `);
    
function setup() {
  let toolPanelWidth = 560;
  background(255);
  penColor = color(0); // Set pen color to black
  createPenPanel();
  canvas=createCanvas(windowWidth-600, windowHeight-20);
  canvas.position(560, 10);
  canvas.class('canvas');
  createAiPanel();
}

function createPenPanel() {
    let penPanel = createDiv("Pen Panel:");
    penPanel.style(panelStyle);
    penPanel.style("height: 120px;");
    penPanel.id('penPanel');
    
    penPanel.child(createDiv());
    
    penPanel.child(createStyleButton('Small Pen','penButton',() => {
      penSize = 5;
      rubberMode = false; 
    }));
    
    penPanel.child(createStyleButton('Medium Pen','penButton',() => {
      penSize = 15;
      rubberMode = false; 
    }));
    
    penPanel.child(createStyleButton('Large Pen','penButton',() => {
      penSize = 45;
      rubberMode = false; 
    }));
    
    penPanel.child(createStyleButton('Rubber','penButton',() => {
      rubberMode = !rubberMode;
    }));

    penPanel.child(createStyleButton('Clear Canvas','penButton',() => {
      background(255);
      document.getElementById('uploadButton').value='';
    }));
    
    let colorPickerContainer = createDiv();
    colorPickerContainer.class('wrappingContainer');
    penPanel.child(colorPickerContainer);
    
    let colorPickerLabel = createDiv('Color:');
    colorPickerLabel.class('defaultLabel');
    colorPickerContainer.child(colorPickerLabel);
    
    let colorPicker = createInput('#000000', 'color');
    colorPicker.id('colorPicker');
    colorPicker.input(() => {
      penColor = color(colorPicker.value());
      rubberMode = false; 
    });
    colorPickerContainer.child(colorPicker);
    
    uploadButton = createFileInput(handleFile);
    uploadButton.id('uploadButton');
    uploadButton.class('uploadButton');
    penPanel.child(uploadButton);
    
    
}

function createAiPanel() {
    aiPanel = createDiv();
    aiPanel.id('aiPanel');
    aiPanel.position(0, 250);
    aiPanel.style(panelStyle);
    
    //aiPanel.mousePressed(() => {aiPanel.style('display', 'none');});
    
    let aiPanelTitle = createDiv('AI Panel');
    aiPanel.child(aiPanelTitle);

    /* 
    * Add labeled "Picture description" text field into AI Panel 
    */
    
    let pictureDescriptionLabel = createDiv('Picture description:');
    pictureDescriptionLabel.style('font-weight', 'bold');
    pictureDescriptionInput = createInput('Two green apples');
    pictureDescriptionInput.attribute('type', 'text');
    pictureDescriptionInput.attribute('id', 'pictureDescription');
    pictureDescriptionInput.size(aiPanel.size().width-50,pictureDescriptionInput.size().height)
    aiPanel.child(pictureDescriptionLabel);
    aiPanel.child(pictureDescriptionInput);
    
    /*
    * Butons 
    */    
    aiPanel.child(createStyleButton('Generate','aiButton',generateImage));
    aiPanel.child(createStyleButton('Describe','aiButton',describeImage));
    aiPanel.child(createStyleButton('Redraw','aiButton',() => describeImage(generateImage)));
    
    //Resolution
    aiPanel.child(createSelector('Resolution:',null,['256x256','512x512','1024x1024'],(value) => {
        resolution=value;}));
    
    //Temperature
    container = createDiv('');
    container.class('wrappingContainer');
    container.child(createDiv('Temperature:').class('defaultLabel'));
    var temperatureValueLabel = createDiv(temperature).class('defaultLabel');
    var temperatureSlider = createSlider(0,1,0,0.01).class('defaultAllign');
    
    temperatureSlider.input(() => { 
            temperature=temperatureSlider.value();
            temperatureValueLabel.html(temperature);
        });
    container.child(temperatureSlider);
    container.child(temperatureValueLabel);
    aiPanel.child(container);
    
    
    // Max Tokens
    container = createDiv('');
    container.class('wrappingContainer');
    container.child(createDiv('Max tokens:').class('defaultLabel'));
    
    var tokenValueLabel = createDiv(maxTokens).class('defaultLabel');
    var tokenSlider = createSlider(1,2048,maxTokens,1).class('defaultAllign');
    tokenSlider.input(() => { 
            maxTokens=tokenSlider.value();
            tokenValueLabel.html(maxTokens);
        });
    container.child(tokenSlider);
    container.child(tokenValueLabel);
    aiPanel.child(container);
    
    // Generator Provider
    aiPanel.child(createSelector('Generator Provider:',(selector) => getImageProviders('image','generation',selector),['deepai'],(value) => { generatorProvider=value; }));
    // Describe Provider
    aiPanel.child(createSelector('Describe Provider:',(selector) => getImageProviders('image','question_answer',selector),['alephalpha'],(value) => {describeProvider=value;}));
    // Api key
    aiPanel.child(createSelector('Api Key:',null,['Sanbox Key','Real Key'],(value) => {
        if(value === "Real Key"){
            apiKey=realApikey;
        }else{
            apiKey=sandboxApikey;    
        }
    }));   
    
    
    // Status panel
    statusPanelText = createDiv('');
    statusPanelReset();
    statusPanelText.class('statusText');
    aiPanel.child(createDiv('Info:').child(statusPanelText));
    
}

function isSandboxMode(){
    return apiKey===sandboxApikey;
}

/*
 *  Creates selector with given options.
 *  If populateSelectorCallback is not null, it will be used to re-populate options.
 *  setValueCallback function is called anytime selector changes it value.
 */
function createSelector(name,populateSelectorCallback,options,setValueCallback){
    var container = createDiv('');
    container.class('wrappingContainer');
    container.child(createDiv(name).class('defaultLabel'));
    var selector = createSelect(name).class('defaultAllign');
    options.forEach(option => selector.option(option));
    container.child(selector);

    if(populateSelectorCallback!==null){
        populateSelectorCallback(selector);
    }

    selector.input(()=>{
        setValueCallback(selector.value());
    });

    return container;
}
/*
* Calls edenai and populate given selector with name of image providers for given subfeature
*/
function getImageProviders(featureName,subfeatureName,selector){
    
    console.log("Getting all image providers for "+subfeatureName);
    const settings = {
        async: true,
        crossDomain: true,
        url: 'https://api.edenai.run/v2/info/provider_subfeatures?feature__name='+featureName+'&subfeature__name='+subfeatureName,
        method: 'GET',
        headers: {
          accept: 'application/json'
          }
        }
    $.ajax(settings)
            .done(function (response) {
                console.log(response);
                response.forEach(item => {
                    selector.option(item.provider.name,item.provider.name);
                    console.log(item.provider.name);    
                });
            })
            .fail(error => {
                console.error(error);
            });
}

function createStyleButton(name, styleClass, mousePressCallback){
    let btn = createButton(name);
    btn.class(styleClass);
    btn.mousePressed(mousePressCallback);
    return btn;
}

function handleFile(file) {
  if (file.type === 'image') {
    loadImage(file.data, img => {
      uploadedImage = img;
      image(uploadedImage, 0, 0);
    });
  } else {
    console.log('Please upload a valid image file (JPEG).')
  }
}

function generateImage() {
    statusPanelText.html(htmlTextInColor("Generating image...","blue"));
    
    var description=pictureDescriptionInput.value();
    console.log("Generating image for description: "+description);
    //'stabilityai,openai,deepai,replicate'
    var provider=generatorProvider;
    
    if(description === ''){
        console.log("No Image description provided!");
        statusPanelText.html("No Image description provided!");
        return;
    }
    
    const settings = {
        "url": 'https://api.edenai.run/v2/image/generation',
        "method": 'POST',
        "headers": {
            "Accept": 'application/json',
            'content-type': 'application/json',
            "Authorization": "Bearer " + apiKey,
        },
        "processData": false,
        "data": JSON.stringify({
                response_as_dict: true,
                attributes_as_list: false,
                show_original_response: false,
                resolution: resolution,
                num_images: 1,
                providers: provider,
                text: description
        })
      };
       $.ajax(settings)
        .fail(error => handleErrorResponse(error))
        .done(response => {
            console.log(response);
            if(response[provider].status === 'fail'){
                console.error(response[provider].error);
                statusPanelText.html(htmlTextInColor(response[provider].error.message,'red'));
            }else{
                background(255);
                console.log("Image data...");
                console.log("data:image/png;base64,"+response[provider].items[0].image);
                img=createImg("data:image/png;base64,"+response[provider].items[0].image,'generated', 'anonymous', img => {
                    image(img, 0, 0);
                })
                img.remove();
                displayCost(response[provider].cost);
            }
        });
    
}
 /**
  * Calls edenai api to 
  */
 
function describeImage(additionalCallback) {
    statusPanelText.html(htmlTextInColor("Guessing what is in the image...","blue"));
    
    var provider=describeProvider;
    var form = getFormDataImageQuestion(question,provider);
    const settings = {
      "url": "https://api.edenai.run/v2/image/question_answer",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer " + apiKey,
        "Accept": "application/json"
      },
      "processData": false,
      "mimeType": "multipart/form-data",
      "contentType": false,
      "data": form,
      "success": function (response) {console.log(response);}
    };
    $.ajax(settings)
        .fail(error => handleErrorResponse(error))
        .done(response => {
            responseJson = JSON.parse(response);
            // remove question from answer
            description=responseJson[provider].answers[0].replace(question, '');
            pictureDescriptionInput.value(description);
            displayCost(responseJson[provider].cost);
            if(additionalCallback!== undefined && additionalCallback!==null ){
                additionalCallback();
            }
        });
}

function displayCost(cost){
    if(!isSandboxMode()){
        statusPanelText.html(htmlTextInColor("Last call price: "+cost+"$","blue"));    
    }else{
        statusPanelReset();
    }
}

/*
 * Returns FormData to be sent for answer question feature
 */ 
function getFormDataImageQuestion(question,providers){
    
    var form = new FormData();
    form.append("providers", providers);
    form.append("response_as_dict", "true");
    form.append("attributes_as_list", "false");
    form.append("show_original_response", "false");
    //form.append("settings", "[object Object]");
    
    var base64Image = document.getElementById("defaultCanvas0").toDataURL('image/png').split(',')[1];
    
    // Convert base64 to Blob
    var byteCharacters = atob(base64Image);
    var byteNumbers = new Array(byteCharacters.length);
    for (let i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i);
    }
    var byteArray = new Uint8Array(byteNumbers);
    var blob = new Blob([byteArray], { type: 'image/png' }); // Adjust the MIME type accordingly
    form.append("file", blob, 'image.png');
    form.append("temperature", temperature);
    form.append("question", question);
    form.append("max_tokens", maxTokens);
    return form;
}


function htmlTextInColor(text,color){
    return "<font color="+color+"><b>"+text+"</b></font>"
}

function statusPanelReset(){
    statusPanelText.html(htmlTextInColor("In the Sandbox mode the api calls are free of charge.","blue"));    
}

function handleErrorResponse(response){
    console.error(response.responseText);
    statusPanelText.html(htmlTextInColor(response.responseText,"red"));
}

/*
 *  Bellow are p5 methods definitions
 */


function draw() {
   
  if (painting) {
    stroke(penColor);
    strokeWeight(penSize);
    if (rubberMode) {
      // Erase by drawing with a white stroke
      stroke(255);
    }
    line(prevX, prevY, mouseX, mouseY);
    prevX = mouseX;
    prevY = mouseY;
  }
  
}

function mousePressed() {
  painting = true;
  prevX = mouseX;
  prevY = mouseY;
}

function mouseReleased() {
  painting = false;
}