Code viewer for World: Improving your Writing wit...
// adapted from:
//  https://platform.openai.com/docs/api-reference/making-requests
//  https://ancientbrain.com/world.php?world=2850716357


const openaiURL = "https://api.openai.com/v1/chat/completions";           // can POST to this 3rd party URL
  
var themodel = "gpt-3.5-turbo";       // the OpenAI model we are going to talk to 
var temperature = 0.7; // default value

var apikey = "";
var promptForAi = "Can you rewrite the following text to make it more coherent and engaging. Please keep the number of sentences similar to the original text. Provide a single, improved version:";
var selectedStyles = [];
var numSuggestions = $('#num-suggestions-selector').val() || "1";

// default body is margin 0 and padding 0 
// give it more whitespace:

  $('body').css( "margin", "20px" );
  $('body').css( "padding", "20px" );
 
document.write ( `

<style>
    @import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Roboto:wght@400;500;700&display=swap');

    /* <Text and heading styling> */ 
    body, input, textarea, button, select, option {
        font-family: 'Roboto', sans-serif;
        color: #34568B; /* Primary text color */
    }

    h1, h2, h3 {
        font-family: 'Lato', sans-serif;
    }
    
    /* </Text and heading styling> */

    /* <Button styling> */
    
    .ab-normbutton {
        background: linear-gradient(to bottom right, #394c6d, #506680);
        color: #e5e9f0;
        border: none;
        padding: 10px 20px;
        border-radius: 30px;
        cursor: pointer;
        transition: all 0.3s ease-in-out;
        font-weight: bold;
        font-size: 16px;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); /* Slight outer drop shadow */
    }
    
    .ab-normbutton:hover {
        background: linear-gradient(to bottom right, #506680, #63788a); /* Slightly lighter gradient for hover */
        color: #eceff4;
        transform: translateY(-2px); /* Subtle lift effect on hover */
        box-shadow: 0 5px 8px rgba(0, 0, 0, 0.3); /* Enhanced outer drop shadow for lift effect */
    }
    
    /* </Button styling> */


    /* <Selected model button styling> */
    
    .model-selector {
        margin-bottom: 10px;
    }
    
    .slider-box {
        position: relative;
        display: inline-flex;
        background-color: darkgray; /* Background for non-selected side */
        border-radius: 5px;
        overflow: hidden;
    }
    
    .slider {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        width: 50%;
        background-color: navy;
        transition: left 0.3s ease;
        z-index: 1;
    }
    
    .model-button {
        flex: 1;
        padding: 10px 20px;
        border: none;
        background-color: transparent;
        color: white;
        font-weight: bold;
        cursor: pointer;
        position: relative;
        z-index: 2;
    }

    
    /* </Selected model button styling> */
    
    /* <Selected style button styling> */
    
    .selected-style {
        background: linear-gradient(to bottom right, #81d4fa, #b3e5fc); /* Bright, light blue gradient for selected styles */
        color: #2e3440 !important; /* Dark text color for contrast */
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
    }
    
    /* </Selected button styling> */


    /* <Dialog> */
    
    .dialog {
        display: none;
        position: fixed;
        z-index: 1;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        overflow: auto;
        background-color: rgba(0,0,0,0.4);
    }
    
    .dialog-content {
        background-color: #fefefe;
        margin: 15% auto;
        padding: 20px;
        border: 1px solid #888;
        width: 80%;
    }

    /* </Dialog> */

    /* <Editable text area styling> */

    .editable-text-area {
        border: 1px solid #D0D0D0;
        position: relative;
        padding: 10px;
        min-height: 150px; /* Minimum height */
        width: 794px; /* A4 width approximation */
        max-width: 100%;
        max-height: 1000px;
        resize: vertical; /* Allow vertical resizing */
        overflow: hidden; /* Scrollable content */
        /* margin: auto; */
        font-family: 'Roboto', sans-serif;
        color: #000000;
        font-size: 16px;
        line-height: 1.5;
        background-color: #fff;
    }
    
    .editable-text-area-container {
        position: relative;
        padding-right: 40px;
    }
    
    .toggle-size-btn {
        display: block;
        margin: 10px 0;
        z-index: 2;
    }

    /* </Editable text area styling> */
    
    /* <Grid container styling> */
    
    .grid-container > div {
        border: none;
        border-radius: 10px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        padding: 20px;
        background-color: #f9f9f9;
    }

    .grid-container {
        display: grid;
        grid-template-columns: 1fr;
        grid-gap: 20px; /* Space between grid items */
        margin: 20px;
    }
    
    .grid-container h3 {
        font-size: 1.2em;
        margin-bottom: 15px;
        color: #34568B;
    }
    
    /* </Grid container styling> */
    
    /* <Temperature slider styling */
    
    .temperature-selection {
        margin-bottom: 20px;
    }
    
    #temperature-slider {
        width: 20%;
    }
    
    /* </Temperature slider styling */
    
    /* <Info icon and tooltip> */
    
    .info-icon {
        cursor: pointer;
        display: inline-block;
        position: relative;
    }
    
    .tooltip-text {
        visibility: hidden;
        width: 250px;
        background-color: #f9f9f9;
        color: #000;
        text-align: left;
        border-radius: 6px;
        padding: 10px;
        position: absolute;
        z-index: 3;
        top: 50%;
        left: 100%;
        margin-left: -60px;
        box-shadow: 0px 0px 6px 0px rgba(0,0,0,0.3);
        /* Hides the tooltip */
        opacity: 0;
        transition: opacity 0.3s;
        font-size: 14px;
        line-height: 1.5;
    }
    
    .tooltip-text p {
    margin: 5px 0; /* Spacing between paragraphs */
    }
    
    .tooltip-text ul {
        list-style-type: disc; /* Add bullets to the list */
        margin-left: 20px;
        padding-left: 0;
    }
    
    .tooltip-text li {
        margin-bottom: 5px; /* Spacing between list items */
    }
    
    .info-icon:hover .tooltip-text {
        visibility: visible;
        opacity: 1;
    }

    .circle-icon {
        display: inline-block;
        width: 20px;
        height: 20px;
        line-height: 20px; /* Center the character vertically */
        text-align: center; /* Center the character horizontally */
        background-color: #f2f2f2;
        color: #34568B;
        border-radius: 50%;
        border: 1px solid #34568B;
        font-size: 16px;
    }
    
    /* </Info icon and tooltip> */
    
    /* <Custom Alert Styling> */
    
    #custom-alert {
        padding: 10px;
        background-color: red;
        color: white;
        text-align: center;
        margin-top: 10px;
        border-radius: 5px;
    }
    
    /* </Custom Alert Styling> */
    
    /* <AI Suggestions styling> */
    
    .num-suggestions-selection {
        display: flex; /* Enables flexbox layout */
        align-items: center; /* Vertically aligns items in the middle */
        justify-content: start; /* Aligns items to the start of the container */
        margin-bottom: 10px;
    }
    
    .num-suggestions-selection h4 {
        margin-right: 10px;
    }
    
    .new-line {
        display: block;
        margin-top: 10px;
    }

    /* </AI Suggestions styling> */

</style>


<h1> Improving your Writing with chatGPT </h1>

<div class="grid-container">

    <div class="api-key-entry">
        <h3>Enter API Key
            <span class="info-icon" title="">
                <span class="circle-icon">ℹ️</span>
                <span class="tooltip-text">
                    <p>An API Key is required to use this service.</p>
                    <p>Register for free and get your API key <a href='https://platform.openai.com/account/api-keys'>here</a>.</p>
                    <p>This world will never store your API key!</p>
                    <p>You can view the <a href= 'https://ancientbrain.com/edit.world.php?world=5688356630'> source code</a> to see that is true! </p>
                </span>
            </span>
        </h3>
        <input style='width:25vw;' maxlength='2000' name="apikey" id="apikey" value=''>
        <button onclick='setApiKey();' class='ab-normbutton' id='set-api-key-button'>Set API Key</button>
        <button id="reset-api-key" onclick='resetApiKey();' class='ab-normbutton' style='display:none; margin-left: 10px;'>Reset API Key</button>
        <span id="api-key-set-message" style="display: none;"><b>API key has been set.</b></span>
    </div>

    
    <div class="model-selector">
        <h3>Select AI Model
            <span class="info-icon" title="">
                <span class="circle-icon">ℹ️</span>
                <span class="tooltip-text">
                    <p>According to <a href="https://www.howtogeek.com/882274/gpt-3-5-vs-gpt-4/#:~:text=Compared%20to%20GPT%2D3.5%2C%20GPT%2D4%20is%20smarter%2C%20can%20handle%20longer%20prompts%20and%20conversations%2C%20and%20doesn%27t%20make%20as%20many%20factual%20errors.%20However%2C%20GPT%2D3.5%20is%20faster%20in%20generating%20responses%20and%20doesn%27t%20come%20with%20the%20hourly%20prompt%20restrictions%20GPT%2D4%20does." target="_blank">HowToGeek</a>:</p>
                    <ul>
                        <li>Compared to GPT-3.5, GPT-4 is smarter, can handle longer prompts and conversations, and doesn't make as many factual errors.</li> 
                        <li>However, GPT-3.5 is faster in generating responses and doesn't come with the hourly prompt restrictions GPT-4 does.</li>
                    </ul>
                </span>
            </span>
        </h3>
        <div class="slider-box">
            <div class="slider"></div>
            <button class="model-button" id="model-gpt-3-5-turbo" onclick="selectModel('gpt-3.5-turbo');">GPT-3.5-Turbo</button>
            <button class="model-button" id="model-gpt-4" onclick="selectModel('gpt-4');">GPT-4</button>
        </div>
    </div>
    
    <div class="temperature-selection">
        <h3>Adjust Temperature 
            <span class="info-icon" title="">
                <span class="circle-icon">ℹ️</span>
                <span class="tooltip-text">
                    <p><strong>Temperature Guide:</strong></p>
                    <ul>
                        <li><strong>Lower values (e.g., 0.2):</strong> More consistent outputs, less creativity.</li>
                        <li><strong>Higher values (e.g., 2.0):</strong> More diverse and creative results.</li>
                    </ul>
                    <p>The temperature range is from 0 to 2.</p>
                    <ul>
                        <li><strong> Recommended values for consistency: 0.7 to 1.0.</strong></li>
                    </ul>
                    <p>See <a href="https://platform.openai.com/docs/guides/text-generation/faq#:~:text=How%20should%20I,0%20to%202." target="_blank">OpenAI</a> for more details.</p>
                </span>
            </span>
        </h3>
        <input type="range" id="temperature-slider" min="0" max="2" step="0.1" value="0.7">
        <span id="temperature-value">0.7</span>
    </div>

    
    <div class="writing-style-selection">
        <h3>Writing Style
            <span class="info-icon" title="">
                <span class="circle-icon">ℹ️</span>
                <span class="tooltip-text">
                    Selected the writing style(s) you would like the AI to use it it's response.
                </span>
            </span>
        </h3>
        <button id="style-Formal" onclick="selectStyle('Formal');" class=ab-normbutton>Formal</button>
        <button id="style-Informal" onclick="selectStyle('Informal');" class=ab-normbutton>Informal</button>
        <button id="style-Narrative" onclick="selectStyle('Narrative');" class=ab-normbutton>Narrative</button>
        <button id="style-Descriptive" onclick="selectStyle('Descriptive');" class=ab-normbutton>Descriptive</button>
        <button id="style-Persuasive" onclick="selectStyle('Persuasive');" class=ab-normbutton>Persuasive</button>
        <button id="style-Expository" onclick="selectStyle('Expository');" class=ab-normbutton>Expository</button>
    </div>

    <div class="saved-texts">
        <h3>Saved Texts</h3>
        <button onclick="toggleTextAreaSize(this);" class="ab-normbutton toggle-size-btn" data-target="saved-texts">Expand</button>
        <div id="saved-texts" contenteditable="true" class="editable-text-area"></div>
        <button onclick="editSavedText();" class="ab-normbutton">Edit</button>
        <button onclick="saveEditedText();" class="ab-normbutton">Save</button>
    </div>
    
    <!-- Custom Confirm Edit Dialog -->
    <div id="confirmEditDialog" class="dialog">
        <div class="dialog-content">
            <p>Are you sure you would like to edit the text? This will remove any unsaved text from the Editor Area.</p>
            <button id="confirmEditBtn" class="ab-normbutton">Confirm</button>
            <button id="cancelEditBtn" class="ab-normbutton">Cancel</button>
        </div>
    </div>

    <div class="editor-area">
        <h3>Editor Area</h3>
        <button onclick="toggleTextAreaSize(this);" class="ab-normbutton toggle-size-btn" data-target="editor-input">Expand</button>
        <div id="editor-input" contenteditable="true" class="editable-text-area"></div>
        <button onclick="sendchat();" class="ab-normbutton">Get AI Suggestion</button>
        <button onclick="clearEditor();" class="ab-normbutton">Clear Editor</button>
        <button onclick="saveCurrentText();" class="ab-normbutton">Save Text</button>
    </div>

    <div id="custom-alert" style="display: none; /* Additional styling */"></div>
    
    <div class="ai-suggestions">
        <h3>AI Suggestions</h3>
        
        <div class="num-suggestions-selection">
            <h4>Desired number of suggestions:</h4>
            <select id="num-suggestions-selector">
                <option value="1" selected>1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <!-- Add more options as needed -->
            </select>
        </div>
    
        <div id="ai-suggestions-message">AI suggestions will appear here...</div>
        <div id="ai-suggestions-content"></div>
    </div>

</div>
 
 <p> <i> Be warned that GPT replies are often completely inaccurate.<br> 
 All LLM systems <a href="https://www.google.com/search?q=llm+hallucination"> "hallucinate"</a>.
 It is how they work. </i> </p>

<pre>

</pre>

` );

