User:Polygnotus/Scripts/AI Editsummary.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.
This user script seems to have a documentation page at User:Polygnotus/Scripts/AI Editsummary.
// <nowiki>
(function() {
'use strict';
console.log('EditSummaryGenerator: Script loaded');
// Only run on edit pages
if (mw.config.get('wgAction') !== 'edit') {
console.log('EditSummaryGenerator: Not on edit page, exiting');
return;
}
console.log('EditSummaryGenerator: On edit page, continuing');
class EditSummaryGenerator {
constructor() {
console.log('EditSummaryGenerator: Constructor called');
// Provider configurations
this.providers = {
claude: {
name: 'Claude',
storageKey: 'claude_api_key',
model: 'claude-sonnet-4-20250514'
},
gemini: {
name: 'Gemini',
storageKey: 'gemini_api_key',
model: 'gemini-2.5-flash-preview-05-20'
},
openai: {
name: 'ChatGPT',
storageKey: 'openai_api_key',
model: 'gpt-4o'
}
};
this.currentProvider = localStorage.getItem('ai_editsummary_provider') || 'claude';
this.button = null;
console.log('EditSummaryGenerator: Current provider:', this.currentProvider);
console.log('EditSummaryGenerator: API key exists:', !!this.getCurrentApiKey());
this.init();
}
getCurrentApiKey() {
return localStorage.getItem(this.providers[this.currentProvider].storageKey);
}
init() {
console.log('EditSummaryGenerator: Init called');
console.log('EditSummaryGenerator: jQuery available:', typeof $ !== 'undefined');
console.log('EditSummaryGenerator: mw.loader available:', typeof mw !== 'undefined' && typeof mw.loader !== 'undefined');
mw.loader.using(['oojs-ui-core', 'mediawiki.api', 'mediawiki.notification'])
.then(() => {
console.log('EditSummaryGenerator: Modules loaded');
console.log('EditSummaryGenerator: OO.ui available:', typeof OO !== 'undefined' && typeof OO.ui !== 'undefined');
$(function() {
console.log('EditSummaryGenerator: Document ready');
this.createButton();
}.bind(this));
})
.catch((error) => {
console.error('EditSummaryGenerator: Failed to load modules:', error);
});
}
createButton() {
console.log('EditSummaryGenerator: createButton called');
const wpDiffWidget = $('#wpDiffWidget');
console.log('EditSummaryGenerator: wpDiffWidget found:', wpDiffWidget.length > 0);
console.log('EditSummaryGenerator: wpDiffWidget element:', wpDiffWidget[0]);
this.button = new OO.ui.ButtonWidget({
label: 'AI Edit Summary',
framed: true
});
console.log('EditSummaryGenerator: Button created:', this.button);
this.button.on('click', () => this.generateEditSummary());
$('#wpDiffWidget').after(' ', this.button.$element);
console.log('EditSummaryGenerator: Button inserted after wpDiffWidget');
console.log('EditSummaryGenerator: Button element:', this.button.$element[0]);
}
async generateEditSummary() {
const apiKey = this.getCurrentApiKey();
if (!apiKey) {
mw.notify('API key not found for ' + this.providers[this.currentProvider].name + '. Please set it in localStorage.', {type: 'error'});
return;
}
// Change button state to "working"
const originalLabel = this.button.getLabel();
this.button.setLabel('Working...');
this.button.setDisabled(true);
try {
// Get the current article title
const pageTitle = mw.config.get('wgPageName');
// Get the edited text from the textbox
const editedText = $('#wpTextbox1').val();
// Get the diff data
const diffData = await this.getRevisionDiff(pageTitle, editedText);
// Generate edit summary using the current provider
let editSummary;
switch(this.currentProvider) {
case 'claude':
editSummary = await this.callClaudeAPI(diffData);
break;
case 'gemini':
editSummary = await this.callGeminiAPI(diffData);
break;
case 'openai':
editSummary = await this.callOpenAIAPI(diffData);
break;
}
// Put the summary in the edit summary box
$('#wpSummary').val(editSummary).trigger('input');
// Change button state to "done"
this.button.setLabel('Done!');
setTimeout(() => {
this.button.setLabel(originalLabel);
this.button.setDisabled(false);
}, 2000);
} catch (error) {
console.error('Error generating edit summary:', error);
mw.notify('Failed to generate edit summary: ' + error.message, {type: 'error'});
this.button.setLabel(originalLabel);
this.button.setDisabled(false);
}
}
async getCurrentRevision(pageTitle, revId) {
// If no revision exists (new page), return empty string
if (!revId || revId === 0) {
return '';
}
const api = new mw.Api();
const response = await api.get({
action: 'query',
prop: 'revisions',
titles: pageTitle,
rvprop: 'content',
rvlimit: 1,
formatversion: 2
});
if (response.query && response.query.pages && response.query.pages[0]) {
const page = response.query.pages[0];
if (page.revisions && page.revisions[0]) {
return page.revisions[0].content || '';
}
}
return '';
}
async getRevisionDiff(pageTitle, editedText) {
const currentRevId = mw.config.get('wgCurRevisionId');
// For new pages, there's no diff to show
if (!currentRevId || currentRevId === 0) {
return {
isNewPage: true,
newText: editedText
};
}
// Get current revision text
const currentText = await this.getCurrentRevision(pageTitle, currentRevId);
// Simple line-based diff
const currentLines = currentText.split('\n');
const editedLines = editedText.split('\n');
const diff = [];
let i = 0, j = 0;
// Simple diff algorithm - compare line by line
while (i < currentLines.length || j < editedLines.length) {
if (i >= currentLines.length) {
// Only new lines left
diff.push(`+${editedLines[j]}`);
j++;
} else if (j >= editedLines.length) {
// Only old lines left
diff.push(`-${currentLines[i]}`);
i++;
} else if (currentLines[i] === editedLines[j]) {
// Lines match, skip them (we only show changes)
i++;
j++;
} else {
// Lines differ - show removal and addition
diff.push(`-${currentLines[i]}`);
diff.push(`+${editedLines[j]}`);
i++;
j++;
}
}
return {
isNewPage: false,
diff: diff.join('\n')
};
}
async callClaudeAPI(diffData) {
let userPrompt;
if (diffData.isNewPage) {
userPrompt = `This is a NEW PAGE being created.\n\nContent:\n${diffData.newText}\n\nGenerate an edit summary.`;
} else {
userPrompt = `Here are the changes being made (diff format, lines starting with - are removed, lines starting with + are added):\n\n${diffData.diff}\n\nGenerate an edit summary describing these changes.`;
}
const requestBody = {
model: this.providers.claude.model,
max_tokens: 200,
system: `You are a Wikipedia edit summary generator. Generate a concise edit summary (max 100 characters) that describes the changes made to the article. Follow Wikipedia's edit summary conventions:
- Be specific and concise
- Use standard abbreviations (e.g., "fmt" for formatting, "typo" for typos, "ce" for copyedit)
- Separate multiple changes with semicolons or commas
- Don't use complete sentences or punctuation at the end
- Examples: "Added section on early life", "Fixed typos; updated references", "Reverted vandalism"
Generate ONLY the edit summary text, nothing else.`,
messages: [{
role: "user",
content: userPrompt
}]
};
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();
if (!data.content || !data.content[0] || !data.content[0].text) {
throw new Error('Invalid API response format');
}
// Trim and limit to 250 characters (Wikipedia's edit summary limit)
let summary = data.content[0].text.trim();
if (summary.length > 250) {
summary = summary.substring(0, 247) + '...';
}
return summary;
}
async callGeminiAPI(diffData) {
const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${this.providers.gemini.model}:generateContent?key=${this.getCurrentApiKey()}`;
const systemPrompt = `You are a Wikipedia edit summary generator. Generate a concise edit summary (max 100 characters) that describes the changes made to the article. Follow Wikipedia's edit summary conventions:
- Be specific and concise
- Use standard abbreviations (e.g., "fmt" for formatting, "typo" for typos, "ce" for copyedit)
- Separate multiple changes with semicolons or commas
- Don't use complete sentences or punctuation at the end
- Examples: "Added section on early life", "Fixed typos; updated references", "Reverted vandalism"
Generate ONLY the edit summary text, nothing else.`;
let userPrompt;
if (diffData.isNewPage) {
userPrompt = `This is a NEW PAGE being created.\n\nContent:\n${diffData.newText}\n\nGenerate an edit summary.`;
} else {
userPrompt = `Here are the changes being made (diff format, lines starting with - are removed, lines starting with + are added):\n\n${diffData.diff}\n\nGenerate an edit summary describing these changes.`;
}
const requestBody = {
contents: [{
parts: [{ "text": userPrompt }],
}],
systemInstruction: {
parts: [{ "text": systemPrompt }]
},
generationConfig: {
maxOutputTokens: 200,
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.');
}
// Trim and limit to 250 characters
let summary = responseData.candidates[0].content.parts[0].text.trim();
if (summary.length > 250) {
summary = summary.substring(0, 247) + '...';
}
return summary;
}
async callOpenAIAPI(diffData) {
let userPrompt;
if (diffData.isNewPage) {
userPrompt = `This is a NEW PAGE being created.\n\nContent:\n${diffData.newText}\n\nGenerate an edit summary.`;
} else {
userPrompt = `Here are the changes being made (diff format, lines starting with - are removed, lines starting with + are added):\n\n${diffData.diff}\n\nGenerate an edit summary describing these changes.`;
}
const requestBody = {
model: this.providers.openai.model,
max_tokens: 200,
messages: [
{
role: "system",
content: `You are a Wikipedia edit summary generator. Generate a concise edit summary (max 100 characters) that describes the changes made to the article. Follow Wikipedia's edit summary conventions:
- Be specific and concise
- Use standard abbreviations (e.g., "fmt" for formatting, "typo" for typos, "ce" for copyedit)
- Separate multiple changes with semicolons or commas
- Don't use complete sentences or punctuation at the end
- Examples: "Added section on early life", "Fixed typos; updated references", "Reverted vandalism"
Generate ONLY the edit summary text, nothing else.`
},
{
role: "user",
content: userPrompt
}
],
temperature: 0.3
};
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');
}
// Trim and limit to 250 characters
let summary = data.choices[0].message.content.trim();
if (summary.length > 250) {
summary = summary.substring(0, 247) + '...';
}
return summary;
}
}
console.log('EditSummaryGenerator: About to instantiate');
new EditSummaryGenerator();
console.log('EditSummaryGenerator: Instantiated');
})();
// </nowiki>