User:Polygnotus/Scripts/AI Source Verification.js
Appearance
From Wikipedia, the free encyclopedia
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 code will be executed when previewing this page.
This user script seems to have a documentation page at User:Polygnotus/Scripts/AI Source Verification.
//Inspired by User:Phlsph7/SourceVerificationAIAssistant.js (function(){ 'use strict'; classWikipediaSourceVerifier{ constructor(){ this.providers={ claude:{ name:'Claude', storageKey:'claude_api_key', color:'#0645ad', model:'claude-sonnet-4-20250514' }, gemini:{ name:'Gemini', storageKey:'gemini_api_key', color:'#4285F4', model:'gemini-2.5-flash-preview-05-20' }, openai:{ name:'ChatGPT', storageKey:'openai_api_key', color:'#10a37f', model:'gpt-4o' } }; this.currentProvider=localStorage.getItem('source_verifier_provider')||'claude'; this.sidebarWidth=localStorage.getItem('verifier_sidebar_width')||'400px'; this.isVisible=localStorage.getItem('verifier_sidebar_visible')!=='false'; this.currentResults=localStorage.getItem('verifier_current_results')||''; this.buttons={}; this.activeClaim=null; this.activeSource=null; this.init(); } init(){ this.loadOOUI().then(()=>{ this.createUI(); this.attachEventListeners(); this.attachReferenceClickHandlers(); this.adjustMainContent(); }); } asyncloadOOUI(){ awaitmw.loader.using(['oojs-ui-core','oojs-ui-widgets','oojs-ui-windows']); } getCurrentApiKey(){ returnlocalStorage.getItem(this.providers[this.currentProvider].storageKey); } setCurrentApiKey(key){ localStorage.setItem(this.providers[this.currentProvider].storageKey,key); } removeCurrentApiKey(){ localStorage.removeItem(this.providers[this.currentProvider].storageKey); } getCurrentColor(){ returnthis.providers[this.currentProvider].color; } createUI(){ constsidebar=document.createElement('div'); sidebar.id='source-verifier-sidebar'; this.createOOUIButtons(); sidebar.innerHTML=` <div id="verifier-sidebar-header"> <h3>Source Verifier</h3> <div id="verifier-sidebar-controls"> <div id="verifier-close-btn-container"></div> </div> </div> <div id="verifier-sidebar-content"> <div id="verifier-controls"> <div id="verifier-provider-container"></div> <div id="verifier-buttons-container"></div> </div> <div id="verifier-claim-section"> <h4>Selected Claim</h4> <div id="verifier-claim-text">Click on a reference number [1] next to a claim to verify it against its source.</div> </div> <div id="verifier-source-section"> <h4>Source Content</h4> <div id="verifier-source-text">No source loaded yet.</div> </div> <div id="verifier-results"> <h4>Verification Result</h4> <div id="verifier-output">${this.currentResults}</div> </div> </div> <div id="verifier-resize-handle"></div> `; this.createVerifierTab(); this.createStyles(); document.body.append(sidebar); this.appendOOUIButtons(); if(!this.isVisible){ this.hideSidebar(); } this.makeResizable(); } createStyles(){ conststyle=document.createElement('style'); style.textContent=` #source-verifier-sidebar { position: fixed; top: 0; right: 0; width: ${this.sidebarWidth}; height: 100vh; background: #fff; border-left: 2px solid ${this.getCurrentColor()}; box-shadow: -2px 0 8px rgba(0,0,0,0.1); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; display: flex; flex-direction: column; transition: all 0.3s ease; } #verifier-sidebar-header { background: ${this.getCurrentColor()}; color: white; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } #verifier-sidebar-header h3 { margin: 0; font-size: 16px; } #verifier-sidebar-controls { display: flex; gap: 8px; } #verifier-sidebar-content { padding: 15px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; } #verifier-controls { flex-shrink: 0; } #verifier-provider-container { margin-bottom: 10px; } #verifier-buttons-container { display: flex; flex-direction: column; gap: 8px; } #verifier-buttons-container .oo-ui-buttonElement { width: 100%; } #verifier-buttons-container .oo-ui-buttonElement-button { width: 100%; justify-content: center; } #verifier-claim-section, #verifier-source-section, #verifier-results { flex-shrink: 0; } #verifier-claim-section h4, #verifier-source-section h4, #verifier-results h4 { margin: 0 0 8px 0; color: ${this.getCurrentColor()}; font-size: 14px; font-weight: bold; } #verifier-claim-text, #verifier-source-text { padding: 10px; background: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; line-height: 1.4; max-height: 120px; overflow-y: auto; } #verifier-output { padding: 10px; background: #fafafa; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; line-height: 1.5; max-height: 480px; overflow-y: auto; white-space: pre-wrap; } #verifier-output h1, #verifier-output h2, #verifier-output h3 { color: ${this.getCurrentColor()}; margin-top: 16px; margin-bottom: 8px; } #verifier-output h1 { font-size: 1.3em; } #verifier-output h2 { font-size: 1.2em; } #verifier-output h3 { font-size: 1.1em; } #verifier-output ul, #verifier-output ol { padding-left: 18px; } #verifier-output p { margin-bottom: 10px; } #verifier-output strong { color: #d33; } #verifier-resize-handle { position: absolute; left: 0; top: 0; width: 4px; height: 100%; background: transparent; cursor: ew-resize; z-index: 10001; } #verifier-resize-handle:hover { background: ${this.getCurrentColor()}; opacity: 0.5; } #ca-verifier { display: none; } #ca-verifier a { color: ${this.getCurrentColor()} !important; text-decoration: none !important; padding: 0.5em !important; } #ca-verifier a:hover { text-decoration: underline !important; } body { margin-right: ${this.isVisible?this.sidebarWidth:'0'}; transition: margin-right 0.3s ease; } .verifier-error { color: #d33; background: #fef2f2; border: 1px solid #fecaca; padding: 8px; border-radius: 4px; } .verifier-sidebar-hidden body { margin-right: 0 !important; } .verifier-sidebar-hidden #source-verifier-sidebar { display: none; } .verifier-sidebar-hidden #ca-verifier { display: list-item !important; } .reference:hover { background-color: #e6f3ff; cursor: pointer; } .reference.verifier-active { background-color: ${this.getCurrentColor()}; color: white; } .claim-highlight { background-color: #fff3cd; border-left: 3px solid ${this.getCurrentColor()}; padding-left: 5px; margin-left: -8px; } `; document.head.appendChild(style); } createOOUIButtons(){ this.buttons.close=newOO.ui.ButtonWidget({ icon:'close', title:'Close', framed:false, classes:['verifier-close-button'] }); // Provider selector this.buttons.providerSelect=newOO.ui.DropdownWidget({ menu:{ items:Object.keys(this.providers).map(key=> newOO.ui.MenuOptionWidget({ data:key, label:this.providers[key].name }) ) } }); this.buttons.providerSelect.getMenu().selectItemByData(this.currentProvider); this.buttons.setKey=newOO.ui.ButtonWidget({ label:'Set API Key', flags:['primary','progressive'], disabled:false }); this.buttons.verify=newOO.ui.ButtonWidget({ label:'Verify Claim', flags:['primary','progressive'], icon:'check', disabled:true }); this.buttons.changeKey=newOO.ui.ButtonWidget({ label:'Change Key', flags:['safe'], icon:'edit', disabled:false }); this.buttons.removeKey=newOO.ui.ButtonWidget({ label:'Remove API Key', flags:['destructive'], icon:'trash', disabled:false }); this.updateButtonVisibility(); } appendOOUIButtons(){ document.getElementById('verifier-close-btn-container').appendChild(this.buttons.close.$element[0]); document.getElementById('verifier-provider-container').appendChild(this.buttons.providerSelect.$element[0]); constcontainer=document.getElementById('verifier-buttons-container'); if(this.getCurrentApiKey()){ container.appendChild(this.buttons.verify.$element[0]); container.appendChild(this.buttons.changeKey.$element[0]); container.appendChild(this.buttons.removeKey.$element[0]); }else{ container.appendChild(this.buttons.setKey.$element[0]); } } updateButtonVisibility(){ constcontainer=document.getElementById('verifier-buttons-container'); if(!container)return; container.innerHTML=''; if(this.getCurrentApiKey()){ // Enable verify button if we have API key and either claim+source OR just show it enabled consthasClaimAndSource=this.activeClaim&&this.activeSource; this.buttons.verify.setDisabled(!hasClaimAndSource); container.appendChild(this.buttons.verify.$element[0]); container.appendChild(this.buttons.changeKey.$element[0]); container.appendChild(this.buttons.removeKey.$element[0]); // Debug logging console.log('Button visibility update:',{ hasApiKey:!!this.getCurrentApiKey(), activeClaim:!!this.activeClaim, activeSource:!!this.activeSource, buttonDisabled:!hasClaimAndSource }); }else{ this.buttons.verify.setDisabled(true); container.appendChild(this.buttons.setKey.$element[0]); } } createVerifierTab(){ if(typeofmw!=='undefined'&&mw.config.get('wgNamespaceNumber')===0){ letportletId='p-namespaces'; if(mw.config.get('skin')==='vector-2022'){ portletId='p-associated-pages'; } constverifierLink=mw.util.addPortletLink( portletId, '#', 'Verify', 't-verifier', 'Verify claims against sources', 'v', ); verifierLink.addEventListener('click',(e)=>{ e.preventDefault(); this.showSidebar(); }); } } attachReferenceClickHandlers(){ // Attach click handlers to all reference links constreferences=document.querySelectorAll('.reference a'); references.forEach(ref=>{ ref.addEventListener('click',(e)=>{ e.preventDefault(); e.stopPropagation(); this.handleReferenceClick(ref); }); }); } asynchandleReferenceClick(refElement){ try{ // Clear previous highlights this.clearHighlights(); // Show sidebar if hidden this.showSidebar(); // Extract claim text (sentence before the reference) constclaim=this.extractClaimText(refElement); if(!claim){ this.updateStatus('Could not extract claim text',true); return; } // Highlight the claim in the article this.highlightClaim(refElement,claim); // Mark this reference as active refElement.parentElement.classList.add('verifier-active'); // Extract reference URL constrefUrl=this.extractReferenceUrl(refElement); if(!refUrl){ this.updateStatus('Could not extract reference URL',true); return; } // Update UI with claim this.activeClaim=claim; document.getElementById('verifier-claim-text').textContent=claim; // Fetch source content (URL extraction) this.updateStatus('Extracting source URL...'); constsourceInfo=awaitthis.fetchSourceContent(refUrl); if(!sourceInfo){ this.updateStatus('Could not extract source URL',true); return; } // Update UI with source info this.activeSource=sourceInfo; constsourceElement=document.getElementById('verifier-source-text'); // Show the URL and indicate content will be fetched by AI consturlMatch=sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/); if(urlMatch){ sourceElement.innerHTML=`<strong>Source URL:</strong><br><a href="${urlMatch[1]}" target="_blank" style="word-break: break-all;">${urlMatch[1]}</a><br><br><em>Content will be fetched and analyzed by AI during verification.</em>`; }else{ sourceElement.textContent=sourceInfo; } // Enable verify button now that we have both claim and source this.updateButtonVisibility(); this.updateStatus('Ready to verify claim against source'); console.log('Reference click completed:',{ claim:this.activeClaim?'Set':'Missing', source:this.activeSource?'Set':'Missing', apiKey:this.getCurrentApiKey()?'Set':'Missing' }); }catch(error){ console.error('Error handling reference click:',error); this.updateStatus(`Error: ${error.message}`,true); } } extractClaimText(refElement){ // Get the paragraph or container element constcontainer=refElement.closest('p, li, td, div, section'); if(!container){ return''; } // Get all text content from the container letfullText=''; constwalker=document.createTreeWalker( container, NodeFilter.SHOW_TEXT, null, false ); lettextNode; consttextNodes=[]; while(textNode=walker.nextNode()){ textNodes.push({ node:textNode, text:textNode.textContent, offset:fullText.length }); fullText+=textNode.textContent; } // Find the position of the reference in the full text constrefText=refElement.textContent;// This should be like "[1]" letrefPosition=-1; // Look for the reference pattern in the text constrefPattern=newRegExp('\\[\\s*'+refText.replace(/[\[\]]/g,'')+'\\s*\\]'); constrefMatch=fullText.match(refPattern); if(refMatch){ refPosition=refMatch.index; }else{ // Fallback: find any reference pattern before our element constallRefs=fullText.match(/\[\d+\]/g); if(allRefs){ // Find the last reference before the end letlastRefIndex=-1; letsearchPos=0; for(constrefofallRefs){ constindex=fullText.indexOf(ref,searchPos); if(index!==-1){ lastRefIndex=index; searchPos=index+ref.length; } } refPosition=lastRefIndex; } } if(refPosition===-1){ refPosition=fullText.length; } // Extract text before the reference consttextBeforeRef=fullText.substring(0,refPosition).trim(); // Find the last complete sentence constsentences=textBeforeRef.split(/([.!?]+\s+)/); if(sentences.length===1){ // No sentence breaks found, return the entire text returntextBeforeRef; } // Reconstruct the last sentence letlastSentence=''; letfoundSentenceEnd=false; // Work backwards through the sentence fragments for(leti=sentences.length-1;i>=0;i--){ constfragment=sentences[i]; if(fragment.match(/^[.!?]+\s*$/)){ // This is punctuation + whitespace if(!foundSentenceEnd){ foundSentenceEnd=true; continue; }else{ // We've found the end of the previous sentence break; } }else{ // This is text content lastSentence=fragment+lastSentence; if(foundSentenceEnd){ // We have a complete sentence break; } } } // If we didn't find a proper sentence boundary, fall back to a more aggressive approach if(!lastSentence.trim()||lastSentence.trim().length<10){ // Look for other potential sentence boundaries constalternativeBreaks=textBeforeRef.split(/([.!?:;]\s+|^\s*[A-Z])/); if(alternativeBreaks.length>1){ lastSentence=alternativeBreaks[alternativeBreaks.length-1]; }else{ // As a last resort, take a reasonable chunk from the end constwords=textBeforeRef.split(/\s+/); if(words.length>15){ lastSentence=words.slice(-15).join(' '); }else{ lastSentence=textBeforeRef; } } } // Clean up the result lastSentence=lastSentence.trim(); // Ensure it ends with proper punctuation for context if(lastSentence&&!lastSentence.match(/[.!?]$/)){ // Check if the original text continues with punctuation constnextChar=fullText.charAt(refPosition-lastSentence.length-1); if(nextChar.match(/[.!?]/)){ lastSentence=nextChar+' '+lastSentence; } } // Remove any remaining reference brackets that might have been included lastSentence=lastSentence.replace(/\[\d+\]/g,'').trim(); returnlastSentence; } extractReferenceUrl(refElement){ // Get the reference ID from the href consthref=refElement.getAttribute('href'); if(!href||!href.startsWith('#')){ returnnull; } constrefId=href.substring(1); constrefTarget=document.getElementById(refId); if(!refTarget){ returnnull; } // Look for URLs in the reference constlinks=refTarget.querySelectorAll('a[href^="http"]'); if(links.length===0){ returnnull; } // Return the first external URL found returnlinks[0].href; } asyncfetchSourceContent(url){ // Instead of trying to fetch the content directly (which fails due to CORS), // we'll use the AI API to fetch and analyze the content try{ // For now, return the URL - the AI will fetch it during verification return`Source URL: ${url}\n\n[Content will be fetched by AI during verification]`; }catch(error){ console.error('Error with source URL:',error); thrownewError(`Invalid source URL: ${error.message}`); } } highlightClaim(refElement,claim){ constparentElement=refElement.closest('p, li, td, div'); if(parentElement&&!parentElement.classList.contains('claim-highlight')){ parentElement.classList.add('claim-highlight'); } } clearHighlights(){ // Remove active reference highlighting document.querySelectorAll('.reference.verifier-active').forEach(el=>{ el.classList.remove('verifier-active'); }); // Remove claim highlighting document.querySelectorAll('.claim-highlight').forEach(el=>{ el.classList.remove('claim-highlight'); }); } makeResizable(){ consthandle=document.getElementById('verifier-resize-handle'); constsidebar=document.getElementById('source-verifier-sidebar'); if(!handle||!sidebar)return; letisResizing=false; handle.addEventListener('mousedown',(e)=>{ isResizing=true; document.addEventListener('mousemove',handleMouseMove); document.addEventListener('mouseup',handleMouseUp); e.preventDefault(); }); consthandleMouseMove=(e)=>{ if(!isResizing)return; constnewWidth=window.innerWidth-e.clientX; constminWidth=300; constmaxWidth=window.innerWidth*0.8; if(newWidth>=minWidth&&newWidth<=maxWidth){ constwidthPx=newWidth+'px'; sidebar.style.width=widthPx; document.body.style.marginRight=widthPx; this.sidebarWidth=widthPx; localStorage.setItem('verifier_sidebar_width',widthPx); } }; consthandleMouseUp=()=>{ isResizing=false; document.removeEventListener('mousemove',handleMouseMove); document.removeEventListener('mouseup',handleMouseUp); }; } showSidebar(){ constverifierTab=document.getElementById('ca-verifier'); document.body.classList.remove('verifier-sidebar-hidden'); if(verifierTab)verifierTab.style.display='none'; document.body.style.marginRight=this.sidebarWidth; this.isVisible=true; localStorage.setItem('verifier_sidebar_visible','true'); } hideSidebar(){ constverifierTab=document.getElementById('ca-verifier'); document.body.classList.add('verifier-sidebar-hidden'); if(verifierTab)verifierTab.style.display='list-item'; document.body.style.marginRight='0'; this.clearHighlights(); this.isVisible=false; localStorage.setItem('verifier_sidebar_visible','false'); } adjustMainContent(){ if(this.isVisible){ document.body.style.marginRight=this.sidebarWidth; }else{ document.body.style.marginRight='0'; } } attachEventListeners(){ this.buttons.close.on('click',()=>{ this.hideSidebar(); }); this.buttons.providerSelect.getMenu().on('select',(item)=>{ this.currentProvider=item.getData(); localStorage.setItem('source_verifier_provider',this.currentProvider); this.updateButtonVisibility(); this.updateTheme(); this.updateStatus(`Switched to ${this.providers[this.currentProvider].name}`); }); this.buttons.setKey.on('click',()=>{ this.setApiKey(); }); this.buttons.changeKey.on('click',()=>{ this.setApiKey(); }); this.buttons.verify.on('click',()=>{ this.verifyClaim(); }); this.buttons.removeKey.on('click',()=>{ this.removeApiKey(); }); } updateTheme(){ constcolor=this.getCurrentColor(); // Update theme colors similar to the original implementation // This would update sidebar border, header background, etc. } setApiKey(){ constprovider=this.providers[this.currentProvider]; constdialog=newOO.ui.MessageDialog(); consttextInput=newOO.ui.TextInputWidget({ placeholder:`Enter your ${provider.name} API Key...`, type:'password', value:this.getCurrentApiKey()||'' }); constwindowManager=newOO.ui.WindowManager(); $('body').append(windowManager.$element); windowManager.addWindows([dialog]); windowManager.openWindow(dialog,{ title:`Set ${provider.name} API Key`, message:$('<div>').append( $('<p>').text(`Enter your ${provider.name} API Key to enable source verification:`), textInput.$element ), actions:[ { action:'save', label:'Save', flags:['primary','progressive'] }, { action:'cancel', label:'Cancel', flags:['safe'] } ] }).closed.then((data)=>{ if(data&&data.action==='save'){ constkey=textInput.getValue().trim(); if(key){ this.setCurrentApiKey(key); this.updateButtonVisibility(); this.updateStatus('API key set successfully!'); // If we already have claim and source, enable verify button if(this.activeClaim&&this.activeSource){ console.log('API key set - enabling verification'); this.updateButtonVisibility(); } } } windowManager.destroy(); }); } removeApiKey(){ OO.ui.confirm('Are you sure you want to remove the stored API key?').done((confirmed)=>{ if(confirmed){ this.removeCurrentApiKey(); this.updateButtonVisibility(); this.updateStatus('API key removed successfully!'); } }); } updateStatus(message,isError=false){ // For now, we'll update the claim section to show status // In a full implementation, you might want a dedicated status area if(isError){ console.error('Verifier Error:',message); }else{ console.log('Verifier Status:',message); } } asyncverifyClaim(){ if(!this.getCurrentApiKey()||!this.activeClaim||!this.activeSource){ this.updateStatus('Missing API key, claim, or source content',true); return; } try{ this.buttons.verify.setDisabled(true); this.updateStatus('Verifying claim against source...'); constprovider=this.providers[this.currentProvider]; letresult; switch(this.currentProvider){ case'claude': result=awaitthis.callClaudeAPI(this.activeClaim,this.activeSource); break; case'gemini': result=awaitthis.callGeminiAPI(this.activeClaim,this.activeSource); break; case'openai': result=awaitthis.callOpenAIAPI(this.activeClaim,this.activeSource); break; } this.updateStatus('Verification complete!'); this.updateOutput(result,true); }catch(error){ console.error('Verification error:',error); this.updateStatus(`Error: ${error.message}`,true); }finally{ this.buttons.verify.setDisabled(false); } } asynccallClaudeAPI(claim,sourceInfo){ // Extract URL from sourceInfo consturlMatch=sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/); constsourceUrl=urlMatch?urlMatch[1]:null; constrequestBody={ model:this.providers.claude.model, max_tokens:3000, system:`You are a fact-checking assistant. Your task is to verify whether a claim from a Wikipedia article is supported by the source content at the provided URL. Instructions: 1. First, fetch and read the content from the provided URL 2. Analyze the claim and determine what specific facts it asserts 3. Search through the source content for information that supports or contradicts the claim 4. Provide a clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR 5. Quote the specific sentences from the source that support your verdict 6. Explain any discrepancies or missing information Be precise and objective in your analysis.`, messages:[{ role:"user", content:sourceUrl? `Please fetch the content from this URL and verify the claim against it. Claim to verify: "${claim}" Source URL: ${sourceUrl}`: `Claim to verify: "${claim}" Source content: "${sourceInfo}"` }] }; constresponse=awaitfetch('https://api.anthropic.com/v1/messages',{ method:'POST', headers:{ 'Content-Type':'application/json', 'x-api-key':this.getCurrentApiKey(), 'anthropic-version':'2023年06月01日', 'anthropic-dangerous-direct-browser-access':'true' }, body:JSON.stringify(requestBody) }); if(!response.ok){ consterrorText=awaitresponse.text(); thrownewError(`API request failed (${response.status}): ${errorText}`); } constdata=awaitresponse.json(); returndata.content[0].text; } asynccallGeminiAPI(claim,sourceInfo){ constAPI_URL=`https://generativelanguage.googleapis.com/v1beta/models/${this.providers.gemini.model}:generateContent?key=${this.getCurrentApiKey()}`; // Extract URL from sourceInfo consturlMatch=sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/); constsourceUrl=urlMatch?urlMatch[1]:null; constsystemPrompt=`You are a fact-checking assistant. Verify whether a Wikipedia claim is supported by the source content at the provided URL. Instructions: 1. Fetch and read the content from the provided URL 2. Analyze the claim and determine what specific facts it asserts 3. Search through the source content for information that supports or contradicts the claim 4. Provide a clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR 5. Quote the specific sentences from the source that support your verdict 6. Explain any discrepancies or missing information Be precise and objective in your analysis.`; constrequestBody={ contents:[{ parts:[{"text":sourceUrl? `Please fetch the content from this URL and verify the claim against it. Claim to verify: "${claim}" Source URL: ${sourceUrl}`: `Claim to verify: "${claim}" Source content: "${sourceInfo}"`}], }], systemInstruction:{ parts:[{"text":systemPrompt}] }, generationConfig:{ maxOutputTokens:2048, temperature:0.0, }, tools:[ {urlContext:{}}, {googleSearch:{}}, ], }; constresponse=awaitfetch(API_URL,{ method:'POST', headers:{ 'Content-Type':'application/json', }, body:JSON.stringify(requestBody) }); constresponseData=awaitresponse.json(); if(!response.ok){ consterrorDetail=responseData.error?responseData.error.message:response.statusText; thrownewError(`API request failed (${response.status}): ${errorDetail}`); } if(!responseData.candidates||!responseData.candidates[0]|| !responseData.candidates[0].content||!responseData.candidates[0].content.parts|| !responseData.candidates[0].content.parts[0]||!responseData.candidates[0].content.parts[0].text){ thrownewError('Invalid API response format or no content generated.'); } returnresponseData.candidates[0].content.parts[0].text; } asynccallOpenAIAPI(claim,sourceInfo){ // Extract URL from sourceInfo consturlMatch=sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/); constsourceUrl=urlMatch?urlMatch[1]:null; constrequestBody={ model:this.providers.openai.model, max_tokens:2000, messages:[ { role:"system", content:`You are a fact-checking assistant. Your task is to verify whether a claim from a Wikipedia article is supported by the source content. Instructions: 1. Analyze the claim and determine what specific facts it asserts 2. Examine the source information provided 3. Provide a clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR 4. Explain your reasoning based on the available information 5. Note any limitations due to inability to access the full source content Be precise and objective in your analysis.` }, { role:"user", content:sourceUrl? `I need to verify this claim against a source, but I can only provide the URL since direct content fetching isn't available. Claim to verify: "${claim}" Source URL: ${sourceUrl} Please provide analysis based on what you can determine from the URL and any known information about the source. Note that full verification would require accessing the complete source content.`: `Claim to verify: "${claim}" Source information: "${sourceInfo}"` } ], temperature:0.1 }; constresponse=awaitfetch('https://api.openai.com/v1/chat/completions',{ method:'POST', headers:{ 'Content-Type':'application/json', 'Authorization':`Bearer ${this.getCurrentApiKey()}` }, body:JSON.stringify(requestBody) }); if(!response.ok){ consterrorText=awaitresponse.text(); leterrorMessage; try{ consterrorData=JSON.parse(errorText); errorMessage=errorData.error?.message||errorText; }catch{ errorMessage=errorText; } thrownewError(`API request failed (${response.status}): ${errorMessage}`); } constdata=awaitresponse.json(); if(!data.choices||!data.choices[0]||!data.choices[0].message||!data.choices[0].message.content){ thrownewError('Invalid API response format'); } returndata.choices[0].message.content; } updateOutput(content,isMarkdown=false){ constoutputEl=document.getElementById('verifier-output'); letprocessedContent=content; if(isMarkdown){ processedContent=this.markdownToHtml(content); outputEl.innerHTML=processedContent; }else{ outputEl.textContent=content; } if(content){ this.currentResults=processedContent; localStorage.setItem('verifier_current_results',this.currentResults); }else{ this.currentResults=''; localStorage.removeItem('verifier_current_results'); } } markdownToHtml(markdown){ lethtml=markdown; // Convert headers html=html.replace(/^### (.*$)/gim,'<h3>1ドル</h3>'); html=html.replace(/^## (.*$)/gim,'<h2>1ドル</h2>'); html=html.replace(/^# (.*$)/gim,'<h1>1ドル</h1>'); // Convert bold and italic html=html.replace(/\*\*(.*?)\*\*/g,'<strong>1ドル</strong>'); html=html.replace(/\*(.*?)\*/g,'<em>1ドル</em>'); html=html.replace(/_(.*?)_/g,'<em>1ドル</em>'); // Convert lists html=html.replace(/^\s*[\*\-] (.*$)/gim,'<li>1ドル</li>'); html=html.replace(/^\s*\d+\. (.*$)/gim,'<li>1ドル</li>'); // Wrap consecutive list items in ul tags html=html.replace(/((<li>.*<\/li>\s*)+)/g,(match,p1)=>{ return`<ul>${p1.replace(/\s*<li>/g,'<li>')}</ul>`; }); // Convert paragraphs html=html.split(/\n\s*\n/).map(paragraph=>{ paragraph=paragraph.trim(); if(!paragraph)return''; if(paragraph.startsWith('<h')||paragraph.startsWith('<ul')||paragraph.startsWith('<ol')||paragraph.startsWith('<li')){ returnparagraph; } return`<p>${paragraph.replace(/\n/g,'<br>')}</p>`; }).join(''); // Clean up html=html.replace(/<p>\s*(<(?:ul|ol|h[1-6])[^>]*>[\s\S]*?<\/(?:ul|ol|h[1-6])>)\s*<\/p>/gi,'1ドル'); html=html.replace(/<p>\s*<\/p>/gi,''); returnhtml; } } // Initialize the source verifier when MediaWiki is ready if(typeofmw!=='undefined'&&mw.config.get('wgNamespaceNumber')===0){ mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui-core','oojs-ui-widgets','oojs-ui-windows']).then(function(){ $(function(){ newWikipediaSourceVerifier(); }); }); } })();