let openaiCubeAngle = 0;
let anthropicCubeAngle = 0;
let googleCubeAngle = 0;
let isProcessingOpenAI = false;
let isProcessingAnthropic = false;
let isProcessingGoogle = false;
const cubeSize = 100;
var openaiTexture, anthropicTexture, googleTexture;
let openaiResult = "Processing...";
let anthropicResult = "Processing...";
let googleResult = "Processing...";
//setup the ui elemetns
AB.world.newRun = function () {
const inputStyle = `
position: absolute;
padding: 10px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: left;`
;
let openaiInput = document.createElement("input");
openaiInput.type = "password";
openaiInput.id = "openaiInput";
openaiInput.placeholder = "Enter your OpenAI API key";
openaiInput.style.cssText = `${inputStyle}; top: 220px; left: calc(33% - 150px);`;
document.body.appendChild(openaiInput);
let openaiRenderArea = document.createElement("p");
openaiRenderArea.id = "openaiRenderArea";
openaiRenderArea.innerText = "";
openaiRenderArea.style.cssText = `position: absolute; top: 600px; left: calc(33% - 150px); text-align: center; width: 250px;`;
document.body.appendChild(openaiRenderArea);
let anthropicInput = document.createElement("input");
anthropicInput.type = "password";
anthropicInput.id = "anthropicInput";
anthropicInput.placeholder = "Enter your Anthropic API key";
anthropicInput.style.cssText = `${inputStyle}; top: 220px; left: calc(50% - 150px);`;
document.body.appendChild(anthropicInput);
let anthropicRenderArea = document.createElement("p");
anthropicRenderArea.id = "anthropicRenderArea";
anthropicRenderArea.innerText = "";
anthropicRenderArea.style.cssText = `position: absolute; top: 600px; left: calc(50% - 150px); text-align: center; width: 250px;`;
document.body.appendChild(anthropicRenderArea);
let googleInput = document.createElement("input");
googleInput.type = "password";
googleInput.id = "googleInput";
googleInput.placeholder = "Enter your Google Access Token";
googleInput.style.cssText = `${inputStyle}; top: 220px; left: calc(67% - 150px);`;
document.body.appendChild(googleInput);
let googleRenderArea = document.createElement("p");
googleRenderArea.id = "googleRenderArea";
googleRenderArea.innerText = "";
googleRenderArea.style.cssText = `position: absolute; top: 600px; left: calc(67% - 150px); text-align: center; width: 250px;`;
document.body.appendChild(googleRenderArea);
let tickerInput = document.createElement("input");
tickerInput.type = "text";
tickerInput.id = "tickerInput";
tickerInput.placeholder = "Enter stock ticker (e.g., AAPL)";
tickerInput.style.cssText = `${inputStyle}; top: 10px; left: 50%; transform: translateX(-50%);`;
document.body.appendChild(tickerInput);
let contentInput = document.createElement("textarea");
contentInput.id = "contentInput";
contentInput.placeholder = "Paste the tweet or article content here...";
contentInput.style.cssText = `${inputStyle}; top: 70px; left: 50%; transform: translateX(-50%); height: 100px;`;
document.body.appendChild(contentInput);
let submitButton = document.createElement("button");
submitButton.id = "submitButton";
submitButton.innerText = "Analyse Sentiment";
submitButton.style.cssText = `
position: absolute;
top: 270px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
font-size: 14px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
`;
submitButton.onclick = analyseSentiment;
document.body.appendChild(submitButton);
//this used to be where all the outputs ended up, but I didn't bother to change it.
let resultArea = document.createElement("p");
resultArea.id = "resultArea";
resultArea.innerText = "Enter your API key, stock ticker, and content, then click analyse.";
resultArea.style.cssText = `
position: absolute;
top: 180px;
left: 50%;
transform: translateX(-50%);
text-align: center;
width: 60%;
font-size: 14px;
color: #333;
`;
document.body.appendChild(resultArea);
document.querySelectorAll("input, textarea, button, p").forEach((element) => {
element.style.position = "absolute";
element.style.zIndex = "10";
});
};
function preload() {
openaiTexture = loadImage ('/uploads/meadeb2/openai-logo.png');
anthropicTexture = loadImage ('/uploads/meadeb2/anthropic-logo.png');
googleTexture = loadImage ('/uploads/meadeb2/google-logo.jpg');
}
//issue with canvas being place over the interactive elemnts, set z index to -1 so it would be under the other elemnts.
function setup() {
const canvas = createCanvas(window.innerWidth, window.innerHeight, WEBGL);
canvas.style('z-index', '-1');
canvas.position(0, 0);
}
function draw() {
background(240);
push();
translate(-330, 0, 0);
texture(openaiTexture);
if (isProcessingOpenAI) rotateY(openaiCubeAngle);
if (isProcessingOpenAI) rotateZ(openaiCubeAngle);
box(cubeSize);
pop();
push();
translate(0, 0, 0);
texture(anthropicTexture);
if (isProcessingAnthropic) rotateX(anthropicCubeAngle);
if (isProcessingAnthropic) rotateY(anthropicCubeAngle);
box(cubeSize);
pop();
push();
translate(330, 0, 0);
texture(googleTexture);
if (isProcessingGoogle) rotateZ(googleCubeAngle);
if (isProcessingGoogle) rotateX(googleCubeAngle);
box(cubeSize);
pop();
if (isProcessingOpenAI) openaiCubeAngle += 0.05;
if (isProcessingAnthropic) anthropicCubeAngle += 0.05;
if (isProcessingGoogle) googleCubeAngle += 0.05;
}
//a collapsible response box
function createResponseBox(id, summary, details) {
const container = document.createElement("div");
container.style.cssText = `
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 15px;
width: 250px;
margin: 7px auto;
cursor: pointer;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
`;
const summaryElement = document.createElement("p");
summaryElement.style.cssText = `
font-size: 20px;
font-weight: bold;
margin: 0;
color: #007bff;
font-family: Arial, sans-serif;
`;
summaryElement.innerText = summary;
const detailsElement = document.createElement("p");
detailsElement.style.cssText = `
font-size: 14px;
margin-top: 10px;
display: none;
`;
detailsElement.innerText = details;
container.onclick = () => {
const isHidden = detailsElement.style.display === "none";
detailsElement.style.display = isHidden ? "block" : "none";
};
container.appendChild(summaryElement);
container.appendChild(detailsElement);
const renderArea = document.getElementById(id);
renderArea.innerHTML = "";
renderArea.appendChild(container);
}
//calls each available API and organises their responses
async function analyseSentiment() {
const openaiKey = document.getElementById("openaiInput").value.trim();
const anthropicKey = document.getElementById("anthropicInput").value.trim();
const googleKey = document.getElementById("googleInput").value.trim();
const ticker = document.getElementById("tickerInput").value.trim();
const content = document.getElementById("contentInput").value.trim();
document.getElementById("openaiRenderArea").innerText = "";
document.getElementById("anthropicRenderArea").innerText = "";
document.getElementById("googleRenderArea").innerText = "";
if (!ticker) {
document.getElementById("resultArea").innerText = "Please enter a stock ticker.";
return;
}
if (!content) {
document.getElementById("resultArea").innerText = "Please paste some content to analyse.";
return;
}
document.getElementById("resultArea").innerText = "";
isProcessingOpenAI = !!openaiKey;
isProcessingAnthropic = !!anthropicKey;
isProcessingGoogle = !!googleKey;
//check which API Keys are available to call APIs asynchronously
try {
if (openaiKey) {
document.getElementById("openaiRenderArea").innerText = "";
const openaiResult = await callOpenAI(openaiKey, ticker, content);
const [summary, ...details] = openaiResult.split("\n");
createResponseBox("openaiRenderArea", summary, details.join("\n"));
}
if (anthropicKey) {
document.getElementById("anthropicRenderArea").innerText = "";
const anthropicResult = await callAnthropic(anthropicKey, ticker, content);
const [summary, ...details] = anthropicResult.split("\n");
createResponseBox("anthropicRenderArea", summary, details.join("\n"));
}
if (googleKey) {
document.getElementById("googleRenderArea").innerText = "";
const googleResult = await callGoogleGemini(googleKey, ticker, content);
const [summary, ...details] = googleResult.split("\n");
createResponseBox("googleRenderArea", summary, details.join("\n"));
}
document.getElementById("resultArea").innerText = "";
} catch (error) {
document.getElementById("resultArea").innerText = "An error occurred during the analysis. Please try again.";
console.error(error);
} finally {
isProcessingOpenAI = false;
isProcessingAnthropic = false;
isProcessingGoogle = false;
}
}
//OpenAI API Homepage
//https://platform.openai.com
//used this tutorial at the start https://www.youtube.com/watch?v=aqdWSYWC_LI
//decided on 0.5 tempurature, to allow a little bit of creativity in responses
async function callOpenAI(apiKey, ticker, content) {
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-3.5-turbo",
messages: [
{
role: "system",
content: "You are an assistant that provides concise sentiment analysis with a positive percentage score (0-100%)focused on a specific stock ticker. Respond with the following format only: 'BUY!/SELL!/HOLD!' as the recommendation, then on a new line the positive sentiment percentage as a percentage (0-100%) and a brief justification. Use the specific stock ticker as the focal point for your analysis. Take into account how what is said in the submission might affect the market value of a company beyond simply positive and negative connotations. Focus on how the content might influence the market value of ${ticker}, considering direct and indirect effects. Provide a concise yet clear justification, no longer than three sentences, focusing on the impact on the specified ticker."
},
{
role: "user",
content: `Analyse the sentiment of the following content for the stock ticker ${ticker}:\n\n${content}`
}
],
max_tokens: 100,
temperature: 0.5
})
});
const analysisData = await response.json();
if (!response.ok) {
throw new Error(analysisData.error.message);
}
return analysisData.choices[0].message.content.trim();
} catch (error) {
console.error("OpenAI API error:", error);
return "OpenAI API call failed.";
}
}
//Anthropic API Homepage
//https://www.anthropic.com/api
async function callAnthropic(apiKey, ticker, content) {
const systemPrompt = `You are an assistant that provides concise sentiment analysis with a positive percentage score (0-100%) focused on a specific stock ticker. Respond with the following format only: 'BUY!/SELL!/HOLD!' as the recommendation then on a new line the positive sentiment percentage as a percentage (0-100%) and a brief justification. Use the specific stock ticker as the focal point for your analysis. Take into account how what is said in the submission might affect the market value of a company beyond simply positive and negative connotations. Focus on how the content might influence the market value of ${ticker}, considering direct and indirect effects. Provide a concise yet clear justification, no longer than three sentences, focusing on the impact on the specified ticker.`;
const messages = [
{
role: "user",
content: `Analyse the sentiment of the following content for the stock ticker ${ticker}:\n\n${content}`
}
];
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
"anthropic-version": "2023-06-01",
//https://github.com/anthropics/anthropic-sdk-typescript/blob/e400d2e8a54aa736717ed849ef8b44a3490fce68/src/index.ts#L151
//this took way too long to figure out, thank you Anthropic
"anthropic-dangerous-direct-browser-access": "true"
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
system: systemPrompt,
messages: messages,
max_tokens: 100,
temperature: 0.5
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error?.message || `Anthropic API status: ${response.status}`);
}
const assistantResponse = data.content?.map(block => block.text).join("") || "No response content.";
return assistantResponse.trim();
} catch (error) {
console.error("Anthropic API Error:", error);
return "Anthropic API call failed.";
}
}
//Google Vertex AI Homepage
//https://cloud.google.com/vertex-ai?hl=en
async function callGoogleGemini(apiKey, ticker, content) {
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${apiKey}`;
const requestBody = {
contents: [
{
parts: [
{
text: `You are an assistant that provides concise sentiment analysis with a positive percentage score (0-100%) focused on the stock ticker ${ticker}. Respond with the following format only: 'BUY!/SELL!/HOLD!' as the recommendation then on a new line the positive sentiment percentage as a percentage (0-100%) and a brief justification. Use the specific stock ticker as the focal point for your analysis. Take into account how what is said in the submission might affect the market value of a company beyond simply positive and negative connotations. Focus on how the content might influence the market value of ${ticker}, considering direct and indirect effects. Provide a concise yet clear justification, no longer than three sentences, focusing on the impact on the specified ticker. Analyse the following content:\n\n${content}`
}
]
}
],
generationConfig: {
maxOutputTokens: 256,
temperature: 0.5
}
};
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error?.message || `Google API status: ${response.status}`);
}
console.log("full Google API Response:", data);
//google responses are weirdly formatted, had to look into the developer console to find where the text response was.
const output = data.candidates?.[0]?.content?.parts?.map(part => part.text).join("") || "No response content.";
return output.trim();
} catch (error) {
console.error("GoogleAPI error:", error);
return "Google API call failed.";
}
}
AB.world.endRun = function () {
document.getElementById("openaiInput").remove();
document.getElementById("anthropicInput").remove();
document.getElementById("tickerInput").remove();
document.getElementById("contentInput").remove();
document.getElementById("submitButton").remove();
document.getElementById("resultArea").remove();
};