// Author: Sam McElligott
// Student Number: 22493902
document.write(`
<!doctype html>
<html>
<head>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: Arial, sans-serif;
}
hr {
margin: 1rem 0;
}
input {
width: 100%;
padding: 0.5rem;
border-radius: 0.5rem;
border: 1px solid #ccc;
}
input:active {
border: 1px solid #15803d;
}
label {
margin-bottom: 0.5rem;
}
.form-item {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin-bottom: 1rem;
}
.container {
max-width: 200px;
}
.card {
border: 1px solid #ccc;
border-radius: 0.5rem;
padding: 1rem;
margin: auto;
margin-top: 1rem;
max-width: 400px;
background-color: white;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
background-color: #15803d;
color: white;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
textarea {
border-radius: 0.5rem;
padding: 0.5rem;
}
.hover {
box-shadow: 0 0 8px 2px #cccccc9f;
}
.spread {
width: 100%;
display: flex;
justify-content: space-between;
}
.m-left {
margin-left: auto;
}
.grid {
width: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
gap: 2rem;
}
.grid .card {
margin: 0;
max-width: 100%;
}
@media (max-width: 768px) {
.grid {
flex-direction: column;
}
}
.responses {
margin-top: 1rem;
width: 100%;
}
</style>
</head>
<body>
<form class="card hover" id="key-form">
<div class="form-item">
<label for="chatgpt-key"> ChatGPT Key </label>
<input id="chatgpt-key" required name="chatgpt" placeholder="Enter key here" />
</div>
<div class="form-item">
<label for="gemini-key"> Gemini Key </label>
<input id="gemini-key" required name="gemini" placeholder="Enter key here" />
</div>
<hr />
<div class="form-item">
<label for="one-compiler-key"> OneCompiler Key </label>
<input id="one-compiler-key" required name="one-compiler" placeholder="Enter key here" />
</div>
<div class="form-item">
<button type="submit" class="m-left">Submit</button>
</div>
</form>
<div class="responses"></div>
</body>
</html>
`);
// Manages the form for the API keys, and manages
// storing them in local storage for ease of use
class KeyManager {
constructor() {
this.form = document.querySelector("#key-form");
this.values = {
chatGPT: null,
gemini: null,
oneCompiler: null,
};
this.ready = false;
}
init() {
this.form.onsubmit = (e) => {
e.preventDefault();
keys.setKeys(e.target);
if (!keys.ready) {
alert("Please fill in all fields");
return;
}
showPrompt();
};
const stored = this.getStoredKeys();
if (!stored) return;
this.values = stored;
this.ready = this.validateKeys();
if (this.ready) {
showPrompt();
}
}
setKeys(formObject) {
this.values.chatGPT = formObject["chatgpt"].value;
this.values.gemini = formObject["gemini"].value;
this.values.oneCompiler = formObject["one-compiler"].value;
this.ready = this.validateKeys();
if (this.ready) {
this.storeKeys();
}
}
storeKeys() {
localStorage.setItem("keys", JSON.stringify(this.values));
}
getStoredKeys() {
const keys = localStorage.getItem("keys");
return keys ? JSON.parse(keys) : null;
}
validateKeys() {
for (const key in this.values) {
if (this.values[key] === "") {
return false;
}
}
return true;
}
clearKeys() {
localStorage.removeItem("keys");
window.location.reload();
}
}
// Manages the form for the code generation prompt,
// including calling the APIs
class PromptManager {
constructor(keys) {
this.keys = keys;
this.prompt = null;
this.ready = false;
this.responses = {
ChatGPT: null,
Gemini: null,
lLama: null,
oneCompiler: null,
};
}
init() {
this.form = document.querySelector("#prompt-form");
this.form.onsubmit = async (e) => {
e.preventDefault();
this.prompt = e.target["prompt"].value;
this.ready = this.prompt !== "";
if (!this.ready) {
alert("Please fill in all fields");
return;
}
const loading = document.createElement("h1");
loading.id = "loading";
loading.innerHTML = "Loading...";
loading.style.textAlign = "center";
document.querySelector(".responses").prepend(loading);
// Reset form
handleInput("");
this.form["prompt"].value = "";
document.querySelector("#prompt-form button[type='submit']").disabled =
true;
await this.generateResponses();
this.showResponses();
};
}
// When awaited, this method pauses the main thread
// for 'delay' milliseconds
async wait(delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
}
async generateResponses() {
// Code generation requests
const [ChatGPT, Gemini] = await Promise.all([
this.fetchChatGPT(),
this.fetchGemini(),
]);
// MH edit
//console.log ("ChatGPT:"); console.log(ChatGPT);
//console.log ("Gemini:"); console.log(Gemini);
// Code running requests
const [ChatGPTSuccess, GeminiSuccess] = await Promise.all([
this.runCode(ChatGPT, "ChatGPT", { skip: ChatGPT.error }),
this.runCode(Gemini, "Gemini", { skip: Gemini.error, delay: 500 }),
]);
// MH edit
//console.log ("ChatGPTSuccess:"); console.log(ChatGPTSuccess);
//console.log ("GeminiSuccess:"); console.log(GeminiSuccess);
this.responses = {
ChatGPT: {
code: ChatGPT,
success: ChatGPTSuccess[0],
time: ChatGPTSuccess[1],
},
Gemini: {
code: Gemini,
success: GeminiSuccess[0],
time: GeminiSuccess[1],
},
};
}
// Once the responses have been generated, display them on the page in code blocks
showResponses() {
document.querySelector("#loading").remove();
const container = document.querySelector(".responses");
const heading = document.createElement("h1");
heading.innerHTML = `"<i>${this.prompt}</i>"`;
heading.style.textAlign = "center";
const responseContainer = document.createElement("div");
responseContainer.classList.add("grid");
// MH edit
//console.log ("responses:"); console.log (this.responses);
for (const key in this.responses) {
const { success, code, time } = this.responses[key];
const color = success ? "green" : "red";
const response = document.createElement("div");
response.classList.add("card", "container");
response.innerHTML = `<h1 style="color: ${color};">${key}</h1>
<p>Took <b>${time}</b>ms to run</p>
<hr />
<pre>
<code class="language-python" id=${key}>${code}</code>
</pre>
<button style="margin-top: 1rem;" onclick="handleCopy('${key}')">Copy</button>
`;
responseContainer.appendChild(response);
}
container.prepend(responseContainer);
container.prepend(heading);
container.prepend(document.createElement("hr"));
}
async fetchChatGPT() {
const body = {
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
"You must generate only code, which must be in Python and contain test cases. Don't include markdown or comments.",
},
{
role: "user",
content: this.prompt,
},
],
};
const response = await this.fetch({
url: "https://api.openai.com/v1/chat/completions",
headers: {
Authorization: `Bearer ${this.keys.values.chatGPT} `,
// MH edit
//"OpenAI-Organization": "org-WS419FrNJEV9muDDrmXw2AXd",
},
body,
});
console.log({ chat: response });
return response.data.choices[0].message.content.trim() + "\n";
}
async fetchGemini() {
const body = {
contents: [
{
parts: [
{
text: `You must generate only code, which must be in Python and contain test cases. Don't include markdown or comments: ${this.prompt}`,
},
],
},
],
};
const result = await this.fetch({
url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${this.keys.values.gemini}`,
body,
});
console.log({ gemini: result });
if (result.error) {
return result.message;
}
const code = result.data.candidates[0].content.parts[0].text;
return code.replace("```python\n", "").replace("\n```", "").trim() + "\n";
}
// Helper function to send a POST request
async fetch({ url, headers, body }) {
try {
const result = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
...headers,
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(10000),
});
if (!result.ok) {
const data = await result.json();
console.log(data);
throw new Error("Request failed: " + data.error.message);
}
const data = await result.json();
return { error: false, data };
} catch (e) {
if (e.name === "AbortError") {
return { error: true, message: "Request timed out (10s)" };
}
return { error: true, message: e.message };
}
}
// Send a request to the onecompiler api to execute each code snippet
async runCode(code, name, { skip = false, delay = 0 }) {
if (skip) return [false, 0];
// Delay introduced because the onecompiler api complains if
// you send back to back requests
await this.wait(delay);
const response = await fetch(
"https://onecompiler-apis.p.rapidapi.com/api/v1/run",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-rapidapi-key": this.keys.values.oneCompiler,
"x-rapidapi-host": "onecompiler-apis.p.rapidapi.com",
},
body: JSON.stringify({
language: "python",
files: [
{
name,
content: code,
},
],
}),
},
);
const data = await response.json();
// MH edit
console.log (data.stderr);
return [data.stderr === null, data.executionTime];
}
}
const keys = new KeyManager();
const codePrompt = new PromptManager(keys);
keys.init();
// Show the prompt form after keys have been entered
function showPrompt() {
const keyForm = document.querySelector("#key-form");
keyForm.innerHTML = "";
keyForm.id = "prompt-form";
keyForm.innerHTML = `
<div class="form-item">
<label for="prompt">Prompt</label>
<textarea
id="prompt"
oninput="handleInput(this.value)"
name="prompt"
required
placeholder="Enter a code generation prompt (Max 200 chars)"
maxlength="200"
cols="40"
rows="5"
></textarea>
<p id="counter">0/200</p>
<div class="spread">
<button onclick="keys.clearKeys()" style="background: transparent; color: red; border: 1px solid red">Clear stored keys</button>
<button type="submit" disabled>Generate Code</button>
</div>
</div >
`;
codePrompt.init();
}
// Update the character counter and enable the submit button on the prompt form
function handleInput(value) {
const button = document.querySelector("#prompt-form button[type='submit']");
const counter = document.querySelector("#counter");
button.disabled = false;
if (value === "") {
counter.innerHTML = "0/200";
return;
}
button.disabled = false;
if (value.length > 200) {
value = value.slice(0, 200);
}
counter.innerHTML = `${value.length}/200`;
}
function handleCopy(key) {
const code = codePrompt.responses[key].code;
navigator.clipboard.writeText(code);
alert("Code copied to clipboard");
}