fix(keep-notes): sidebar chevron, labels sync, batch org errors, perf guards

- Notebooks: chevron visible when expanded (remove overflow clip), functional expand state
- Labels: sync/cleanup by notebookId, reconcile after note move
- Settings: refresh notebooks after cleanup; label dialog routing
- ConnectionsBadge lazy-load; reminder check persistence; i18n keys

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-04-13 22:07:09 +02:00
parent fa7e166f3e
commit 39671c6472
16 changed files with 469 additions and 303 deletions

View File

@@ -443,15 +443,37 @@ Deine Antwort (nur JSON):
},
})
// Assign label to all suggested notes (updateMany doesn't support relations)
// Assign to notes: UI reads `Note.labels` (JSON string[]); relations must stay in sync
for (const noteId of suggestedLabel.noteIds) {
const note = await prisma.note.findFirst({
where: { id: noteId, userId, notebookId },
select: { labels: true },
})
if (!note) continue
let names: string[] = []
if (note.labels) {
try {
const parsed = JSON.parse(note.labels) as unknown
names = Array.isArray(parsed)
? parsed.filter((n): n is string => typeof n === 'string' && n.trim().length > 0)
: []
} catch {
names = []
}
}
const trimmed = suggestedLabel.name.trim()
if (!names.some((n) => n.toLowerCase() === trimmed.toLowerCase())) {
names = [...names, suggestedLabel.name]
}
await prisma.note.update({
where: { id: noteId },
data: {
labels: JSON.stringify(names),
labelRelations: {
connect: {
id: label.id,
},
connect: { id: label.id },
},
},
})

View File

@@ -1,5 +1,5 @@
import { prisma } from '@/lib/prisma'
import { getAIProvider } from '@/lib/ai/factory'
import { getTagsProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
export interface NoteForOrganization {
@@ -102,24 +102,12 @@ export class BatchOrganizationService {
): Promise<OrganizationPlan> {
const prompt = this.buildPrompt(notes, notebooks, language)
try {
const config = await getSystemConfig()
const provider = getAIProvider(config)
const response = await provider.generateText(prompt)
const config = await getSystemConfig()
const provider = getTagsProvider(config)
const response = await provider.generateText(prompt)
// Parse AI response
const plan = this.parseAIResponse(response, notes, notebooks)
return plan
} catch (error) {
console.error('Failed to create organization plan:', error)
// Return empty plan on error
return {
notebooks: [],
totalNotes: notes.length,
unorganizedNotes: notes.length,
}
}
const plan = this.parseAIResponse(response, notes, notebooks)
return plan
}
/**
@@ -870,6 +858,10 @@ ${notesList}
return instructions[language] || instructions['en'] || instructions['fr']
}
private normalizeForMatch(str: string): string {
return str.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim()
}
/**
* Parse AI response into OrganizationPlan
*/
@@ -879,7 +871,6 @@ ${notesList}
notebooks: any[]
): OrganizationPlan {
try {
// Try to parse JSON response
const jsonMatch = response.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
throw new Error('No JSON found in response')
@@ -889,9 +880,10 @@ ${notesList}
const notebookOrganizations: NotebookOrganization[] = []
// Process each notebook in AI response
for (const aiNotebook of aiData.carnets || []) {
const notebook = notebooks.find(nb => nb.name === aiNotebook.nom)
const aiName = this.normalizeForMatch(aiNotebook.nom || '')
const notebook = notebooks.find(nb => this.normalizeForMatch(nb.name) === aiName)
|| notebooks.find(nb => this.normalizeForMatch(nb.name).includes(aiName) || aiName.includes(this.normalizeForMatch(nb.name)))
if (!notebook) continue
const noteAssignments = aiNotebook.notes
@@ -933,12 +925,8 @@ ${notesList}
unorganizedNotes: unorganizedCount,
}
} catch (error) {
console.error('Failed to parse AI response:', error)
return {
notebooks: [],
totalNotes: notes.length,
unorganizedNotes: notes.length,
}
console.error('Failed to parse AI response:', error, '\nRaw response:', response.substring(0, 500))
throw new Error(`AI response parsing failed: ${error instanceof Error ? error.message : 'Invalid JSON'}`)
}
}