fix: label system overhaul - sync dual storage, fix suggestions
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 53s

- Replace broken upsert (nbScope ?? '') with findFirst+create for null notebookId
- Remove aggressive orphan cleanup that deleted notebook labels after save
- Add syncNoteLabels() to update both Note.labels JSON and labelRelations
- Fix createNote, updateNote, auto-labeling to use syncNoteLabels
- Add fallback: suggestFromExistingLabels → suggestNewLabels if empty
- Lower confidence threshold 0.6→0.3, max suggestions 3→5
- Case-insensitive label matching in suggestions

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 00:00:53 +02:00
parent d906a77223
commit 0a4aa47690
2 changed files with 71 additions and 84 deletions

View File

@@ -55,10 +55,12 @@ export class ContextualAutoTagService {
// CASE 1: Notebook has existing labels → suggest from them (IA2)
if (notebook.labels.length > 0) {
return await this.suggestFromExistingLabels(noteContent, notebook, language)
const existing = await this.suggestFromExistingLabels(noteContent, notebook, language)
if (existing.length > 0) return existing
// Fallback: no existing label matched → suggest new ones too
}
// CASE 2: Notebook has NO labels → suggest NEW labels to create
// CASE 2: No labels in notebook (or none matched) → suggest NEW labels
return await this.suggestNewLabels(noteContent, notebook, language)
}
@@ -126,20 +128,23 @@ export class ContextualAutoTagService {
return []
}
// Filter and map suggestions
// Filter and map suggestions (case-insensitive)
const lowerAvailable = availableLabels.map((l: string) => l.toLowerCase())
const suggestions = suggestionsArray
.filter((s: any) => {
// Must be in available labels
return availableLabels.includes(s.label) && s.confidence > 0.6
return s.label && lowerAvailable.includes(s.label.toLowerCase()) && (s.confidence || 0) > 0.3
})
.map((s: any) => ({
label: s.label,
.map((s: any) => {
const originalLabel = availableLabels.find((l: string) => l.toLowerCase() === s.label.toLowerCase()) || s.label
return {
label: originalLabel,
confidence: Math.round(s.confidence * 100),
reasoning: s.reasoning || '',
isNewLabel: false,
}))
}
})
.sort((a: any, b: any) => b.confidence - a.confidence)
.slice(0, 3) // Max 3 suggestions
.slice(0, 5)
return suggestions as LabelSuggestion[]
} catch (error) {
@@ -213,7 +218,7 @@ export class ContextualAutoTagService {
// Filter and map suggestions
const suggestions = suggestionsArray
.filter((s: any) => {
return s.label && s.label.length > 0 && s.confidence > 0.6
return s.label && s.label.length > 0 && (s.confidence || 0) > 0.3
})
.map((s: any) => ({
label: s.label,
@@ -222,7 +227,7 @@ export class ContextualAutoTagService {
isNewLabel: true, // Mark as new label suggestion
}))
.sort((a: any, b: any) => b.confidence - a.confidence)
.slice(0, 3) // Max 3 suggestions
.slice(0, 5)
return suggestions as LabelSuggestion[]
} catch (error) {