feat: Organisateur IA — analyse carnet + tags + doublons + regroupements
- Service notebook-organizer.service.ts : analyse IA des notes - Endpoint /api/ai/organize-notebook - Dialog avec 4 sections : 1. Résumé de l'état du carnet 2. Tags suggérés (cliquable pour appliquer) 3. Regroupements logiques par catégorie 4. Détection de doublons avec explication - Bouton 'Organiser' (Wand2) dans la barre du carnet - i18n FR/EN complet - Complète les 3 scénarios : Prof (wizard+exercices), Étudiant (wizard+planning), Ingénieur (organisateur)
This commit is contained in:
104
memento-note/lib/ai/services/notebook-organizer.service.ts
Normal file
104
memento-note/lib/ai/services/notebook-organizer.service.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { getChatProvider } from '../factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
|
||||
export interface OrganizationSuggestion {
|
||||
suggestedTags: Array<{ name: string; noteIds: string[] }>
|
||||
duplicates: Array<{ note1Id: string; note1Title: string; note2Id: string; note2Title: string; reason: string }>
|
||||
categories: Array<{ name: string; noteIds: string[]; noteTitles: string[] }>
|
||||
summary: string
|
||||
}
|
||||
|
||||
export class NotebookOrganizerService {
|
||||
async analyze(
|
||||
notes: Array<{ id: string; title: string; contentPreview: string }>,
|
||||
language?: string
|
||||
): Promise<OrganizationSuggestion> {
|
||||
const lang = language || 'fr'
|
||||
const langName = lang === 'fr' ? 'français' : lang === 'fa' ? 'فارسی' : 'English'
|
||||
|
||||
const notesList = notes.map(n =>
|
||||
`ID: ${n.id}\nTitre: ${n.title}\nAperçu: ${n.contentPreview.slice(0, 200)}`
|
||||
).join('\n---\n')
|
||||
|
||||
const prompt = `Tu es un expert en organisation de connaissances. Analyse ces notes et propose une organisation.
|
||||
|
||||
LANGUE : ${langName}
|
||||
|
||||
NOTES DANS LE CARNET :
|
||||
${notesList}
|
||||
|
||||
Analyse les notes et retourne :
|
||||
|
||||
1. **suggestedTags** — Tags pertinents à appliquer (3-8 tags). Pour chaque tag, liste les IDs des notes qui correspondent.
|
||||
2. **duplicates** — Notes qui se chevauchent ou répètent la même information. Pour chaque paire, explique pourquoi.
|
||||
3. **categories** — Regroupements logiques (2-5 catégories). Pour chaque catégorie, liste les IDs et titres des notes.
|
||||
4. **summary** — Un résumé court de l'état du carnet et des recommandations.
|
||||
|
||||
FORMAT JSON UNIQUEMENT :
|
||||
\`\`\`json
|
||||
{
|
||||
"suggestedTags": [
|
||||
{ "name": "nom-du-tag", "noteIds": ["id1", "id2"] }
|
||||
],
|
||||
"duplicates": [
|
||||
{ "note1Id": "id1", "note1Title": "...", "note2Id": "id2", "note2Title": "...", "reason": "..." }
|
||||
],
|
||||
"categories": [
|
||||
{ "name": "Nom catégorie", "noteIds": ["id1"], "noteTitles": ["Titre"] }
|
||||
],
|
||||
"summary": "Résumé court..."
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Si aucune note en double n'est trouvée, retourne un array vide pour "duplicates".`
|
||||
|
||||
const config = await getSystemConfig()
|
||||
const provider = getChatProvider(config)
|
||||
const raw = await provider.generateText(prompt)
|
||||
|
||||
return this.parseResponse(raw)
|
||||
}
|
||||
|
||||
private parseResponse(raw: string): OrganizationSuggestion {
|
||||
const jsonMatch = raw.match(/```json\s*([\s\S]+?)\s*```/)
|
||||
let jsonStr = jsonMatch ? jsonMatch[1] : raw
|
||||
|
||||
const start = jsonStr.indexOf('{')
|
||||
const end = jsonStr.lastIndexOf('}')
|
||||
if (start >= 0 && end > start) {
|
||||
jsonStr = jsonStr.slice(start, end + 1)
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr)
|
||||
return {
|
||||
suggestedTags: (parsed.suggestedTags || []).map((t: any) => ({
|
||||
name: String(t.name || ''),
|
||||
noteIds: Array.isArray(t.noteIds) ? t.noteIds.map(String) : [],
|
||||
})),
|
||||
duplicates: (parsed.duplicates || []).map((d: any) => ({
|
||||
note1Id: String(d.note1Id || ''),
|
||||
note1Title: String(d.note1Title || ''),
|
||||
note2Id: String(d.note2Id || ''),
|
||||
note2Title: String(d.note2Title || ''),
|
||||
reason: String(d.reason || ''),
|
||||
})),
|
||||
categories: (parsed.categories || []).map((c: any) => ({
|
||||
name: String(c.name || ''),
|
||||
noteIds: Array.isArray(c.noteIds) ? c.noteIds.map(String) : [],
|
||||
noteTitles: Array.isArray(c.noteTitles) ? c.noteTitles.map(String) : [],
|
||||
})),
|
||||
summary: String(parsed.summary || ''),
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
suggestedTags: [],
|
||||
duplicates: [],
|
||||
categories: [],
|
||||
summary: 'Analyse impossible — réessayer plus tard.',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const notebookOrganizerService = new NotebookOrganizerService()
|
||||
Reference in New Issue
Block a user