Code viewer for World: Vibe Coding Practical

// Clone by Isabella Avila of:
// "Chat with GPT model" by Starter user
// https://ancientbrain.com/world.php?world=2850716357
// Please leave this clone trail here.



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


const openaiURL = "https://api.openai.com/v1/chat/completions";    // can POST to this 3rd party URL
  
const themodel = "gpt-3.5-turbo";       // the OpenAI model we are going to talk to 
    

// default API key and prompt:

var apikey = "";
var theprompt = "hello";
var message = [];

// store previous code outputs
var responses = [];
var index;

// debugging messages
var debugMessage = []
var debugPrompt = "Give me debugging suggestions for this code";
 
// default body is margin 0 and padding 0 
// give it more whitespace:

  $('body').css( "margin", "20px" );
  $('body').css( "padding", "20px" );

 // Add some things to the console to aid the user
 console.log("Welcome to the console!");
 console.log("If you wish to see the history of your conversation with the GPT model, type: message");
 console.log("If you wish to see a list of all the code that the GPT model has returned, type: responses");
 console.log("Other useful information: if there are any errors when trying to run the code that is produced by the GPT model, it will appear here");
 
 // Picked colors for the site using: https://htmlcolorcodes.com/color-picker/
 
document.write ( `

<h1 style="padding: 1rem; background-color: BED8E5; width: fit-content; border-radius: 2rem;"> Vibe Coding </h1>

<div style="margin-inline: 1rem;">
Running World:
<a href='https://ancientbrain.com/world.php?world=2850716357'>Vibe Coding</a>.
<br> 
Chat with
  <A HREF="https://platform.openai.com/docs/models/overview">GPT 3.5</A>
 using the 
<A HREF="https://en.wikipedia.org/wiki/OpenAI">OpenAI    </A>  API.
 <br> 
 Create an application with Vibe Coding using Chat GPT!
</div>

<pre>

</pre>

<div style="background-color: C7E5BE; padding: 1rem; padding-bottom: 2rem; width: fit-content; border: 1px solid black; border-radius: 0.5rem;">
<h3> Enter API key </h3>

The crucial thing is you need an    "API key" to talk to OpenAI. <br>
Register for free and get your API key  
<a href='https://platform.openai.com/account/api-keys'>here</a>.
<br>
You enter your API key below and then chat away.
Then communications to OpenAI come from your IP address using your API key. 
<br>
This World   will never  store your API key. 
You can view the <a href='https://ancientbrain.com/viewjs.php?world=2850716357'> source code</a>  to see that is true!
<div id=enterkey style="margin-top:1rem">
    Enter API key: 
	<input    style='width:25vw;'    maxlength='2000'   NAME="apikey"    id="apikey"       VALUE='' >  
	<button onclick='setkey();'  class=ab-normbutton >Set API key</button>
</div>
</div>

<pre>

</pre>

<!-- Have used flex in previous projects but referenced the MDN docs while implementing it: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/flex-->
<div style="display:flex; gap: 2rem; align-items:center">
    <div style="width:38vw; height: 100%; background-color:F5EEE1;  border: 1px solid black; padding: 20px; border-radius: 0.5rem;">
        <h3> Enter a "prompt" </h3>
        <!-- referenced textarea MDN docs: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/textarea -->
        <textarea style="width:100%; height:15rem; padding: 1rem; margin-bottom: 1rem; text-wrap: wrap;" id=me placeholder="Enter your own question..." ></textarea>
        <button onclick="sendchat(0);"     class=ab-normbutton > Send </button> 
        <button onclick="resetChatHistory();" class=ab-normbutton > Clear Prompt History </button>
        <p>Each submission builds on previous submissions unless the previous submissions are cleared using the "Clear Prompt History" button.</p>
        
    </div>

    <h4>OR</h4>

    <div style="width:60vw; background-color:F5EEE1; border: 1px solid black; padding:20px; border-radius: 0.5rem;">
        <h3>Select a sample prompt</h3>
        <button onclick="sendchat(event);"     class=ab-normbutton style="background-color:DBCAB2; color:black; width: 100%; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;"  onmouseover="this.style.backgroundColor='darkcyan'; this.style.color='white'" onmouseout="this.style.backgroundColor='#DBCAB2'; this.style.color='black'"> Create a riddle app that gives the user a riddle and a hint button if the user is stuck, and allows the user to input an answer, checking if it is right. </button>
        <button onclick="sendchat(event);"     class=ab-normbutton style="background-color:DBCAB2; color:black; width: 100%; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem; " onmouseover="this.style.backgroundColor='darkcyan'; this.style.color='white'" onmouseout="this.style.backgroundColor='#DBCAB2'; this.style.color='black'"> Create a fashion app where users can upload pictures of their clothes and the app helps them create outfits. </button>
        <button onclick="sendchat(event);"     class=ab-normbutton style="background-color:DBCAB2; color:black; width: 100%; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem; " onmouseover="this.style.backgroundColor='darkcyan'; this.style.color='white'" onmouseout="this.style.backgroundColor='#DBCAB2'; this.style.color='black'"> Create an app where runners can log their mileage, pace, how they were feeling, and what the weather was on their runs. </button>
        <button onclick="sendchat(event);"     class=ab-normbutton style="background-color:DBCAB2; color:black; width: 100%; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem; " onmouseover="this.style.backgroundColor='darkcyan'; this.style.color='white'" onmouseout="this.style.backgroundColor='#DBCAB2'; this.style.color='black'"> Create an app where someone looking for a house can go and log houses they've looked at, where they are located (map feature), some pictures of the house, what they liked, and what they disliked. </button>
        <button onclick="sendchat(event);"     class=ab-normbutton style="background-color:DBCAB2; color:black; width: 100%; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem; " onmouseover="this.style.backgroundColor='darkcyan'; this.style.color='white'" onmouseout="this.style.backgroundColor='#DBCAB2'; this.style.color='black'"> Create an app for people looking for information about dogs that contains many breds with information and a picture for each. Have it allow the user to scroll through and filter the list by certain attributes.</button>
    </div>
</div>
<div id="responseArea" style="display: flex; gap: 2rem; margin-block: 1.25rem;">
    <div id="responseOutput" style="width:60vw; background-color:BED8E5;  height: auto; border: 1px solid black; padding: 1rem; border-radius: 0.5rem;">
        <h3> GPT Output </h3>
        <div id="appDisplay" style="margin:1rem; padding: 1rem;"></div>
        <div id="responseNum" style="margin-top: 1rem;"> </div>
        <div id="buttonContainer" style="display: flex; gap: 2rem; margin-block: 1rem;">
            <button onclick="display(-1)"   id="prev"   class=ab-normbutton     style="display:none">Prev</button>
            <button onclick="display(1)"    id="next"   class=ab-normbutton     style="display:none">Next</button>
            <button onclick="display(2)"    id="codeToggle"   class=ab-normbutton     style="display:none">Show Code</button>
        </div>
    </div>
    <div id="responseDebug" style="width: 38vw; background-color:C7E5BE; border: 1px solid black; padding: 1rem; border-radius: 0.5rem;">
        <div id="debugQuestion">
            <h3>Enter debugging question: </h3>
            <input    style='width:25vw;'    maxlength='2000'   NAME="debugQ"    id="debugQ"       VALUE='' >  
    	    <button onclick='sendDebugChat();'  class=ab-normbutton >Send</button>
	    </div>
	    <div id="debugAnswer" style="margin-block: 1rem;"> </div>
	    <p id="debugWarning" style="margin-block: 1rem;"> <b>Note</b>: If the program does not function as expected, check for errors in the console. You can copy these errors into the debugger or directly into the prompt textbox to try and solve them.</p>
    </div>
</div>
 
 <p> <i> Be warned that GPT replies are often completely inaccurate.<br> 
 All LLM systems <a href="https://www.google.com/search?q=llm+hallucination"> "hallucinate"</a>.
 It is how they work. </i> </p>

<pre>

</pre>

` );



