feat(Index.tsx): refactor tag generation and display for notes to use comma-separated values and allow manual editing
This commit is contained in:
@@ -73,6 +73,7 @@ const Index = () => {
|
||||
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);
|
||||
const [scratchPad, setScratchPad] = useState('');
|
||||
const [scratchId, setScratchId] = useState<string | null>(null);
|
||||
@@ -251,7 +252,7 @@ Focus on:
|
||||
- Problem domains or contexts
|
||||
- Key technical terms that someone might search for
|
||||
|
||||
Return ONLY a JSON array of strings, no other text. Example: ["golang", "testing", "array-comparison", "cmp-library"]
|
||||
Return ONLY a comma-separated list of tags, no other text. Example: golang, testing, array-comparison, cmp-library
|
||||
|
||||
Keep tags concise, use lowercase, and separate words with hyphens if needed.`;
|
||||
|
||||
@@ -273,37 +274,25 @@ ${content}`;
|
||||
}, 'Generate Tags');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate tags');
|
||||
const errorText = await response.text().catch(() => 'Unknown error');
|
||||
throw new Error(`Ollama API error ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const responseText = data.response?.trim();
|
||||
|
||||
if (!responseText) {
|
||||
throw new Error('Empty response from Ollama');
|
||||
throw new Error('Empty response from Ollama - check if model is loaded');
|
||||
}
|
||||
|
||||
// Try to parse JSON from the response
|
||||
try {
|
||||
const tags = JSON.parse(responseText);
|
||||
if (Array.isArray(tags) && tags.every(tag => typeof tag === 'string')) {
|
||||
addDebugInfo(`Generated ${tags.length} tags: ${tags.join(', ')}`);
|
||||
return tags;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('Failed to parse tags as JSON, trying to extract from text:', responseText);
|
||||
}
|
||||
// Parse CSV format from the response
|
||||
const tags = responseText
|
||||
.split(',')
|
||||
.map((tag: string) => tag.trim())
|
||||
.filter((tag: string) => tag.length > 0);
|
||||
|
||||
// Fallback: try to extract tags from text response
|
||||
const tagMatch = responseText.match(/\[(.*?)\]/);
|
||||
if (tagMatch) {
|
||||
const tags = tagMatch[1]
|
||||
.split(',')
|
||||
.map((tag: string) => tag.trim().replace(/"/g, ''))
|
||||
.filter((tag: string) => tag.length > 0);
|
||||
addDebugInfo(`Extracted ${tags.length} tags from text: ${tags.join(', ')}`);
|
||||
return tags;
|
||||
}
|
||||
addDebugInfo(`Generated ${tags.length} tags: ${tags.join(', ')}`);
|
||||
return tags;
|
||||
|
||||
// Last resort: return empty array
|
||||
addDebugInfo('Could not extract tags from Ollama response');
|
||||
@@ -311,13 +300,34 @@ ${content}`;
|
||||
} catch (error) {
|
||||
console.error('Error generating tags:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
addDebugInfo(`Tag generation failed: ${errorMessage}`);
|
||||
|
||||
// Check if it's a connection error
|
||||
if (errorMessage.includes('fetch') || errorMessage.includes('network') || errorMessage.includes('ECONNREFUSED')) {
|
||||
addDebugInfo('Ollama appears to be offline. Please ensure Ollama is running on localhost:11434');
|
||||
// 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';
|
||||
} else if (errorMessage.includes('Failed to fetch')) {
|
||||
addDebugInfo('Tag generation failed: Ollama connection refused - check if Ollama is running');
|
||||
userMessage = 'Ollama connection refused - check if Ollama is running';
|
||||
} else if (errorMessage.includes('404')) {
|
||||
addDebugInfo('Tag generation failed: Ollama endpoint not found - check model name');
|
||||
userMessage = 'Model not found - check if llama3.2:3b is installed';
|
||||
} else if (errorMessage.includes('500')) {
|
||||
addDebugInfo('Tag generation failed: Ollama server error - check model is loaded');
|
||||
userMessage = 'Ollama server error - check if model is loaded';
|
||||
} else {
|
||||
addDebugInfo(`Tag generation failed: ${errorMessage}`);
|
||||
userMessage = `Tag generation failed: ${errorMessage}`;
|
||||
}
|
||||
|
||||
// Show error to user
|
||||
toast({
|
||||
title: "Tag Generation Failed",
|
||||
description: userMessage,
|
||||
variant: "destructive",
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -434,9 +444,9 @@ ${content}`;
|
||||
const { mostFrequentLetter, mostFrequentLetterFrequency, letterCount } = calculateLetterFrequency(trimmedContent);
|
||||
const now = new Date();
|
||||
|
||||
// Generate tags using Ollama if enabled
|
||||
let tags: string[] = [];
|
||||
if (autoGenerateTags) {
|
||||
// Use current note tags or generate tags using Ollama if enabled
|
||||
let tags: string[] = currentNoteTags;
|
||||
if (autoGenerateTags && tags.length === 0) {
|
||||
addDebugInfo('Generating tags for new note...');
|
||||
tags = await generateTags(trimmedContent);
|
||||
}
|
||||
@@ -480,6 +490,7 @@ ${content}`;
|
||||
setNoteCache(prev => [newNote, ...prev]);
|
||||
setPreviousNote(newNote);
|
||||
setCurrentNoteIndex(0);
|
||||
setCurrentNoteTags([]);
|
||||
|
||||
toast({
|
||||
title: "Note saved",
|
||||
@@ -1696,28 +1707,44 @@ ${content}`;
|
||||
ref={previousNoteRef}
|
||||
className="flex-1 bg-card rounded-lg border border-border shadow-sm p-6 overflow-auto cursor-pointer select-none"
|
||||
>
|
||||
<div className={`${getTextClass('2xl')} text-muted-foreground mb-3 flex justify-between items-center`}>
|
||||
<div className={`${getTextClass('2xl')} text-muted-foreground mb-3 flex items-center gap-4`}>
|
||||
<div>
|
||||
Previous Entry {currentNoteIndex > 0 && `(${currentNoteIndex + 1} of ${noteCache.length})`}
|
||||
<span className={`ml-2 ${getTextClass('xl')} text-muted-foreground`}>Scroll to navigate</span>
|
||||
</div>
|
||||
{previousNote && !autoGenerateTags && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (previousNote) {
|
||||
addDebugInfo('Manually generating tags...');
|
||||
const tags = await generateTags(previousNote.content);
|
||||
{previousNote && (
|
||||
<div className="flex-1 flex items-center gap-2">
|
||||
<span className={`${getTextClass('base')} text-muted-foreground`}>Tags:</span>
|
||||
<input
|
||||
type="text"
|
||||
value={(previousNote.tags || []).join(', ')}
|
||||
onChange={(e) => {
|
||||
const tags = e.target.value.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
const updatedNote = { ...previousNote, tags };
|
||||
setPreviousNote(updatedNote);
|
||||
setIsPreviousNoteModified(true);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={`${getTextClass('base')} px-2 py-1`}
|
||||
>
|
||||
Generate Tags
|
||||
</Button>
|
||||
}}
|
||||
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);
|
||||
const updatedNote = { ...previousNote, tags };
|
||||
setPreviousNote(updatedNote);
|
||||
setIsPreviousNoteModified(true);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={`${getTextClass('base')} px-2 py-1 h-6`}
|
||||
>
|
||||
AI
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{previousNote ? (
|
||||
@@ -1725,15 +1752,6 @@ ${content}`;
|
||||
<div className={`${getTextClass('xl')} text-muted-foreground`}>
|
||||
{formatDate(previousNote.epochTime)}
|
||||
</div>
|
||||
{previousNote.tags && previousNote.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{previousNote.tags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className={`${getTextClass('base')} px-2 py-1`}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Textarea
|
||||
value={previousNote.content}
|
||||
onChange={(e) => {
|
||||
@@ -1745,7 +1763,7 @@ ${content}`;
|
||||
}
|
||||
}}
|
||||
onBlur={handlePreviousNoteBlur}
|
||||
className={`h-[calc(100%-${previousNote.tags && previousNote.tags.length > 0 ? '6rem' : '3rem'})] border-0 resize-none focus:ring-0 bg-transparent ${getTextClass('2xl')}`}
|
||||
className={`h-[calc(100%-3rem)] border-0 resize-none focus:ring-0 bg-transparent ${getTextClass('2xl')}`}
|
||||
placeholder="Previous entry content..."
|
||||
/>
|
||||
</div>
|
||||
@@ -1758,7 +1776,37 @@ ${content}`;
|
||||
|
||||
{/* 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`}>Current Entry</div>
|
||||
<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) => {
|
||||
const tags = e.target.value.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
setCurrentNoteTags(tags);
|
||||
}}
|
||||
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
|
||||
/>
|
||||
{!autoGenerateTags && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (currentNote.trim()) {
|
||||
addDebugInfo('Manually generating tags for current note...');
|
||||
const tags = await generateTags(currentNote);
|
||||
setCurrentNoteTags(tags);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={`${getTextClass('base')} px-2 py-1 h-6`}
|
||||
>
|
||||
AI
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Textarea
|
||||
ref={currentNoteRef}
|
||||
value={currentNote}
|
||||
@@ -1812,12 +1860,8 @@ ${content}`;
|
||||
{formatDate(note.epochTime)}
|
||||
</div>
|
||||
{note.tags && note.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{note.tags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className={`${getTextClass('base')} px-2 py-1`}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
<div className={`${getTextClass('base')} text-muted-foreground mb-3`}>
|
||||
Tags: {note.tags.join(', ')}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
Reference in New Issue
Block a user