Code viewer for World: Sentiment Analysis
// 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>`;
  }
}