Starting from Claude 3.5 Sonnet and now available in other AI tools as well, the so called “Artifact” feature can be easily replicated for web development by using IFRAMES.
This little tutorial shows how to implement such functionality in a vanilla javascript chatbot based on ChatGPT-4o-mini.
Front-end system to allow input and output:
<main>
<div class="userinput">
<input type="password" id="apikey" placeholder="Your API key"><br>
<textarea rows=6 type="text" id="prompt" placeholder="Describe the kind of landing page you want to generate" autocomplete="off" autofocus></textarea><br>
<button class="btncopy" onclick="sendMessage()">Send</button>
<button class="btncopy" id="savetxt">Save</button>
</div>
<section id="content">
<div id="chathistory">
</div>
</section>
</main>
I am deliberately leaving blank the CSS section which is out of scope now.
The Javascript part has three main functions:
Creating the Memory system and SendMessage function:
let chatMemory = [];
chatMemory = createMemory([
{
role: "system",
content: "You are a web designer bot. Your output is made of code to satisfy the received request. You will not greet or thank the user for the request, your aim is to produce a website layout design to satisfy the request so you will produce a well structured code line by line without interruptions and without dividing it in sections. You will only use internal CSS, placeholder images, and all data available in the request to produce a complete website layout."
}
]);
console.log(chatMemory);
// Function to send the message
async function sendMessage() {
const apikey = document.getElementById("apikey");
console.log(apikey); // simple feedback
if (apikey === "") {
alert("No OpenAI API Key found.");
} else {
console.log(apikey);
}
const inputElement = document.getElementById("prompt");
const userInput = inputElement.value.trim();
if (userInput !== "") {
showMessage("Guest", userInput, "");
chatMemory = await getChatGPTResponse(userInput, chatMemory);
inputElement.value = "";
}
}
function createMemory(messages) {
const memory = [];
for (const msg of messages) {
memory.push({ role: msg.role, content: msg.content });
}
return memory;
}
Showing messages in the chat container, dividing the code from the visual result.
// Show the message in the chat container
function showMessage(sender, message, tokens, downloadLink) {
const chatContainer = document.getElementById(“chathistory”);
const typingIndicator = document.getElementById(“typing-indicator”);
if (typingIndicator && sender === “Chatbot”) {
chatContainer.removeChild(typingIndicator);
}
// Create a new message element
const messageElement = document.createElement(“div”);
// Handle the Guest message separately
if (sender === “Guest”) {
messageElement.innerHTML = +[${hour}:${minute}] - ${sender}: ${message}
;
messageElement.classList.add(“user-message”);
} else {
// Show the message and the tokens of the chatbot response
const timestampElement = document.createElement(“p”);
timestampElement.innerHTML = -[${hour}:${minute}] - ${sender}:
;
timestampElement.classList.add(“chatgpt-message”);
// Add the timestamp above the iframe
messageElement.appendChild(timestampElement);
// Create the flex div to contain frontend and code:
const flexedPanel = document.createElement(“div”);
flexedPanel.style.width = “100%”;
flexedPanel.style.display = “flex”;
flexedPanel.style.justifyContent = “space-between”;
// Create an iframe for the code generated by the chatbot
const iframe = document.createElement(“iframe”);
iframe.style.width = “58%”;
iframe.style.height = “800px”; // Height of the iframe
iframe.style.border = “1px solid black”; // Border style for the iframe
iframe.style.boxShadow = “0px 4px 10px black”;
// Add the iframe to the message
flexedPanel.appendChild(iframe);
// Create a Blob for the HTML content
const blob = new Blob([`
// Create a URL for the Blob
const url = URL.createObjectURL(blob);
// Set the iframe with the Blob URL
iframe.src = url;
// Show the related code generated
const resultCode = document.createElement(“div”);
resultCode.style.width = “40%”;
resultCode.style.height = “800px”;
resultCode.style.overflowY = “auto”;
resultCode.style.marginLeft = “10px”;
resultCode.style.padding = “4px 6px”;
resultCode.style.border = “1px solid black”;
resultCode.style.boxShadow = “0px 4px 10px black”;
resultCode.innerText = ${message}
;
flexedPanel.appendChild(resultCode);
messageElement.appendChild(flexedPanel);
// Show the message and the tokens of the chatbot response
const separator = document.createElement(“p”);
separator.innerHTML = ${tokens}
;
messageElement.classList.add(“chatgpt-message”);
messageElement.appendChild(separator);
// Add the link for downloading the generated code
const downloadElem = document.createElement(“div”);
downloadElem.innerHTML = downloadLink;
messageElement.appendChild(downloadElem);
}
// Append the message to the container
chatContainer.appendChild(messageElement);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
Interrogating the LLM model and get the response
// Function to interrogate OpenAI model and get response (TXT chatbot - simple chat)
async function getChatGPTResponse(userInput, chatMemory = ) {
const apikey = document.getElementById(“apikey”);
if (apikey === “”) {
alert(“No OpenAI API Key found.”);
} else {
console.log(apikey);
}
const chatContainer = document.getElementById(“chathistory”);
const typingIndicator = document.createElement(“p”);
typingIndicator.id = “typing-indicator”;
typingIndicator.innerHTML =
‘Loading…’; // replace with any message or image
chatContainer.appendChild(typingIndicator);
try {
const response = await fetch(“https://api.openai.com/v1/chat/completions”, {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
Authorization: "Bearer " + apikey
},
body: JSON.stringify({
model: “gpt-4o-mini”,
messages: […chatMemory, { role: “user”, content: userInput }]
})
});
if (!response.ok) {
throw new Error(“Error while requesting to the API”);
}
const data = await response.json();
if (
!data.choices ||
!data.choices.length ||
!data.choices[0].message ||
!data.choices[0].message.content
) {
throw new Error(“Invalid API response”);
}
const chatGPTResponse = data.choices[0].message.content.trim();
var cleanResponse = chatGPTResponse.replace(
/(||||||.net|||)(.?)/gs,
“$2”
);
console.log(chatGPTResponse);
cleanResponse = cleanResponse.replace(//g, “”);
cleanResponse = cleanResponse.replace(/**(.?)**/g, “$1”);
// manages token from the response
const tokenCount = document.createElement(“p”);
// Check if “completion_tokens” is present in the structure of the objects returned by your API
if (data.usage.completion_tokens) {
const requestTokens = data.usage.prompt_tokens;
const responseTokens = data.usage.completion_tokens;
const totalTokens = data.usage.total_tokens;
const pricepertokenprompt = 0.15 / 1000000; // uses gpt-4o-mini price of 0.15/Mt USD
const pricepertokenresponse = 0.60 / 1000000; // uses gpt-4o-mini price of 0.60/Mt USD
const priceperrequest = pricepertokenprompt * requestTokens;
const priceperresponse = pricepertokenresponse * responseTokens;
const totalExpense = priceperrequest + priceperresponse;
tokenCount.innerHTML = <hr>Your request used ${requestTokens} tokens and costed ${priceperrequest.toFixed(6)} USD<br>This response used ${responseTokens} tokens and costed ${priceperresponse.toFixed(6)} USD<br>Total Tokens: ${totalTokens}. This interaction costed you: ${totalExpense.toFixed(6)} USD.
;
} else {
tokenCount.innerHTML = “Unable to track the number of used tokens.”;
}
// Create a blob and a link to download the code
const blob = new Blob([cleanResponse], { type: ‘text/html’ });
const url = URL.createObjectURL(blob);
const downloadLink = <a href="${url}" download="generated_code.html">Click here to download the generated HTML code</a>
;
// Show the message with the final download link now created
showMessage(“Chatbot”, cleanResponse, tokenCount.innerHTML, downloadLink);
// Adds response to memory context
chatMemory.push({ role: “user”, content: userInput });
chatMemory.push({ role: “assistant”, content: cleanResponse });
// Updates context memory
return chatMemory;
} catch (error) {
console.error(error);
// Manage errors with a warning
alert(
“An error occurred during the request. Check your OpenAI account or retry later.”
);
}
}
Limitations:
The tool is limited to manage basic front-end languages, HTML, CSS and Javascript. It can also generate PHP code but it will be only added to the code section and not interpreted in the visual result portion fo the page. Yet, it can easily be modified to implement jQuery, Bootstrap, and other functional / styling libraries.
Another limitation is that this code uses an input field to catch the API key, so it must be entered manually at every page load. A more eficient method would be to store it in localStorage as a variable and retrieve it, i.e.:
Have an input box with button to write and save the key:
saveAPIKeyBtn.addEventListener('click', () => {
localStorage.setItem('openaikey', openaikeyInput.value);
});
Retrieve the key from the localStorage variable
const apikey = localStorage.getItem("openaikey");
this retrieve must be used in the sendMessage() and in the getChatGPTResponse() functions