User:Polygnotus/Scripts/AI Source Verification.js

This is an old revision of this page, as edited by Polygnotus (talk | contribs) at 11:17, 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: 200px;
                    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
                this.updateStatus('Fetching source content...');
                const sourceContent = await this.fetchSourceContent(refUrl);
                
                if (!sourceContent) {
                    this.updateStatus('Could not fetch source content', true);
                    return;
                }
                
                // Update UI with source
                this.activeSource = sourceContent;
                const sourceElement = document.getElementById('verifier-source-text');
                sourceElement.textContent = sourceContent.substring(0, 1000) + (sourceContent.length > 1000 ? '...' : '');
                
                // 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) {
            try {
                // Use CORS proxy for external URLs
                const proxyUrl = `https://corsproxy.io/?${encodeURIComponent(url)}`;
                
                const response = await fetch(proxyUrl, {
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (compatible; WikipediaSourceVerifier/1.0)'
                    }
                });
                
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                
                const html = await response.text();
                
                // Extract text content from HTML
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                
                // Remove script and style elements
                const unwanted = doc.querySelectorAll('script, style, nav, header, footer, aside, .ad, .advertisement');
                unwanted.forEach(el => el.remove());
                
                // Try to find main content
                let mainContent = doc.querySelector('main, article, .content, .post-content, .article-content, .story-body') 
                    || doc.querySelector('body');
                
                if (!mainContent) {
                    mainContent = doc.body;
                }
                
                let text = mainContent.textContent || mainContent.innerText || '';
                
                // Clean up the text
                text = text.replace(/\s+/g, ' ').trim();
                
                // Limit length to avoid token limits
                if (text.length > 10000) {
                    text = text.substring(0, 10000);
                }
                
                return text;
                
            } catch (error) {
                console.error('Error fetching source content:', error);
                throw new Error(`Failed to fetch source: ${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, source) {
            const requestBody = {
                model: this.providers.claude.model,
                max_tokens: 2000,
                system: `You are a fact-checking assistant. Your task is to verify whether a claim from a Wikipedia article is supported by the provided source content.

Instructions:
1. Analyze the claim and determine what specific facts it asserts
2. Search through the source content for information that supports or contradicts the claim
3. Provide a clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR
4. Quote the specific sentences from the source that support your verdict
5. Explain any discrepancies or missing information

Be precise and objective in your analysis.`,
                messages: [{
                    role: "user",
                    content: `Claim to verify: "${claim}"

Source content: "${source}"`
                }]
            };
            
            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, source) {
            const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${this.providers.gemini.model}:generateContent?key=${this.getCurrentApiKey()}`;
            
            const systemPrompt = `You are a fact-checking assistant. Verify whether a Wikipedia claim is supported by the provided source content.

Provide:
1. A clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR
2. Specific quotes from the source that support your verdict
3. Explanation of any discrepancies or missing information

Be precise and objective in your analysis.`;
            
            const requestBody = {
                contents: [{
                    parts: [{ "text": `Claim to verify: "${claim}"\n\nSource content: "${source}"` }],
                }],
                systemInstruction: {
                    parts: [{ "text": systemPrompt }]
                },
                generationConfig: {
                    maxOutputTokens: 2048,
                    temperature: 0.0,
                },
            };
            
            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, source) {
            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 provided source content.

Instructions:
1. Analyze the claim and determine what specific facts it asserts
2. Search through the source content for information that supports or contradicts the claim
3. Provide a clear verdict: SUPPORTED, PARTIALLY SUPPORTED, NOT SUPPORTED, or UNCLEAR
4. Quote the specific sentences from the source that support your verdict
5. Explain any discrepancies or missing information

Be precise and objective in your analysis.`
                    },
                    {
                        role: "user",
                        content: `Claim to verify: "${claim}"\n\nSource content: "${source}"`
                    }
                ],
                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();
            });
        });
    }
})();