// 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.");
}
}