refactor(Index.tsx): improve tag generation logic and Ollama status handling
This commit is contained in:
@@ -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
|
||||
|
Reference in New Issue
Block a user