// Function that collects the apikey
function setkey()          
{
	apikey =  jQuery("input#apikey").val();
	apikey = apikey.trim();
	$("#enterkey").html ( "<b> API key has been set. </b>" );
	resetChatHistory(); // initializes the message list
}

// Gives the GPT model background information to try 
// and make it's response have the right form/structure
// Referenced this stream: https://github.com/openai/openai-cookbook/issues/275
// and these from openai: https://platform.openai.com/docs/api-reference/threads/createThread and https://platform.openai.com/docs/guides/text#message-roles-and-instruction-following
// to understand how to store the history of the calls and responses to the api
function resetChatHistory()
{
    message = [
        { "role": "developer", "content": "Create a javascript only program that is only code (no comments) and displays in a div with the id=appDisplay (any html structure that is used for the app within this div must be created using javascript, ensuring all buttons created work properly) that creates a visual to match the user prompt" }
    ];

}

// Gives the GPT model instructions for what to return about the question and code that will be provided
// Uses the same storing technique as resetChatHistory()
function resetDebugHistory()
{
    debugMessage = [
        { "role": "developer", "content": "Give me debugging suggestions for the following code given the question" }
    ]
}



// Enter will also send chat:

	document.getElementById('me').onkeydown   = function(event) 	{ if (event.keyCode == 13)  sendchat(0); };




