feat(Index.tsx): implement debounced text correction using Ollama API
This commit is contained in:
@@ -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<NodeJS.Timeout>();
|
||||
|
||||
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<string> => {
|
||||
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<string[]> => {
|
||||
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,11 +2475,13 @@ $current`);
|
||||
<div className="flex-1 flex p-4 gap-4 overflow-hidden">
|
||||
{/* Left Panel - 70% */}
|
||||
<div className="flex-[7] flex flex-col gap-4 h-[calc(100vh-8rem)] overflow-hidden">
|
||||
{/* Previous Note - Top Half */}
|
||||
{/* Previous Note - Top Half - Split Layout */}
|
||||
<div
|
||||
ref={previousNoteRef}
|
||||
className="flex-1 bg-card rounded-lg border border-border shadow-sm p-6 overflow-auto cursor-pointer select-none"
|
||||
className="flex-1 flex gap-4 overflow-hidden cursor-pointer select-none"
|
||||
>
|
||||
{/* Left Side - Original Previous Note */}
|
||||
<div className="flex-1 bg-card rounded-lg border border-border shadow-sm p-6 overflow-auto">
|
||||
<div className={`${getTextClass('2xl')} text-muted-foreground mb-3 flex items-center gap-4`}>
|
||||
<div>
|
||||
Previous Entry {currentNoteIndex > 0 && `(${currentNoteIndex + 1} of ${noteCache.length})`}
|
||||
@@ -2419,6 +2552,37 @@ $current`);
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - AI Suggestions for Previous Note */}
|
||||
<div className="flex-1 bg-card rounded-lg border border-border shadow-sm p-6 overflow-auto">
|
||||
<div className={`${getTextClass('2xl')} text-muted-foreground mb-3 flex items-center justify-between`}>
|
||||
<div>AI Suggestions</div>
|
||||
{previousNoteCorrectedContent && previousNoteCorrectedContent !== previousNote?.content && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (previousNote) {
|
||||
const updatedNote = { ...previousNote, content: previousNoteCorrectedContent };
|
||||
setPreviousNote(updatedNote);
|
||||
setIsPreviousNoteModified(true);
|
||||
setPreviousNoteCorrectedContent('');
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
variant="default"
|
||||
className={`${getTextClass('base')}`}
|
||||
>
|
||||
ACCEPT
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Textarea
|
||||
value={previousNoteCorrectedContent || (previousNote ? previousNote.content : '')}
|
||||
readOnly
|
||||
className={`h-[calc(100%-2rem)] border-0 resize-none focus:ring-0 bg-transparent ${getTextClass('2xl')}`}
|
||||
placeholder={previousNote ? "AI is analyzing your text..." : "No previous entry to analyze"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Note - Bottom Half */}
|
||||
<div className="flex-1 bg-card rounded-lg border border-border shadow-sm p-6 overflow-hidden">
|
||||
<div className={`${getTextClass('2xl')} text-muted-foreground mb-3 flex items-center gap-4`}>
|
||||
|
Reference in New Issue
Block a user