Jump to content
Wikipedia The Free Encyclopedia

User:Phlsph7/SpellGrammarSuggestions.js

From Wikipedia, the free encyclopedia
This is the current revision of this page, as edited by Phlsph7 (talk | contribs) at 13:28, 27 August 2025 (fix error involving text like ".mw-parser-output"). The present address (URL) is a permanent link to this version.Revision as of 13:28, 27 August 2025 by Phlsph7 (talk | contribs) (fix error involving text like ".mw-parser-output")
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
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.
This user script seems to have a documentation page at User:Phlsph7/SpellGrammarSuggestions.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 // Uses the OpenAI API to provide suggestions on spelling and grammar. The suggestions are in wikitext using the template "text diff".
 (function(){
 constscriptName='SpellGrammarSuggestions';

 $.when(mw.loader.using('mediawiki.util'),$.ready).then(function(){
 constportletLink=mw.util.addPortletLink('p-tb','#',scriptName,scriptName+'Id');
 portletLink.onclick=function(e){
 e.preventDefault();
 start();
 };
 });

 constoriginalSentenceTag="**Original sentence:**";
 constcorrectionTag="**Correction:**";
 constexplanationTag="**Explanation:**";
 constnoSuggestionsMessage="**No errors found.**";
 lethasError=false;
 letmodalTextarea;
 letmodalPreviewButton;
 letmodalPreviewDiv;
 letwikitext='';

 functionstart(){
 openModalWithTextarea();
 }

 // get AI assessments for each text segment, transform them to wikitext, and display the result in the log area
 asyncfunctiongetAssessments(){
 consttextSegments=getTextSegments();
 constassessments=[];

 for(leti=0;i<textSegments.length;i++){
 letmessage=`Processing text segment ${i+1}/${textSegments.length} ...`;
 logTextarea(message);
 assessment=awaitgetAssessment(textSegments[i])+'\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{
 letfullAssessment=assessments.join('\n').split('\r').join('');
 fullAssessment=fullAssessment.split(noSuggestionsMessage).join('');
 letaltNoSuggestionsMessage=noSuggestionsMessage.split('*').join('');
 fullAssessment=fullAssessment.split(altNoSuggestionsMessage).join('');

 fullAssessment=fullReplace(fullAssessment,'\n\n\n','\n\n');
 fullAssessment=fullReplace(fullAssessment,'\n ','\n');
 while(fullAssessment[0]==='\n'){
 fullAssessment=fullAssessment.substring(1);
 }

 constarticleTitle=firstHeading.innerText;
 wikitext=`== [[${articleTitle}]] ==\n\n`;
 fullAssessment=fullAssessment.trim();
 letnoErrorDetectedString='The script did not detect any spelling or grammar errors.\n'
 if(fullAssessment.length<20){
 wikitext+=noErrorDetectedString;
 }
 else{
 wikitext+=fullAssessmentToWikitext(fullAssessment)+'\n';

 if(wikitext.split('\r').join('').trim().split('\n').length<4){
 wikitext+=noErrorDetectedString;
 }
 }

 clearTextarea();
 logTextarea(wikitext);
 modalPreviewButton.disabled=false;
 }
 }

 // get an individual assessment for a single text segment
 asyncfunctiongetAssessment(textSegment){
 constsystemPrompt=
 `Check the provided encyclopedic text for spelling and grammar errors, and suggest corrections.

 - Review the text thoroughly to identify any spelling mistakes or grammatical issues.
 - Offer clear corrections for each identified error.
 - Maintain the original meaning and encyclopedic style of the text.
 - Only report sentences that contain objective errors. Do not make subjective style suggestions.
 - Do not alter spellings that have multiple accepted forms; maintain the original spelling as provided.
 - Leave all instances of American and British English unchanged, even if they are mixed within the text.
 - Provide a concise explanation for each correction about why it is necessary.

 # Output Format

 The output should be a list of identified errors and their corrections in the following format:

 ${originalSentenceTag} "[original sentence with error]"
 ${correctionTag} "[corrected sentence]"
 ${explanationTag} "[explanation of the correction]"

 ${originalSentenceTag} ...

 Do not list sentences that contain no errors. If the whole text contains no errors, respond only with the following standard message:

 ${noSuggestionsMessage}`;
 constmessages=[
 {role:"system",content:systemPrompt},
 {role:"user",content:textSegment},
 ];

 console.log(messages);

 consturl="https://api.openai.com/v1/chat/completions";
 constbody=JSON.stringify({
 "messages":messages,
 "model":"gpt-5",
 reasoning_effort:"minimal",
 });
 constheaders={
 "content-type":"application/json",
 Authorization:"Bearer "+localStorage.getItem('SpellGrammarSuggestionsAPIKey'),
 };

 constinit={
 method:"POST",
 body:body,
 headers:headers
 };

 letassessment;
 constresponse=awaitfetch(url,init);
 console.log(response);
 if(response.ok){
 constjson=awaitresponse.json();
 assessment=json.choices[0].message.content;
 }
 else{
 hasError=true;
 assessment='error';
 }

 console.log(assessment);
 returnassessment;
 }

 // transform the html paragraphs containing the article text into text segments
 functiongetTextSegments(){
 constelementContainer=$('#mw-content-text').find('.mw-parser-output').eq(0)[0].cloneNode(true);

 // remove references
 constrefs=elementContainer.querySelectorAll('.reference, .Inline-Template');
 for(letrefofrefs){
 ref.outerHTML='';
 }

 // remove style elements
 conststyleElements=elementContainer.querySelectorAll('style');
 for(letstyleElementofstyleElements){
 styleElement.outerHTML='';
 }

 // remove annotation (of math elements)
 constanElements=elementContainer.querySelectorAll('annotation');
 for(letanElementofanElements){
 anElement.outerHTML='';
 }

 // get p-elements
 constchildren=Array.from(elementContainer.children);
 constelementParas=children.filter(function(item){
 returnitem.tagName.toLowerCase()==='p';
 });

 // transform p-elements to text
 consttextParas=[];
 for(letelementParaofelementParas){
 lettempText=elementPara.innerText;

 // adjustments like removing newlines caused by mathematical formulas
 tempText=tempText.split('\r').join('')
 .split('\n').join(' ')
 .split('\t').join(' ');
 tempText=fullReplace(tempText,' ',' ');
 tempText=tempText.trim();

 // ignore very short paragraphs
 constminTextParaLength=50;
 if(tempText.length>=minTextParaLength){
 textParas.push(tempText);
 }
 }

 // combine textParas to form longer textSegments
 constmaxTextSegmentLength=5000;
 consttextSegments=textParas.splice(0,1);
 letcurSegmentIndex=0;
 while(textParas.length>0){
 letcurPara=textParas.splice(0,1)[0];
 if(textSegments[curSegmentIndex].length+curPara.length<5000){
 textSegments[curSegmentIndex]+='\n\n'+curPara;
 }
 else{
 textSegments.push(curPara);
 curSegmentIndex++;
 }
 }

 returntextSegments;
 }

 // transform the full assessment to wikitext
 functionfullAssessmentToWikitext(fullAssessment){
 letwikitext='';
 letindividualAssessments=fullAssessment.split(originalSentenceTag);
 for(letindividualAssessmentofindividualAssessments){
 if(hasExpectedFormat(individualAssessment)){
 letoriginalSentence=individualAssessment.split(correctionTag)[0].trim();
 originalSentence=removeQuotationMarks(originalSentence);
 letremainingAssesment=individualAssessment.split(correctionTag)[1];
 letcorrectedSentence=remainingAssesment.split(explanationTag)[0].trim();
 correctedSentence=removeQuotationMarks(correctedSentence);
 letexplanationSentence=remainingAssesment.split(explanationTag)[1].trim();
 letpageURL=window.location.href.split('#')[0];
 letpageScrollURL=pageURL+'#:~:text='+encodeURIComponent(originalSentence);
 letpageEditURL=pageURL.replace('/wiki/','/w/index.php?title=')+'&action=edit';
 letlinkString=`([${pageScrollURL} scroll to sentence in article] | [${pageEditURL} edit article])`;
 letassessmentWikitext=`{{text diff|${originalSentence}|${correctedSentence}}}\n:::Explanation: ${explanationSentence}${linkString}\n\n\n\n`;

 if(passesFilter(originalSentence,correctedSentence,explanationSentence)){
 wikitext+=assessmentWikitext;
 }
 else{
 console.log('filtered out: '+assessmentWikitext);
 }
 }
 elseif(individualAssessment.trim()>50){
 individualAssessment=originalSentenceTag+' '+individualAssessment.trim();
 individualAssessment=individualAssessment.split(':**').join('')
 .split('**').join('*');
 individualAssessment+='\n*:[This suggestion is not displayed using the template "text diff" because the AI response did not have the expected format.]\n\n\n\n';
 }
 }

 returnwikitext.trim();

 // LLM output format may be inconsistent so it has to be checked
 functionhasExpectedFormat(individualAssessment){
 letcorrectionTagCount=individualAssessment.split(correctionTag).length-1;
 letexplanationTagCount=individualAssessment.split(correctionTag).length-1;

 returncorrectionTagCount===1&&explanationTagCount===1;
 }

 functionremoveQuotationMarks(text){
 if(text.startsWith('"')){
 text=text.substring(1);
 }
 if(text.endsWith('"')){
 text=text.substring(0,text.length-1);
 }
 returntext;
 }

 // filter out common AI errors
 functionpassesFilter(originalSentence,correctedSentence,explanationSentence){
 letexplanationExclusionList=['American English','British English','The sentence is correct as it is','The sentence is correct as is','No correction is needed','No correction needed','no errors were found'];
 for(letitemofexplanationExclusionList){
 if(explanationSentence.toLowerCase().includes(item.toLowerCase())){
 returnfalse;
 }
 }
 returntrue;
 }
 }

 // create the modal overlay to display controls and output
 functionopenModalWithTextarea(){
 constoverlay=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';

 constmodal=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);

 consttitle=document.createElement('div');
 title.innerHTML="SpellGrammarSuggestions";
 title.style.marginBottom='15px';
 modal.appendChild(title);

 consttextarea=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);
 modalTextarea=textarea;

 constbuttonContainer=document.createElement('div');
 buttonContainer.style.display='flex';
 buttonContainer.style.flexDirection='row';
 modal.appendChild(buttonContainer);

 conststartButton=addButton("Start",function(){
 clearTextarea();
 letcurrentAPIKey=localStorage.getItem('SpellGrammarSuggestionsAPIKey');
 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(){
 letcurrentAPIKey=localStorage.getItem('SpellGrammarSuggestionsAPIKey');
 if(currentAPIKey==='null'||currentAPIKey===null){
 currentAPIKey='';
 }

 letinput=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('SpellGrammarSuggestionsAPIKey',input);
 }

 startButton.disabled=false;
 });

 modalPreviewButton=addButton("Preview & Close",asyncfunction(){
 document.body.removeChild(overlay);
 openModalWithPreview();
 });
 modalPreviewButton.disabled=true;

 addButton("Close",function(){
 document.body.removeChild(overlay);
 });

 document.body.appendChild(overlay);

 functionaddButton(textContent,clickFunction){
 constbutton=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);
 returnbutton;
 }
 }

 // create the modal overlay to display preview
 asyncfunctionopenModalWithPreview(){
 constoverlay=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';

 constmodal=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);

 consttitle=document.createElement('div');
 title.innerHTML="Preview";
 title.style.marginBottom='15px';
 modal.appendChild(title);

 constpreviewDiv=document.createElement('div');
 previewDiv.style.width='100%';
 previewDiv.style.height='80%';
 previewDiv.style.resize='none';
 previewDiv.style.marginBottom='15px';
 previewDiv.style.borderRadius='5px';
 previewDiv.style.overflowY='auto';
 previewDiv.innerHTML=awaitwikitextToHTML(wikitext);
 modal.appendChild(previewDiv);
 modalPreviewDiv=previewDiv;

 constbuttonContainer=document.createElement('div');
 buttonContainer.style.display='flex';
 buttonContainer.style.flexDirection='row';
 modal.appendChild(buttonContainer);

 addButton("Close",function(){
 document.body.removeChild(overlay);
 });

 document.body.appendChild(overlay);

 functionaddButton(textContent,clickFunction){
 constbutton=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);
 returnbutton;
 }

 asyncfunctionwikitextToHTML(wikitext){
 constapiUrl='https://en.wikipedia.org/w/api.php';

 constparams={
 action:'parse',
 format:'json',
 contentmodel:'wikitext',
 text:wikitext
 };

 constresponse=awaitfetch(apiUrl,{
 method:'POST',
 headers:{
 'Content-Type':'application/x-www-form-urlencoded'
 },
 body:newURLSearchParams(params)
 });

 constdata=awaitresponse.json();

 if(data.error){
 console.error('Error from API:',data.error);
 returnnull;
 }

 consthtmlContent=data.parse.text['*'];
 returnhtmlContent
 }
 }

 functionlogTextarea(text){
 modalTextarea.value=text+'\n'+modalTextarea.value;
 }

 functionclearTextarea(){
 modalTextarea.value='';
 }

 // get the title of the page
 functiongetTitle(){
 letinnerText=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:')){
 letparts=innerText.split('/');
 parts.shift();
 innerText=parts.join('/');
 }
 returninnerText;
 }

 // replace the old string with the new string until no instance of the old string remains
 functionfullReplace(string,oldSubstring,newSubstring){
 letnewString=string;
 while(newString.includes(oldSubstring)){
 newString=newString.split(oldSubstring).join(newSubstring);
 }
 returnnewString;
 }
 })();

AltStyle によって変換されたページ (->オリジナル) /