// --- Send my line of text to create the app ----------------------------------------------------------------

function sendchat( inputType )
{
    // Assigns theprompt the text that the user either selected or wrote 
    if (inputType == 0) { // Custom prompt
        theprompt = $("#me").val();
    } else { // Selected one of the 5 option buttons
        resetDebugHistory();
        resetChatHistory();
        document.getElementById('debugAnswer').innerHTML = ""; // referenced MDN docs on innerHTML: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
        // Referenced https://stackoverflow.com/questions/7846268/javascript-click-event-handler-how-do-i-get-the-reference-to-the-clicked-item
        // to understand how to identify what was clicked on and grab its data
        theprompt = inputType.target.innerHTML;
    }
    message.push({ "role": "user", "content": theprompt}); // adds to the history stream of messages
    document.getElementById('me').value = ""; // found how to clear the textarea here: https://stackoverflow.com/questions/77123895/how-to-clear-input-and-textarea-fields-as-type-text-using-a-button
  
    // construct request as JSON
    // "Lowering temperature means it will take fewer risks, and completions will be more accurate and deterministic. Increasing temperature will result in more diverse completions."
    
    var thedata = {
         "model": themodel,
         "temperature": 0.7,
         "messages": message
       };
       
    // then as string representing that JSON:
    var thedatastring = JSON.stringify ( thedata );   
       
    // HTTP headers must be set up with API key: 
    
    $.ajaxSetup({
       headers:
       {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + apikey  
       }
    });
    
    
    // POST to 3rd party URL: 
    
    $.ajax({
        type: "POST",
        url: openaiURL,
        data: thedatastring,
        dataType: "json",
        success: function ( d, rc ) { successfn ( d, rc ); },
          error: function()         { errorfn (); }
    });
 
}

// Send my line of text to AI for debugging  ----------------------------------------------------------------
// very similar to and based off of sendchat() but with adjustments
// has its own storage of request history separate from that of the code creation 
function sendDebugChat()
{
    debugPrompt = $("#debugQ").val();
    debugMessage.push({ "role": "user", "content": "Question to answer: " + debugPrompt + " Code: " + responses[index]});
    document.getElementById('debugQ').value = "";
      
    // construct request as JSON
    // "Lowering temperature means it will take fewer risks, and completions will be more accurate and deterministic. Increasing temperature will result in more diverse completions."
    
    var thedata = {
         "model": themodel,
         "temperature": 0.7,
         "messages": debugMessage
       };
       
    // then as string representing that JSON:
    var thedatastring = JSON.stringify ( thedata );   
       
    // HTTP headers must be set up with API key: 
    
    $.ajaxSetup({
       headers:
       {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + apikey  
       }
    });
    
    
    // POST to 3rd party URL: 
    
    $.ajax({
        type: "POST",
        url: openaiURL,
        data: thedatastring,
        dataType: "json",
        success: function ( d, rc ) { debugSuccess( d, rc ); },  //successfn ( d, rc ); },
          error: function()         { errorfn (); }
    });
}


