User:Polygnotus/Scripts/tmp.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/tmp.
// <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>