Forum Discussion

KrisShenenbe782's avatar
KrisShenenbe782
Community Member
3 months ago

Integrating a Chatbot into My Storyline Project

Hi all,

I'd like to integrate chatbots in my Storyline courses in the near future. I tried a little test with my ChatGPT API key. It doesn't work because I don't want to buy API tokens. Also, I read other posts that let me know that I would be exposing myself to malicious hackers if I used my API key in a front-end server (I think I'm saying that right).

What I'd like to know is, has anyone successfully integrated a chatbot without using an API key? Is there a better option than ChatGPT? 

Thanks!

Kris

    • KrisShenenbe782's avatar
      KrisShenenbe782
      Community Member

      Thanks Tom, I'll check him out. I was able to create a mentor-type AI assistant with ChatGPT. I'd share it but I'm using my personal API key. Thanks again!

  • Hi Kris,
    Did you have any success embedding a ChatGPT-bot into you Articulate courses? How did you embed WITH and API-key in the first place?

    Best
    Anders

  • Dave-Ruckley's avatar
    Dave-Ruckley
    Community Member

    I've used the API to get this working within storyline using this code:

    const player = GetPlayer();
    
    // You should change the following 3 variables. Make sure that the text is enclosed by quotation marks.
    const systemRequest = "XXXXX";
    const userInputVariable = "Message_to_Colleague";
    const aiOutputVariable = "gptresponse";
    const colleagueChatVariable = "Colleague_Chat";
    
    const userEntry = player.GetVar(userInputVariable);
    const token = player.GetVar("OpenAI_API");
    const auth = "Bearer " + token;
    
    console.log("Authorization:", auth); // Log the entire Authorization header
    
    function sendRequest() {
        fetch("https://api.openai.com/v1/chat/completions", {
            method: 'POST',
            headers: {
                'Authorization': auth,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                "model": "gpt-3.5-turbo",
                "messages": [
                  {
                    "role": "system",
                    "content": systemRequest
                  },
                  {
                    "role": "user",
                    "content": userEntry
                  }
                ]
            })
        }).then(response => {
            if (!response.ok) {
                return response.json().then(err => {
                    throw new Error(`HTTP error! status: ${response.status}, message: ${err.error?.message || "Unknown error"}`);
                });
            }
            return response.json();
        }).then(data => {
            console.log("Response Data:", data); // Log the response data
            let aiText = data.choices[0].message.content;
            player.SetVar(aiOutputVariable, aiText);
            
            // Format and update the Colleague_Chat variable
            let formattedMessage = "<b>Q: </b>" + userEntry + "<br><b>A: </b>" + aiText;
            let currentChat = player.GetVar(colleagueChatVariable) || ""; // Get current chat history
            let updatedChat = currentChat + "<br>" + formattedMessage; // Append new message
            
            player.SetVar(colleagueChatVariable, updatedChat);
    
    sendRequest();

    The slide is just a regular text input box, textbox and button plus the appropriate variables in storyline.

    The API is populated from a seperate XML file that's uploaded to the VLE seperately. That gives us more control over how and when students can use the chatGPT function and combined with the resource being available only to a select set of students, it's pretty secure.

    • KrisShenenbe782's avatar
      KrisShenenbe782
      Community Member

      Thanks so much! I'm still new to JavaScript and putting together a separate XLM file is a bit above my head. I'll talk to tech. It would be better to have more control over it. Thank you for the script. I'll try it. Here's what I (and ChatGPT) came up with:

      // Step 1: Capture the learner's SMART goal from the Storyline variable
      var player = GetPlayer();
      var userGoal = player.GetVar("GoalText");  // Replace 'GoalText' with the actual variable name in Storyline
      
      // Step 2: OpenAI API setup
      const apiKey = 'myKey';  // Replace this with your OpenAI API key once you have it
      const apiUrl = 'https://api.openai.com/v1/chat/completions';
      
      // Step 3: Prepare the data to send to OpenAI
      const data = {
          model: "gpt-4",  // Model can be gpt-3.5-turbo or gpt-4 depending on your API access
          messages: [
              {
                  role: "system",
                  content: "You are an assistant that helps users write SMART goals. Provide feedback on Specific, Measurable, Achievable, Relevant, and Time-bound aspects of the goal. Please be succinct and ensure you can answer fully with the 500 max_limit tokens. Include a concise summary of your conclusions with a rating score of 1-5, with 5 being the highest quality."
              },
              {
                  role: "user",
                  content: `Here is the SMART goal: "${userGoal}". Please analyze and provide feedback on it.`
              }
          ],
          max_tokens: 500,  // Limit the response length to avoid using too many tokens
          temperature: 0.7  // Adjusts the creativity of the response (0.7 is a good balance)
      };
      
      // Step 4: Send the API request to OpenAI
      fetch(apiUrl, {
          method: 'POST',
          headers: {
              'Authorization': `Bearer ${apiKey}`,
              'Content-Type': 'application/json'
          },
          body: JSON.stringify(data)
      })
      .then(response => response.json())
      .then(data => {
          // Step 5: Handle the response and set it to a Storyline variable
          if (data && data.choices && data.choices.length > 0) {
              var feedback = data.choices[0].message.content.trim();  // Extract feedback from the response
              player.SetVar("AI_Feedback", feedback);  // Store the feedback in a Storyline variable
          } else {
              player.SetVar("AI_Feedback", "No valid feedback received.");  // In case no feedback is returned
          }
      })
      .catch(error => {
          console.error("Error:", error);
          player.SetVar("AI_Feedback", "There was an error connecting to the AI service.");
      });

      It's geared towards mentoring about a specific topic. I'd like to embed a chatbot that users can access on any page of the course and ask questions about the content. That's a much different animal.

      • Dave-Ruckley's avatar
        Dave-Ruckley
        Community Member

        No worries. ChatGPT has helped me a lot with figring out the javascript tricks I now use. 

        The XML bit is supr simple. You just need to create file called something like keys.xml and structure the content like this:

        <?xml version="1.0" encoding="utf-8"?>
        <SO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <!-- OpenAI API Key -->
          <folder type="OpenAI">
            <APIKey>12345</APIKey>
          </folder>
        </SO>

        then in storyline use this code as a javascript trigger that runs when the timeline starts:

        // Define getAPIKeys function
        window.getAPIKeys = function(callback) {
            var OAPI = "";
            var player = GetPlayer(); // Now only called after Storyline is ready
            var xmlDoc = "";
            var xmlhttp = new XMLHttpRequest();
            var dataLoadError = false; // Flag for error tracking
        
            // Request the xml doc from the server (Keys)
            xmlhttp.open("GET", "keys.xml", true); // Asynchronous request
        
            xmlhttp.onload = function() {
                if (xmlhttp.status === 200) { // Check if the request was successful
                    xmlDoc = xmlhttp.responseXML;
        
                    try {
                        // Retrieve the OpenAI API keys from the XML file
                        var apiKeyElement = xmlDoc.getElementsByTagName("APIKey")[0];
                        
                        if (apiKeyElement && apiKeyElement.childNodes[0]) {
                            OAPI = apiKeyElement.childNodes[0].nodeValue;
        
                            // Set them as Storyline variables
                            player.SetVar("OpenAI_API", OAPI);
        
                            // Execute the callback if provided
                            if (callback) {
                                callback();
                            }
                        } else {
                            throw new Error("API key not found in the XML.");
                        }
                    } catch (error) {
                        console.error("Error processing XML: ", error.message);
                        dataLoadError = true;
                        player.SetVar("Data_Load_Error", true);
                    }
                } else {
                    console.error("Error loading XML: Status ", xmlhttp.status);
                    dataLoadError = true;
                    player.SetVar("Data_Load_Error", true);
                }
            };
        
            xmlhttp.onerror = function() {
                console.error("Network error while fetching keys.xml");
                dataLoadError = true;
                player.SetVar("Data_Load_Error", true);
            };
        
            xmlhttp.send();
        };

        Then in your exported zip file, add the xml file to it and upload the whole thing to your VLE. If you want to remove access all you have to do is remove the xml file from the VLE.