// Boolean that keeps track of whether the code or the visual is being displayed 
    // 0 means when the button is clicked it will display code
    // 1 means when the button is clicked it will execute the code
var clicked = 0;

// function to display the selected response code
function display ( operation )
{
    // Clear div where the answer is displayed referencing the documentation: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
    document.getElementById('appDisplay').innerHTML = "";
    // remove styling of the div so that styles from previous responses do not affect the display of the current response 
    // https://dev.to/nikolasbarwicki/remove-all-styling-with-one-line-of-css-19e4
    document.getElementById('appDisplay').style.all = "unset";
    document.getElementById('appDisplay').style.backgroundColor = "none";
    
    
    if (operation == 2 && clicked == 0) { // switches the display from the execution of the code to just displaying the code as text
       $("#appDisplay").text(responses[index]); // display code as text without styling: https://stackoverflow.com/questions/37761650/how-do-i-display-the-code-as-plain-text
       document.getElementById('codeToggle').innerHTML = "Execute Code";
       clicked = 1;
    } else {
        if (operation == 0) { // displays latest response
            index = responses.length - 1;
        } else if (operation == -1) { // displays previous response
            index -= 1;
        } else if (operation == 1 && index < (responses.length - 1)) { // displays next response
            index += 1;
        }
        
        if (clicked == 1) { // switches the display from the code as text to executing the code
            clicked = 0;
            document.getElementById('codeToggle').innerHTML = "Show Code";
        }
        
        // referenced this to catch errors: https://stackoverflow.com/questions/63709689/how-to-catch-errors-thrown-by-eval-properly
        try {
            eval(responses[index]); // checked MDN docs to know if eval was safe to use: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
        } catch (e) {
            console.log("Eval didn't work");
            document.getElementById('appDisplay').innerHTML = "Error in executing code. Error message: " + e;
        } 
    }
    
    // Display or hide prev and next buttons depending on which response is being displayed
    if (responses.length != 1) {
        document.getElementById('prev').style.display = "block";
        document.getElementById('next').style.display = "block";
        if (index == 0 ) {
            document.getElementById('prev').style.display = "none";
        } else if (index == (responses.length - 1)) {
            document.getElementById('next').style.display = "none";
        }
    }
    
    // Display the Response number and the button to toggle the code view
    document.getElementById('responseNum').innerHTML = "Response: " + (index + 1) + " of " + responses.length;
    document.getElementById('codeToggle').style.display = "block";
    
}
 
 // global variable to examine return data in console 
 var a;
 
 
 
 function successfn ( data, rc )
 {
     a = data;
     var answer = a["choices"][0].message.content;
     message.push({ "role": "assistant", "content": answer });
     // Referenced "Text to 3D Graphics Generator  " by Antoine Podvin in creating this display -> https://ancientbrain.com/viewjs.php?world=5162534048
     answer = answer.replace("```javascript", "");
     answer = answer.replace("```", "");
     // Add answer to responses list 
     responses.push(answer);
     // Display the response (input 0 signifies displaying the last generated response)
     display(0);
 }
 
 function debugSuccess ( data, rc )
 {
     a = data;
     var answer = a["choices"][0].message.content;
     debugMessage.push({ "role": "assistant", "content": answer });
     document.getElementById('debugAnswer').innerHTML = answer;
 }
 
 function errorfn()
 {
     if ( apikey == "" )    $("#appDisplay").html ( "<font color=red><b> Enter API key to be able to chat. </b></font>" );
     else                   $("#appDisplay").html ( "<font color=red><b> Unknown error. </b></font>" ); 
 }