Skip to content

Commit 703ab01

Browse files
committed
Add docs for spell checker
1 parent 8112faa commit 703ab01

2 files changed

Lines changed: 304 additions & 0 deletions

File tree

spellcheck.docs.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This simple spell checker uses the Anthropic Claude API to find and fix spelling errors in textareas on the page. Enter some text, click "Check Spelling," and any detected mistakes can be corrected with one click.
2+
3+
<!-- Generated from commit: afeb07c454165e58a8671861eeefe3520f8d0d3d -->

spellcheck.html

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Spell Checker</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
max-width: 700px;
11+
margin: 0 auto;
12+
padding: 20px;
13+
}
14+
textarea {
15+
width: 100%;
16+
height: 200px;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<h1>Spell Checker</h1>
22+
<textarea placeholder="Enter text to check..." id="spellcheck-text"></textarea>
23+
<script>
24+
// Textarea Spell Checker with Claude API Integration
25+
26+
let ANTHROPIC_API_KEY = null;
27+
28+
class TextareaSpellChecker {
29+
constructor() {
30+
this.init();
31+
}
32+
33+
init() {
34+
// Find all textareas on the page and add spell check functionality
35+
const textareas = document.querySelectorAll('textarea');
36+
textareas.forEach(textarea => this.addSpellCheckToTextarea(textarea));
37+
38+
// Watch for dynamically added textareas
39+
this.observeNewTextareas();
40+
}
41+
42+
addSpellCheckToTextarea(textarea) {
43+
// Skip if already processed
44+
if (textarea.hasAttribute('data-spell-check-added')) {
45+
return;
46+
}
47+
48+
textarea.setAttribute('data-spell-check-added', 'true');
49+
50+
// Create container for spell check UI
51+
const container = document.createElement('div');
52+
container.className = 'spell-check-container';
53+
container.style.marginTop = '8px';
54+
55+
// Create spell check button
56+
const button = document.createElement('button');
57+
button.textContent = 'Check Spelling';
58+
button.style.cssText = `
59+
background: #4CAF50;
60+
color: white;
61+
border: none;
62+
padding: 8px 16px;
63+
border-radius: 4px;
64+
cursor: pointer;
65+
font-size: 14px;
66+
margin-right: 10px;
67+
`;
68+
69+
button.onmouseover = () => button.style.background = '#45a049';
70+
button.onmouseout = () => button.style.background = '#4CAF50';
71+
72+
// Create loading indicator
73+
const loadingSpinner = document.createElement('span');
74+
loadingSpinner.textContent = '⏳ Checking...';
75+
loadingSpinner.style.display = 'none';
76+
loadingSpinner.style.color = '#666';
77+
78+
// Create results container
79+
const resultsContainer = document.createElement('div');
80+
resultsContainer.className = 'spell-check-results';
81+
resultsContainer.style.marginTop = '10px';
82+
83+
container.appendChild(button);
84+
container.appendChild(loadingSpinner);
85+
container.appendChild(resultsContainer);
86+
87+
// Insert after textarea
88+
textarea.parentNode.insertBefore(container, textarea.nextSibling);
89+
90+
// Add click handler
91+
button.addEventListener('click', () => this.checkSpelling(textarea, button, loadingSpinner, resultsContainer));
92+
}
93+
94+
observeNewTextareas() {
95+
const observer = new MutationObserver(mutations => {
96+
mutations.forEach(mutation => {
97+
mutation.addedNodes.forEach(node => {
98+
if (node.nodeType === Node.ELEMENT_NODE) {
99+
// Check if the added node is a textarea
100+
if (node.tagName === 'TEXTAREA') {
101+
this.addSpellCheckToTextarea(node);
102+
}
103+
// Check for textareas within added elements
104+
const textareas = node.querySelectorAll ? node.querySelectorAll('textarea') : [];
105+
textareas.forEach(textarea => this.addSpellCheckToTextarea(textarea));
106+
}
107+
});
108+
});
109+
});
110+
111+
observer.observe(document.body, { childList: true, subtree: true });
112+
}
113+
114+
async getApiKey() {
115+
if (!ANTHROPIC_API_KEY) {
116+
// Try to read from localStorage first
117+
ANTHROPIC_API_KEY = localStorage.getItem('ANTHROPIC_API_KEY');
118+
119+
if (!ANTHROPIC_API_KEY) {
120+
// Prompt for API key if not found in localStorage
121+
ANTHROPIC_API_KEY = prompt('Please enter your Anthropic API key:');
122+
if (!ANTHROPIC_API_KEY) {
123+
throw new Error('API key is required for spell checking');
124+
}
125+
// Save to localStorage for future use
126+
localStorage.setItem('ANTHROPIC_API_KEY', ANTHROPIC_API_KEY);
127+
}
128+
}
129+
return ANTHROPIC_API_KEY;
130+
}
131+
132+
async checkSpelling(textarea, button, loadingSpinner, resultsContainer) {
133+
try {
134+
const apiKey = await this.getApiKey();
135+
const text = textarea.value.trim();
136+
137+
if (!text) {
138+
this.showMessage(resultsContainer, '⚠️ Please enter some text to check', '#ff9800');
139+
return;
140+
}
141+
142+
// Show loading state
143+
button.style.display = 'none';
144+
loadingSpinner.style.display = 'inline';
145+
resultsContainer.innerHTML = '';
146+
147+
const response = await this.callClaudeAPI(apiKey, text);
148+
this.displayResults(textarea, response, resultsContainer);
149+
150+
} catch (error) {
151+
console.error('Spell check error:', error);
152+
this.showMessage(resultsContainer, `❌ Error: ${error.message}`, '#f44336');
153+
} finally {
154+
// Restore button state
155+
button.style.display = 'inline-block';
156+
loadingSpinner.style.display = 'none';
157+
}
158+
}
159+
160+
async callClaudeAPI(apiKey, text) {
161+
const tools = [
162+
{
163+
name: "highlight_error",
164+
description: "Report a spelling error found in the text",
165+
input_schema: {
166+
type: "object",
167+
properties: {
168+
word_to_replace: {
169+
type: "string",
170+
description: "The incorrectly spelled word"
171+
},
172+
replacement_string: {
173+
type: "string",
174+
description: "The correct spelling of the word"
175+
},
176+
commentary: {
177+
type: "string",
178+
description: "Brief explanation of the error or correction"
179+
}
180+
},
181+
required: ["word_to_replace", "replacement_string", "commentary"]
182+
}
183+
}
184+
];
185+
186+
const messages = [
187+
{
188+
role: "user",
189+
content: `Please check the following text for spelling errors. For each spelling error you find, use the highlight_error tool to report it. Only report actual spelling errors, not grammar or style issues.\n\nText to check:\n${text}`
190+
}
191+
];
192+
193+
const response = await fetch('https://api.anthropic.com/v1/messages', {
194+
method: 'POST',
195+
headers: {
196+
'Content-Type': 'application/json',
197+
'x-api-key': apiKey,
198+
'anthropic-version': '2023-06-01'
199+
},
200+
body: JSON.stringify({
201+
model: 'claude-3-sonnet-20240229',
202+
max_tokens: 1000,
203+
tools: tools,
204+
messages: messages
205+
})
206+
});
207+
208+
if (!response.ok) {
209+
const errorData = await response.json().catch(() => ({}));
210+
throw new Error(`API request failed: ${response.status} ${response.statusText}. ${errorData.error?.message || ''}`);
211+
}
212+
213+
return await response.json();
214+
}
215+
216+
displayResults(textarea, response, resultsContainer) {
217+
// Extract tool calls from response
218+
const toolCalls = [];
219+
220+
if (response.content) {
221+
response.content.forEach(block => {
222+
if (block.type === 'tool_use' && block.name === 'highlight_error') {
223+
toolCalls.push(block.input);
224+
}
225+
});
226+
}
227+
228+
if (toolCalls.length === 0) {
229+
// No errors found
230+
this.showMessage(resultsContainer, '✅ No spelling errors found!', '#4CAF50');
231+
return;
232+
}
233+
234+
// Display errors
235+
const errorsDiv = document.createElement('div');
236+
errorsDiv.innerHTML = `<h4 style="margin: 0 0 10px 0; color: #333;">Found ${toolCalls.length} spelling error(s):</h4>`;
237+
238+
toolCalls.forEach((error, index) => {
239+
const errorDiv = document.createElement('div');
240+
errorDiv.style.cssText = `
241+
background: #fff3cd;
242+
border: 1px solid #ffeaa7;
243+
border-radius: 4px;
244+
padding: 10px;
245+
margin: 5px 0;
246+
font-size: 14px;
247+
`;
248+
249+
errorDiv.innerHTML = `
250+
<strong>"${error.word_to_replace}"</strong> → <strong style="color: #4CAF50;">"${error.replacement_string}"</strong>
251+
<br><small style="color: #666;">${error.commentary}</small>
252+
<br><button class="apply-fix-btn" data-original="${error.word_to_replace}" data-replacement="${error.replacement_string}"
253+
style="background: #2196F3; color: white; border: none; padding: 4px 8px; border-radius: 3px;
254+
cursor: pointer; font-size: 12px; margin-top: 5px;">Apply Fix</button>
255+
`;
256+
257+
errorsDiv.appendChild(errorDiv);
258+
});
259+
260+
resultsContainer.appendChild(errorsDiv);
261+
262+
// Add event listeners for apply fix buttons
263+
errorsDiv.querySelectorAll('.apply-fix-btn').forEach(btn => {
264+
btn.addEventListener('click', (e) => {
265+
const original = e.target.getAttribute('data-original');
266+
const replacement = e.target.getAttribute('data-replacement');
267+
this.applyFix(textarea, original, replacement);
268+
e.target.textContent = '✓ Applied';
269+
e.target.disabled = true;
270+
e.target.style.background = '#4CAF50';
271+
});
272+
});
273+
}
274+
275+
applyFix(textarea, original, replacement) {
276+
// Simple find and replace - could be enhanced for better word boundary matching
277+
const currentText = textarea.value;
278+
const regex = new RegExp(`\\b${original.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}\\b`, 'gi');
279+
textarea.value = currentText.replace(regex, replacement);
280+
281+
// Trigger change event
282+
textarea.dispatchEvent(new Event('change', { bubbles: true }));
283+
}
284+
285+
showMessage(container, message, color) {
286+
container.innerHTML = `<div style="color: ${color}; font-weight: bold; padding: 10px 0;">${message}</div>`;
287+
}
288+
}
289+
290+
// Initialize the spell checker when DOM is ready
291+
if (document.readyState === 'loading') {
292+
document.addEventListener('DOMContentLoaded', () => new TextareaSpellChecker());
293+
} else {
294+
new TextareaSpellChecker();
295+
}
296+
297+
// For manual initialization
298+
window.TextareaSpellChecker = TextareaSpellChecker;
299+
</script>
300+
</body>
301+
</html>

0 commit comments

Comments
 (0)