User:Phlsph7/SourceVerificationAIAssistant.js

(function{	// restrict script to mainspace, userspace, template, and draftspace	const namespaceNumber = mw.config.get('wgNamespaceNumber');	const allowedNamespaces = [0, 2, 10, 118];	const scriptName = 'Source Verification AI Assistant';	const apiKeyName = scriptName.split(' ').join('') + 'ApiKey';	if (allowedNamespaces.indexOf(namespaceNumber) != -1) {		$.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; openScript(getSelectedText); };		});	}	function getSelectedText{		hideRefs;		let selectedText = window.getSelection.toString;		showRefs;		return selectedText;

function hideRefs{ let refs = document.body.querySelectorAll('.reference, .Inline-Template'); for(let ref of refs){ ref.style.display = 'none'; }		}

function showRefs{ let refs = document.body.querySelectorAll('.reference, .Inline-Template'); for(let ref of refs){ ref.style.display = ''; }		}	}	function openScript(selectedText){ const tokenLimit = 16384; const tokenLimit4k = 4096; const charToTokenRatio = 3.5; const maxCharactersClaim = 500; const maxCharactersSource = Math.floor(getCharacterEstimate(tokenLimit) * 0.8); const temperature = 0.5;

const content = document.getElementById('content'); const contentContainer = content.parentElement; content.style.display = 'none';

let scriptContainer = document.createElement('div'); contentContainer.appendChild(scriptContainer); scriptContainer.outerHTML = ` textarea { resize: none; border-radius: 5px; padding: 5px; }				button { margin: 5px; }				span { margin-bottom: 10px; }			${scriptName} Claim:  max chars Reliable source:  max chars Suggestions:  Placeholder Copy prompt Suggest supporting sentences Remove excessive source text Set API key Close script `;		const taClaim = document.getElementById('taClaim'); taClaim.value = selectedText; taClaim.addEventListener('input', updateClaimLengthCounter); const taSource = document.getElementById('taSource'); taSource.addEventListener('input', updateSourceLengthCounter); const btRemove = document.getElementById('btRemove'); btRemove.addEventListener('click', removeExcessiveSourceText); const btSuggest = document.getElementById('btSuggest'); btSuggest.addEventListener('click', getSuggestions); const btSetApiKey = document.getElementById('btSetApiKey'); btSetApiKey.addEventListener('click', setApiKey); const btCopy = document.getElementById('btCopy'); btCopy.addEventListener('click', copyPrompt); const btClose = document.getElementById('btClose'); btClose.addEventListener('click', closeScript); updateClaimLengthCounter; updateSourceLengthCounter; document.getElementById('scriptHeading').scrollIntoView; if(taClaim.value.length < 1){ taClaim.focus; }		else{ taSource.focus; }

function updateClaimLengthCounter{ let span = document.getElementById('claimCounter'); let currentCharacters = taClaim.value.length; let maxCharacters = maxCharactersClaim; updateLengthCounter(span, currentCharacters, maxCharacters); }

function updateSourceLengthCounter{ let span = document.getElementById('sourceCounter'); let currentCharacters = taSource.value.length; let maxCharacters = maxCharactersSource; updateLengthCounter(span, currentCharacters, maxCharacters); }

function updateLengthCounter(span, currentCharacters, maxCharacters){ span.innerText = `(${currentCharacters}/${maxCharacters} characters)`; if(currentCharacters > maxCharacters){ span.style.color = '#f00'; }			else{ span.style.color = '#ccc'; }		}

function removeExcessiveSourceText{ taSource.value = taSource.value.substring(0, maxCharactersSource); updateSourceLengthCounter; }		function setApiKey{ let currentAPIKey = localStorage.getItem(apiKeyName); 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); if(input !== null){ localStorage.setItem(apiKeyName, input); }		}

function copyPrompt{ let claimText = taClaim.value.trim; let sourceText = reformatText(taSource.value).trim; const promptText = getPromptText(claimText, sourceText); copyToClipboard(promptText); alert("The prompt was copied to the clipboard."); function copyToClipboard(text) { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select; document.execCommand('copy'); document.body.removeChild(textarea); }		}

function closeScript{ let scriptContainer = document.getElementById('scriptContainer'); scriptContainer.parentElement.removeChild(scriptContainer); content.style.display = ''; }		function getSuggestions{ let claimText = taClaim.value.trim; let sourceText = reformatText(taSource.value).trim; let apiKey = localStorage.getItem(apiKeyName); if(claimText.length < 1){ alert('Error: enter text in the field for the claim.'); }			else if(sourceText.length < 1){ alert('Error: enter text in the field for the reliable source.'); }			else if(claimText.length > maxCharactersClaim){ alert('Error: the text in the claim field is too long.'); }			else if(sourceText.length > maxCharactersSource){ alert('Error: the text in the reliable source field is too long.'); }			else if(apiKey === null || apiKey.length < 1){ alert('Error: use the button "Set API key" to enter a valid OpenAI API key.'); }			else{ const promptText = getPromptText(claimText, sourceText); const promptTokens = getTokenEstimate(promptText); let model; let remainingTokenEstimate; if(promptTokens < Math.floor(tokenLimit4k * 0.8)){ model = 'gpt-3.5-turbo'; remainingTokenEstimate = tokenLimit4k - promptTokens - 50; }				else{ model = 'gpt-3.5-turbo-16k'; remainingTokenEstimate = tokenLimit - promptTokens - 50; }				getResponse(promptText, model, remainingTokenEstimate, apiKey, false); }			function getResponse(promptText, model, remainingTokenEstimate, apiKey, isRetry){ disableButtons; taOutput.value = ''; taOutput.placeholder = ''; console.log(`Getting response ... (Model: ${model}; PromptTokenEstimate: ${getTokenEstimate(promptText)}; RemainingTokenEstimate: ${remainingTokenEstimate})`); const url = "https://api.openai.com/v1/chat/completions"; const body = JSON.stringify({					"messages": [{"role":"user","content": promptText}],					"model": model,					"temperature": temperature,					"max_tokens": remainingTokenEstimate,				}); const headers = { "content-type": "application/json", Authorization: "Bearer " + apiKey, };				const init = { method: "POST", body: body, headers: headers };				fetch(url, init).then(function(response){					enableButtons;					if(response.ok){						response.json.then(function(json){ const responseText = json.choices[0].message.content; const responseTextTokenEstimate = getTokenEstimate(responseText); console.log(`answer received (${responseTextTokenEstimate}/${remainingTokenEstimate} tokens) tokens`); const fixedResponseText = fixResponseText(responseText, claimText, sourceText); const warning = '(Please double-check the following information yourself and do not blindly trust it)\n'; const outputMessage = warning + fixedResponseText; const taOutput = document.getElementById('taOutput'); taOutput.value = outputMessage; });					}					else {						let errorCode = response.status;						if(errorCode == 400){							// This error sometimes happens with foreign languages because they have more tokens per word. One retry with a reduced token number							let adjustedRemainingTokens = Math.floor((remainingTokenEstimate - 50) / 2);							if(!isRetry && adjustedRemainingTokens > 50){								getResponse(promptText, model, adjustedRemainingTokens, apiKey, true);							}							else{								alert(`Error: the error code is ${errorCode}. This indicates that the text of the reliable source was too long.`);							}						}						else if(errorCode == 401){							alert(`Error: the error code is ${errorCode}. This indicates that no API key was entered or that the entered API key is incorrect.`);						}						else if(errorCode == 429){							alert(`Error: the error code is ${errorCode}. This indicates that you have sent requests too quickly or that you have reached your monthly limit.`);						}						else {							alert(`Error: the error code is ${errorCode}. You can try to use google and search for "OpenAI api error ${errorCode}" to learn more about this error.`);						}						console.log(response);					}				}); }		}		function getPromptText(claimText, articleText){ //let command = 'I have a claim and a reliable source. I want to find out whether the text in the reliable supports all parts of the claim. If it does then please cite all the sentences in the reliable that directly support the claim.'; //let command = 'I have a claim and a reliable source. I want to find out whether the text in the reliable supports all parts of the claim. If it does then please cite all the sentences in the reliable that directly support the claim.'; //let command = 'I have a claim and a reliable source. Cite the sentences in the reliable source that provide some support to the claim.'; let command = 'I have a claim and a reliable source. Cite the sentences in the reliable source that support the claim.'; let context = `\n\nClaim: """${claimText}"""\n\nReliable source: """${articleText}"""`; let promptText = command + context; return promptText; }		function getTokenEstimate(text){ return Math.floor(text.length / charToTokenRatio); }

function getCharacterEstimate(tokenNumber){ return Math.floor(tokenNumber * charToTokenRatio); }		function disableButtons{ btRemove.disabled = true; btSuggest.disabled = true; btSetApiKey.disabled = true; btClose.disabled = true; btCopy.disabled = true; }		function enableButtons{ btRemove.disabled = false; btSuggest.disabled = false; btSetApiKey.disabled = false; btClose.disabled = false; btCopy.disabled = false; }		// remove arbitrary linebreaks for pdf text function reformatText(text){ let paragraphEnd = "PARAGRAPH_END_PLACEHOLDER"; let reformatedText = text.split('\r').join('') .split('\n\n').join(paragraphEnd) .split('\n').join(' ') .split(' ').join(' ') .split(paragraphEnd).join('\n\n'); return reformatedText; }		// fix cases where the response simply repeats the claimText function fixResponseText(responseText, claimText, sourceText){ if(sourceText.includes(claimText)){ return `The exact sentence "${claimText}" is found in the reliable source. Please ensure that this sentence is attributed to the author to avoid a copyright violation.`; }			else if(responseText.includes(claimText)){ const claimText1 = '"' + claimText + '"'; const claimText2 = "'" + claimText + "'"; const fixedResponseText = responseText.split(claimText1).join('') .split(claimText2).join('') .split(claimText).join(''); console.log('responseText fixed'); return fixedResponseText; }			else{ return responseText; }		}	} });