refactor(Index.tsx): improve tag generation logic and Ollama status handling

This commit is contained in:
2025-08-29 13:21:59 +02:00
parent 48f3545454
commit d4dfe11cef

View File

@@ -69,7 +69,7 @@ const mapHitToNote = (hit: any): Note => ({
const Index = () => {
// Log component mount time immediately
console.log(`Component mounted after ${Date.now() - GLOBAL_START_TIME}ms`);
const [currentNote, setCurrentNote] = useState('');
const [currentNoteTags, setCurrentNoteTags] = useState<string[]>([]);
const [previousNote, setPreviousNote] = useState<Note | null>(null);
@@ -94,7 +94,7 @@ const Index = () => {
const [debugInfo, setDebugInfo] = useState<string[]>([]);
const [showDebugPanel, setShowDebugPanel] = useState(false);
const [autoGenerateTags, setAutoGenerateTags] = useState(true);
const [ollamaStatus, setOllamaStatus] = useState<'unknown' | 'online' | 'offline'>('unknown');
const [ollamaStatus, setOllamaStatus] = useState<'unknown' | 'online' | 'offline'>('unknown');
const [includeTagsInSearch, setIncludeTagsInSearch] = useState(true);
const [tagGenerationTimeout, setTagGenerationTimeout] = useState<NodeJS.Timeout>();
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
@@ -139,12 +139,12 @@ $current`);
const response = await fetch(url, options);
const duration = Date.now() - startTime;
debugTiming(operation, startTime);
// Add network diagnostics for slow requests
if (duration > 1000) {
addDebugInfo(`Slow request: ${operation} took ${duration}ms`);
}
return response;
} catch (error) {
const duration = Date.now() - startTime;
@@ -246,7 +246,7 @@ $current`);
// Add timeout to prevent hanging
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 500);
const response = await fetch(`${ollamaEndpoint}/api/tags`, {
method: 'GET',
headers: {
@@ -254,9 +254,9 @@ $current`);
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (response.ok) {
setOllamaStatus('online');
addDebugInfo('Ollama connection successful');
@@ -286,7 +286,7 @@ $current`);
if (tagGenerationTimeout) {
clearTimeout(tagGenerationTimeout);
}
const timeout = setTimeout(async () => {
if (content.trim() && autoGenerateTags) {
try {
@@ -305,14 +305,14 @@ $current`);
}
}
}, 300);
setTagGenerationTimeout(timeout);
};
// Generate tags using Ollama
const generateTags = async (content: string, noteIndex?: number): Promise<string[]> => {
try {
// Get context from previous notes only
// Get context from previous notes only
let previousNotes = '';
if (noteIndex !== undefined && noteCache.length > 0) {
const contextNotes = [];
@@ -357,7 +357,7 @@ $current`);
const data = await response.json();
const responseText = data.response?.trim();
if (!responseText) {
throw new Error('Empty response from Ollama - check if model is loaded');
}
@@ -367,9 +367,18 @@ $current`);
.split(',')
.map((tag: string) => tag.trim())
.filter((tag: string) => tag.length > 0);
addDebugInfo(`Generated ${tags.length} tags: ${tags.join(', ')}`);
return tags;
// Filter out tags that already exist in the content
const filteredTags = tags.filter((tag: string) => {
const tagExists = content.toLowerCase().includes(tag.toLowerCase());
if (tagExists) {
addDebugInfo(`Removing tag "${tag}" - already exists in content`);
}
return !tagExists;
});
addDebugInfo(`Generated ${tags.length} tags, filtered to ${filteredTags.length}: ${filteredTags.join(', ')}`);
return filteredTags;
// Last resort: return empty array
addDebugInfo('Could not extract tags from Ollama response');
@@ -377,10 +386,10 @@ $current`);
} catch (error) {
console.error('Error generating tags:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
// More detailed error logging and user notification
let userMessage = 'Failed to generate tags';
if (error instanceof TypeError && error.message.includes('fetch')) {
addDebugInfo('Tag generation failed: Network error - Ollama not reachable at localhost:11434');
userMessage = 'Ollama not reachable - check if it\'s running on localhost:11434';
@@ -397,14 +406,14 @@ $current`);
addDebugInfo(`Tag generation failed: ${errorMessage}`);
userMessage = `Tag generation failed: ${errorMessage}`;
}
// Show error to user
toast({
title: "Tag Generation Failed",
description: userMessage,
variant: "destructive",
});
return [];
}
};
@@ -599,7 +608,7 @@ $current`);
const noteIndex = noteCache.findIndex(n => n.id === note.id);
tags = await generateTags(trimmedContent, noteIndex);
}
// Generate tags if none are present and auto-generation is enabled
if (autoGenerateTags && tags.length === 0) {
addDebugInfo('No tags present, generating tags before saving...');
@@ -716,31 +725,31 @@ $current`);
const data = await response.json();
const notes: Note[] = data.hits.map((hit: any) => mapHitToNote(hit));
if (offset === 0) {
setNoteCache(notes);
if (notes.length > 0) {
setPreviousNote(notes[0]);
setCurrentNoteIndex(0);
}
const loadTime = Date.now() - loadStartTime;
addDebugInfo(`Loaded ${notes.length} notes in ${loadTime}ms`);
} else {
setNoteCache(prev => [...prev, ...notes]);
const loadTime = Date.now() - loadStartTime;
addDebugInfo(`Loaded ${notes.length} additional notes in ${loadTime}ms`);
if (offset === 0) {
setNoteCache(notes);
if (notes.length > 0) {
setPreviousNote(notes[0]);
setCurrentNoteIndex(0);
}
} catch (error) {
console.error('Error loading notes:', error);
const loadTime = Date.now() - loadStartTime;
addDebugInfo(`Failed to load notes after ${loadTime}ms`);
toast({
title: "Error",
description: "Failed to load notes. Please check your connection.",
variant: "destructive",
});
} finally {
setIsLoading(false);
addDebugInfo(`Loaded ${notes.length} notes in ${loadTime}ms`);
} else {
setNoteCache(prev => [...prev, ...notes]);
const loadTime = Date.now() - loadStartTime;
addDebugInfo(`Loaded ${notes.length} additional notes in ${loadTime}ms`);
}
} catch (error) {
console.error('Error loading notes:', error);
const loadTime = Date.now() - loadStartTime;
addDebugInfo(`Failed to load notes after ${loadTime}ms`);
toast({
title: "Error",
description: "Failed to load notes. Please check your connection.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
// Load notes around a specific note
@@ -1097,7 +1106,7 @@ $current`);
try {
setIsLoading(true);
addDebugInfo('Regenerating tags for all notes...');
// Load all notes
const response = await fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/search`, {
method: 'POST',
@@ -1118,7 +1127,7 @@ $current`);
const data = await response.json();
const notes: Note[] = data.hits.map((hit: any) => mapHitToNote(hit));
let updatedCount = 0;
for (let i = 0; i < notes.length; i++) {
const note = notes[i];
@@ -1134,7 +1143,7 @@ $current`);
console.error(`Error updating tags for note ${note.id}:`, error);
}
}
addDebugInfo(`Tag regeneration complete: ${updatedCount} notes updated`);
toast({
title: "Tag regeneration complete",
@@ -1203,7 +1212,7 @@ $current`);
document.addEventListener('keydown', handleKeyDown);
window.addEventListener('focus', handleWindowFocus);
return () => {
document.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('focus', handleWindowFocus);
@@ -1352,19 +1361,19 @@ $current`);
try {
console.log('Starting initialization...');
addDebugInfo('Starting initialization...');
// Check all indexes in parallel for faster initialization
addDebugInfo('Checking all indexes in parallel...');
const indexCheckStart = Date.now();
const [notesExists, scratchExists, settingsExists] = await Promise.all([
indexExists(NOTE_INDEX),
indexExists(SCRATCH_INDEX),
indexExists(SCRATCH_INDEX),
indexExists(SETTINGS_INDEX)
]);
addDebugInfo(`All index checks completed: ${Date.now() - indexCheckStart}ms`);
// Create missing indexes
const createPromises = [];
if (!notesExists) {
@@ -1381,7 +1390,7 @@ $current`);
}),
}, 'Create Notes Index'));
}
if (!scratchExists) {
addDebugInfo('Creating scratch index...');
createPromises.push(fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes`, {
@@ -1396,7 +1405,7 @@ $current`);
}),
}, 'Create Scratch Index'));
}
if (!settingsExists) {
addDebugInfo('Creating settings index...');
createPromises.push(fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes`, {
@@ -1411,7 +1420,7 @@ $current`);
}),
}, 'Create Settings Index'));
}
if (createPromises.length > 0) {
await Promise.all(createPromises);
addDebugInfo('All missing indexes created');
@@ -1420,7 +1429,7 @@ $current`);
// Configure all indexes in parallel
addDebugInfo('Configuring all indexes in parallel...');
const configStart = Date.now();
await Promise.all([
// Notes index configurations
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/settings/sortable-attributes`, {
@@ -1431,7 +1440,7 @@ $current`);
},
body: JSON.stringify(['date']),
}, 'Configure Notes Sortable Attributes'),
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/settings/ranking-rules`, {
method: 'PUT',
headers: {
@@ -1440,7 +1449,7 @@ $current`);
},
body: JSON.stringify(['sort', 'words', 'typo', 'proximity', 'attribute', 'exactness']),
}, 'Configure Notes Ranking Rules'),
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/settings/filterable-attributes`, {
method: 'PUT',
headers: {
@@ -1449,7 +1458,7 @@ $current`);
},
body: JSON.stringify(['date', 'topLetter', 'letterCount', 'topLetterFrequency', 'tags']),
}, 'Configure Notes Filterable Attributes'),
// Scratch index configurations
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${SCRATCH_INDEX}/settings/sortable-attributes`, {
method: 'PUT',
@@ -1459,7 +1468,7 @@ $current`);
},
body: JSON.stringify(['date']),
}, 'Configure Scratch Sortable Attributes'),
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${SCRATCH_INDEX}/settings/filterable-attributes`, {
method: 'PUT',
headers: {
@@ -1468,7 +1477,7 @@ $current`);
},
body: JSON.stringify(['date']),
}, 'Configure Scratch Filterable Attributes'),
// Settings index configurations
fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${SETTINGS_INDEX}/settings/filterable-attributes`, {
method: 'PUT',
@@ -1479,7 +1488,7 @@ $current`);
body: JSON.stringify(['key', 'value']),
}, 'Configure Settings Filterable Attributes'),
]);
addDebugInfo(`All index configurations completed: ${Date.now() - configStart}ms`);
setIsInitialized(true);
@@ -1518,7 +1527,7 @@ $current`);
currentNoteRef.current?.focus();
console.log('Focus set on current note field');
}, 100);
return () => clearTimeout(focusTimer);
}, []);
@@ -1536,7 +1545,7 @@ $current`);
const dataLoadStartTime = Date.now();
console.log('Starting data loading...');
addDebugInfo('Starting data loading...');
await loadNotes();
await loadLatestScratch();
await loadFontSizeSetting();
@@ -1547,7 +1556,7 @@ $current`);
await loadIncludeTagsSetting();
await loadAllOllamaSettings();
await checkOllamaStatus();
// Retry Ollama connection after a delay if it failed
setTimeout(async () => {
if (ollamaStatus === 'offline') {
@@ -1555,12 +1564,12 @@ $current`);
await checkOllamaStatus();
}
}, 2000);
const totalTime = Date.now() - dataLoadStartTime;
debugTiming('Total Data Loading', dataLoadStartTime);
addDebugInfo(`Total Data Loading: ${totalTime}ms`);
console.log('Data loading complete');
// Re-focus after data loading to ensure focus is maintained
currentNoteRef.current?.focus();
}
@@ -2270,19 +2279,19 @@ $current`);
<Trash2 className="h-8 w-8" />
Cleanup
</Button>
<Button
onClick={() => setIsSettingsOpen(true)}
size="sm"
variant="outline"
<Button
onClick={() => setIsSettingsOpen(true)}
size="sm"
variant="outline"
className={getTextClass('base')}
>
<Settings className="h-4 w-4 mr-2" />
Settings
</Button>
<Button
onClick={() => setShowDebugPanel(!showDebugPanel)}
size="sm"
variant="outline"
<Button
onClick={() => setShowDebugPanel(!showDebugPanel)}
size="sm"
variant="outline"
className={getTextClass('base')}
>
{showDebugPanel ? 'Hide' : 'Show'} Debug
@@ -2298,7 +2307,7 @@ $current`);
<div className="flex items-center gap-4 mb-3">
<div className={`${getTextClass('base')} flex items-center gap-2`}>
<span>Ollama Status:</span>
<Badge
<Badge
variant={ollamaStatus === 'online' ? 'default' : ollamaStatus === 'offline' ? 'destructive' : 'secondary'}
className={`${getTextClass('base')} px-2 py-1`}
>
@@ -2307,7 +2316,7 @@ $current`);
</div>
<div className={`${getTextClass('base')} flex items-center gap-2`}>
<span>Auto-tags:</span>
<Badge
<Badge
variant={autoGenerateTags ? 'default' : 'secondary'}
className={`${getTextClass('base')} px-2 py-1`}
>
@@ -2358,16 +2367,16 @@ $current`);
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
/>
{!autoGenerateTags && (
<Button
onClick={async () => {
if (previousNote) {
addDebugInfo('Manually generating tags...');
const tags = await generateTags(previousNote.content, currentNoteIndex);
const updatedNote = { ...previousNote, tags };
setPreviousNote(updatedNote);
setIsPreviousNoteModified(true);
}
}}
<Button
onClick={async () => {
if (previousNote) {
addDebugInfo('Manually generating tags...');
const tags = await generateTags(previousNote.content, currentNoteIndex);
const updatedNote = { ...previousNote, tags };
setPreviousNote(updatedNote);
setIsPreviousNoteModified(true);
}
}}
size="sm"
variant="outline"
className={`${getTextClass('base')} px-2 py-1 h-6`}
@@ -2414,17 +2423,17 @@ $current`);
<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`}>
<div>Current Entry</div>
<div className="flex-1 flex items-center gap-2">
<span className={`${getTextClass('base')} text-muted-foreground`}>Tags:</span>
<input
type="text"
value={currentNoteTags.join(', ')}
onChange={(e) => {
setCurrentNoteTags([e.target.value]);
}}
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
/>
</div>
<div className="flex-1 flex items-center gap-2">
<span className={`${getTextClass('base')} text-muted-foreground`}>Tags:</span>
<input
type="text"
value={currentNoteTags.join(', ')}
onChange={(e) => {
setCurrentNoteTags([e.target.value]);
}}
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
/>
</div>
</div>
<Textarea
ref={currentNoteRef}
@@ -2603,8 +2612,8 @@ $current`);
<Button onClick={getProblematicNotes} className={`w-full ${getTextClass('2xl')} py-6`}>
Analyze Notes
</Button>
<Button
onClick={regenerateAllTags}
<Button
onClick={regenerateAllTags}
variant="outline"
className={`w-full ${getTextClass('2xl')} py-6`}
disabled={isLoading}
@@ -2663,7 +2672,7 @@ $current`);
{/* Tag Generation Settings */}
<div className="space-y-6">
<h3 className={`${getTextClass('2xl')} font-semibold`}>Tag Generation</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
@@ -2735,7 +2744,7 @@ $current`);
{/* Display Settings */}
<div className="space-y-6">
<h3 className={`${getTextClass('2xl')} font-semibold`}>Display</h3>
<div>
<label className={`${getTextClass('xl')} font-medium mb-2 block`}>Font Size</label>
<Select value={fontSize} onValueChange={saveFontSizeSetting}>
@@ -2755,18 +2764,18 @@ $current`);
{/* Ollama Settings */}
<div className="space-y-6">
<h3 className={`${getTextClass('2xl')} font-semibold`}>Ollama Configuration</h3>
<div className="space-y-4">
<div className="flex items-center gap-2">
<span className={`${getTextClass('base')}`}>Status:</span>
<Badge
<Badge
variant={ollamaStatus === 'online' ? 'default' : ollamaStatus === 'offline' ? 'destructive' : 'secondary'}
className={`${getTextClass('base')} px-2 py-1`}
>
{ollamaStatus === 'online' ? 'Online' : ollamaStatus === 'offline' ? 'Offline' : 'Unknown'}
</Badge>
</div>
<div>
<label className={`${getTextClass('xl')} font-medium mb-2 block`}>Endpoint</label>
<Input