////////// <API Key Setup> ////////////////

function setApiKey() {
    apikey = $("#apikey").val().trim();
    if (apikey) {
        $("#apikey").hide(); // Hide the input field
        $("#set-api-key-button").hide(); // Hide the set button
        $("#api-key-label").hide(); // Hide the label
        $("#reset-api-key").show(); // Show the reset button
        $("#api-key-set-message").show(); // Show the "API key has been set" message
        console.log("API Key has been set!");
        console.log("Current prompt: ", promptForAi);
    } else {
        showCustomAlert("Please enter an API key.");
    }
}

function resetApiKey() {
    apikey = "";
    $("#apikey").val('').show(); // Clear and show the input field
    $("#set-api-key-button").show(); // Show the set button again
    $("#api-key-label").show(); // Show the label
    $("#reset-api-key").hide(); // Hide the reset button
    $("#api-key-set-message").hide(); // Hide the "API key has been set" message
    console.log("API Key has been reset!");
}


////////// </API Key Setup> /////////////////


////// <Model Selection> //////////

function selectModel(model) {
    themodel = model;
    console.log("Selected Model: ", themodel);
    
    // Remove highlighting from all model buttons
    document.querySelectorAll('.model-button').forEach(btn => btn.classList.remove('selected-model'));

    // Highlight the selected button
    let selectedButton = document.getElementById('model-' + model.replace('.', '-'));
    selectedButton.classList.add('selected-model');

    // Move the slider to the selected button
    let sliderPosition = selectedButton.offsetLeft;
    document.querySelector('.slider').style.left = sliderPosition + 'px';
}

