User:Polygnotus/Scripts/AI Source Verification.js

This is an old revision of this page, as edited by Polygnotus (talk | contribs) at 11:43, 12 June 2025. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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: 330px;
                    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) {
            // Find the text node containing the reference
            let currentNode = refElement.parentElement.previousSibling;
            let claim = '';
            
            // Look backwards to find the start of the sentence
            while (currentNode) {
                if (currentNode.nodeType === Node.TEXT_NODE) {
                    const text = currentNode.textContent;
                    claim = text + claim;
                    
                    // Stop at sentence boundaries
                    const sentenceEnd = text.match(/[.!?]\s*$/);
                    if (sentenceEnd) {
                        break;
                    }
                } else if (currentNode.nodeType === Node.ELEMENT_NODE) {
                    // Include text from elements but stop at certain boundaries
                    if (currentNode.tagName && ['BR', 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(currentNode.tagName)) {
                        break;
                    }
                    claim = currentNode.textContent + claim;
                }
                currentNode = currentNode.previousSibling;
            }
            
            // Clean up the claim text
            claim = claim.trim();
            
            // If we didn't find a sentence boundary, try to extract from current element
            if (!claim || claim.length < 10) {
                const parentElement = refElement.closest('p, li, td, div');
                if (parentElement) {
                    const fullText = parentElement.textContent;
                    const refIndex = fullText.indexOf('[');
                    if (refIndex > 0) {
                        claim = fullText.substring(0, refIndex).trim();
                        // Find the last sentence before the reference
                        const sentences = claim.split(/[.!?]/);
                        if (sentences.length > 1) {
                            claim = sentences[sentences.length - 2] + '.';
                        }
                    }
                }
            }
            
            return claim.trim();
        }
        
        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();
            });
        });
    }
})();