Jump to content

User:Polygnotus/Scripts/tmp.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.
// <nowiki>
(function() {
    'use strict';
    
    if (mw.config.get('skin') === 'minerva') {
        return;
    }
    
    class WikipediaAIProofreader {
        constructor() {
            this.color = '#4285F4';
            this.model = 'gemini-2.5-flash';
            this.sidebarWidth = localStorage.getItem('ai_sidebar_width') || '350px';
            this.isVisible = localStorage.getItem('ai_sidebar_visible') !== 'false';
            this.currentResults = localStorage.getItem('ai_current_results') || '';
            this.buttons = {};
            this.init();
        }
        
        init() {
            this.loadOOUI().then(() => {
                this.createUI();
                this.attachEventListeners();
                this.adjustMainContent();
            });
        }
        
        async loadOOUI() {
            await mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']);
        }
        
        getApiKey() {
            return localStorage.getItem('gemini_api_key');
        }
        
        setApiKey(key) {
            localStorage.setItem('gemini_api_key', key);
        }
        
        removeApiKey() {
            localStorage.removeItem('gemini_api_key');
        }
        
        createUI() {
            const sidebar = document.createElement('div');
            sidebar.id = 'ai-proofreader-sidebar';
            
            this.createOOUIButtons();
            
            sidebar.innerHTML = `
                <div id="ai-sidebar-header">
                    <h3>KI-Korrekturleser</h3>
                    <div id="ai-sidebar-controls">
                        <div id="ai-close-btn-container"></div>
                    </div>
                </div>
                <div id="ai-sidebar-content">
                    <div id="ai-controls">
                        <div id="ai-buttons-container"></div>
                    </div>
                    <div id="ai-results">
                        <div id="ai-status">Bereit zur Prüfung</div>
                        <div id="ai-output">${this.currentResults}</div>
                    </div>
                </div>
                <div id="ai-resize-handle"></div>
            `;
            
            this.createAITab();
            this.createStyles();
            document.body.append(sidebar);
            this.appendOOUIButtons();
            
            if (!this.isVisible) {
                this.hideSidebar();
            }
            
            this.makeResizable();
        }
        
        createStyles() {
            const style = document.createElement('style');
            style.textContent = `
                #ai-proofreader-sidebar {
                    position: fixed;
                    top: 0;
                    right: 0;
                    width: ${this.sidebarWidth};
                    height: 100vh;
                    background: #fff;
                    border-left: 2px solid ${this.color};
                    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;
                }
                #ai-sidebar-header {
                    background: ${this.color};
                    color: white;
                    padding: 12px 15px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    flex-shrink: 0;
                }
                #ai-sidebar-header h3 {
                    margin: 0;
                    font-size: 16px;
                }
                #ai-sidebar-controls {
                    display: flex;
                    gap: 8px;
                }
                #ai-sidebar-content {
                    padding: 15px;
                    flex: 1;
                    overflow-y: auto;
                    display: flex;
                    flex-direction: column;
                }
                #ai-controls {
                    margin-bottom: 15px;
                    flex-shrink: 0;
                }
                #ai-buttons-container {
                    display: flex;
                    flex-direction: column;
                    gap: 8px;
                }
                #ai-buttons-container .oo-ui-buttonElement {
                    width: 100%;
                }
                #ai-buttons-container .oo-ui-buttonElement-button {
                    width: 100%;
                    justify-content: center;
                }
                #ai-results {
                    flex: 1;
                    display: flex;
                    flex-direction: column;
                    min-height: 0;
                }
                #ai-status {
                    font-weight: bold;
                    margin-bottom: 10px;
                    padding: 8px;
                    background: #f8f9fa;
                    border-radius: 4px;
                    flex-shrink: 0;
                }
                #ai-output {
                    line-height: 1.5;
                    flex: 1;
                    overflow-y: scroll;
                    max-height: calc(100vh - 220px);
                    border: 1px solid #ddd;
                    padding: 12px;
                    border-radius: 4px;
                    background: #fafafa;
                    font-size: 13px;
                    white-space: pre-wrap;
                }
                #ai-output h1, #ai-output h2, #ai-output h3 {
                    color: ${this.color};
                    margin-top: 16px;
                    margin-bottom: 8px;
                }
                #ai-output h1 { font-size: 1.3em; }
                #ai-output h2 { font-size: 1.2em; }
                #ai-output h3 { font-size: 1.1em; }
                #ai-output ul, #ai-output ol {
                    padding-left: 18px;
                }
                #ai-output p {
                    margin-bottom: 10px;
                }
                #ai-output strong {
                    color: #d33;
                }
                #ai-resize-handle {
                    position: absolute;
                    left: 0;
                    top: 0;
                    width: 4px;
                    height: 100%;
                    background: transparent;
                    cursor: ew-resize;
                    z-index: 10001;
                }
                #ai-resize-handle:hover {
                    background: ${this.color};
                    opacity: 0.5;
                }
                #ca-ai {
                    display: none;
                }
                #ca-ai a {
                    color: ${this.color} !important;
                    text-decoration: none !important;
                    padding: 0.5em !important;
                }
                #ca-ai a:hover {
                    text-decoration: underline !important;
                }
                body {
                    margin-right: ${this.isVisible ? this.sidebarWidth : '0'};
                    transition: margin-right 0.3s ease;
                }
                .ai-error {
                    color: #d33;
                    background: #fef2f2;
                    border: 1px solid #fecaca;
                    padding: 8px;
                    border-radius: 4px;
                }
                .ai-sidebar-hidden body {
                    margin-right: 0 !important;
                }
                .ai-sidebar-hidden #ai-proofreader-sidebar {
                    display: none;
                }
                .ai-sidebar-hidden #ca-ai {
                    display: list-item !important;
                }
            `;
            document.head.appendChild(style);
        }
        
        createOOUIButtons() {
            this.buttons.close = new OO.ui.ButtonWidget({
                icon: 'close',
                title: 'Schließen',
                framed: false
            });
            
            this.buttons.setKey = new OO.ui.ButtonWidget({
                label: 'API-Schlüssel eingeben',
                flags: ['primary', 'progressive']
            });
            
            this.buttons.proofread = new OO.ui.ButtonWidget({
                label: 'Artikel prüfen',
                flags: ['primary', 'progressive'],
                icon: 'check',
                disabled: !this.getApiKey()
            });
            
            this.buttons.changeKey = new OO.ui.ButtonWidget({
                label: 'Schlüssel ändern',
                flags: ['safe'],
                icon: 'edit'
            });
            
            this.buttons.removeKey = new OO.ui.ButtonWidget({
                label: 'API-Schlüssel entfernen',
                flags: ['destructive'],
                icon: 'trash'
            });
            
            this.updateButtonVisibility();
        }
        
        appendOOUIButtons() {
            document.getElementById('ai-close-btn-container').appendChild(this.buttons.close.$element[0]);
            const container = document.getElementById('ai-buttons-container');
            if (this.getApiKey()) {
                container.appendChild(this.buttons.proofread.$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('ai-buttons-container');
            if (!container) return;
            container.innerHTML = '';
            if (this.getApiKey()) {
                this.buttons.proofread.setDisabled(false);
                container.appendChild(this.buttons.proofread.$element[0]);
                container.appendChild(this.buttons.changeKey.$element[0]);
                container.appendChild(this.buttons.removeKey.$element[0]);
            } else {
                this.buttons.proofread.setDisabled(true);
                container.appendChild(this.buttons.setKey.$element[0]);
            }
        }
        
        createAITab() {
            if (typeof mw === 'undefined' || mw.config.get('wgNamespaceNumber') !== 0) {
                return;
            }
            const skin = mw.config.get('skin');
            const portletCandidates = {
                'vector-2022': 'p-associated-pages',
                'vector':      'p-associated-pages',
                'monobook':    'p-namespaces',
                'timeless':    'p-namespaces',
            };
            const portletId = portletCandidates[skin] || 'p-namespaces';
            const aiLink = mw.util.addPortletLink(portletId, '#', 'KI', 't-prp-ai', 'Artikel mit KI prüfen', 'm');
            if (!aiLink) {
                console.warn(`KI-Korrekturleser: Portlet-Link konnte nicht zu "${portletId}" hinzugefügt werden (Skin: ${skin})`);
                return;
            }
            aiLink.addEventListener('click', (e) => {
                e.preventDefault();
                this.showSidebar();
            });
        }
        
        makeResizable() {
            const handle = document.getElementById('ai-resize-handle');
            const sidebar = document.getElementById('ai-proofreader-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 = 250;
                const maxWidth = window.innerWidth * 0.7;
                if (newWidth >= minWidth && newWidth <= maxWidth) {
                    const widthPx = newWidth + 'px';
                    sidebar.style.width = widthPx;
                    document.body.style.marginRight = widthPx;
                    const skin = mw.config.get('skin');
                    if (skin === 'vector' && !skin.includes('vector-2022')) {
                        const head = document.querySelector('#mw-head');
                        if (head) {
                            head.style.width = `calc(100% - ${widthPx})`;
                            head.style.right = widthPx;
                        }
                    }
                    this.sidebarWidth = widthPx;
                    localStorage.setItem('ai_sidebar_width', widthPx);
                }
            };
            const handleMouseUp = () => {
                isResizing = false;
                document.removeEventListener('mousemove', handleMouseMove);
                document.removeEventListener('mouseup', handleMouseUp);
            };
        }
        
        showSidebar() {
            document.body.classList.remove('ai-sidebar-hidden');
            const aiTab = document.getElementById('ca-ai');
            if (aiTab) aiTab.style.display = 'none';
            const skin = mw.config.get('skin');
            if (skin === 'vector' && !skin.includes('vector-2022')) {
                const head = document.querySelector('#mw-head');
                if (head) {
                    head.style.width = `calc(100% - ${this.sidebarWidth})`;
                    head.style.right = this.sidebarWidth;
                }
            }
            document.body.style.marginRight = this.sidebarWidth;
            this.isVisible = true;
            localStorage.setItem('ai_sidebar_visible', 'true');
        }
        
        hideSidebar() {
            document.body.classList.add('ai-sidebar-hidden');
            const aiTab = document.getElementById('ca-ai');
            if (aiTab) aiTab.style.display = 'list-item';
            document.body.style.marginRight = '0';
            const skin = mw.config.get('skin');
            if (skin === 'vector' && !skin.includes('vector-2022')) {
                const head = document.querySelector('#mw-head');
                if (head) {
                    head.style.width = '100%';
                    head.style.right = '0';
                }
            }
            this.isVisible = false;
            localStorage.setItem('ai_sidebar_visible', 'false');
        }
        
        adjustMainContent() {
            document.body.style.marginRight = this.isVisible ? this.sidebarWidth : '0';
        }
        
        attachEventListeners() {
            this.buttons.close.on('click', () => this.hideSidebar());
            this.buttons.setKey.on('click', () => this.promptApiKey());
            this.buttons.changeKey.on('click', () => this.promptApiKey());
            this.buttons.proofread.on('click', () => this.proofreadArticle());
            this.buttons.removeKey.on('click', () => this.promptRemoveKey());
        }
        
        promptApiKey() {
            const dialog = new OO.ui.MessageDialog();
            const textInput = new OO.ui.TextInputWidget({
                placeholder: 'Gemini-API-Schlüssel eingeben …',
                type: 'password',
                value: this.getApiKey() || ''
            });
            const windowManager = new OO.ui.WindowManager();
            $('body').append(windowManager.$element);
            windowManager.addWindows([dialog]);
            windowManager.openWindow(dialog, {
                title: 'Gemini-API-Schlüssel festlegen',
                message: $('<div>').append(
                    $('<p>').html('<a href="https://aistudio.google.com/app/apikey" target="_blank">Kostenlosen Gemini-API-Schlüssel</a> eingeben, um die Prüfung zu aktivieren:'),
                    textInput.$element
                ),
                actions: [
                    { action: 'save', label: 'Speichern', flags: ['primary', 'progressive'] },
                    { action: 'cancel', label: 'Abbrechen', flags: ['safe'] }
                ]
            }).closed.then((data) => {
                if (data && data.action === 'save') {
                    const key = textInput.getValue().trim();
                    if (key) {
                        this.setApiKey(key);
                        this.updateButtonVisibility();
                        this.updateStatus('API-Schlüssel erfolgreich gespeichert!');
                    } else {
                        OO.ui.alert('Bitte einen gültigen API-Schlüssel eingeben.').then(() => this.promptApiKey());
                    }
                }
                windowManager.destroy();
            });
            setTimeout(() => textInput.focus(), 300);
        }
        
        promptRemoveKey() {
            OO.ui.confirm('Soll der gespeicherte API-Schlüssel wirklich entfernt werden?').done((confirmed) => {
                if (confirmed) {
                    this.removeApiKey();
                    this.updateButtonVisibility();
                    this.updateStatus('API-Schlüssel erfolgreich entfernt!');
                    this.updateOutput('');
                }
            });
        }
        
        updateStatus(message, isError = false) {
            const statusEl = document.getElementById('ai-status');
            statusEl.textContent = message;
            statusEl.className = isError ? 'ai-error' : '';
        }
        
        updateOutput(content, isMarkdown = false) {
            const outputEl = document.getElementById('ai-output');
            let processedContent = content;
            if (isMarkdown) {
                processedContent = this.markdownToHtml(content);
                outputEl.innerHTML = processedContent;
            } else {
                outputEl.textContent = content;
            }
            if (content) {
                this.currentResults = processedContent;
                localStorage.setItem('ai_current_results', this.currentResults);
            } else {
                this.currentResults = '';
                localStorage.removeItem('ai_current_results');
            }
        }
        
        markdownToHtml(markdown) {
            let html = markdown;
            html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
            html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
            html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
            html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
            html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
            html = html.replace(/_(.*?)_/g, '<em>$1</em>');
            html = html.replace(/^\s*[\*\-] (.*$)/gim, '<li>$1</li>');
            html = html.replace(/^\s*\d+\. (.*$)/gim, '<li>$1</li>');
            html = html.replace(/((<li>.*<\/li>\s*)+)/g, (match, p1) => `<ul>${p1.replace(/\s*<li>/g, '<li>')}</ul>`);
            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('');
            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;
        }
        
        async proofreadArticle() {
            if (!this.getApiKey()) {
                this.updateStatus('Bitte zuerst einen API-Schlüssel eingeben!', true);
                return;
            }
            try {
                this.updateStatus('Artikelinhalt wird abgerufen …');
                this.buttons.proofread.setDisabled(true);
                
                const articleTitle = this.getArticleTitle();
                if (!articleTitle) throw new Error('Der Artikeltitel konnte nicht ermittelt werden.');
                
                const wikicode = await this.fetchWikicode(articleTitle);
                if (!wikicode) throw new Error('Der Wikicode konnte nicht abgerufen werden.');
                
                if (wikicode.length > 100000) {
                    const confirmed = await new Promise(resolve => {
                        OO.ui.confirm(`Dieser Artikel ist sehr lang (${wikicode.length} Zeichen). Trotzdem fortfahren?`).done(resolve);
                    });
                    if (!confirmed) {
                        this.updateStatus('Vorgang abgebrochen.');
                        return;
                    }
                }
                
                this.updateStatus('Wird mit Gemini verarbeitet … Bitte warten …');
                let result = await this.callGeminiAPI(wikicode);
                result = `${articleTitle}:\n${result}`;
                this.updateStatus('Prüfung abgeschlossen!');
                this.updateOutput(result, true);
                
            } catch (error) {
                console.error('Fehler bei der Prüfung:', error);
                this.updateStatus(`Fehler: ${error.message}`, true);
                this.updateOutput('');
            } finally {
                this.buttons.proofread.setDisabled(false);
            }
        }
        
        getArticleTitle() {
            if (mw && mw.config && mw.config.get('wgPageName')) {
                return mw.config.get('wgPageName').replace(/_/g, ' ');
            }
            const url = window.location.href;
            let match = url.match(/\/wiki\/(.+?)(?:#|\?|$)/);
            if (match) return decodeURIComponent(match[1]).replace(/_/g, ' ');
            match = url.match(/[?&]title=([^&]+)/);
            if (match) return decodeURIComponent(match[1]).replace(/_/g, ' ');
            return null;
        }
        
        async fetchWikicode(articleTitle) {
            if (typeof mw !== 'undefined' && mw.Api) {
                const api = new mw.Api();
                try {
                    const data = await api.get({
                        action: 'query',
                        titles: articleTitle,
                        prop: 'revisions',
                        rvprop: 'content',
                        rvslots: 'main',
                        format: 'json',
                        formatversion: 2
                    });
                    const page = data.query.pages[0];
                    if (page.missing) throw new Error(`Artikel „${articleTitle}" nicht gefunden.`);
                    const content = page.revisions[0].slots.main.content;
                    if (typeof content === 'string' && content.length >= 10) return content;
                } catch (error) {
                    console.error('Fehler mit mw.Api, Fallback auf fetch:', error);
                }
            }
            const language = window.location.hostname.split('.')[0] || 'de';
            const apiUrl = `https://${language}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(articleTitle)}&prop=revisions&rvprop=content&format=json&formatversion=2&origin=*`;
            const response = await fetch(apiUrl);
            if (!response.ok) throw new Error(`Wikipedia-API-Anfrage fehlgeschlagen (${response.status}).`);
            const data = await response.json();
            const page = data.query.pages[0];
            if (page.missing) throw new Error('Artikel nicht gefunden.');
            const content = page.revisions[0].content;
            if (!content || content.length < 50) throw new Error('Inhalt zu kurz.');
            return content;
        }
        
        async callGeminiAPI(wikicode) {
            const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.getApiKey()}`;
            const systemPrompt = `Du bist ein professioneller Wikipedia-Korrekturleser. Deine Aufgabe ist es, deutschsprachige Wikipedia-Artikel im Wikicode-Format auf folgende Probleme zu untersuchen:

1.  **Rechtschreibung und Tippfehler**: Suche nach falsch geschriebenen Wörtern, insbesondere bei Eigennamen, Fachbegriffen und gebräuchlichen Wörtern.
2.  **Grammatik und Stil**: Identifiziere grammatikalische Fehler, ungeschickte Formulierungen, Schachtelsätze und Verstöße gegen die Richtlinien der deutschsprachigen Wikipedia (WP:WSIGA, WP:NPOV). Achte besonders auf korrekte Kommasetzung, Groß- und Kleinschreibung sowie einheitliche Zeitformen.
3.  **Sachliche Unstimmigkeiten oder Unplausibilitäten**: Weise auf widersprüchliche Angaben innerhalb des Artikels hin. Heute ist der ${new Date().toLocaleDateString('de-DE', { day: 'numeric', month: 'long', year: 'numeric' })}. Hebe Aussagen hervor, die hochgradig unplausibel oder ohne Kontext veraltet wirken.
4.  **Klarheit und Prägnanz**: Schlage Verbesserungen für unnötig ausschweifende oder unklare Formulierungen vor.
5.  **Wikicode-Probleme (nur offensichtliche)**: Weise kurz auf schwerwiegende Wikicode-Fehler hin, etwa nicht geschlossene Vorlagen oder fehlerhafte Verlinkungen.

**Wichtige Hinweise:**
*   Konzentriere dich auf den gerenderten Inhalt, nicht auf die Syntax selbst.
*   Melde keine Datumsangaben als fehlerhaft, sofern es sich nicht um eindeutige Fehler handelt.
*   Zitiere die problematische Textstelle.
*   Schlage Korrekturen vor, wo angemessen.
*   Gliedere deine Ergebnisse in klar benannte Kategorien.
*   Verwende Markdown für deine Antwort.
*   Sei gründlich, aber prägnant.
*   Beginne direkt mit den Befunden – ohne einleitende oder abschließende Bemerkungen.`;

            const requestBody = {
                contents: [{ parts: [{ text: wikicode }] }],
                systemInstruction: { parts: [{ text: systemPrompt }] },
                generationConfig: { maxOutputTokens: 65536, 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-Anfrage fehlgeschlagen (${response.status}): ${errorDetail}`);
            }
            
            const candidate = responseData.candidates?.[0];
            if (!candidate?.content?.parts?.[0]?.text) {
                const reason = candidate?.finishReason || 'unbekannt';
                throw new Error(`Kein Inhalt generiert. Abschlussgrund: ${reason}`);
            }
            
            return candidate.content.parts[0].text;
        }
    }
    
    mw.loader.using(['mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']).then(function() {
        $(function() {
            new WikipediaAIProofreader();
        });
    });
})();
// </nowiki>