//////// </Model Selection> /////////


////// <Adjusting Temperature> /////////

// Update temperature variable when slider changes
$("#temperature-slider").on("input", function() {
    temperature = $(this).val();
    $("#temperature-value").text(temperature); // Update the displayed value
});

$(document).ready(function() {
    // Set initial value for temperature slider
    $("#temperature-value").text($("#temperature-slider").val());

    // Update temperature variable when slider changes
    $("#temperature-slider").on("input", function() {
        var newTemperature = $(this).val();
        $("#temperature-value").text(newTemperature); // Update the displayed value

        console.log("Temperature changed to: ", newTemperature);
    });
});

/////// </Adjusting Temperature> ///////////

//////// <Writing Style and Number of Suggestions> ///////////

function selectStyle(style) {
    var styleButtonId = "style-" + style;
    var index = selectedStyles.indexOf(style);

    if (index === -1) {
        selectedStyles.push(style);
        $("#" + styleButtonId).addClass("selected-style");
    } else {
        selectedStyles.splice(index, 1);
        $("#" + styleButtonId).removeClass("selected-style");
    }

    updatePrompt(); // Call updatePrompt to reflect the change in style
}

$('#num-suggestions-selector').change(function() {
    numSuggestions = $(this).val();
    updatePrompt(); // Call updatePrompt to reflect the change in number of suggestions
});

