Keep/keep-notes/lib/ai/services/notebook-summary.service.ts
sepehr 3dd48e248c fix: 8 AI services were not using configured provider
CRITICAL FIX: Auto-labels, notebook summaries, and other AI features
were not working because 8 services were calling getAIProvider() WITHOUT
passing the config parameter.

This caused them to use the default 'ollama' provider instead of
the configured OpenAI provider from the database.

ROOT CAUSE ANALYSIS:
Working features (titles):
- title-suggestions/route.ts: getAIProvider(config) ✓

Broken features (auto-labels, summaries):
- contextual-auto-tag.service.ts: getAIProvider() ✗ (2x)
- notebook-summary.service.ts: getAIProvider() ✗
- auto-label-creation.service.ts: getAIProvider() ✗
- notebook-suggestion.service.ts: getAIProvider() ✗
- batch-organization.service.ts: getAIProvider() ✗
- embedding.service.ts: getAIProvider() ✗ (2x)

FIXED: All 8 services now properly call:
  const config = await getSystemConfig()
  const provider = getAIProvider(config)

This ensures ALL AI features use the provider configured in the admin
interface (OpenAI) instead of defaulting to Ollama.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 23:34:16 +01:00

192 lines
4.9 KiB
TypeScript

import { prisma } from '@/lib/prisma'
import { getAIProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
export interface NotebookSummary {
notebookId: string
notebookName: string
notebookIcon: string | null
summary: string // Markdown formatted summary
stats: {
totalNotes: number
totalLabels: number
labelsUsed: string[]
}
generatedAt: Date
}
/**
* Service for generating AI-powered notebook summaries
* (Story 5.6 - IA6)
*/
export class NotebookSummaryService {
/**
* Generate a summary for a notebook
* @param notebookId - Notebook ID
* @param userId - User ID (for authorization)
* @returns Notebook summary or null
*/
async generateSummary(notebookId: string, userId: string): Promise<NotebookSummary | null> {
// 1. Get notebook with notes and labels
const notebook = await prisma.notebook.findFirst({
where: {
id: notebookId,
userId,
},
include: {
labels: {
select: {
id: true,
name: true,
},
},
_count: {
select: { notes: true },
},
},
})
if (!notebook) {
throw new Error('Notebook not found')
}
// Get all notes in this notebook
const notes = await prisma.note.findMany({
where: {
notebookId,
userId,
},
select: {
id: true,
title: true,
content: true,
createdAt: true,
updatedAt: true,
labelRelations: {
select: {
name: true,
},
},
},
orderBy: {
updatedAt: 'desc',
},
take: 100, // Limit to 100 most recent notes for summary
})
if (notes.length === 0) {
return null
}
// 2. Generate summary using AI
const summary = await this.generateAISummary(notes, notebook)
// 3. Get labels used in this notebook
const labelsUsed = Array.from(
new Set(
notes.flatMap(note =>
note.labelRelations.map(l => l.name)
)
)
)
return {
notebookId: notebook.id,
notebookName: notebook.name,
notebookIcon: notebook.icon,
summary,
stats: {
totalNotes: notebook._count.notes,
totalLabels: notebook.labels.length,
labelsUsed,
},
generatedAt: new Date(),
}
}
/**
* Use AI to generate notebook summary
*/
private async generateAISummary(notes: any[], notebook: any): Promise<string> {
// Build notes summary for AI
const notesSummary = notes
.map((note, index) => {
const title = note.title || 'Sans titre'
const content = note.content.substring(0, 200) // Limit content length
const labels = note.labelRelations.map((l: any) => l.name).join(', ')
const date = new Date(note.createdAt).toLocaleDateString('fr-FR')
return `[${index + 1}] **${title}** (${date})
${labels ? `Labels: ${labels}` : ''}
${content}...`
})
.join('\n\n')
const prompt = this.buildPrompt(notesSummary, notebook.name)
try {
const config = await getSystemConfig()
const provider = getAIProvider(config)
const summary = await provider.generateText(prompt)
return summary.trim()
} catch (error) {
console.error('Failed to generate notebook summary:', error)
throw error
}
}
/**
* Build prompt for AI (always in French - interface language)
*/
private buildPrompt(notesSummary: string, notebookName: string): string {
return `
Tu es un assistant qui génère des synthèses structurées de carnets de notes.
CARNET: ${notebookName}
NOTES DU CARNET:
${notesSummary}
TÂCHE:
Génère une synthèse structurée et organisée de ce carnet en analysant toutes les notes.
FORMAT DE LA RÉPONSE (Markdown avec emojis):
# 📊 Synthèse du Carnet ${notebookName}
## 🌍 Thèmes Principaux
• Identifie 3-5 thèmes récurrents ou sujets abordés
## 📝 Statistiques
• Nombre total de notes analysées
• Principales catégories de contenu
## 📅 Éléments Temporels
• Dates ou périodes importantes mentionnées
• Événements planifiés vs passés
## ⚠️ Points d'Attention / Actions Requises
• Tâches ou actions identifiées dans les notes
• Rappels ou échéances importantes
• Éléments nécessitant une attention particulière
## 💡 Insights Clés
• Résumé des informations les plus importantes
• Tendances ou patterns observés
• Connexions entre les différentes notes
RÈGLES:
- Utilise le format Markdown avec emojis comme dans l'exemple
- Sois concis et organise l'information de manière claire
- Identifie les vraies tendances, ne pas inventer d'informations
- Si une section n'est pas pertinente, utilise "N/A" ou omets-la
- Ton: professionnel mais accessible
Ta réponse :
`.trim()
}
}
// Export singleton instance
export const notebookSummaryService = new NotebookSummaryService()