feat(Index.tsx): enhance tag generation and search functionality

This commit is contained in:
2025-08-29 11:00:56 +02:00
parent b25103f74b
commit 5fbe98630b

View File

@@ -97,6 +97,7 @@ const Index = () => {
const [showDebugPanel, setShowDebugPanel] = useState(false); const [showDebugPanel, setShowDebugPanel] = useState(false);
const [autoGenerateTags, setAutoGenerateTags] = useState(true); 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 { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
@@ -240,7 +241,7 @@ const Index = () => {
}; };
// Generate tags using Ollama // Generate tags using Ollama
const generateTags = async (content: string): Promise<string[]> => { const generateTags = async (content: string, noteIndex?: number): Promise<string[]> => {
try { try {
const systemPrompt = `You are a helpful assistant that generates searchable tags for journal entries. const systemPrompt = `You are a helpful assistant that generates searchable tags for journal entries.
@@ -256,9 +257,29 @@ Return ONLY a comma-separated list of tags, no other text. Example: golang, test
Keep tags concise, use lowercase, and separate words with hyphens if needed.`; Keep tags concise, use lowercase, and separate words with hyphens if needed.`;
// Get context from surrounding notes
let context = '';
if (noteIndex !== undefined && noteCache.length > 0) {
const contextNotes = [];
const start = Math.max(0, noteIndex - 2);
const end = Math.min(noteCache.length, noteIndex + 3);
for (let i = start; i < end; i++) {
if (i !== noteIndex) {
const note = noteCache[i];
const date = new Date(note.epochTime).toLocaleDateString();
contextNotes.push(`[${date}] ${note.content.substring(0, 200)}${note.content.length > 200 ? '...' : ''}`);
}
}
if (contextNotes.length > 0) {
context = `\n\nContext from surrounding notes:\n${contextNotes.join('\n\n')}`;
}
}
const userPrompt = `Generate tags for this journal entry: const userPrompt = `Generate tags for this journal entry:
${content}`; ${content}${context}`;
const response = await fetchWithTiming(`${OLLAMA_ENDPOINT}/api/generate`, { const response = await fetchWithTiming(`${OLLAMA_ENDPOINT}/api/generate`, {
method: 'POST', method: 'POST',
@@ -448,7 +469,7 @@ ${content}`;
let tags: string[] = currentNoteTags; let tags: string[] = currentNoteTags;
if (autoGenerateTags && tags.length === 0) { if (autoGenerateTags && tags.length === 0) {
addDebugInfo('Generating tags for new note...'); addDebugInfo('Generating tags for new note...');
tags = await generateTags(trimmedContent); tags = await generateTags(trimmedContent, 0); // New note will be at index 0
} }
const document = { const document = {
@@ -519,7 +540,8 @@ ${content}`;
let tags = note.tags || []; let tags = note.tags || [];
if (autoGenerateTags && trimmedContent !== note.content) { if (autoGenerateTags && trimmedContent !== note.content) {
addDebugInfo('Content changed, regenerating tags...'); addDebugInfo('Content changed, regenerating tags...');
tags = await generateTags(trimmedContent); const noteIndex = noteCache.findIndex(n => n.id === note.id);
tags = await generateTags(trimmedContent, noteIndex);
} }
const document = { const document = {
@@ -767,7 +789,7 @@ ${content}`;
const searchStartTime = Date.now(); const searchStartTime = Date.now();
console.log(`Starting search for: "${query}"`); console.log(`Starting search for: "${query}"`);
addDebugInfo(`Searching for: "${query}"`); addDebugInfo(`Searching for: "${query}"${includeTagsInSearch ? ' (including tags)' : ' (content only)'}`);
try { try {
const response = await fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/search`, { const response = await fetchWithTiming(`${MEILISEARCH_ENDPOINT}/indexes/${NOTE_INDEX}/search`, {
@@ -780,7 +802,7 @@ ${content}`;
q: query, q: query,
matchingStrategy: 'all', matchingStrategy: 'all',
limit: 50, limit: 50,
attributesToHighlight: ['content', 'tags'], attributesToHighlight: includeTagsInSearch ? ['content', 'tags'] : ['content'],
showRankingScore: true, showRankingScore: true,
highlightPreTag: '<mark class="bg-yellow-200">', highlightPreTag: '<mark class="bg-yellow-200">',
highlightPostTag: '</mark>', highlightPostTag: '</mark>',
@@ -1034,9 +1056,10 @@ ${content}`;
const notes: Note[] = data.hits.map((hit: any) => mapHitToNote(hit)); const notes: Note[] = data.hits.map((hit: any) => mapHitToNote(hit));
let updatedCount = 0; let updatedCount = 0;
for (const note of notes) { for (let i = 0; i < notes.length; i++) {
const note = notes[i];
try { try {
const newTags = await generateTags(note.content); const newTags = await generateTags(note.content, i);
if (JSON.stringify(newTags) !== JSON.stringify(note.tags || [])) { if (JSON.stringify(newTags) !== JSON.stringify(note.tags || [])) {
const updatedNote = { ...note, tags: newTags }; const updatedNote = { ...note, tags: newTags };
await updateNote(updatedNote); await updateNote(updatedNote);
@@ -1727,16 +1750,16 @@ ${content}`;
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`} className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
/> />
{!autoGenerateTags && ( {!autoGenerateTags && (
<Button <Button
onClick={async () => { onClick={async () => {
if (previousNote) { if (previousNote) {
addDebugInfo('Manually generating tags...'); addDebugInfo('Manually generating tags...');
const tags = await generateTags(previousNote.content); const tags = await generateTags(previousNote.content, currentNoteIndex);
const updatedNote = { ...previousNote, tags }; const updatedNote = { ...previousNote, tags };
setPreviousNote(updatedNote); setPreviousNote(updatedNote);
setIsPreviousNoteModified(true); setIsPreviousNoteModified(true);
} }
}} }}
size="sm" size="sm"
variant="outline" variant="outline"
className={`${getTextClass('base')} px-2 py-1 h-6`} className={`${getTextClass('base')} px-2 py-1 h-6`}
@@ -1790,14 +1813,14 @@ ${content}`;
className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`} className={`flex-1 border-0 bg-transparent ${getTextClass('base')} p-0 outline-none`}
/> />
{!autoGenerateTags && ( {!autoGenerateTags && (
<Button <Button
onClick={async () => { onClick={async () => {
if (currentNote.trim()) { if (currentNote.trim()) {
addDebugInfo('Manually generating tags for current note...'); addDebugInfo('Manually generating tags for current note...');
const tags = await generateTags(currentNote); const tags = await generateTags(currentNote, 0); // Current note context
setCurrentNoteTags(tags); setCurrentNoteTags(tags);
} }
}} }}
size="sm" size="sm"
variant="outline" variant="outline"
className={`${getTextClass('base')} px-2 py-1 h-6`} className={`${getTextClass('base')} px-2 py-1 h-6`}
@@ -1840,14 +1863,24 @@ ${content}`;
<DialogTitle className={`${getTextClass('4xl')}`}>Search Notes</DialogTitle> <DialogTitle className={`${getTextClass('4xl')}`}>Search Notes</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<Input <div className="flex items-center gap-4">
ref={searchInputRef} <Input
type="text" ref={searchInputRef}
placeholder="Search your notes..." type="text"
value={searchQuery} placeholder="Search your notes..."
onChange={(e) => setSearchQuery(e.target.value)} value={searchQuery}
className={`${getTextClass('2xl')} bg-secondary border-input text-secondary-foreground`} onChange={(e) => setSearchQuery(e.target.value)}
/> className={`flex-1 ${getTextClass('2xl')} bg-secondary border-input text-secondary-foreground`}
/>
<div className="flex items-center gap-2">
<span className={`${getTextClass('base')} text-muted-foreground`}>Include tags</span>
<Switch
checked={includeTagsInSearch}
onCheckedChange={setIncludeTagsInSearch}
aria-label="Toggle tag search"
/>
</div>
</div>
<div className="overflow-auto max-h-[50vh] space-y-4"> <div className="overflow-auto max-h-[50vh] space-y-4">
{searchResults.length > 0 ? ( {searchResults.length > 0 ? (
searchResults.map((note) => ( searchResults.map((note) => (