function updatePrompt() {
    var promptBase = "Can you rewrite the following text to make it more coherent and engaging";

    // Append styles to the base prompt if any are selected
    if (selectedStyles.length > 0) {
        var textStyles = selectedStyles.join(", ");
        promptBase += ", while adhering to the following styles: " + textStyles;
    }

    // Append the number of suggestions request to the prompt
    if (numSuggestions === "1") {
        promptForAi = promptBase + ". Please keep the number of sentences similar to the original text and provide a single, improved version.";
    } else {
        promptForAi = promptBase + ". Please provide " + numSuggestions + " distinct versions, formatted as 'Suggestion 1: [text]', 'Suggestion 2: [text]', ..., 'Suggestion " + numSuggestions + ": [text]'.";
    }

    console.log("Current prompt: ", promptForAi);
}

// Initialize the prompt on page load
updatePrompt();

////// </Writing Style and number of suggestions> ////////

function showConfirmEditDialog() {
    document.getElementById("confirmEditDialog").style.display = "block";
}

// Function to close the dialog
function closeConfirmEditDialog() {
    document.getElementById("confirmEditDialog").style.display = "none";
}

// When the user clicks on "Confirm" button
document.getElementById("confirmEditBtn").onclick = function() {
    var savedText = $("#saved-texts").text().trim();
    $("#editor-input").text(savedText);
    $("#saved-texts").text('');
    closeConfirmEditDialog();
};

