User:Polygnotus/Scripts/Claude3.js
Appearance
From Wikipedia, the free encyclopedia
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/Claude3.
(function(){ 'use strict'; classWikipediaClaudeProofreader{ constructor(){ this.apiKey=localStorage.getItem('claude_api_key'); this.sidebarWidth=localStorage.getItem('claude_sidebar_width')||'350px'; this.isVisible=localStorage.getItem('claude_sidebar_visible')!=='false'; this.currentResults=localStorage.getItem('claude_current_results')||''; this.init(); } init(){ this.createUI(); this.attachEventListeners(); this.adjustMainContent(); } createUI(){ // Create sidebar container constsidebar=document.createElement('div'); sidebar.id='claude-proofreader-sidebar'; sidebar.innerHTML=` <div id="claude-sidebar-header"> <h3>Claude Proofreader</h3> <div id="claude-sidebar-controls"> <button id="claude-close-btn" title="Close"&g×ばつ</button> </div> </div> <div id="claude-sidebar-content"> <div id="claude-controls"> <button id="claude-set-key-btn" ${this.apiKey?'style="display:none"':''}>Set API Key</button> <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-clear-btn" ${!this.currentResults?'style="display:none"':''}>Clear Results</button> </div> <div id="claude-results"> <div id="claude-status">Ready to proofread</div> <div id="claude-output">${this.currentResults}</div> </div> </div> <div id="claude-resize-handle"></div> `; // Create Claude tab for when sidebar is closed this.createClaudeTab(); // Add CSS styles conststyle=document.createElement('style'); style.textContent=` #claude-proofreader-sidebar { position: fixed; top: 0; right: 0; width: ${this.sidebarWidth}; height: 100vh; background: #fff; border-left: 2px solid #0645ad; 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; } #claude-sidebar-header { background: #0645ad; color: white; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } #claude-sidebar-header h3 { margin: 0; font-size: 16px; } #claude-sidebar-controls { 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 { padding: 15px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; } #claude-controls { margin-bottom: 15px; flex-shrink: 0; } #claude-controls button { background: #0645ad; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; margin-right: 8px; margin-bottom: 8px; font-size: 12px; } #claude-controls button:hover { background: #0a5bb3; } #claude-controls button:disabled { background: #ccc; cursor: not-allowed; } #claude-results { flex: 1; display: flex; flex-direction: column; min-height: 0; } #claude-status { font-weight: bold; margin-bottom: 10px; padding: 8px; background: #f8f9fa; border-radius: 4px; flex-shrink: 0; } #claude-output { line-height: 1.5; flex: 1; overflow-y: auto; border: 1px solid #ddd; padding: 12px; border-radius: 4px; background: #fafafa; font-size: 13px; } #claude-output h1, #claude-output h2, #claude-output h3 { color: #0645ad; margin-top: 16px; margin-bottom: 8px; } #claude-output h1 { font-size: 1.3em; } #claude-output h2 { font-size: 1.2em; } #claude-output h3 { font-size: 1.1em; } #claude-output ul, #claude-output ol { padding-left: 18px; } #claude-output p { margin-bottom: 10px; } #claude-output strong { color: #d33; } #claude-resize-handle { position: absolute; left: 0; top: 0; width: 4px; height: 100%; background: transparent; cursor: ew-resize; z-index: 10001; } #claude-resize-handle:hover { background: #0645ad; opacity: 0.5; } #ca-claude { display: none; } #ca-claude a { color: #0645ad !important; text-decoration: none !important; padding: 0.5em !important; } #ca-claude a:hover { text-decoration: underline !important; } body { margin-right: ${this.isVisible?this.sidebarWidth:'0'}; transition: margin-right 0.3s ease; } .claude-error { color: #d33; background: #fef2f2; border: 1px solid #fecaca; padding: 8px; border-radius: 4px; } .claude-sidebar-hidden body { margin-right: 0 !important; } .claude-sidebar-hidden #claude-proofreader-sidebar { display: none; } .claude-sidebar-hidden #ca-claude { display: list-item !important; } `; document.head.appendChild(style); document.body.appendChild(sidebar); // Set initial state if(!this.isVisible){ this.hideSidebar(); } // Make sidebar resizable this.makeResizable(); } createClaudeTab(){ // Only create tab if we're in the main article namespace if(typeofmw!=='undefined'&&mw.config.get('wgNamespaceNumber')>=0){ // Create the Claude tab constclaudeTab=document.createElement('li'); claudeTab.id='ca-claude'; constclaudeLink=document.createElement('a'); claudeLink.href='#'; claudeLink.title='Proofread with Claude AI'; claudeLink.textContent='Claude'; claudeLink.addEventListener('click',(e)=>{ e.preventDefault(); this.showSidebar(); }); claudeTab.appendChild(claudeLink); // Find the talk tab and add Claude tab after it consttalkTab=document.getElementById('ca-talk')||document.querySelector('.ca-talk'); if(talkTab&&talkTab.parentNode){ talkTab.parentNode.insertBefore(claudeTab,talkTab.nextSibling); } // Fallback: add to any tabs container we can find else{ consttabsContainer=document.querySelector('#p-namespaces ul')|| document.querySelector('.vector-menu-tabs ul')|| document.querySelector('#p-views ul'); if(tabsContainer){ tabsContainer.appendChild(claudeTab); } } } } makeResizable(){ consthandle=document.getElementById('claude-resize-handle'); constsidebar=document.getElementById('claude-proofreader-sidebar'); if(!handle||!sidebar)return; letisResizing=false; handle.addEventListener('mousedown',(e)=>{ isResizing=true; document.addEventListener('mousemove',handleMouseMove); document.addEventListener('mouseup',handleMouseUp); e.preventDefault(); }); consthandleMouseMove=(e)=>{ if(!isResizing)return; constnewWidth=window.innerWidth-e.clientX; constminWidth=250; constmaxWidth=window.innerWidth*0.7; if(newWidth>=minWidth&&newWidth<=maxWidth){ constwidthPx=newWidth+'px'; sidebar.style.width=widthPx; document.body.style.marginRight=widthPx; this.sidebarWidth=widthPx; localStorage.setItem('claude_sidebar_width',widthPx); } }; consthandleMouseUp=()=>{ isResizing=false; document.removeEventListener('mousemove',handleMouseMove); document.removeEventListener('mouseup',handleMouseUp); }; } showSidebar(){ constclaudeTab=document.getElementById('ca-claude'); document.body.classList.remove('claude-sidebar-hidden'); if(claudeTab)claudeTab.style.display='none'; document.body.style.marginRight=this.sidebarWidth; this.isVisible=true; localStorage.setItem('claude_sidebar_visible','true'); } hideSidebar(){ constclaudeTab=document.getElementById('ca-claude'); document.body.classList.add('claude-sidebar-hidden'); if(claudeTab)claudeTab.style.display='list-item'; document.body.style.marginRight='0'; this.isVisible=false; localStorage.setItem('claude_sidebar_visible','false'); } adjustMainContent(){ if(this.isVisible){ document.body.style.marginRight=this.sidebarWidth; }else{ document.body.style.marginRight='0'; } } attachEventListeners(){ document.getElementById('claude-close-btn').addEventListener('click',()=>{ this.hideSidebar(); }); document.getElementById('claude-set-key-btn').addEventListener('click',()=>{ this.setApiKey(); }); document.getElementById('claude-change-key-btn').addEventListener('click',()=>{ this.setApiKey(); }); document.getElementById('claude-proofread-btn').addEventListener('click',()=>{ this.proofreadArticle(); }); document.getElementById('claude-clear-btn').addEventListener('click',()=>{ this.clearResults(); }); } setApiKey(){ constkey=prompt('Enter your Claude API Key:'); if(key&&key.trim()){ this.apiKey=key.trim(); localStorage.setItem('claude_api_key',this.apiKey); // Update UI document.getElementById('claude-set-key-btn').style.display='none'; document.getElementById('claude-proofread-btn').style.display='inline-block'; document.getElementById('claude-change-key-btn').style.display='inline-block'; this.updateStatus('API key set successfully!'); } } clearResults(){ this.currentResults=''; localStorage.removeItem('claude_current_results'); this.updateOutput(''); document.getElementById('claude-clear-btn').style.display='none'; this.updateStatus('Results cleared. Ready to proofread.'); } updateStatus(message,isError=false){ conststatusEl=document.getElementById('claude-status'); statusEl.textContent=message; statusEl.className=isError?'claude-error':''; } updateOutput(content,isMarkdown=false){ constoutputEl=document.getElementById('claude-output'); if(isMarkdown){ content=this.markdownToHtml(content); outputEl.innerHTML=content; }else{ outputEl.textContent=content; } // Store results and show clear button if(content){ this.currentResults=content; localStorage.setItem('claude_current_results',content); document.getElementById('claude-clear-btn').style.display='inline-block'; } } markdownToHtml(markdown){ returnmarkdown // Headers .replace(/^### (.*$)/gim,'<h3>1ドル</h3>') .replace(/^## (.*$)/gim,'<h2>1ドル</h2>') .replace(/^# (.*$)/gim,'<h1>1ドル</h1>') // Bold .replace(/\*\*(.*?)\*\*/g,'<strong>1ドル</strong>') // Italic .replace(/\*(.*?)\*/g,'<em>1ドル</em>') // Lists .replace(/^\* (.*$)/gim,'<li>1ドル</li>') .replace(/(<li>.*<\/li>)/s,'<ul>1ドル</ul>') .replace(/^\d+\. (.*$)/gim,'<li>1ドル</li>') // Line breaks .replace(/\n\n/g,'</p><p>') .replace(/\n/g,'<br>') // Wrap in paragraphs .replace(/^(?!<[hul])/gm,'<p>') .replace(/(?<!>)$/gm,'</p>') // Clean up .replace(/<p><\/p>/g,'') .replace(/<p>(<[hul])/g,'1ドル') .replace(/(<\/[hul]>)<\/p>/g,'1ドル'); } asyncproofreadArticle(){ if(!this.apiKey){ this.updateStatus('Please set your API key first!',true); return; } try{ this.updateStatus('Fetching article content...',false); constproofreadBtn=document.getElementById('claude-proofread-btn'); proofreadBtn.disabled=true; // Get current article title constarticleTitle=this.getArticleTitle(); if(!articleTitle){ thrownewError('Could not extract article title from current page'); } // Fetch wikicode constwikicode=awaitthis.fetchWikicode(articleTitle); if(!wikicode){ thrownewError('Could not fetch article wikicode'); } // Check length and warn user if(wikicode.length>100000){ if(!confirm(`This article is quite long (${wikicode.length} characters). Processing may take a while and use significant API credits. Continue?`)){ this.updateStatus('Operation cancelled by user.'); proofreadBtn.disabled=false; return; } } this.updateStatus('Processing with Claude... Please wait...'); // Call Claude API constresult=awaitthis.callClaudeAPI(wikicode); this.updateStatus('Proofreading complete!'); this.updateOutput(result,true); }catch(error){ console.error('Proofreading error:',error); this.updateStatus(`Error: ${error.message}`,true); this.updateOutput(''); }finally{ document.getElementById('claude-proofread-btn').disabled=false; } } getArticleTitle(){ // Extract title from URL consturl=window.location.href; letmatch=url.match(/\/wiki\/(.+)$/); if(match){ returndecodeURIComponent(match[1]); } // Check if we're on an edit page match=url.match(/[?&]title=([^&]+)/); if(match){ returndecodeURIComponent(match[1]); } returnnull; } asyncfetchWikicode(articleTitle){ // Get language from current URL constlanguage=window.location.hostname.split('.')[0]||'en'; constapiUrl=`https://${language}.wikipedia.org/w/api.php?`+ `action=query&titles=${encodeURIComponent(articleTitle)}&`+ `prop=revisions&rvprop=content&format=json&formatversion=2&origin=*`; try{ constresponse=awaitfetch(apiUrl); if(!response.ok){ thrownewError(`Wikipedia API request failed: ${response.status}`); } constdata=awaitresponse.json(); if(!data.query||!data.query.pages||data.query.pages.length===0){ thrownewError('No pages found in API response'); } constpage=data.query.pages[0]; if(page.missing){ thrownewError('Wikipedia page not found'); } if(!page.revisions||page.revisions.length===0){ thrownewError('No revisions found'); } constcontent=page.revisions[0].content; if(!content||content.length<50){ thrownewError('Retrieved content is too short'); } returncontent; }catch(error){ console.error('Error fetching wikicode:',error); throwerror; } } asynccallClaudeAPI(wikicode){ constrequestBody={ model:"claude-sonnet-4-20250514", max_tokens:4000, messages:[{ role:"user", content:`It is currently June 2025. Please proofread this Wikipedia article and identify any typos, grammatical errors and factual inconsistencies. Ignore formatting issues and date inconsistencies. Focus on the actual article content. Format your response as a detailed analysis with specific issues found:\n\n${wikicode}` }] }; try{ constresponse=awaitfetch('https://api.anthropic.com/v1/messages',{ method:'POST', headers:{ 'Content-Type':'application/json', 'x-api-key':this.apiKey, 'anthropic-version':'2023年06月01日', 'anthropic-dangerous-direct-browser-access':'true' }, body:JSON.stringify(requestBody) }); if(!response.ok){ consterrorText=awaitresponse.text(); thrownewError(`API request failed (${response.status}): ${errorText}`); } constdata=awaitresponse.json(); if(!data.content||!data.content[0]||!data.content[0].text){ thrownewError('Invalid API response format'); } returndata.content[0].text; }catch(error){ console.error('Claude API error:',error); throwerror; } } } // Initialize the proofreader when page loads if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded',()=>{ newWikipediaClaudeProofreader(); }); }else{ newWikipediaClaudeProofreader(); } })();