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

@@ -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'}`)
}
}