Created
October 18, 2023 12:04
-
-
Save lukestanley/937ffd4de748cdc00a5dd701bd6fbdfb to your computer and use it in GitHub Desktop.
Browser user script using OpenAI GPT-3.5-turbo on text boxes to suggest British English spelling, grammar text improvments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Fix spelling with GPT | |
// @namespace https://gist.github.com/lukestanley/937ffd4de748cdc00a5dd701bd6fbdfb | |
// @version 1.0 | |
// @description User script for checking spelling and grammar with OpenAI API | |
// @match http://*/* | |
// @match https://*/* | |
// @run-at document-end | |
// @grant GM_xmlhttpRequest | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const apiKey = "sk-INSERT_KEY_HERE"; | |
const apiUrl = "https://api.openai.com/v1/chat/completions"; | |
let focusedElement = null; | |
let lastFocusedElement = null; | |
let timer = null; | |
document.addEventListener('focus', function(event) { | |
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { | |
focusedElement = event.target; | |
lastFocusedElement = event.target; | |
} | |
}, true); | |
document.addEventListener('blur', function() { | |
focusedElement = null; | |
}, true); | |
function throttle(func, delay) { | |
if (timer) { | |
clearTimeout(timer); | |
} | |
timer = setTimeout(func, delay); | |
} | |
async function checkText() { | |
if (focusedElement && focusedElement.type !== 'password') { | |
const text = focusedElement.value; | |
const words = text.split(' '); | |
if (text && text.length >= 3 && words.length <= 100 && text.length <= 300) { | |
throttle(() => checkSpellingAndGrammar(text), 1000); | |
} | |
} | |
} | |
function parseResponse(data, inputText) { | |
console.dir('Response parser', data); | |
const suggestions = []; | |
if (data.choices) { | |
for (const choice of data.choices) { | |
if (choice.message.function_call) { | |
const arg = JSON.parse(choice.message.function_call.arguments); | |
if (arg.suggestions) { | |
for (const suggestion of arg.suggestions) { | |
const correctedText = suggestion.text; | |
const explanation = suggestion.explanation; | |
if (correctedText !== inputText && !suggestions.some(item => item.text === correctedText)) { | |
suggestions.push({ text: correctedText, explanation }); | |
} | |
} | |
} | |
} | |
} | |
} | |
return suggestions; | |
} | |
function clearSuggestions(suggestionsContainer) { | |
suggestionsContainer.innerHTML = ''; | |
document.body.removeChild(suggestionsContainer); | |
} | |
async function checkSpellingAndGrammar(inputText) { | |
console.log('Spell corrector found text to check / fix:' + inputText); | |
const requestBody = { | |
model: "gpt-3.5-turbo", | |
temperature: 0.5, | |
n: 2, | |
messages: [ | |
{ | |
role: "system", | |
content: "You output improved text using British English! Do not use words with the letter 'z'!" | |
}, | |
{ | |
role: "user", | |
content: "1. We need highly clear text. The text should be simple, plain English. Critically consider ways the text could be more clear by reviewing it. 2. Propose multiple suggestions. Input text: `" + inputText + "`\nFor the review, let's think step by step to provide good suggestions." | |
} | |
], | |
functions: [ | |
{ | |
name: "text_eval", | |
parameters: { | |
"type": "object", | |
"properties": { | |
"review": { | |
"type": "string", | |
"description": "A step-by-step review of the original text, with detailed reasoning so that we understand it well." | |
}, | |
"suggestions": { | |
"type": "array", | |
"description": "Suggest improvments using British English!", | |
"items": { | |
"type": "object", | |
"properties": { | |
"text": { | |
"type": "string", | |
"description": "A suggested improvement for the original text." | |
}, | |
"isBritish": { | |
"type": "boolean", | |
"description": "If the suggestion is US English but not British, this should be false. We only want UK spellings!" | |
}, | |
"explanation": { | |
"type": "string", | |
"description": "Why this could be a good improvement upon the original text. TO BE SHOWN TO THE END-USER!" | |
} | |
}, | |
"required": [ | |
"text", | |
"explanation" | |
] | |
}, | |
"minItems": 0, | |
"maxItems":4 | |
} | |
}, | |
"required": ["review", "suggestions"] | |
} | |
} | |
], | |
function_call: { name: "text_eval" } | |
}; | |
GM_xmlhttpRequest({ | |
method: "POST", | |
url: apiUrl, | |
headers: { | |
"Content-Type": "application/json", | |
"Authorization": `Bearer ${apiKey}` | |
}, | |
data: JSON.stringify(requestBody), | |
onload: function(response) { | |
if (response.status >= 200 && response.status < 400) { | |
try { | |
const responseData = JSON.parse(response.responseText); | |
const correctedTexts = parseResponse(responseData, inputText); | |
if (correctedTexts.length < 1) { | |
console.log('Spell checker has no suggestions.'); | |
return | |
} | |
if (lastFocusedElement && lastFocusedElement.getBoundingClientRect()) { | |
console.log('Spell checker still has DOM element found'); | |
} else { | |
console.log('Spell checker exiting because no focusedElement, no DOM element found.'); | |
return; | |
}; | |
const suggestionsContainer = document.createElement('ul'); | |
suggestionsContainer.style.position = 'absolute'; | |
suggestionsContainer.style.zIndex = '1000'; | |
suggestionsContainer.style.left = `${focusedElement.getBoundingClientRect().left}px`; | |
suggestionsContainer.style.top = `${focusedElement.getBoundingClientRect().bottom}px`; | |
suggestionsContainer.style.background = '#fff'; | |
suggestionsContainer.style.border = '1px solid #ccc'; | |
suggestionsContainer.style.borderRadius = '4px'; | |
suggestionsContainer.style.color = 'black'; | |
suggestionsContainer.innerHTML = correctedTexts.map((textObj, index) => { | |
return `<li data-index="${index}" style="cursor:pointer"> | |
"${textObj.text}" | |
<small style="font-size:0.5em;"> | |
${textObj.explanation} | |
</small> | |
</li>`; | |
}).join(''); | |
const listOfCorrections = correctedTexts.map(item => `${item.text} (Explanation: ${item.explanation})`).join(', '); | |
console.log('Spell checker suggestions and explanations: ' + listOfCorrections); | |
document.body.appendChild(suggestionsContainer); | |
lastFocusedElement.addEventListener('input', function() { | |
if (!lastFocusedElement.value.includes(inputText)) { | |
clearSuggestions(suggestionsContainer); | |
lastFocusedElement.removeEventListener('input', this); | |
} | |
}); | |
suggestionsContainer.addEventListener('click', function(event) { | |
const selectedIndex = event.target.getAttribute('data-index'); | |
const selectedCorrection = correctedTexts[selectedIndex]; | |
console.log('to replace `' + inputText + '` the user selected: ' + selectedCorrection.text); | |
if (focusedElement) { | |
focusedElement.value = selectedCorrection.text; | |
} else if (lastFocusedElement && lastFocusedElement.value && lastFocusedElement.value === inputText) { | |
lastFocusedElement.value = selectedCorrection.text; | |
} | |
console.log('Explanation for the correction: ' + selectedCorrection.explanation); | |
// Clear the suggestions after selection | |
clearSuggestions(suggestionsContainer); | |
}); | |
} catch (error) { | |
console.error('Spell corrector error:', error); | |
} | |
} else { | |
console.error(`Spell corrector HTTP error! Status: ${response.status}`); | |
} | |
}, | |
onerror: function(error) { | |
console.error('Spell corrector request error:', error); | |
} | |
}); | |
} | |
document.addEventListener('input', checkText); | |
console.log('waiting to fix spelling etc'); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment