From 333d44e621dc04c5cbf92eaf4c0abcfd6907c547 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Fri, 29 Aug 2025 18:04:59 +0200 Subject: [PATCH] feat(Index.tsx): implement debounced text correction using Ollama API --- src/pages/Index.tsx | 284 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 224 insertions(+), 60 deletions(-) diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 39ba6d1..ec13191 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -121,6 +121,9 @@ Keep tags concise, use lowercase, and separate words with hyphens if needed.`); $current`); const [contextSize, setContextSize] = useState(3); + const [correctedContent, setCorrectedContent] = useState(''); + const [previousNoteCorrectedContent, setPreviousNoteCorrectedContent] = useState(''); + const [correctionTimeout, setCorrectionTimeout] = useState(); const { resolvedTheme, setTheme } = useTheme(); @@ -309,6 +312,124 @@ $current`); setTagGenerationTimeout(timeout); }; + // Debounced text correction for current note + const debouncedCorrectText = (content: string) => { + if (correctionTimeout) { + clearTimeout(correctionTimeout); + } + + const timeout = setTimeout(async () => { + if (content.trim()) { + try { + addDebugInfo('Correcting current note text...'); + const corrected = await correctText(content); + setCorrectedContent(corrected); + } catch (error) { + console.error('Text correction failed:', error); + setCorrectedContent(''); + } + } else { + setCorrectedContent(''); + } + }, 500); + + setCorrectionTimeout(timeout); + }; + + // Debounced text correction for previous note + const debouncedCorrectPreviousNote = (content: string) => { + if (correctionTimeout) { + clearTimeout(correctionTimeout); + } + + const timeout = setTimeout(async () => { + if (content.trim()) { + try { + addDebugInfo('Correcting previous note text...'); + const corrected = await correctText(content); + setPreviousNoteCorrectedContent(corrected); + } catch (error) { + console.error('Previous note text correction failed:', error); + setPreviousNoteCorrectedContent(''); + } + } else { + setPreviousNoteCorrectedContent(''); + } + }, 500); + + setCorrectionTimeout(timeout); + }; + + // Correct text using Ollama + const correctText = async (content: string): Promise => { + try { + const response = await fetchWithTiming(`${ollamaEndpoint}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: ollamaModel, + system: `You are a helpful assistant that corrects ONLY spelling and punctuation errors in text. + +Your task is to fix obvious spelling mistakes and punctuation errors. +DO NOT change any words, phrases, or meaning. +DO NOT replace informal language, slang, or profanity. +DO NOT rephrase or rewrite anything. +Preserve the exact original tone, style, and meaning. +Only fix clear spelling errors and punctuation mistakes. + +Return ONLY the corrected text, no explanations.`, + prompt: `Fix only spelling and punctuation errors in this text. Do not change any words or meaning: + +${content}`, + stream: false, + keep_alive: ollamaKeepAlive, + temperature: 0.1, + }), + }, 'Correct Text'); + + if (!response.ok) { + const errorText = await response.text().catch(() => 'Unknown error'); + throw new Error(`Ollama API error ${response.status}: ${errorText}`); + } + + const data = await response.json(); + const correctedText = data.response?.trim(); + + if (!correctedText) { + throw new Error('Empty response from Ollama - check if model is loaded'); + } + + addDebugInfo(`Text corrected successfully`); + return correctedText; + } catch (error) { + console.error('Error correcting text:', error); + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + + let userMessage = 'Failed to correct text'; + + if (error instanceof TypeError && error.message.includes('fetch')) { + addDebugInfo('Text correction failed: Network error - Ollama not reachable'); + userMessage = 'Ollama not reachable - check if it\'s running'; + } else if (errorMessage.includes('Failed to fetch')) { + addDebugInfo('Text correction failed: Ollama connection refused'); + userMessage = 'Ollama connection refused - check if Ollama is running'; + } else { + addDebugInfo(`Text correction failed: ${errorMessage}`); + userMessage = `Text correction failed: ${errorMessage}`; + } + + toast({ + title: "Text Correction Failed", + description: userMessage, + variant: "destructive", + }); + + return content; // Return original content if correction fails + } + }; + // Generate tags using Ollama const generateTags = async (content: string, noteIndex?: number): Promise => { try { @@ -1051,6 +1172,12 @@ $current`); setCurrentNoteIndex(newIndex); setPreviousNote(noteCache[newIndex]); setIsPreviousNoteModified(false); + setPreviousNoteCorrectedContent(''); // Clear corrected content when note changes + + // Automatically load AI corrections for the new note + if (noteCache[newIndex].content.trim()) { + debouncedCorrectPreviousNote(noteCache[newIndex].content); + } } // Load more notes if we're getting close to the end @@ -1224,6 +1351,10 @@ $current`); if (tagGenerationTimeout) { clearTimeout(tagGenerationTimeout); } + // Clear any pending correction timeout on cleanup + if (correctionTimeout) { + clearTimeout(correctionTimeout); + } }; }, [currentNote, previousNote, scratchPad, isPreviousNoteModified, isSearchOpen, isGotoOpen, isCleanupOpen]); @@ -2344,79 +2475,112 @@ $current`);
{/* Left Panel - 70% */}
- {/* Previous Note - Top Half */} + {/* Previous Note - Top Half - Split Layout */}
-
-
- Previous Entry {currentNoteIndex > 0 && `(${currentNoteIndex + 1} of ${noteCache.length})`} + {/* Left Side - Original Previous Note */} +
+
+
+ Previous Entry {currentNoteIndex > 0 && `(${currentNoteIndex + 1} of ${noteCache.length})`} +
+ {previousNote && ( +
+ Tags: + { + const updatedNote = { ...previousNote, tags: [e.target.value] }; + setPreviousNote(updatedNote); + setIsPreviousNoteModified(true); + }} + className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`} + /> + {!autoGenerateTags && ( + + )} +
+ )}
- {previousNote && ( -
- Tags: - +
+ {formatDate(previousNote.epochTime)} +
+