User:Phlsph7/SpellGrammarHelper.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Phlsph7/SpellGrammarHelper. |
(function(){
const scriptName = 'SpellGrammarHelper';
$.when(mw.loader.using('mediawiki.util'), $.ready).then(function(){
const portletLink = mw.util.addPortletLink('p-tb', '#', scriptName, scriptName + 'Id');
portletLink.onclick = function(e) {
e.preventDefault();
start();
};
});
const noSuggestionsMessage = "[No errors found]";
let hasError = false;
let modalTextarea;
function start(){
modalTextarea = openModalWithTextarea();
}
async function getAssessments(){
const elements = getElements();
for(const element of elements){
adjustElement(element);
}
const assessments = [];
for(let i = 0; i < elements.length; i++){
let message = `Processing element ${i+1}/${elements.length} ...`;
logTextarea(message);
let innerText = elements[i].innerText;
let assessment;
if(isList(elements[i])){
assessment = await getAssessment(innerText, 'list items') + '\n';
}
else{
assessment = await getAssessment(innerText, 'text') + '\n';
}
if(hasError){
break;
}
assessments.push(assessment);
}
if(hasError){
clearTextarea();
logTextarea(`There was an error. The most likely sources of the error are:
* You entered a false OpenAI API key.
* Your OpenAI account ran out of credit.
You can ask at the script talk page if you are unable to resolve the error.`);
}
else{
let fullAssessment = assessments.join('\n').split('\r').join('');
fullAssessment = fullAssessment.split(noSuggestionsMessage).join('');
while(fullAssessment.includes('\n\n\n')){
fullAssessment = fullAssessment.split('\n\n\n').join('\n\n');
}
while(fullAssessment.includes('\n ')){
fullAssessment = fullAssessment.split('\n ').join('\n');
}
while(fullAssessment[0] === '\n'){
fullAssessment = fullAssessment.substring(1);
}
if(fullAssessment.length < 20){
fullAssessment = 'The script did not detect any spelling or grammar errors.';
}
clearTextarea();
logTextarea(fullAssessment);
}
}
function getElements(){
const elementContainer = $('#mw-content-text').find('.mw-parser-output').eq(0)[0].cloneNode(true);
const children = Array.from(elementContainer.children);
const elements = children.filter(function(item){return isPara(item) || isList(item)});
for(let i = elements.length-1; i >= 0; i--){
if(elements[i].innerText.length < 20){
elements.splice(i, 1);
}
}
return elements;
}
function adjustElement(element){
removeRefs(element);
replaceMathFormulas(element);
function removeRefs(element){
let refs = element.querySelectorAll('.reference, .Inline-Template');
for(let ref of refs){
ref.outerHTML = '';
}
}
function replaceMathFormulas(element){
let formulas = element.querySelectorAll('.mwe-math-element');
for(let formula of formulas){
formula.outerHTML = '<span>[MATHEMATICAL FORMULA]</span>';
}
}
}
function isPara(element){
return hasTagName(element, 'p');
}
function isList(element){
return hasTagName(element, 'ul') || hasTagName(element, 'ol');
}
function hasTagName(element, tagName){
return element.tagName.toLowerCase() === tagName.toLowerCase();
}
async function getAssessment(text, textType){
const messages = [
{role: "system", content: `Check the user's ${textType} from the Wikipedia article "${getTitle()}" for spelling and grammar errors. If there are no errors, respond with "${noSuggestionsMessage}". Report each major error in the following format:
* Sentence: ...
** Suggested change: ...
** Explanation: ...
Do not provide any other information. Ignore factual errors. Ignore minor errors, like errors pertaining to apostrophe format.`},
{role: "user", content: '"""As is well-known, this subject have been widely discussed. However, there are still deep disagreements among experts."""'},
{role: "assistant", content: `* Sentence: ...this subject have been widely discussed.
** Suggested change: replace "have" with "has"
** Explanation: "this subject" is singular and requires the singular verb form "has"`},
{role: "user", content: '"""Nonetheless, this leads to an increase various factors such as productivity and efficiency."""'},
{role: "assistant", content: `* Sentence: ...this leads to an increase various factors...
** Suggested change: add "in" after "increase"
** Explanation: the preposition "in" is required to connect "increase" with "various factors"`},
{role: "user", content: '"""' + text + '"""'},
];
console.log(messages);
const url = "https://api.openai.com/v1/chat/completions";
const body = JSON.stringify({
"messages": messages,
"model": "gpt-4o",
"temperature": 0,
});
const headers = {
"content-type": "application/json",
Authorization: "Bearer " + localStorage.getItem('SpellGrammarHelperAPIKey'),
};
const init = {
method: "POST",
body: body,
headers: headers
};
let assessment;
const response = await fetch(url, init);
console.log(response);
if(response.ok){
const json = await response.json();
assessment = json.choices[0].message.content;
}
else{
hasError = true;
assessment = 'error';
}
return assessment;
}
function openModalWithTextarea() {
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
overlay.style.zIndex = '1000';
const modal = document.createElement('div');
modal.style.backgroundColor = 'white';
modal.style.padding = '15px';
modal.style.borderRadius = '5px';
modal.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)';
modal.style.width = '80%';
modal.style.height = '70%';
modal.style.display = 'flex';
modal.style.flexDirection = 'column';
overlay.appendChild(modal);
const title = document.createElement('div');
title.innerHTML = "SpellGrammarHelper";
title.style.marginBottom = '15px';
modal.appendChild(title);
const textarea = document.createElement('textarea');
textarea.style.width = '100%';
textarea.style.height = '80%';
textarea.style.resize = 'none';
textarea.style.marginBottom = '15px';
textarea.style.borderRadius = '5px';
textarea.readOnly = true;
modal.appendChild(textarea);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'row';
modal.appendChild(buttonContainer);
const startButton = addButton("Start", function(){
clearTextarea();
let currentAPIKey = localStorage.getItem('SpellGrammarHelperAPIKey');
if(currentAPIKey === 'null' || currentAPIKey === null || currentAPIKey === ''){
clearTextarea();
logTextarea('No OpenAI API key detected. This script requires an OpenAI API key. Use the button below to add one.');
}
else{
getAssessments();
startButton.disabled = true;
}
});
addButton("Copy", function(){
modalTextarea.select();
document.execCommand("copy");
});
addButton("Add/Remove API key", function(){
let currentAPIKey = localStorage.getItem('SpellGrammarHelperAPIKey');
if(currentAPIKey === 'null' || currentAPIKey === null){
currentAPIKey = '';
}
let input = prompt('Please enter your OpenAI API key. It starts with "sk-...". It will be saved locally on your device. It will not be shared with anyone and will only be used for your queries to OpenAI. To delete your API key, leave this field empty and press [OK].', currentAPIKey);
// check that the cancel-button was not pressed
if(input !== null){
localStorage.setItem('SpellGrammarHelperAPIKey', input);
}
startButton.disabled = false;
});
addButton("Close", function(){
document.body.removeChild(overlay);
});
document.body.appendChild(overlay);
return textarea;
function addButton(textContent, clickFunction){
const button = document.createElement('button');
button.textContent = textContent;
button.style.padding = '5px';
button.style.margin = '5px';
button.style.flex = '1';
button.addEventListener('click', clickFunction);
buttonContainer.appendChild(button);
return button;
}
}
function logTextarea(text){
modalTextarea.value = text + '\n' + modalTextarea.value;
}
function clearTextarea(){
modalTextarea.value = '';
}
function getTitle(){
let innerText = document.getElementById('firstHeading').innerText;
if(innerText.substring(0, 8) === 'Editing '){
innerText = innerText.substring(8);
}
if(innerText.substring(0, 6) === 'Draft:'){
innerText = innerText.substring(6);
}
if(innerText.includes('User:')){
let parts = innerText.split('/');
parts.shift();
innerText = parts.join('/');
}
return innerText;
}
})();