// When the user clicks on "Cancel" button
document.getElementById("cancelEditBtn").onclick = function() {
    closeConfirmEditDialog();
};

function editSavedText() {
    // Show the confirm edit dialog
    showConfirmEditDialog();
}


function saveEditedText() {
    var editedText = $("#editor-input").text().trim();
    $("#saved-texts").text(editedText); // Save edited text back to Saved Texts
}

////// <Editor Area> ////////////

 // Function to clear the editor area
function clearEditor() {
    $("#editor-input").text('');
    $("#ai-suggestions-content").html('');
}

function saveCurrentText() {
    var currentText = $("#editor-input").text().trim();
    if (currentText) {
        var savedTexts = $("#saved-texts").html();
        $("#saved-texts").html(savedTexts + " " + currentText); // Appends the new text
        $("#editor-input").text(''); // Clears the editor
    } else {
        alert("Please enter some text to save.");
    }
}

function toggleTextAreaSize(button) {
    var targetId = button.getAttribute('data-target');
    var textArea = document.getElementById(targetId);
    
    // Toggle state based on the button's text content
    if (button.textContent.includes('Expand')) {
        // Expand the size
        textArea.style.height = 'auto'; 
        textArea.style.height = textArea.scrollHeight + 'px';
        textArea.style.overflow = 'hidden'; // Hide scrollbar in expanded mode
        button.textContent = 'Reduce'; 
    } else {
        // Reduce the size
        textArea.style.height = '150px';
        textArea.style.overflow = 'auto'; // Show scrollbar for reduced mode
        button.textContent = 'Expand'; 
    }
}

////// </Editor Area> /////////////

////// <AI Suggestions> ///////////////

function startLoadingSuggestions() {
    console.log("startLoadingSuggestions called");
    $("#ai-suggestions-message").text("Your AI suggestions are being processed...");
    console.log("Message should now be set.");
}


function acceptSuggestion(suggestionNumber) {
    var selectedSuggestion = $("#ai-suggestions-content .suggestion-item").eq(suggestionNumber - 1).text();
    $("#editor-input").text(selectedSuggestion);
    $("#ai-suggestions-content").html('');
}

function rejectSuggestion() {
    // Clear the AI suggestions content
    $("#ai-suggestions-content").html('');

    // Reset the message to indicate where suggestions will appear
    $("#ai-suggestions-message").text("AI suggestions will appear here...").show();
}


////// </AI Suggestions> /////////////


//////// <Alert Messages> ////////////////

function showCustomAlert(message) {
    $("#custom-alert").text(message).show();
    // Hide alert after certain amount of time
    setTimeout(hideCustomAlert, 5000);
}

function hideCustomAlert() {
    $("#custom-alert").hide();
}

/////// </Alert Messages> ////////////

// global variable to examine return data in console 
var a;

function sendchat(context = 'suggestion') {
    if (apikey === "") {
        showCustomAlert("Please enter an API key.");
        return;
    }

    var inputText;
    var finalPrompt;

    inputText = $("#editor-input").text().trim();
    if (!inputText) {
        $("#ai-suggestions-content").html("<font color='red'><b>Please enter some text for suggestions.</b></font>");
        return;
    }
    
    startLoadingSuggestions(); // Call this function to show the loading message
    
    finalPrompt = promptForAi + inputText;

    var thedata = {
        "model": themodel,
        "temperature": parseFloat(temperature), // use the temperature value
        "messages": [{
            "role": "system",
            "content": finalPrompt
        }]
    };

    // then as string representing that JSON:
    var thedatastring = JSON.stringify(thedata);

    // HTTP headers must be set up with API key:
    $.ajaxSetup({
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + apikey
        }
    });

    // POST to 3rd party URL:
    $.ajax({
        type: "POST",
        url: openaiURL,
        data: thedatastring,
        dataType: "json",
        success: function (data, textStatus) {
            successfn(data, textStatus);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            errorfn(jqXHR, textStatus, errorThrown);
        }
    });
}

function successfn(data, rc) {
    a = data;
    var suggestions = a["choices"][0].message.content.split(/(?=Suggestion \d+:)/);

    $("#ai-suggestions-content").html('');
    suggestions.forEach((suggestion, index) => {
        var cleanSuggestion = suggestion.replace(/Suggestion \d+:/, '').trim();
        $("#ai-suggestions-content").append("<div class='suggestion-item'>" + cleanSuggestion + "</div>");

        var buttonId = "accept-suggestion-" + (index + 1);
        var buttonHTML = "<button id='" + buttonId + "' onclick='acceptSuggestion(" + (index + 1) + ");' class='ab-normbutton'>Accept Suggestion " + (index + 1) + "</button>";
        $("#ai-suggestions-content").append(buttonHTML);
    });

    // Determine the text for the 'Reject Suggestion' button based on the number of suggestions
    var rejectButtonText = suggestions.length > 1 ? "Reject Suggestions" : "Reject Suggestion";
    var rejectButtonHTML = "<button onclick='rejectSuggestion();' class='ab-normbutton'>" + rejectButtonText + "</button>";
    $("#ai-suggestions-content").append("<div class='new-line'>" + rejectButtonHTML + "</div>");

    if (suggestions.length > 0) {
        $("#ai-suggestions-message").hide();
    } else {
        $("#ai-suggestions-message").text("No suggestions available.");
    }
}

// created with the aid of chatGPT 

function errorfn(jqXHR, textStatus, errorThrown) {
    console.log("AJAX Error", jqXHR, textStatus, errorThrown);
    
    if (!jqXHR) {
        // Handle the case where jqXHR is undefined
        showCustomAlert("Error: Failed to connect. Please check your network and try again.");
        return;
    }
    
    if (jqXHR.status === 401) { // Unauthorized, often due to invalid API key
        showCustomAlert("Error: Unauthorized access. Please check your API key.");
    } else if (jqXHR.status === 400) { // Bad request, often due to incorrect parameters
        showCustomAlert("Error: Bad request. Please check your input.");
    } else {
        // Generic error message for other types of errors
        showCustomAlert("Error: An unexpected error occurred.");
    }
}