Jump to content

User:Polygnotus/Scripts/AI Source Verification.js

From Wikipedia, the free encyclopedia
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';
    
    class WikipediaSourceVerifier {
        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();
            });
        }
        
        async loadOOUI() {
            await mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']);
        }
        
        getCurrentApiKey() {
            return localStorage.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() {
            return this.providers[this.currentProvider].color;
        }
        
        createUI() {
            const sidebar = 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() {
            const style = 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 = new OO.ui.ButtonWidget({
                icon: 'close',
                title: 'Close',
                framed: false,
                classes: ['verifier-close-button']
            });
            
            // Provider selector
            this.buttons.providerSelect = new OO.ui.DropdownWidget({
                menu: {
                    items: Object.keys(this.providers).map(key => 
                        new OO.ui.MenuOptionWidget({
                            data: key,
                            label: this.providers[key].name
                        })
                    )
                }
            });
            this.buttons.providerSelect.getMenu().selectItemByData(this.currentProvider);
            
            this.buttons.setKey = new OO.ui.ButtonWidget({
                label: 'Set API Key',
                flags: ['primary', 'progressive'],
                disabled: false
            });
            
            this.buttons.verify = new OO.ui.ButtonWidget({
                label: 'Verify Claim',
                flags: ['primary', 'progressive'],
                icon: 'check',
                disabled: true
            });
            
            this.buttons.changeKey = new OO.ui.ButtonWidget({
                label: 'Change Key',
                flags: ['safe'],
                icon: 'edit',
                disabled: false
            });
            
            this.buttons.removeKey = new OO.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]);
            
            const container = 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() {
            const container = 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
                const hasClaimAndSource = 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 (typeof mw !== 'undefined' && mw.config.get('wgNamespaceNumber') === 0) {
                let portletId = 'p-namespaces';
                if (mw.config.get('skin') === 'vector-2022') {
                    portletId = 'p-associated-pages';
                }
                const verifierLink = 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
            const references = document.querySelectorAll('.reference a');
            references.forEach(ref => {
                ref.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    this.handleReferenceClick(ref);
                });
            });
        }
        
        async handleReferenceClick(refElement) {
            try {
                // Clear previous highlights
                this.clearHighlights();
                
                // Show sidebar if hidden
                this.showSidebar();
                
                // Extract claim text (sentence before the reference)
                const claim = 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
                const refUrl = 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...');
                const sourceInfo = await this.fetchSourceContent(refUrl);
                
                if (!sourceInfo) {
                    this.updateStatus('Could not extract source URL', true);
                    return;
                }
                
                // Update UI with source info
                this.activeSource = sourceInfo;
                const sourceElement = document.getElementById('verifier-source-text');
                
                // Show the URL and indicate content will be fetched by AI
                const urlMatch = 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
		    const container = refElement.closest('p, li, td, div, section');
		    if (!container) {
		        return '';
		    }
		    
		    // Get all text content from the container
		    let fullText = '';
		    const walker = document.createTreeWalker(
		        container,
		        NodeFilter.SHOW_TEXT,
		        null,
		        false
		    );
		    
		    let textNode;
		    const textNodes = [];
		    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
		    const refText = refElement.textContent; // This should be like "[1]"
		    let refPosition = -1;
		    
		    // Look for the reference pattern in the text
		    const refPattern = new RegExp('\\[\\s*' + refText.replace(/[\[\]]/g, '') + '\\s*\\]');
		    const refMatch = fullText.match(refPattern);
		    if (refMatch) {
		        refPosition = refMatch.index;
		    } else {
		        // Fallback: find any reference pattern before our element
		        const allRefs = fullText.match(/\[\d+\]/g);
		        if (allRefs) {
		            // Find the last reference before the end
		            let lastRefIndex = -1;
		            let searchPos = 0;
		            for (const ref of allRefs) {
		                const index = 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
		    const textBeforeRef = fullText.substring(0, refPosition).trim();
		    
		    // Find the last complete sentence
		    const sentences = textBeforeRef.split(/([.!?]+\s+)/);
		    
		    if (sentences.length === 1) {
		        // No sentence breaks found, return the entire text
		        return textBeforeRef;
		    }
		    
		    // Reconstruct the last sentence
		    let lastSentence = '';
		    let foundSentenceEnd = false;
		    
		    // Work backwards through the sentence fragments
		    for (let i = sentences.length - 1; i >= 0; i--) {
		        const fragment = 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
		        const alternativeBreaks = 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
		            const words = 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
		        const nextChar = 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();
		    
		    return lastSentence;
		}







        
        extractReferenceUrl(refElement) {
            // Get the reference ID from the href
            const href = refElement.getAttribute('href');
            if (!href || !href.startsWith('#')) {
                return null;
            }
            
            const refId = href.substring(1);
            const refTarget = document.getElementById(refId);
            
            if (!refTarget) {
                return null;
            }
            
            // Look for URLs in the reference
            const links = refTarget.querySelectorAll('a[href^="http"]');
            if (links.length === 0) {
                return null;
            }
            
            // Return the first external URL found
            return links[0].href;
        }
        
        async fetchSourceContent(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);
                throw new Error(`Invalid source URL: ${error.message}`);
            }
        }
        
        highlightClaim(refElement, claim) {
            const parentElement = 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() {
            const handle = document.getElementById('verifier-resize-handle');
            const sidebar = document.getElementById('source-verifier-sidebar');
            
            if (!handle || !sidebar) return;
            
            let isResizing = false;
            handle.addEventListener('mousedown', (e) => {
                isResizing = true;
                document.addEventListener('mousemove', handleMouseMove);
                document.addEventListener('mouseup', handleMouseUp);
                e.preventDefault();
            });
            
            const handleMouseMove = (e) => {
                if (!isResizing) return;
                
                const newWidth = window.innerWidth - e.clientX;
                const minWidth = 300;
                const maxWidth = window.innerWidth * 0.8;
                
                if (newWidth >= minWidth && newWidth <= maxWidth) {
                    const widthPx = newWidth + 'px';
                    sidebar.style.width = widthPx;
                    document.body.style.marginRight = widthPx;
                    this.sidebarWidth = widthPx;
                    localStorage.setItem('verifier_sidebar_width', widthPx);
                }
            };
            
            const handleMouseUp = () => {
                isResizing = false;
                document.removeEventListener('mousemove', handleMouseMove);
                document.removeEventListener('mouseup', handleMouseUp);
            };
        }
        
        showSidebar() {
            const verifierTab = 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() {
            const verifierTab = 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() {
            const color = this.getCurrentColor();
            // Update theme colors similar to the original implementation
            // This would update sidebar border, header background, etc.
        }
        
        setApiKey() {
            const provider = this.providers[this.currentProvider];
            const dialog = new OO.ui.MessageDialog();
            
            const textInput = new OO.ui.TextInputWidget({
                placeholder: `Enter your ${provider.name} API Key...`,
                type: 'password',
                value: this.getCurrentApiKey() || ''
            });
            
            const windowManager = new OO.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') {
                    const key = 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);
            }
        }
        
        async verifyClaim() {
            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...');
                
                const provider = this.providers[this.currentProvider];
                let result;
                
                switch (this.currentProvider) {
                    case 'claude':
                        result = await this.callClaudeAPI(this.activeClaim, this.activeSource);
                        break;
                    case 'gemini':
                        result = await this.callGeminiAPI(this.activeClaim, this.activeSource);
                        break;
                    case 'openai':
                        result = await this.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);
            }
        }
        
        async callClaudeAPI(claim, sourceInfo) {
            // Extract URL from sourceInfo
            const urlMatch = sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/);
            const sourceUrl = urlMatch ? urlMatch[1] : null;
            
            const requestBody = {
                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}"`
                }]
            };
            
            const response = await fetch('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) {
                const errorText = await response.text();
                throw new Error(`API request failed (${response.status}): ${errorText}`);
            }
            
            const data = await response.json();
            return data.content[0].text;
        }
        
        async callGeminiAPI(claim, sourceInfo) {
            const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${this.providers.gemini.model}:generateContent?key=${this.getCurrentApiKey()}`;
            
            // Extract URL from sourceInfo
            const urlMatch = sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/);
            const sourceUrl = urlMatch ? urlMatch[1] : null;
            
            const systemPrompt = `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.`;
            
            const requestBody = {
                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: {}},
                ],
            };
            
            const response = await fetch(API_URL, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(requestBody)
            });
            
            const responseData = await response.json();
            
            if (!response.ok) {
                const errorDetail = responseData.error ? responseData.error.message : response.statusText;
                throw new Error(`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) {
                throw new Error('Invalid API response format or no content generated.');
            }
            
            return responseData.candidates[0].content.parts[0].text;
        }
        
        async callOpenAIAPI(claim, sourceInfo) {
            // Extract URL from sourceInfo
            const urlMatch = sourceInfo.match(/Source URL: (https?:\/\/[^\s\n]+)/);
            const sourceUrl = urlMatch ? urlMatch[1] : null;
            
            const requestBody = {
                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
            };
            
            const response = await fetch('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) {
                const errorText = await response.text();
                let errorMessage;
                try {
                    const errorData = JSON.parse(errorText);
                    errorMessage = errorData.error?.message || errorText;
                } catch {
                    errorMessage = errorText;
                }
                throw new Error(`API request failed (${response.status}): ${errorMessage}`);
            }
            
            const data = await response.json();
            
            if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
                throw new Error('Invalid API response format');
            }
            
            return data.choices[0].message.content;
        }
        
        updateOutput(content, isMarkdown = false) {
            const outputEl = document.getElementById('verifier-output');
            let processedContent = 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) {
            let html = 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')) {
                    return paragraph;
                }
                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, '');
            
            return html;
        }
    }
    
    // Initialize the source verifier when MediaWiki is ready
    if (typeof mw !== '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() {
                new WikipediaSourceVerifier();
            });
        });
    }
})();