// Quamdeen: Initializes the Sentiment Analysis Dashboard and sets up configurations
function sentimentDashboard() {
// =======================
// ==== API CONFIGURATIONS
// =======================
const apiConfigs = {
MeaningCloud: {
key: '', // Will be set via input
url: 'https://api.meaningcloud.com/sentiment-2.1'
},
Twinword: {
key: '', // Will be set via input
url: 'https://twinword-sentiment-analysis.p.rapidapi.com/analyze/'
},
SentimentAPI: {
key: '', // Will be set via input
url: 'https://sentiment-api3.p.rapidapi.com/sentiment_analysis' // Fixed URL
}
};
// Mapping API names to checkbox IDs
const apiIdMapping = {
MeaningCloud: 'meaningcloud-checkbox',
Twinword: 'twinword-checkbox',
SentimentAPI: 'sentimentapi-checkbox'
};
// Variables to store API keys
let MeaningCloudAPIKey = '';
let TwinwordAPIKey = '';
let SentimentAPIKey = '';
// =======================
// ==== LOAD EXTERNAL SCRIPTS
// =======================
// Patrick: Dynamically loads external JavaScript scripts
function loadScript(url, callback) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = callback;
script.onerror = function () {
console.error(`Failed to load script ${url}`);
displayError(`Failed to load necessary script: ${url}`);
};
document.head.appendChild(script);
}
// Load Chart.js and then create the UI
loadScript('https://cdn.jsdelivr.net/npm/chart.js', createUI);
// =======================
// ==== UI CREATION FUNCTION
// =======================
// Quamdeen: Creates the user interface for the dashboard
function createUI() {
console.log('Creating UI...');
// Create container
const container = document.createElement('div');
container.id = 'sentiment-dashboard';
container.style.maxWidth = '900px';
container.style.margin = '40px auto';
container.style.background = '#fff';
container.style.padding = '30px';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 0 15px rgba(0,0,0,0.2)';
container.style.fontFamily = 'Arial, sans-serif';
// Create title
const title = document.createElement('h1');
title.innerText = 'Enhanced Sentiment Analysis Dashboard';
title.style.textAlign = 'center';
title.style.color = '#2c3e50';
container.appendChild(title);
// Create input section
const inputSection = document.createElement('div');
inputSection.style.marginTop = '20px';
// Textarea for input
const textarea = document.createElement('textarea');
textarea.id = 'text-input';
textarea.placeholder = 'Enter text for sentiment analysis...';
textarea.style.width = '100%';
textarea.style.height = '150px';
textarea.style.padding = '12px';
textarea.style.marginBottom = '15px';
textarea.style.border = '1px solid #bdc3c7';
textarea.style.borderRadius = '4px';
textarea.style.fontSize = '16px';
textarea.style.resize = 'vertical';
textarea.setAttribute('aria-label', 'Text input for sentiment analysis');
inputSection.appendChild(textarea);
// Language selector
const langLabel = document.createElement('label');
langLabel.innerText = 'Select Language: ';
langLabel.setAttribute('for', 'language-select');
langLabel.style.marginRight = '10px';
inputSection.appendChild(langLabel);
const langSelect = document.createElement('select');
langSelect.id = 'language-select';
langSelect.style.padding = '8px';
langSelect.style.border = '1px solid #bdc3c7';
langSelect.style.borderRadius = '4px';
['en', 'es', 'fr', 'de'].forEach((lang) => {
const option = document.createElement('option');
option.value = lang;
option.innerText = lang.toUpperCase();
langSelect.appendChild(option);
});
inputSection.appendChild(langSelect);
// API Keys Section
const apiKeysSection = document.createElement('div');
apiKeysSection.style.marginTop = '20px';
apiKeysSection.style.padding = '15px';
apiKeysSection.style.border = '1px solid #bdc3c7';
apiKeysSection.style.borderRadius = '6px';
apiKeysSection.style.backgroundColor = '#f9f9f9';
const apiKeysTitle = document.createElement('h4');
apiKeysTitle.innerText = 'Enter Your API Keys';
apiKeysTitle.style.color = '#34495e';
apiKeysSection.appendChild(apiKeysTitle);
// Patrick: Creates API key input fields
function createApiKeyInput(apiName) {
const apiKeyLabel = document.createElement('label');
apiKeyLabel.innerText = `${apiName} API Key`;
apiKeyLabel.setAttribute('for', `${apiName.toLowerCase()}ApiKey`);
apiKeyLabel.style.display = 'block';
apiKeyLabel.style.marginTop = '10px';
const apiKeyInput = document.createElement('input');
apiKeyInput.type = 'password';
apiKeyInput.id = `${apiName.toLowerCase()}ApiKey`;
apiKeyInput.className = 'form-control';
apiKeyInput.placeholder = `Enter your ${apiName} API key`;
apiKeyInput.style.width = '100%';
apiKeyInput.style.padding = '8px';
apiKeyInput.style.border = '1px solid #bdc3c7';
apiKeyInput.style.borderRadius = '4px';
apiKeyInput.style.marginTop = '5px';
apiKeysSection.appendChild(apiKeyLabel);
apiKeysSection.appendChild(apiKeyInput);
}
// Create API key inputs for each API
Object.keys(apiConfigs).forEach((apiName) => {
createApiKeyInput(apiName);
});
// Save API Keys button
const saveApiKeysButton = document.createElement('button');
saveApiKeysButton.innerText = 'Save API Keys';
saveApiKeysButton.className = 'btn btn-primary';
saveApiKeysButton.style.marginTop = '15px';
saveApiKeysButton.onclick = saveApiKeys;
apiKeysSection.appendChild(saveApiKeysButton);
const apiMessage = document.createElement('p');
apiMessage.id = 'apiMessage';
apiMessage.style.marginTop = '10px';
apiKeysSection.appendChild(apiMessage);
inputSection.appendChild(apiKeysSection);
// API selection checkboxes
const apiSelection = document.createElement('div');
apiSelection.style.marginTop = '20px';
apiSelection.style.display = 'flex';
apiSelection.style.flexWrap = 'wrap';
apiSelection.style.gap = '15px';
Object.keys(apiConfigs).forEach((apiName) => {
const label = document.createElement('label');
label.style.display = 'flex';
label.style.alignItems = 'center';
label.style.gap = '5px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = apiIdMapping[apiName];
checkbox.checked = true;
label.appendChild(checkbox);
label.appendChild(document.createTextNode(apiName));
apiSelection.appendChild(label);
console.log(`Checkbox created for ${apiName} with ID ${checkbox.id}`);
});
inputSection.appendChild(apiSelection);
// This is for the Analyze sentiment button
const button = document.createElement('button');
button.innerText = 'Analyze Sentiment';
button.style.padding = '12px 25px';
button.style.backgroundColor = '#2980b9';
button.style.border = 'none';
button.style.color = '#fff';
button.style.borderRadius = '4px';
button.style.cursor = 'pointer';
button.style.fontSize = '16px';
button.style.marginTop = '20px';
button.style.display = 'block';
button.style.marginLeft = 'auto';
button.style.marginRight = 'auto';
button.onclick = analyzeSentiment;
button.onmouseover = function () {
button.style.backgroundColor = '#1f618d';
};
button.onmouseout = function () {
button.style.backgroundColor = '#2980b9';
};
inputSection.appendChild(button);
// Append input section to container
container.appendChild(inputSection);
// Results section
const results = document.createElement('div');
results.id = 'results';
results.style.display = 'flex';
results.style.flexWrap = 'wrap';
results.style.marginTop = '30px';
results.style.gap = '20px';
// Patrick: Creates result display boxes for each API
function createResultBox(id, titleText) {
const resultBox = document.createElement('div');
resultBox.className = 'result-box';
resultBox.style.flex = '1 1 220px';
resultBox.style.background = '#ecf0f1';
resultBox.style.padding = '15px';
resultBox.style.borderRadius = '6px';
resultBox.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
resultBox.style.position = 'relative';
const resultTitle = document.createElement('h2');
resultTitle.innerText = titleText;
resultTitle.style.marginTop = '0';
resultTitle.style.fontSize = '18px';
resultTitle.style.color = '#34495e';
resultBox.appendChild(resultTitle);
const sentimentDiv = document.createElement('div');
sentimentDiv.id = id;
sentimentDiv.className = 'sentiment';
sentimentDiv.innerText = 'No data';
sentimentDiv.style.fontSize = '24px';
sentimentDiv.style.fontWeight = 'bold';
sentimentDiv.style.textAlign = 'center';
sentimentDiv.style.padding = '10px';
sentimentDiv.style.borderRadius = '4px';
sentimentDiv.style.color = '#fff';
sentimentDiv.style.marginBottom = '10px';
resultBox.appendChild(sentimentDiv);
// Details button
const detailsButton = document.createElement('button');
detailsButton.innerText = 'View Details';
detailsButton.style.padding = '6px 12px';
detailsButton.style.backgroundColor = '#8e44ad';
detailsButton.style.border = 'none';
detailsButton.style.color = '#fff';
detailsButton.style.borderRadius = '4px';
detailsButton.style.cursor = 'pointer';
detailsButton.style.fontSize = '14px';
detailsButton.onclick = () => showDetails(titleText, sentimentDiv.dataset.raw);
resultBox.appendChild(detailsButton);
return resultBox;
}
// Create Result Boxes for APIs
Object.keys(apiConfigs).forEach((apiName) => {
const apiResultBox = createResultBox(`${apiName.toLowerCase()}-sentiment`, apiName);
results.appendChild(apiResultBox);
});
// Final Aggregated Sentiment
const finalSentimentDiv = document.createElement('div');
finalSentimentDiv.id = 'final-sentiment';
finalSentimentDiv.style.width = '100%';
finalSentimentDiv.style.padding = '15px';
finalSentimentDiv.style.borderRadius = '6px';
finalSentimentDiv.style.background = '#bdc3c7'; // Grey during loading
finalSentimentDiv.style.textAlign = 'center';
finalSentimentDiv.style.fontSize = '20px';
finalSentimentDiv.style.fontWeight = 'bold';
finalSentimentDiv.innerText = 'Aggregated Sentiment: N/A';
results.appendChild(finalSentimentDiv);
// This is the Bar Chart for visual comparison
const chartContainer = document.createElement('div');
chartContainer.style.width = '100%';
chartContainer.style.marginTop = '30px';
chartContainer.style.textAlign = 'center';
const chartTitle = document.createElement('h2');
chartTitle.innerText = 'Sentiment Scores Comparison';
chartTitle.style.color = '#2c3e50';
chartContainer.appendChild(chartTitle);
const chartCanvas = document.createElement('canvas');
chartCanvas.id = 'sentimentChart';
chartCanvas.width = 800;
chartCanvas.height = 400;
chartContainer.appendChild(chartCanvas);
container.appendChild(results);
container.appendChild(chartContainer);
// Modal for detailed API responses
const modal = document.createElement('div');
modal.id = 'apiModal';
modal.className = 'modal';
modal.style.display = 'none'; // Hidden by default
modal.style.position = 'fixed';
modal.style.zIndex = '1000';
modal.style.left = '0';
modal.style.top = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.overflow = 'auto';
modal.style.backgroundColor = 'rgba(0,0,0,0.4)';
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
modalContent.style.backgroundColor = '#fefefe';
modalContent.style.margin = '10% auto';
modalContent.style.padding = '20px';
modalContent.style.border = '1px solid #888';
modalContent.style.width = '80%';
modalContent.style.borderRadius = '8px';
const closeSpan = document.createElement('span');
closeSpan.className = 'close';
closeSpan.innerHTML = '×';
closeSpan.style.color = '#aaa';
closeSpan.style.float = 'right';
closeSpan.style.fontSize = '28px';
closeSpan.style.fontWeight = 'bold';
closeSpan.style.cursor = 'pointer';
closeSpan.onclick = function () {
modal.style.display = 'none';
};
modalContent.appendChild(closeSpan);
const modalHeader = document.createElement('h2');
modalHeader.id = 'modal-title';
modalHeader.innerText = 'API Details';
modalContent.appendChild(modalHeader);
const modalBody = document.createElement('pre');
modalBody.id = 'modal-body';
modalBody.style.whiteSpace = 'pre-wrap';
modalBody.style.wordWrap = 'break-word';
modalContent.appendChild(modalBody);
modal.appendChild(modalContent);
container.appendChild(modal);
// Loading Indicator
const loadingIndicator = document.createElement('div');
loadingIndicator.id = 'loading-indicator';
loadingIndicator.innerText = 'Analyzing sentiment...';
loadingIndicator.style.display = 'none';
loadingIndicator.style.textAlign = 'center';
loadingIndicator.style.marginTop = '20px';
loadingIndicator.style.fontSize = '18px';
loadingIndicator.style.color = '#2980b9';
container.appendChild(loadingIndicator);
// Append container to body
document.body.appendChild(container);
}
// =======================
// ==== DISPLAY ERROR FUNCTION
// =======================
// Patrick: Displays error messages to the user
function displayError(message) {
const apiMessage = document.getElementById('apiMessage');
if (apiMessage) {
apiMessage.textContent = message;
apiMessage.style.color = 'red';
}
}
// =======================
// ==== SUCCESS MESSAGE FUNCTION
// =======================
// Quamdeen: Displays success messages to the user
function displaySuccess(message) {
const apiMessage = document.getElementById('apiMessage');
if (apiMessage) {
apiMessage.textContent = message;
apiMessage.style.color = 'green';
}
}
// =======================
// ==== SAVE API KEYS FUNCTION
// =======================
// Patrick: Saves the API keys entered by the user
function saveApiKeys() {
const meaningCloudKey = document.getElementById('meaningcloudApiKey').value.trim();
const twinwordKey = document.getElementById('twinwordApiKey').value.trim();
const sentimentAPIKey = document.getElementById('sentimentapiApiKey').value.trim();
const apiMessage = document.getElementById('apiMessage');
if (!meaningCloudKey && !twinwordKey && !sentimentAPIKey) {
displayError('At least one API key is required!');
return;
}
MeaningCloudAPIKey = meaningCloudKey;
TwinwordAPIKey = twinwordKey;
SentimentAPIKey = sentimentAPIKey;
apiConfigs.MeaningCloud.key = meaningCloudKey;
apiConfigs.Twinword.key = twinwordKey;
apiConfigs.SentimentAPI.key = SentimentAPIKey;
// apiConfigs.SentimentAPI.url = SentimentAPIURL; // Fixed URL remains unchanged
displaySuccess('API keys saved successfully!');
}
// =======================
// ==== SHOW DETAILS FUNCTION
// =======================
// Quamdeen: Displays detailed API response data in a modal
function showDetails(apiName, rawData) {
const modal = document.getElementById('apiModal');
const modalTitle = document.getElementById('modal-title');
const modalBody = document.getElementById('modal-body');
modalTitle.innerText = `${apiName} - Raw Response`;
modalBody.innerText = rawData ? rawData : 'No data available.';
modal.style.display = 'block';
}
// =======================
// ==== GET SENTIMENT LABEL FUNCTION
// =======================
// Patrick: Determines the sentiment label and corresponding CSS class based on API response
function getSentimentLabel(sentiment, source) {
let label = '';
let className = '';
if (source === 'MeaningCloud') {
switch (sentiment) {
case 'p+':
case 'p':
label = 'Positive';
className = 'positive';
break;
case 'neu':
label = 'Neutral';
className = 'neutral';
break;
case 'n':
case 'n+':
label = 'Negative';
className = 'negative';
break;
default:
label = 'None';
className = 'neutral';
}
} else if (source === 'Twinword') {
switch (sentiment) {
case 'positive':
label = 'Positive';
className = 'positive';
break;
case 'neutral':
label = 'Neutral';
className = 'neutral';
break;
case 'negative':
label = 'Negative';
className = 'negative';
break;
default:
label = 'None';
className = 'neutral';
}
} else if (source === 'SentimentAPI') {
switch (sentiment) {
case 'positive':
label = 'Positive';
className = 'positive';
break;
case 'neutral':
label = 'Neutral';
className = 'neutral';
break;
case 'negative':
label = 'Negative';
className = 'negative';
break;
default:
label = 'None';
className = 'neutral';
}
}
return { label, className };
}
// =======================
// ==== CAPITALIZE FUNCTION
// =======================
// Quamdeen: Capitalizes the first letter of a given string
// Parameters:
// - string (String): The input string to capitalize
// Returns:
// - (String): The input witht the first letter capitalized
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// =======================
// ==== RESET RESULTS FUNCTION
// =======================
// Patrick: Resets previous sentiment analysis results before a new analysis
function resetResults() {
Object.keys(apiConfigs).forEach((apiName) => {
const sentimentDiv = document.getElementById(`${apiName.toLowerCase()}-sentiment`);
if (sentimentDiv) {
sentimentDiv.innerText = 'Loading...';
sentimentDiv.className = 'sentiment neutral'; // Reset classes
sentimentDiv.style.backgroundColor = '#bdc3c7'; // Set to grey during loading
sentimentDiv.dataset.raw = '';
}
});
const finalSentimentDiv = document.getElementById('final-sentiment');
if (finalSentimentDiv) {
finalSentimentDiv.innerText = 'Aggregated Sentiment: N/A';
finalSentimentDiv.style.backgroundColor = '#bdc3c7'; // Reset to grey
}
// Safely destroy existing chart if it exists
if (window.sentimentChart && typeof window.sentimentChart.destroy === 'function') {
window.sentimentChart.destroy();
window.sentimentChart = null; // Reset to null after destruction
console.log('Existing chart destroyed.');
} else {
console.warn('No existing Chart instance to destroy.');
}
// Clear any existing error or success messages
const apiMessage = document.getElementById('apiMessage');
if (apiMessage) {
apiMessage.textContent = '';
}
}
// =======================
// ==== DISPLAY RESULTS FUNCTION
// =======================
// Quamdeen: Displays the sentiment analysis results from each API
function displayResults(apiResults) {
Object.keys(apiResults).forEach((apiName) => {
const result = apiResults[apiName];
const sentimentDiv = document.getElementById(`${apiName.toLowerCase()}-sentiment`);
if (sentimentDiv) {
sentimentDiv.innerText = result.label;
// Reset className to include only 'sentiment' and current class
sentimentDiv.className = `sentiment ${result.className}`;
// Apply background color based on className
sentimentDiv.style.backgroundColor =
result.className === 'positive' ? '#27ae60' : // Green for Positive
result.className === 'negative' ? '#c0392b' : // Red for Negative
'#f39c12'; // Orange for Neutral
sentimentDiv.dataset.raw = JSON.stringify(result.raw, null, 2);
}
});
// Aggregate sentiments
const finalSentiment = aggregateSentiments(apiResults);
const finalSentimentDiv = document.getElementById('final-sentiment');
if (finalSentimentDiv) {
finalSentimentDiv.innerText = `Aggregated Sentiment: ${finalSentiment}`;
// Change background color based on finalSentiment
switch (finalSentiment) {
case 'Positive':
finalSentimentDiv.style.backgroundColor = '#27ae60'; // Green
break;
case 'Negative':
finalSentimentDiv.style.backgroundColor = '#c0392b'; // Red
break;
case 'Neutral':
finalSentimentDiv.style.backgroundColor = '#f39c12'; // Orange
break;
default:
finalSentimentDiv.style.backgroundColor = '#bdc3c7'; // Grey (Default)
}
}
// Update chart
updateChart(apiResults);
}
// =======================
// ==== AGGREGATE SENTIMENTS FUNCTION
// =======================
// Patrick: Aggregates individual sentiments to determine overall sentiment
function aggregateSentiments(apiResults) {
let sentimentCount = { Positive: 0, Neutral: 0, Negative: 0 };
for (let api in apiResults) {
const label = apiResults[api].label;
if (sentimentCount[label] !== undefined) {
sentimentCount[label]++;
}
}
// Determine majority sentiment
let finalSentiment = 'Neutral'; // Default
if (sentimentCount.Positive > sentimentCount.Negative && sentimentCount.Positive > sentimentCount.Neutral) {
finalSentiment = 'Positive';
} else if (sentimentCount.Negative > sentimentCount.Positive && sentimentCount.Negative > sentimentCount.Neutral) {
finalSentiment = 'Negative';
}
return finalSentiment;
}
// =======================
// ==== UPDATE CHART FUNCTION
// =======================
// Quamdeen: Updates the Chart.js bar chart with the latest sentiment scores
function updateChart(apiResults) {
// Ensure Chart.js is loaded
if (typeof Chart === 'undefined') {
console.error('Chart.js is not loaded.');
displayError('Chart.js failed to load. Sentiment scores will not be displayed as a chart.');
return;
}
const labels = Object.keys(apiResults);
const data = labels.map((api) => {
const result = apiResults[api];
if (result.score !== undefined) return result.score;
if (result.confidence !== undefined) return result.confidence;
return 0; // Default
});
const backgroundColors = labels.map((api) => {
const sentiment = apiResults[api].label;
if (sentiment === 'Positive') return 'rgba(39, 174, 96, 0.6)'; // Green
if (sentiment === 'Negative') return 'rgba(192, 57, 43, 0.6)'; // Red
return 'rgba(243, 156, 18, 0.6)'; // Orange for Neutral
});
const borderColors = labels.map((api) => {
const sentiment = apiResults[api].label;
if (sentiment === 'Positive') return 'rgba(39, 174, 96, 1)'; // Green
if (sentiment === 'Negative') return 'rgba(192, 57, 43, 1)'; // Red
return 'rgba(243, 156, 18, 1)'; // Orange Border for Neutral
});
const ctx = document.getElementById('sentimentChart').getContext('2d');
// Safely destroy existing chart if it's a valid Chart.js instance
if (window.sentimentChart && typeof window.sentimentChart.destroy === 'function') {
window.sentimentChart.destroy();
window.sentimentChart = null; // Reset to null after destruction
console.log('Existing chart destroyed before creating a new one.');
}
// Create a new chart
try {
window.sentimentChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Sentiment Score',
data: data,
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 1
}
]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function (context) {
return `Score: ${context.parsed.y}`;
}
}
}
}
}
});
console.log('Chart.js chart created successfully.');
} catch (error) {
console.error('Error creating Chart.js chart:', error);
displayError('An error occurred while creating the sentiment chart.');
}
}
// // Quamdeen: Function to update Chart.js chart with standardized sentiment scores. So I added this part just for visual representation, if you would like to see the results of the analysis when all the scores are standardised
// function updateChart(apiResults) {
// // Ensure Chart.js is loaded
// if (typeof Chart === 'undefined') {
// console.error('Chart.js is not loaded.');
// alert('Chart.js failed to load. Sentiment scores will not be displayed as a chart.');
// return;
// }
// const labels = Object.keys(apiResults);
// const data = labels.map(api => {
// const result = apiResults[api];
// let score;
// // Normalize the scores based on API-specific logic
// if (api === 'MeaningCloud' && result.confidence !== undefined) {
// score = result.confidence; // this stays the same as we are trying to normalise the others to 100
// } else if ((api === 'Twinword' || api === 'SentimentAPI') && result.confidence !== undefined) {
// score = result.confidence * 100; // Convert Twinword/SentimentAPI (0-1) to 0-100
// } else if (api === 'Google' && result.score !== undefined) {
// score = result.score * 100; // Assuming Google score (0-1), convert to 0-100
// } else {
// score = 0; // Default to 0 if no score is available
// }
// return score;
// });
// const backgroundColors = labels.map(api => {
// const sentiment = apiResults[api].label;
// if (sentiment === 'Positive') return 'rgba(46, 204, 113, 0.6)'; // Green
// if (sentiment === 'Negative') return 'rgba(231, 76, 60, 0.6)'; // Red
// return 'rgba(255, 165, 0, 0.6)'; // Neutral Orange (Replaced Grey)
// });
// const borderColors = labels.map(api => {
// const sentiment = apiResults[api].label;
// if (sentiment === 'Positive') return 'rgba(46, 204, 113, 1)'; // Green
// if (sentiment === 'Negative') return 'rgba(231, 76, 60, 1)'; // Red
// return 'rgba(255, 165, 0, 1)'; // Neutral Orange Border (Replaced Grey)
// });
// const ctx = document.getElementById('sentimentChart').getContext('2d');
// // Safely destroy existing chart if it's a valid Chart.js instance
// if (window.sentimentChart && typeof window.sentimentChart.destroy === 'function') {
// window.sentimentChart.destroy();
// window.sentimentChart = null; // Reset to null after destruction
// console.log('Existing chart destroyed before creating a new one.');
// }
// // Create a new Chart.js bar chart
// try {
// window.sentimentChart = new Chart(ctx, {
// type: 'bar',
// data: {
// labels: labels,
// datasets: [{
// label: 'Sentiment Score (Normalized)',
// data: data,
// backgroundColor: backgroundColors,
// borderColor: borderColors,
// borderWidth: 1
// }]
// },
// options: {
// responsive: true,
// scales: {
// y: {
// beginAtZero: true,
// max: 100 // Scale the Y-axis to 100 for standardized scores
// }
// },
// plugins: {
// legend: {
// display: false // Hide the legend for clarity
// },
// tooltip: {
// callbacks: {
// label: function(context) {
// return `Score: ${context.parsed.y}`;
// }
// }
// }
// }
// }
// });
// console.log('Chart.js normalized chart created successfully.');
// } catch (error) {
// console.error('Error creating Chart.js normalized chart:', error);
// alert('An error occurred while creating the sentiment chart.');
// }
// }
// =======================
// ==== API CALL FUNCTIONS
// =======================
// Patrick: Calls the MeaningCloud API for sentiment analysis
async function fetchMeaningCloud(text, lang) {
const startTime = Date.now();
try {
// Sending a POST request to MeaningCloud API with necessary parameters
const response = await fetch(apiConfigs.MeaningCloud.url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
key: apiConfigs.MeaningCloud.key,
txt: text,
lang: lang
})
});
const responseTime = Date.now() - startTime;
if (!response.ok) throw new Error('MeaningCloud API Error');
const data = await response.json();
const sentiment = data.score_tag; // Extracting the sentiment tag from response
const confidence = parseFloat(data.confidence); // Extracting confidence score
const result = getSentimentLabel(sentiment.toLowerCase(), 'MeaningCloud'); // Mapping to label and class
result.raw = data; // Storing raw API response
result.confidence = confidence; // Storing confidence score
result.responseTime = responseTime; // Storing response time
console.log('MeaningCloud Sentiment Analysis Result:', result);
return result;
} catch (error) {
console.error('MeaningCloud Sentiment Analysis Error:', error);
// Returning an error object to handle it gracefully in the UI
return { label: 'Error', className: 'neutral', raw: error.message };
}
}
// Quamdeen: Calls the Twinword API for sentiment analysis
async function fetchTwinword(text, lang) {
const startTime = Date.now();
try {
// Sending a POST request to Twinword API with the input text
const response = await fetch(apiConfigs.Twinword.url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-RapidAPI-Key': apiConfigs.Twinword.key,
'X-RapidAPI-Host': 'twinword-sentiment-analysis.p.rapidapi.com'
},
body: new URLSearchParams({ text })
});
const responseTime = Date.now() - startTime;
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Twinword API Error: ${errorData.message || response.statusText}`);
}
const data = await response.json();
const sentiment = data.type;
const confidence = data.score;
const result = getSentimentLabel(sentiment.toLowerCase(), 'Twinword'); // Ensure lowercase
result.raw = data; // Storing raw API response
result.confidence = confidence; // Storing confidence score
result.responseTime = responseTime; // Storing response time
console.log('Twinword Sentiment Analysis Result:', result);
return result;
} catch (error) {
console.error('Twinword Sentiment Analysis Error:', error);
// Returning an error object to handle it gracefully in the UI
return { label: 'Error', className: 'neutral', raw: error.message };
}
}
// Quamdeen: Calls the SentimentAPI for sentiment analysis
async function fetchSentimentAPI(text, lang) {
const startTime = Date.now();
try {
// Sending a POST request to SentimentAPI with the input text
const response = await fetch(apiConfigs.SentimentAPI.url, {
method: 'POST',
headers: {
'x-rapidapi-key': apiConfigs.SentimentAPI.key,
'x-rapidapi-host': 'sentiment-api3.p.rapidapi.com',
'Content-Type': 'application/json'
},
body: JSON.stringify({
input_text: text
})
});
const responseTime = Date.now() - startTime;
if (!response.ok) {
let errorMessage = `SentimentAPI Error: ${response.status} ${response.statusText}`;
try {
const errorData = await response.json();
errorMessage = `SentimentAPI Error: ${errorData.message || response.statusText}`;
} catch (e) {
// If response is not JSON
}
throw new Error(errorMessage);
}
const data = await response.json();
const sentiment = data.overall_sentiment ? data.overall_sentiment.toLowerCase() : 'none';
// Use 'confidence_score' from the response or fallback to 'sentiment_score'
const confidence = data.confidence_score !== undefined ? data.confidence_score : data.sentiment_score;
const result = getSentimentLabel(sentiment, 'SentimentAPI');
result.raw = data; // Storing raw API response
result.confidence = confidence; // Storing response time
result.responseTime = responseTime;
console.log('SentimentAPI Sentiment Analysis Result:', result);
return result;
} catch (error) {
console.error('SentimentAPI Sentiment Analysis Error:', error);
// Returning an error object to handle it gracefully in the UI
return { label: 'Error', className: 'neutral', raw: error.message };
}
}
// =======================
// ==== SENTIMENT ANALYSIS FUNCTION
// =======================
// Quamdeen: Orchestrates the sentiment analysis process using selected APIs
async function analyzeSentiment() {
const text = document.getElementById('text-input').value.trim();
const selectedLang = document.getElementById('language-select').value;
const apiMessage = document.getElementById('apiMessage');
if (!text) {
displayError('Please enter some text for analysis.');
return;
}
console.log('Starting sentiment analysis...');
// Reset previous results
resetResults();
// Show loading indicator
const loadingIndicator = document.getElementById('loading-indicator');
if (loadingIndicator) {
loadingIndicator.style.display = 'block';
}
// Gather selected APIs using the correct IDs
const selectedAPIs = [];
if (document.getElementById(apiIdMapping.MeaningCloud).checked) selectedAPIs.push('MeaningCloud');
if (document.getElementById(apiIdMapping.Twinword).checked) selectedAPIs.push('Twinword');
if (document.getElementById(apiIdMapping.SentimentAPI).checked) selectedAPIs.push('SentimentAPI');
console.log('Selected APIs:', selectedAPIs);
if (selectedAPIs.length === 0) {
displayError('Please select at least one API for analysis.');
if (loadingIndicator) {
loadingIndicator.style.display = 'none'; // Hide loading indicator
}
return;
}
// Validate that API keys are provided
for (let api of selectedAPIs) {
if (!apiConfigs[api].key) {
displayError(`Please enter the API key for ${api}.`);
if (loadingIndicator) {
loadingIndicator.style.display = 'none';
}
return;
}
// Ther is no need to validate service URL for RapidAPI-hosted APIs as they don't use service urls and their urls are fixed so they don't require user input.
}
// Perform parallel API calls
const apiPromises = selectedAPIs.map((api) => {
if (api === 'MeaningCloud') return fetchMeaningCloud(text, selectedLang);
if (api === 'Twinword') return fetchTwinword(text, selectedLang);
if (api === 'SentimentAPI') return fetchSentimentAPI(text, selectedLang);
});
try {
const apiResultsArray = await Promise.all(apiPromises);
const apiResults = {};
selectedAPIs.forEach((api, index) => {
apiResults[api] = apiResultsArray[index];
});
console.log('API Results:', apiResults);
// Check for any errors in API results
const hasError = Object.values(apiResults).some(result => result.label === 'Error');
if (hasError) {
displayError('One or more APIs failed to process the sentiment analysis. Please check the console for details.');
} else {
// Display results
displayResults(apiResults);
displaySuccess('Sentiment analysis completed successfully!');
}
} catch (error) {
console.error('Error in sentiment analysis:', error);
displayError('An unexpected error occurred during sentiment analysis. Please try again.');
} finally {
// Hide loading indicator
if (loadingIndicator) {
loadingIndicator.style.display = 'none';
}
}
}
// =======================
// ==== MODAL FUNCTIONALITY
// =======================
// Patrick: Closes the modal when clicking outside of it
window.onclick = function (event) {
const modal = document.getElementById('apiModal');
if (event.target == modal) {
modal.style.display = 'none';
}
};
}
// Initialize the Sentiment Dashboard
sentimentDashboard();