User:Polygnotus/Scripts/Claude6.js: Difference between revisions

Content deleted Content added
No edit summary
v5 but with OOUI buttons
Line 7:
this.isVisible = localStorage.getItem('claude_sidebar_visible') !== 'false';
this.currentResults = localStorage.getItem('claude_current_results') || '';
this.buttons = {};
this.init();
}
init() {
this.createUIloadOOUI();.then(() => {
this.attachEventListenerscreateUI();
this.adjustMainContentattachEventListeners();
this.adjustMainContent();
});
}
async loadOOUI() {
// Ensure OOUI is loaded
await mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']);
}
createUI() {
// Create sidebar container
const sidebar = document.createElement('div');
sidebar.id = 'claude-proofreader-sidebar';
// Create OOUI buttons
this.createOOUIButtons();
sidebar.innerHTML = `
<div id="claude-sidebar-header">
<h3>Claude Proofreader</h3>
<div id="claude-sidebar-controls">
<buttondiv id="claude-close-btn" title="Close-container">×</buttondiv>
</div>
</div>
<div id="claude-sidebar-content">
<div id="claude-controls">
<buttondiv id="claude-setbuttons-key-btncontainer" ${this.apiKey ? 'style="display:none"' : ''}>Set API Key</buttondiv>
<button id="claude-proofread-btn" ${!this.apiKey ? 'style="display:none"' : ''}>Proofread Article</button>
<button id="claude-change-key-btn" ${!this.apiKey ? 'style="display:none"' : ''}>Change Key</button>
<button id="claude-remove-key-btn" ${!this.apiKey ? 'style="display:none"' : ''}>Remove API Key</button>
</div>
<div id="claude-results">
Line 39 ⟶ 49:
<div id="claude-resize-handle"></div>
`;
// Create Claude tab for when sidebar is closed
this.createClaudeTab();
// Add CSS styles
const style = document.createElement('style');
Line 76 ⟶ 88:
display: flex;
gap: 8px;
}
#claude-sidebar-controls button {
background: rgba(255,255,255,0.2);
border: none;
color: white;
font-size: 16px;
cursor: pointer;
padding: 4px 8px;
border-radius: 3px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
#claude-sidebar-controls button:hover {
background: rgba(255,255,255,0.3);
}
#claude-sidebar-content {
Line 105 ⟶ 100:
flex-shrink: 0;
}
#claude-controls buttonbuttons-container {
backgrounddisplay: #0645adflex;
colorflex-direction: whitecolumn;
bordergap: none8px;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
margin-right: 8px;
margin-bottom: 8px;
font-size: 12px;
}
#claude-controlsbuttons-container button:hover.oo-ui-buttonElement {
backgroundwidth: #0a5bb3100%;
}
#claude-controlsbuttons-container .oo-ui-buttonElement-button:disabled {
backgroundwidth: #ccc100%;
cursorjustify-content: not-allowedcenter;
}
#claude-remove-key-btn {
background: #d33 !important;
}
#claude-remove-key-btn:hover {
background: #b52d2d !important;
}
#claude-results {
Line 196 ⟶ 179:
}
body {
margin-right: ${this.isVisible ? this.sidebarWidth : '0'} !important;
transition: margin-right 0.3s ease;
}
/* Target Wikipedia's main content containers */
#mw-page-base,
#mw-head-base,
#mw-head,
#content,
#mw-panel,
.mw-body,
#footer {
margin-right: ${this.isVisible ? this.sidebarWidth : '0'} !important;
transition: margin-right 0.3s ease;
}
/* For Vector 2022 skin */
.vector-header-container,
.mw-page-container,
.vector-main-menu-container {
margin-right: ${this.isVisible ? this.sidebarWidth : '0'} !important;
transition: margin-right 0.3s ease;
}
Line 224 ⟶ 189:
border-radius: 4px;
}
.claude-sidebar-hidden body, {
.claude-sidebar-hidden #mw-page-base,
.claude-sidebar-hidden #mw-head-base,
.claude-sidebar-hidden #mw-head,
.claude-sidebar-hidden #content,
.claude-sidebar-hidden #mw-panel,
.claude-sidebar-hidden .mw-body,
.claude-sidebar-hidden #footer,
.claude-sidebar-hidden .vector-header-container,
.claude-sidebar-hidden .mw-page-container,
.claude-sidebar-hidden .vector-main-menu-container {
margin-right: 0 !important;
}
Line 245 ⟶ 200:
`;
document.head.appendChild(style);
document.body.appendChildappend(sidebar);
// Append OOUI buttons to their containers
this.appendOOUIButtons();
// Set initial state
Line 251 ⟶ 209:
this.hideSidebar();
}
// Make sidebar resizable
this.makeResizable();
}
createOOUIButtons() {
// Close button (icon button)
this.buttons.close = new OO.ui.ButtonWidget({
icon: 'close',
title: 'Close',
framed: false,
classes: ['claude-close-button']
});
// Set API Key button
this.buttons.setKey = new OO.ui.ButtonWidget({
label: 'Set API Key',
flags: ['primary', 'progressive'],
disabled: false
});
// Proofread button
this.buttons.proofread = new OO.ui.ButtonWidget({
label: 'Proofread Article',
flags: ['primary', 'progressive'],
icon: 'check',
disabled: !this.apiKey
});
// Change key button
this.buttons.changeKey = new OO.ui.ButtonWidget({
label: 'Change Key',
flags: ['safe'],
icon: 'edit',
disabled: false
});
// Remove key button
this.buttons.removeKey = new OO.ui.ButtonWidget({
label: 'Remove API Key',
flags: ['destructive'],
icon: 'trash',
disabled: false
});
// Set initial visibility
this.updateButtonVisibility();
}
appendOOUIButtons() {
// Append close button
document.getElementById('claude-close-btn-container').appendChild(this.buttons.close.$element[0]);
// Append main buttons
const container = document.getElementById('claude-buttons-container');
if (this.apiKey) {
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('claude-buttons-container');
if (!container) return;
// Clear container
container.innerHTML = '';
// Add appropriate buttons based on API key state
if (this.apiKey) {
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]);
}
}
createClaudeTab() {
// Only create tab if we're in the main article namespace
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 claudeLink = mw.util.addPortletLink(
portletId,
'#',
Line 275 ⟶ 311:
}
}
makeResizable() {
const handle = document.getElementById('claude-resize-handle');
Line 288 ⟶ 325:
e.preventDefault();
});
const handleMouseMove = (e) => {
if (!isResizing) return;
Line 298 ⟶ 336:
const widthPx = newWidth + 'px';
sidebar.style.width = widthPx;
thisdocument.applyMarginToContainers(body.style.marginRight = widthPx);
if (mw.config.get('skin') === 'vector') {
const head = document.querySelector('#mw-head');
head.style.width = `calc(100% - ${widthPx})`;
head.style.right = widthPx;
}
this.sidebarWidth = widthPx;
localStorage.setItem('claude_sidebar_width', widthPx);
}
};
const handleMouseUp = () => {
isResizing = false;
Line 309 ⟶ 353:
};
}
showSidebar() {
const claudeTab = document.getElementById('ca-claude');
Line 314 ⟶ 359:
document.body.classList.remove('claude-sidebar-hidden');
if (claudeTab) claudeTab.style.display = 'none';
 
if (mw.config.get('skin') === 'vector') {
const head = document.querySelector('#mw-head');
head.style.width = `calc(100% - ${this.sidebarWidth})`;
head.style.right = this.sidebarWidth;
}
// Apply margin to document.body.style.marginRight and all main= containersthis.sidebarWidth;
this.applyMarginToContainers(this.sidebarWidth);
this.isVisible = true;
localStorage.setItem('claude_sidebar_visible', 'true');
}
hideSidebar() {
const claudeTab = document.getElementById('ca-claude');
Line 326 ⟶ 377:
document.body.classList.add('claude-sidebar-hidden');
if (claudeTab) claudeTab.style.display = 'list-item';
document.body.style.marginRight = '0';
 
// Remove margin from all containers
thisif (mw.applyMarginToContainersconfig.get('0skin') === 'vector'); {
const head = document.querySelector('#mw-head');
head.style.width = '100%';
head.style.right = '0';
}
this.isVisible = false;
localStorage.setItem('claude_sidebar_visible', 'false');
}
applyMarginToContainers(margin) {
const selectors = [
'body',
'#mw-page-base',
'#mw-head-base',
'#mw-head',
'#content',
'#mw-panel',
'.mw-body',
'#footer',
'.vector-header-container',
'.mw-page-container',
'.vector-main-menu-container'
];
selectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
el.style.marginRight = margin;
});
});
}
adjustMainContent() {
if (this.isVisible) {
thisdocument.body.style.applyMarginToContainers(marginRight = this.sidebarWidth);
} else {
thisdocument.applyMarginToContainers(body.style.marginRight = '0');
}
}
attachEventListeners() {
documentthis.buttons.getElementById('claude-close-btn').addEventListeneron('click', () => {
this.hideSidebar();
});
document.getElementById('claude-set-key-btn').addEventListener('click', () => {
this.buttons.setKey.on('click', () => {
this.setApiKey();
});
document.getElementById('claude-change-key-btn').addEventListener('click', () => {
this.buttons.changeKey.on('click', () => {
this.setApiKey();
});
document.getElementById('claude-proofread-btn').addEventListener('click', () => {
this.buttons.proofread.on('click', () => {
this.proofreadArticle();
});
document.getElementById('claude-remove-key-btn').addEventListener('click', () => {
this.buttons.removeKey.on('click', () => {
this.removeApiKey();
});
}
setApiKey() {
const// keyCreate =OOUI prompt('Enterdialog your Claudefor API Key:');key input
ifconst (keydialog &&= keynew OO.trimui.ProcessDialog()) {;
dialog.title = 'Set Claude this.apiKeyAPI = key.trim()Key';
localStorage.setItem('claude_api_key', this.apiKey);
const textInput = new OO.ui.TextInputWidget({
//placeholder: Update'Enter UIyour Claude API Key...',
document.getElementById('claude-set-key-btn').style.display =type: 'nonepassword';,
document.getElementById('claude-proofread-btn').stylevalue: this.displayapiKey =|| 'inline-block';
});
document.getElementById('claude-change-key-btn').style.display = 'inline-block';
document.getElementById('claude-remove-key-btn').style.display = 'inline-block';
dialog.initialize = function() {
thisOO.updateStatusui.ProcessDialog.prototype.initialize.call('API key set successfully!'this);
} this.content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.content.$element.append(
$('<p>').text('Enter your Claude API Key to enable proofreading:'),
textInput.$element
);
this.$body.append(this.content.$element);
};
dialog.getActionProcess = (action) => {
if (action === 'save') {
return new OO.ui.Process(() => {
const key = textInput.getValue().trim();
if (key) {
this.apiKey = key;
localStorage.setItem('claude_api_key', this.apiKey);
this.updateButtonVisibility();
this.updateStatus('API key set successfully!');
windowManager.closeWindow(dialog);
} else {
textInput.setValidityFlag(false);
return OO.ui.Process.static.createRejectProcess('Please enter a valid API key');
}
});
}
return OO.ui.ProcessDialog.prototype.getActionProcess.call(this, action);
};
dialog.getSetupProcess = function(data) {
return OO.ui.ProcessDialog.prototype.getSetupProcess.call(this, data)
.next(() => {
this.actions.setMode('save');
});
};
const windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
windowManager.addWindows([dialog]);
dialog.getActions().add([
{
action: 'save',
label: 'Save',
flags: ['primary', 'progressive']
},
{
label: 'Cancel',
flags: ['safe']
}
]);
windowManager.openWindow(dialog);
}
removeApiKey() {
// Create OOUI confirmation dialog
if (confirm('Are you sure you want to remove the stored API key?')) {
OO.ui.confirm('Are you sure you want to remove the stored API key?').done((confirmed) => {
this.apiKey = null;
localStorage.removeItemif ('claude_api_key'confirmed); {
this.apiKey = null;
// Update UI localStorage.removeItem('claude_api_key');
this.updateButtonVisibility();
document.getElementById('claude-set-key-btn').style.display = 'inline-block';
document this.getElementByIdupdateStatus('claude-proofread-btn').style.displayAPI =key removed successfully!'none');
document this.getElementByIdupdateOutput('claude-change-key-btn').style.display = 'none';
}
document.getElementById('claude-remove-key-btn').style.display = 'none';
});
this.updateStatus('API key removed successfully!');
this.updateOutput('');
}
}
updateStatus(message, isError = false) {
const statusEl = document.getElementById('claude-status');
Line 414 ⟶ 506:
statusEl.className = isError ? 'claude-error' : '';
}
updateOutput(content, isMarkdown = false) {
const outputEl = document.getElementById('claude-output');
Line 423 ⟶ 516:
outputEl.textContent = content;
}
// Store results
if (content) {
Line 429 ⟶ 523:
}
}
markdownToHtml(markdown) {
return markdown
Line 454 ⟶ 549:
.replace(/(<\/[hul]>)<\/p>/g, '$1');
}
async proofreadArticle() {
if (!this.apiKey) {
Line 459 ⟶ 555:
return;
}
try {
this.updateStatus('Fetching article content...', false);
const proofreadBtn = documentthis.buttons.getElementById('claude-proofread-btn'.setDisabled(true);
proofreadBtn.disabled = true;
// Get current article title
const articleTitle = this.getArticleTitle();
Line 468 ⟶ 565:
throw new Error('Could not extract article title from current page');
}
// Fetch wikicode
const wikicode = await this.fetchWikicode(articleTitle);
Line 473 ⟶ 571:
throw new Error('Could not fetch article wikicode');
}
// Check length and warn user
if (wikicode.length > 100000) {
ifconst confirmed = await new Promise(!resolve => {
OO.ui.confirm(`This article is quite long (${wikicode.length} characters). Processing may take a while and use significant API credits. Continue?`)) {
.done(resolve);
});
if (!confirmed) {
this.updateStatus('Operation cancelled by user.');
proofreadBtnthis.disabled = buttons.proofread.setDisabled(false);
return;
}
}
this.updateStatus('Processing with Claude... Please wait...');
// Call Claude API
const result = await this.callClaudeAPI(wikicode);
Line 487 ⟶ 593:
this.updateStatus('Proofreading complete!');
this.updateOutput(result, true);
} catch (error) {
console.error('Proofreading error:', error);
Line 492 ⟶ 599:
this.updateOutput('');
} finally {
documentthis.buttons.getElementById('claude-proofread-btn').disabled = setDisabled(false);
}
}
getArticleTitle() {
// Extract title from URL
Line 511 ⟶ 619:
return null;
}
async fetchWikicode(articleTitle) {
// Get language from current URL
Line 518 ⟶ 627:
`action=query&titles=${encodeURIComponent(articleTitle)}&` +
`prop=revisions&rvprop=content&format=json&formatversion=2&origin=*`;
try {
const response = await fetch(apiUrl);
Line 523 ⟶ 633:
throw new Error(`Wikipedia API request failed: ${response.status}`);
}
const data = await response.json();
Line 528 ⟶ 639:
throw new Error('No pages found in API response');
}
const page = data.query.pages[0];
if (page.missing) {
throw new Error('Wikipedia page not found');
}
if (!page.revisions || page.revisions.length === 0) {
throw new Error('No revisions found');
}
const content = page.revisions[0].content;
if (!content || content.length < 50) {
throw new Error('Retrieved content is too short');
}
return content;
} catch (error) {
console.error('Error fetching wikicode:', error);
Line 545 ⟶ 661:
}
}
async callClaudeAPI(wikicode) {
const requestBody = {
Line 554 ⟶ 671:
}]
};
try {
const response = await fetch('https://api.anthropic.com/v1/messages', {
Line 565 ⟶ 683:
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();
Line 574 ⟶ 694:
throw new Error('Invalid API response format');
}
return data.content[0].text;
} catch (error) {
console.error('Claude API error:', error);
Line 581 ⟶ 703:
}
}
mw.loader.using( ['mediawiki.util'] ).then( function () {
mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']).then(function() {
// Initialize the proofreader when page loads
if (document.readyState === 'loading') {
Line 590 ⟶ 713:
new WikipediaClaudeProofreader();
}
} );
})();