// Dynamically create the HTML structure
document.body.innerHTML = `
<h1>Sentiment Analysis Comparison</h1>
<p>Enter a sentence to analyse sentiment with MeaningCloud, Nyckel, and IBM APIs:</p>
<input type="text" id="textInput" placeholder="Enter text here..." style="width: 60%;">
<button onclick="compareSentiments()">Analyse Sentiment</button>
<div id="results" class="result">
<h3>Results</h3>
<div id="output"></div>
<canvas id="confidenceChart" width="150" height="150"></canvas>
</div>
`;
// Dynamically add CSS styles
const style = document.createElement("style");
style.textContent = `
.result {
margin-top: 20px;
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
background-color: #f9f9f9;
}
.positive {
color: green;
font-weight: bold;
}
.negative {
color: red;
font-weight: bold;
}
.neutral {
color: blue;
font-weight: bold;
}
canvas {
margin-top: 20px;
}
`;
document.head.appendChild(style);
// Dynamically load Chart.js
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/chart.js";
document.head.appendChild(script);
// Global variable for the Chart.js instance, only 1 instance is active at a time
let confidenceChartInstance;
// Logic for Sentiment Analysis with 3 different AI API (Nickel, MeaningCloud and IBM)
// Nickel API
// This function retrieves an OAuth2 token and then uses it to analyse the sentiment of the provided text.
async function analyseSentimentNyckel(text) {
const clientId = "k6amtrx4roro466ya0gsw11r33jjj4cp"; // Nyckel client ID
const clientSecret = "k3e5zoje32j8ddb5odhvp1wj5jjq7dkmb241s1zlh2othtdpsdv5nh0i6uzg5t6p"; // Nyckel client secret
const tokenEndpoint = "https://www.nyckel.com/connect/token"; // URL to fetch the OAuth2 token
const analyseEndpoint = "https://www.nyckel.com/v1/functions/text-sentiment-analyzer/invoke"; // URL to analyse sentiment
try {
// Fetch the OAuth2 token
const tokenResponse = await fetch(tokenEndpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}`,// Send client ID and secret
});
const tokenData = await tokenResponse.json(); //Json response
const accessToken = tokenData.access_token; // Extract the access Token
// Call the Nyckel sentiment analysis endpoint
const response = await fetch(analyseEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`, // Use the token in the Authorization header
},
body: JSON.stringify({ data: text }), // Stringify input from the user
});
const data = await response.json(); // Parse the Json response
return { label: data.labelName || "Unknown", confidence: (data.confidence * 100).toFixed(2) }; // return Sentiment AND Confidence
} catch (error) {
throw new Error("Error with Nyckel API: " + error.message); // Catch errors during the Nickel API interaction
}
}
// MeaningCloud API
async function analyseSentimentMeaningCloud(text) {
const apiKey = "a072ae1e94b37022acf7c9a97ad519bd"; // MeaningCloud API key
//const apiKey = "d99440852218fcb9a7e6aff9da0d054b"; // MeaningCloud API key rescue
const endpoint = `https://api.meaningcloud.com/sentiment-2.1?key=${apiKey}&lang=en&txt=${encodeURIComponent(text)}`; // API URL with query parameters
try {
const response = await fetch(endpoint, { method: "POST" }); // POST request to the MeaningCloud
const data = await response.json();
// Map MeaningCloud sentiment codes to more readable labels
const sentimentMap = {
"P+": "Strong Positive",
P: "Positive",
"N+": "Strong Negative",
N: "Negative",
NEU: "Neutral",
};
return { label: sentimentMap[data.score_tag] || "Unknown", confidence: data.confidence }; // Extrac sentiment AND confidence
} catch (error) {
throw new Error("Error with MeaningCloud API: " + error.message); // catch API errors
}
}
// IBM Watson API
async function analyseSentimentIBM(text) {
const apiKey = "UGF6FR3ZMCZK3hN1MYcYJ5UoqdaG762l9Q8LuPZwmdvH"; // IBM API key
const endpoint = // specific IBM endpoint
"https://api.eu-gb.natural-language-understanding.watson.cloud.ibm.com/instances/2abaddcb-da9f-49a3-b066-47f3a03be91f/v1/analyze?version=2021-08-01";
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa("apikey:" + apiKey)}`, //encodes the key into Base64 format
},
body: JSON.stringify({ text, features: { sentiment: {} } }),
});
const data = await response.json();
return {
label: ["positive", "very positive"].includes(data.sentiment.document.label)
? "Positive"
: ["negative", "very negative"].includes(data.sentiment.document.label)
? "Negative"
: ["neutral", "mixed"].includes(data.sentiment.document.label)
? "Neutral"
: "Unknown",
confidence: (data.sentiment.document.score * 100).toFixed(2) || "0",
};
} catch (error) {
throw new Error("Error with IBM Watson API: " + error.message);
}
}
// Function to determine the consensus sentiment from the 3 APIs
// Aggregates sentiment results and identifies the majority label.
function determineConsensus(sentiments) {
// Normalize sentiment labels to a common format
const normalizedSentiments = sentiments.map(sentiment => {
if (["Positive", "P+", "P", "positive", "very positive"].includes(sentiment)) return "Positive";
if (["Negative", "N+", "N", "negative", "very negative"].includes(sentiment)) return "Negative";
if (["Neutral", "NEU", "neutral", "mixed"].includes(sentiment)) return "Neutral";
return "Unknown"; // Fallback for unexpected labels
});
const counts = sentiments.reduce((acc, sentiment) => {
acc[sentiment] = (acc[sentiment] || 0) + 1; // Count occurrences of each sentiment
return acc;
}, {});
const maxCount = Math.max(...Object.values(counts)); // Find the highest occurrence count
const consensus = Object.keys(counts).find((key) => counts[key] === maxCount); // Find the sentiment with the highest count
return maxCount > 1 ? consensus : "Unknown"; // Return the consensus or "Unknown" if no majority
}
// Main function to compare sentiment analysis results
// Collects and displays results from Nyckel, MeaningCloud, and IBM APIs
// Displays the results as text and a confidence score bar chart.
async function compareSentiments() {
const text = document.getElementById("textInput").value.trim(); // Get the text field
const outputDiv = document.getElementById("output"); // Get the output container
if (!text) { // Show error if no text provided
outputDiv.innerHTML = `<p class="error">Please enter some text to analyse.</p>`;
return;
}
outputDiv.innerHTML = "<p>Analysing...</p>"; // Loading message
// Fetch sentiment analysis results from all three APIs simultaneously
try {
const [nyckel, meaningCloud, ibm] = await Promise.all([
analyseSentimentNyckel(text),
analyseSentimentMeaningCloud(text),
analyseSentimentIBM(text),
]);
const consensus = determineConsensus([nyckel.label, meaningCloud.label, ibm.label]); // Determine the consensus sentiment
// Display sentiment analysis results and consensus
outputDiv.innerHTML = `
<!-- Display the original input text provided by the user -->
<h4>Input Text:</h4>
<p>${text}</p>
<!-- Nyckel API Results -->
<h4>Nyckel Results:</h4>
<p class="${
// Check Nyckel's sentiment label and assign a CSS class
nyckel.label === "Positive" ? "positive" :
nyckel.label === "Negative" ? "negative" :
"neutral"
}">
Sentiment: ${nyckel.label} <!-- Inject the sentiment label from Nyckel API -->
</p>
<p>Confidence: ${nyckel.confidence}%</p> <!-- Display Nyckel's confidence score -->
<!-- MeaningCloud API Results -->
<h4>MeaningCloud Results:</h4>
<p class="${
// Use substring matching for MeaningCloud's sentiment label to assign a CSS class
meaningCloud.label.includes("Positive") ? "positive" :
meaningCloud.label.includes("Negative") ? "negative" :
"neutral"
}">
Sentiment: ${meaningCloud.label} <!-- Inject the sentiment label from MeaningCloud API -->
</p>
<p>Confidence: ${meaningCloud.confidence}%</p> <!-- Display MeaningCloud's confidence score -->
<!-- IBM API Results -->
<h4>IBM Watson Results:</h4>
<p class="${
// Use strict comparison for IBM's sentiment label to assign a CSS class
ibm.label.toLowerCase() === "positive" ? "positive" :
ibm.label.toLowerCase() === "negative" ? "negative" :
"neutral"
}">
Sentiment: ${ibm.label} <!-- Inject the sentiment label from IBM Watson API -->
</p>
<p>Confidence: ${ibm.confidence}%</p> <!-- Display IBM confidence score -->
<!-- Consensus Sentiment -->
<!-- Display the consensus sentiment calculated across APIs -->
<h4>Consensus Sentiment:</h4>
<p class="${
consensus === "Positive" ? "positive" :
consensus === "Negative" ? "negative" :
"neutral"
}">
${consensus}
</p>
`;
// Chart.js Barchart rendering confidence scores
if (confidenceChartInstance) confidenceChartInstance.destroy(); //Clear the previous chart
const ctx = document.getElementById("confidenceChart").getContext("2d");
confidenceChartInstance = new Chart(ctx, {
type: "bar",
data: {
labels: ["Nyckel", "MeaningCloud", "IBM Watson"],
datasets: [
{
label: "Confidence Scores",
data: [nyckel.confidence, meaningCloud.confidence, ibm.confidence], // Dataset
backgroundColor: ["green", "blue", "red"],
},
],
},
options: {
// Chart size
maintainAspectRatio: true,
scales: { y: { beginAtZero: true, max: 100 } },
},
});
} catch (error) {
outputDiv.innerHTML = `<p class="error">Error: ${error.message}</p>`;
}
}