Build Your Own ChatGPT

Like nearly everyone else on the planet, I was blown away by the mass scale introduction of generative AI and its most popular delivery mechanism, ChatGPT. Although still in its infancy, GTP (Generative Pre-Trained) AI has already surpassed nearly every AI technology that came before it. I have been using it on an almost daily basis for the past several months and I am constantly amazed by some new aspect that I previously missed. It’s tremendously exciting while at the same time, horribly scary. Who knows where this and spinoff technologies will take us, but I guarantee that nearly every aspect of our lives will be impacted.

As a curious programmer, I wasn’t happy to simply ride the ChatGPT train. I wanted to lift the hood to see how it performed its magic. This drove me to the OpenAI developer website where I signed up for an account, got my API key, and started playing with the APIs. My first creations worked well enough, but they were far from optimal. To be honest, my knowledge is still less than what I would like it to be, but I feel comfortable enough to share what I’ve learned.

Reimagining ChatGPT

Since most of my code has been embedded into much larger projects (typically those that involve real-time GPT processing of telephone calls), I put together a simple web application that mimics a lot of what the public ChatGPT application does. I don’t have a fraction of its bells and whistles, but I have enough to make it educational and fun.

In honor of the latest/final Beatles song, “Now and Then”, I used the OpenAI APIs to create a GPT-enabled on-line Beatles enthusiast. The application uses either the OpenAI 3.5 or 4.x AI models to answer questions and gush about all things Beatles. For those of you familiar with these models, you will know that 3.5 is fast, cheap, and fairly accurate while 4.x is slower, expensive, and much more accurate. 3.5 results are interesting and 4.x results can often be astounding.

Before I present the code, please take a look at this Cheapo-Cheapo Productions video of the application in action. As always, my UI design skills leave much to be desired, but they get the job done.

The Down and Dirty

The bulk of the web application’s code is contained in index.html. For simplicity’s sake, I combined the HTML and JavaScript into a single document. Feel free to separate them.

Copy and paste this into index.html and place the file into a directory on your PC.

<!DOCTYPE html>
<html>
<head>
<title>GPT Webchat</title>
<link rel="stylesheet" href="css/openai.css" />
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js
</head>
<body>
<div class="container">
<div class="container-fluid">
<br>
<h1>GPT Beatles Enthusiast</h1>
<br>
<div class="col-sm-12 ">
<div style="width: 100%" class="form-group mb-3">
<textarea placeholder="Message" class="form-control" id="message" rows="1"></textarea>
</div>
<button style="padding: 0.8rem 8rem" id="sendMsg" class="btn btn-primary " onclick="sendMsg()" disabled>Send</button>
<button style="padding: 0.8rem 8rem" id="reset" class="btn btn-primary " onclick="reset()">GPT4</button>
</div>
<br>
<div class="col-sm-12">
<div id="console-log" class="console-div" readonly=""></div>
</div>
</div>
</div>
<script type="text/javascript">
const OPEN_AI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
const OPENAI_AUTH = "xxxxxxxxxx";
const BEATLES = "I am a music enthusiast named Rocky who helps people with their questions about the band, The Beatles. I am upbeat and friendly. I introduce myself when first saying hello. I only introduce myself once. Do not talk about bands other than The Beatles. Whenever I give a website URL that begines with www, I prefix it with https://";
var gptConversation = [];
var systemPrompt = BEATLES;
const GPT4 = "gpt-4-1106-preview";
const TURBO = "gpt-3.5-turbo";
var MODEL;
$(document).ready(init);

function init() {
MODEL = TURBO;
var input = document.getElementById("message");
input.addEventListener("keyup", function(event) {
if (event.key === "Enter") {
event.preventDefault();
document.getElementById("sendMsg").click();
}
});
establishInitialResponse();
}
async function establishInitialResponse() {
gptConversation = [];
mytext = "";
var systemRole = `[{"role":"system", "content":"${systemPrompt}"}]`;
gptConversation = JSON.parse(systemRole);
var user = {
role: "user",
content: "Hello"
}
gptConversation.push(user);
initialPrompt = await callGPT(gptConversation);
$('#sendMsg').prop("disabled", false);
}

function reset() {
if (document.getElementById('reset').innerHTML == "GPT3.5") {
MODEL = TURBO;
document.getElementById('reset').innerHTML = "GPT4"
} else {
MODEL = GPT4;
document.getElementById('reset').innerHTML = "GPT3.5"
}
establishInitialResponse();
clearAllChats();
}

