User:Phlsph7/SourceVerificationAIAssistant.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. |
This user script seems to have a documentation page at User:Phlsph7/SourceVerificationAIAssistant. |
(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 = `
<div id="scriptContainer" style="display:flex; flex-direction: column; min-width: 800px">
<style>
textarea {
resize: none;
border-radius: 5px;
padding: 5px;
}
button {
margin: 5px;
}
span {
margin-bottom: 10px;
}
</style>
<h1 id="scriptHeading">${scriptName}</h1>
<div style="display:flex;">
<div style="flex: 1; display:flex; flex-direction: column; margin: 5px;">
<div style="display:flex; flex-direction: column;">
<label for="taClaim">Claim:</label>
<textarea id="taClaim" rows="4" placeholder="Enter the claim to be verified here. Short and straightforward claims work best."></textarea>
<span id="claimCounter">max chars</span>
</div>
<div style="display:flex; flex-direction: column;">
<label for="taSource">Reliable source:</label>
<textarea id="taSource" rows="15" placeholder="Paste the text of the reliable source here. Try to only include the sections that are relevant."></textarea>
<span id="sourceCounter">max chars</span>
</div>
</div>
<div style="flex: 1; display:flex; flex-direction: column; margin: 5px;">
<label for="taOutput">Suggestions:</label>
<textarea id="taOutput" style="height: 100%;" placeholder="Sentences from the reliable source that are relevant to the claim will be displayed here. Do not blindly trust these suggestions and check for yourself that the sentences actually come from the reliable source and that they support the claim." disabled></textarea>
<span style="visibility: hidden;">Placeholder</span>
</div>
</div>
<div style="display:flex; flex-direction: column;">
<div style="display:flex;">
<button id="btCopy" title="Copies the prompt so it can be used with another AI model." style="flex: 1;">Copy prompt</button>
<button id="btSuggest" title="Attempts to find sentences in the source that support the claim." style="flex: 1;">Suggest supporting sentences</button>
</div>
<div style="display:flex;">
<button id="btRemove" title="Removes source text that goes beyond the character limit. Be careful since this may remove substantial parts of the source." style="flex: 1;">Remove excessive source text</button>
<button id="btSetApiKey" title="Enter the OpenAI API key required for usage." style="flex: 1;">Set API key</button>
</div>
<div style="display:flex;">
<button id="btClose" title="Closes the script and return to the previous Wikipedia article." style="flex: 1;">Close script</button>
</div>
</div>
</div>
`;
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;
}
}
}
})();