11<!DOCTYPE html>
22< html lang ="en ">
33< head >
4+ < link rel ="icon " href ="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📝</text></svg> ">
45 < meta charset ="UTF-8 ">
56 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < title > Grammar Checker</ title >
77 < script src ="https://cdn.tailwindcss.com "> </ script >
88 < script src ="https://cdnjs.cloudflare.com/ajax/libs/json5/2.2.3/index.min.js "> </ script >
9- < script src =" https://cdn.jsdelivr.net/npm/languagedetect/languagedetect.js " > </ script >
9+ < title > GrammarPro </ title >
1010 < style >
11- .error-spelling { background : rgba (255 , 0 , 0 , 0.2 ); }
12- .error-grammar { background : rgba (255 , 165 , 0 , 0.2 ); }
13- .error-style { background : rgba (0 , 128 , 0 , 0.2 ); }
14- .error-punctuation { background : rgba (0 , 0 , 255 , 0.2 ); }
15- .tooltip { position : absolute; background : white; border : 1px solid # ccc ; padding : 8px ; border-radius : 4px ; box-shadow : 2px 2px 10px rgba (0 , 0 , 0 , 0.1 ); z-index : 1000 ; max-width : 300px ; }
11+ .error-highlight {
12+ position : relative;
13+ display : inline-block;
14+ cursor : pointer;
15+ }
16+
17+ .grammar { border-bottom : 2px solid # ef4444 ; }
18+ .spelling { border-bottom : 2px solid # 3b82f6 ; }
19+ .style { border-bottom : 2px solid # 10b981 ; }
20+ .punctuation { border-bottom : 2px solid # f59e0b ; }
21+
22+ .tooltip {
23+ visibility : hidden;
24+ position : absolute;
25+ bottom : 100% ;
26+ left : 50% ;
27+ transform : translateX (-50% );
28+ padding : 8px ;
29+ border-radius : 4px ;
30+ font-size : 14px ;
31+ white-space : nowrap;
32+ z-index : 100 ;
33+ transition : visibility 0.2s ;
34+ }
35+
36+ .error-highlight : hover .tooltip {
37+ visibility : visible;
38+ }
1639 </ style >
1740</ head >
18- < body class ="bg-gray-100 min-h-screen ">
19- < div class ="container mx-auto max-w-4xl p-6 ">
20- < h1 class ="text-3xl font-bold text-center mb-6 "> Grammar Checker</ h1 >
21- < div class ="bg-white rounded-lg shadow-lg p-6 ">
22- < textarea id ="textInput " class ="w-full h-40 p-4 border rounded-lg mb-4 " placeholder ="Enter your text here... "> </ textarea >
23- < button id ="checkGrammar " class ="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition-colors "> Check Grammar</ button >
24- < div id ="result " class ="mt-6 p-4 border rounded-lg "> </ div >
41+ < body class ="bg-gray-100 flex justify-center items-center min-h-screen ">
42+ < div class ="container mx-auto max-w-4xl p-5 bg-white rounded-lg shadow-lg ">
43+ < h1 class ="text-3xl font-bold text-center mb-6 "> GrammarPro</ h1 >
44+
45+ < div class ="mb-6 ">
46+ < label for ="input " class ="block text-sm font-medium text-gray-700 mb-2 "> Enter Text:</ label >
47+ < textarea id ="input " rows ="6 " class ="w-full p-3 border rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 "
48+ placeholder ="Type or paste your text here... "> </ textarea >
2549 </ div >
26- < div class ="mt-4 flex gap-4 justify-center ">
27- < div class ="flex items-center "> < span class ="w-4 h-4 inline-block mr-2 error-spelling "> </ span > Spelling</ div >
28- < div class ="flex items-center "> < span class ="w-4 h-4 inline-block mr-2 error-grammar "> </ span > Grammar</ div >
29- < div class ="flex items-center "> < span class ="w-4 h-4 inline-block mr-2 error-style "> </ span > Style</ div >
30- < div class ="flex items-center "> < span class ="w-4 h-4 inline-block mr-2 error-punctuation "> </ span > Punctuation</ div >
50+
51+ < div class ="flex justify-between items-center mb-6 ">
52+ < button id ="check-grammar " class ="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 ">
53+ Check Grammar
54+ </ button >
55+
56+ < div class ="flex gap-4 ">
57+ < div class ="flex items-center ">
58+ < span class ="w-3 h-3 bg-red-500 rounded-full mr-2 "> </ span >
59+ < span class ="text-sm "> Grammar</ span >
60+ </ div >
61+ < div class ="flex items-center ">
62+ < span class ="w-3 h-3 bg-blue-500 rounded-full mr-2 "> </ span >
63+ < span class ="text-sm "> Spelling</ span >
64+ </ div >
65+ < div class ="flex items-center ">
66+ < span class ="w-3 h-3 bg-green-500 rounded-full mr-2 "> </ span >
67+ < span class ="text-sm "> Style</ span >
68+ </ div >
69+ < div class ="flex items-center ">
70+ < span class ="w-3 h-3 bg-yellow-500 rounded-full mr-2 "> </ span >
71+ < span class ="text-sm "> Punctuation</ span >
72+ </ div >
73+ </ div >
3174 </ div >
75+
76+ < div id ="result " class ="p-4 border rounded-md min-h-[100px] bg-gray-50 "> </ div >
3277</ div >
3378
3479< script >
35- const grammarChecker = {
36- init ( ) {
37- this . checkButton = document . getElementById ( 'checkGrammar' ) ;
38- this . textInput = document . getElementById ( 'textInput' ) ;
39- this . resultDiv = document . getElementById ( 'result' ) ;
40- this . checkButton . addEventListener ( 'click' , ( ) => this . check ( ) ) ;
41- this . tooltip = null ;
42- } ,
43-
44- async check ( ) {
45- const text = this . textInput . value . trim ( ) ;
46- if ( ! text ) return ;
47-
48- this . checkButton . disabled = true ;
49- this . checkButton . textContent = 'Checking...' ;
50-
51- try {
52- const response = await this . callLLM ( text ) ;
53- this . displayResults ( response ) ;
54- } catch ( error ) {
55- console . error ( 'Error:' , error ) ;
56- this . resultDiv . innerHTML = 'An error occurred while checking the text.' ;
57- } finally {
58- this . checkButton . disabled = false ;
59- this . checkButton . textContent = 'Check Grammar' ;
60- }
61- } ,
62-
63- async callLLM ( text ) {
64- const prompt = `Act as a grammar checking API. Analyze the following text and return a JSON response in this exact format:
65- {
66- "errors": [
67- {
68- "type": "spelling|grammar|style|punctuation",
69- "start": <number>,
70- "end": <number>,
71- "text": "<problematic text>",
72- "suggestion": "<corrected text>",
73- "description": "<error description>"
74- }
75- ]
76- }
77-
78- Example input: "I dont like there attitude."
79- Example response: {
80- "errors": [
81- {
82- "type": "spelling",
83- "start": 2,
84- "end": 6,
85- "text": "dont",
86- "suggestion": "don't",
87- "description": "Missing apostrophe in contraction"
88- },
89- {
90- "type": "grammar",
91- "start": 7,
92- "end": 12,
93- "text": "there",
94- "suggestion": "their",
95- "description": "Incorrect use of 'there' instead of the possessive 'their'"
80+ document . getElementById ( 'check-grammar' ) . addEventListener ( 'click' , checkGrammar ) ;
81+
82+ async function checkGrammar ( ) {
83+ const inputElement = document . getElementById ( 'input' ) ;
84+ const resultContainer = document . getElementById ( 'result' ) ;
85+ const button = document . getElementById ( 'check-grammar' ) ;
86+
87+ const inputText = inputElement . value . trim ( ) ;
88+ if ( ! inputText ) {
89+ alert ( 'Please enter some text to check.' ) ;
90+ return ;
9691 }
97- ]
98- }
9992
100- Text to analyze: ${ text } ` ;
93+ button . disabled = true ;
94+ button . textContent = 'Checking...' ;
95+
96+ const botMessage = `I am a grammar checking API. I analyze text for grammar, spelling, style, and punctuation errors. I always respond in this JSON format:
97+ {
98+ "detectedLanguage": "string",
99+ "errors": [
100+ {
101+ "type": "grammar|spelling|style|punctuation",
102+ "text": "original text",
103+ "suggestion": "suggested correction",
104+ "description": "error description"
105+ }
106+ ]
107+ }
101108
109+ Example response for "I dont like there attitude":
110+ {
111+ "detectedLanguage": "English",
112+ "errors": [
113+ {
114+ "type": "spelling",
115+ "text": "dont",
116+ "suggestion": "don't",
117+ "description": "Missing apostrophe in contraction"
118+ },
119+ {
120+ "type": "grammar",
121+ "text": "there",
122+ "suggestion": "their",
123+ "description": "Incorrect use of 'there'. Use 'their' for possession"
124+ }
125+ ]
126+ }` ;
127+
128+ try {
102129 const response = await fetch ( 'https://chatgpt.tobiasmue91.workers.dev/' , {
103130 method : 'POST' ,
104131 headers : { "Content-Type" : "application/json" } ,
105132 body : JSON . stringify ( {
106133 model : "gpt-3.5-turbo" ,
134+ max_tokens : 800 ,
135+ temperature : 0.3 ,
107136 messages : [
108- { role : "user" , content : prompt }
109- ]
137+ {
138+ role : "assistant" ,
139+ content : botMessage ,
140+ } ,
141+ {
142+ role : "user" ,
143+ content : `Check this text: ${ inputText } ` ,
144+ } ,
145+ ] ,
110146 } )
111147 } ) ;
112148
149+ if ( ! response . ok ) throw new Error ( 'Network response was not ok' ) ;
150+
113151 const data = await response . json ( ) ;
114- const content = data . choices [ 0 ] . message . content ;
115- return JSON . parse ( content ) ;
116- } ,
117-
118- displayResults ( response ) {
119- const text = this . textInput . value ;
120- let html = text ;
121- const errors = response . errors . sort ( ( a , b ) => b . start - a . start ) ;
122-
123- errors . forEach ( error => {
124- const errorClass = `error-${ error . type } ` ;
125- const replacement = `<span class="${ errorClass } " data-error='${ JSON . stringify ( error ) } '>${ error . text } </span>` ;
126- html = html . substring ( 0 , error . start ) + replacement + html . substring ( error . end ) ;
127- } ) ;
152+ const responseContent = data . choices [ 0 ] . message . content ;
153+ const jsonMatch = responseContent . match ( / \{ [ \s \S ] * \} / ) ;
128154
129- this . resultDiv . innerHTML = html ;
130- this . attachErrorListeners ( ) ;
131- } ,
155+ if ( ! jsonMatch ) throw new Error ( 'Invalid response format' ) ;
132156
133- attachErrorListeners ( ) {
134- const errorSpans = this . resultDiv . querySelectorAll ( 'span[data-error]' ) ;
135- errorSpans . forEach ( span => {
136- span . addEventListener ( 'mouseover' , ( e ) => this . showTooltip ( e ) ) ;
137- span . addEventListener ( 'mouseout' , ( ) => this . hideTooltip ( ) ) ;
138- span . addEventListener ( 'click' , ( e ) => this . applyCorrection ( e ) ) ;
139- } ) ;
140- } ,
141-
142- showTooltip ( event ) {
143- const error = JSON . parse ( event . target . dataset . error ) ;
144- if ( this . tooltip ) this . hideTooltip ( ) ;
145-
146- this . tooltip = document . createElement ( 'div' ) ;
147- this . tooltip . className = 'tooltip' ;
148- this . tooltip . innerHTML = `
149- <div class="font-bold">${ error . type . charAt ( 0 ) . toUpperCase ( ) + error . type . slice ( 1 ) } Error</div>
150- <div class="mt-1">${ error . description } </div>
151- <div class="mt-2 text-sm text-gray-600">Click to replace with: "${ error . suggestion } "</div>
152- ` ;
153-
154- document . body . appendChild ( this . tooltip ) ;
155- const rect = event . target . getBoundingClientRect ( ) ;
156- this . tooltip . style . left = `${ rect . left } px` ;
157- this . tooltip . style . top = `${ rect . bottom + 5 } px` ;
158- } ,
159-
160- hideTooltip ( ) {
161- if ( this . tooltip ) {
162- this . tooltip . remove ( ) ;
163- this . tooltip = null ;
164- }
165- } ,
166-
167- applyCorrection ( event ) {
168- const error = JSON . parse ( event . target . dataset . error ) ;
169- event . target . textContent = error . suggestion ;
170- event . target . classList . remove ( `error-${ error . type } ` ) ;
171- event . target . removeAttribute ( 'data-error' ) ;
172- this . hideTooltip ( ) ;
173- }
174- } ;
157+ const grammarData = JSON5 . parse ( jsonMatch [ 0 ] ) ;
158+ displayResults ( inputText , grammarData . errors ) ;
175159
176- document . addEventListener ( 'DOMContentLoaded' , ( ) => grammarChecker . init ( ) ) ;
160+ } catch ( error ) {
161+ console . error ( 'Error:' , error ) ;
162+ resultContainer . textContent = 'An error occurred while checking the text. Please try again.' ;
163+ } finally {
164+ button . disabled = false ;
165+ button . textContent = 'Check Grammar' ;
166+ }
167+ }
168+
169+ function displayResults ( originalText , errors ) {
170+ const resultContainer = document . getElementById ( 'result' ) ;
171+ let displayText = originalText ;
172+
173+ // Sort errors by position (longest matches first to avoid nested replacements)
174+ errors . sort ( ( a , b ) => b . text . length - a . text . length ) ;
175+
176+ // Replace each error with highlighted span
177+ errors . forEach ( error => {
178+ const highlightHtml = `<span class="error-highlight ${ error . type } ">
179+ ${ error . text }
180+ <div class="tooltip bg-gray-800 text-white">
181+ ${ error . description } <br>
182+ Suggestion: ${ error . suggestion }
183+ </div>
184+ </span>` ;
185+
186+ displayText = displayText . replace ( error . text , highlightHtml ) ;
187+ } ) ;
188+
189+ resultContainer . innerHTML = displayText ;
190+
191+ // Add click handlers for corrections
192+ document . querySelectorAll ( '.error-highlight' ) . forEach ( highlight => {
193+ highlight . addEventListener ( 'click' , function ( ) {
194+ const error = errors . find ( e => e . text === this . textContent . trim ( ) ) ;
195+ if ( error ) {
196+ const input = document . getElementById ( 'input' ) ;
197+ input . value = input . value . replace ( error . text , error . suggestion ) ;
198+ }
199+ } ) ;
200+ } ) ;
201+ }
177202</ script >
178203</ body >
179204</ html >
0 commit comments