Jump to content
Wikipedia The Free Encyclopedia

User:Polygnotus/Scripts/AI Source Verification.js

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 user script seems to have a documentation page at User:Polygnotus/Scripts/AI Source Verification.
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.
 //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();
 });
 });
 }
 })();

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