function clearAllChats() {
$('#console-log').val("");
$("#console-log span").remove();
}
async function callGPT(message) {
const openAI = {
model: MODEL,
messages: message,
max_tokens: 1200,
presence_penalty: 0.5,
frequency_penalty: 0.5,
temperature: 0
};
var settings = {
"url": OPEN_AI_CHAT_URL,
"method": "post",
"timeout": 0,
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${OPENAI_AUTH}`,
},
data: JSON.stringify(openAI)
};
await $.ajax(settings).done(function(response) {
console.log(response);
var gptMessage = {
role: "assistant",
content: response.choices[0].message.content
}
gptConversation.push(gptMessage);
addToConsole(`Agent: ${response.choices[0].message.content}`, "Agent");
return response.choices[0].message.content;
});
}
async function sendMsg() {
addToConsole(`User: ${document.getElementById('message').value}`, "User");
var gptMessage = {
role: "user",
content: document.getElementById('message').value
}
gptConversation.push(gptMessage);
response = await callGPT(gptConversation);
document.getElementById('message').value = "";
}

function writeToTrace(text, requestor) {
text = text.trim();
addToConsole(text, requestor);
}

function addToConsole(msg, requestor) {
var span = createLogElement(msg, requestor);
$('#console-log').append(span);
document.getElementById("console-log").scrollTop = document.getElementById("console-log").scrollHeight;
}

function createLogElement(msg, type) {
var span = document.createElement('span');
if (type == "User") {
$(span).addClass('log-element');
} else {
$(span).addClass('log-element-agent');
}
var msgArray = msg.split(':');
var sender = msgArray[0];
var restMsg = "";
for (var i = 1; i < msgArray.length; i++) {
if (i == 1) {
restMsg += " " + msgArray[i];
} else {
restMsg += ":" + msgArray[i];
}
}
var sendSpan = document.createElement('span');
$(sendSpan).addClass('log-element-sender');
sendSpan.innerHTML = sender + ":";
var msgSpan = document.createElement('span');
$(msgSpan).addClass('log-element-msg');
restMsg = restMsg.replaceAll(`\n`, `<BR>`)
$(msgSpan).append(messageWithLink(restMsg));
$(span).append(sendSpan);
$(span).append(msgSpan);
return span;
}

function messageWithLink(msg) {
var matches = msg.match(/[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g);
if (matches) {
for (var i = 0; i < matches.length; i++) {
if (matches[i].toLowerCase().indexOf("http") != -1) {
// The user or agent put a link into the chat window
console.log(matches[i]);
msg = msg.replace(matches[i], ' <a href = "' + matches[i] + '"target = "_blank" > ' + matches[i] + ' </a>');
}
}
}
return msg;
}
</script>
</body>
</html>

In order to format the output data, I created a simple css file I call openai.css. Create a new directory where you stored index.html. Name it css and store openai.css in that directory.

.log-element {
    display: block;
    margin-top: 5px;
    margin-bottom: 5px;
    margin-left: 1%;
    margin-right: 1%;
    background: #fce4c5;
    border-radius: 5px;
    padding: 10px;
    font-family: "Noto Sans JP", sans-serif;
    font-size: 13px;
    max-width: 470px;
    box-sizing: content-box;
}

.log-element-agent {
    display: block;
    margin-top: 5px;
    margin-bottom: 5px;
    margin-left: 1%;
    margin-right: 1%;
    background: #43c2dc;
    border-radius: 5px;
    padding: 10px;
    font-family: "Noto Sans JP", sans-serif;
    font-size: 13px;
    max-width: 470px;
    box-sizing: content-box;
}

.log-element-sender {
    font-weight: bold;
    font-style: italic;
    font-size: 13px;
    font-family: "Noto Sans JP", sans-serif;
}

.log-element-msg {
    word-wrap: break-word;
}

#console-log {
    background-color: white;
    padding: 2%;
    width: 100%;
    overflow-y: scroll;
    min-height: 100px;
}

.console-div{
    display:block;
    width:100%;
    height:650px;
    background-color:#475;
    overflow:scroll;
}

Before you can use the application, you need to create an OpenAI developer account and obtain an API Key. Once you have your key, update this line in index.html:

const OPENAI_AUTH = “xxxxxxxxxxxxxxxx”;

Once the OPENAI_AUTH constant has been updated, simply double click on index.html and the application will launch. You can always host it on a web server, but it runs fine on your PC.

A Few Notes

I used a System Prompt to set the personality to be a Beatles enthusiast. You can turn your bot into anything you’d like by changing this line:

 const BEATLES = "I am a music enthusiast named Rocky who helps people with their questions about the band, The Beatles. I am upbeat and friendly. I introduce myself when first saying hello. I only introduce myself once.  Do not talk about bands other than The Beatles. Whenever I give a website URL that begins with www, I prefix it with https://";

Note how I set a personality and tell the bot how to respond to questions. For the last bit, I wanted to make all URLs clickable so I told GPT to add https:// to all URLs that begin with www. I found through using the bot that it gave the Beatles’ website as http://www.thebeatles.com. The prompt engineering converts that to https://thebeatles.com. So, instead of doing that in code, I had GPT do it for me. The following shows that in action:

I use a JavaScript array (gptConversation) to manage the roles and their content. The bot’s personality is set by the “system” role, GPT responses are contained in the “assistant” role, and anything the user types is contained in the “user” role. The following code is used by sendMsg() to format the user’s input for the OpenAI chat API. Notice how I build the role and content and then push it into the gptConversation array.

        var gptMessage = {
          role: "user",
          content: document.getElementById('message').value
        }
        gptConversation.push(gptMessage);

Mischief Managed

And that’s all there is. I hope you found article this useful and are willing to download the code and build your own ChatGPT agent. Please let me know if you do! Especially if you pretty-up my mundane user interface.

Feel free to contact me with any questions or comments. Happy programming!

Leave a comment