## Translation Files - Add 11 new language files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ missing translation keys across all 15 languages - New sections: notebook, pagination, ai.batchOrganization, ai.autoLabels - Update nav section with workspace, quickAccess, myLibrary keys ## Component Updates - Update 15+ components to use translation keys instead of hardcoded text - Components: notebook dialogs, sidebar, header, note-input, ghost-tags, etc. - Replace 80+ hardcoded English/French strings with t() calls - Ensure consistent UI across all supported languages ## Code Quality - Remove 77+ console.log statements from codebase - Clean up API routes, components, hooks, and services - Keep only essential error handling (no debugging logs) ## UI/UX Improvements - Update Keep logo to yellow post-it style (from-yellow-400 to-amber-500) - Change selection colors to #FEF3C6 (notebooks) and #EFB162 (nav items) - Make "+" button permanently visible in notebooks section - Fix grammar and syntax errors in multiple components ## Bug Fixes - Fix JSON syntax errors in it.json, nl.json, pl.json, zh.json - Fix syntax errors in notebook-suggestion-toast.tsx - Fix syntax errors in use-auto-tagging.ts - Fix syntax errors in paragraph-refactor.service.ts - Fix duplicate "fusion" section in nl.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Ou une version plus courte si vous préférez : feat(i18n): Add 15 languages, remove logs, update UI components - Create 11 new translation files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ translation keys: notebook, pagination, AI features - Update 15+ components to use translations (80+ strings) - Remove 77+ console.log statements from codebase - Fix JSON syntax errors in 4 translation files - Fix component syntax errors (toast, hooks, services) - Update logo to yellow post-it style - Change selection colors (#FEF3C6, #EFB162) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
import { prisma } from '@/lib/prisma'
|
|
import { getAIProvider } from '@/lib/ai/factory'
|
|
import type { Notebook } from '@/lib/types'
|
|
|
|
export class NotebookSuggestionService {
|
|
/**
|
|
* Suggest the most appropriate notebook for a note
|
|
* @param noteContent - Content of the note
|
|
* @param userId - User ID (for fetching user's notebooks)
|
|
* @returns Suggested notebook or null (if no good match)
|
|
*/
|
|
async suggestNotebook(noteContent: string, userId: string): Promise<Notebook | null> {
|
|
// 1. Get all notebooks for this user
|
|
const notebooks = await prisma.notebook.findMany({
|
|
where: { userId },
|
|
include: {
|
|
labels: true,
|
|
_count: {
|
|
select: { notes: true },
|
|
},
|
|
},
|
|
orderBy: { order: 'asc' },
|
|
})
|
|
|
|
if (notebooks.length === 0) {
|
|
return null // No notebooks to suggest
|
|
}
|
|
|
|
// 2. Build prompt for AI (always in French - interface language)
|
|
const prompt = this.buildPrompt(noteContent, notebooks)
|
|
|
|
// 3. Call AI
|
|
try {
|
|
const provider = getAIProvider()
|
|
|
|
const response = await provider.generateText(prompt)
|
|
|
|
const suggestedName = response.trim().toUpperCase()
|
|
|
|
// 5. Find matching notebook
|
|
const suggestedNotebook = notebooks.find(nb =>
|
|
nb.name.toUpperCase() === suggestedName
|
|
)
|
|
|
|
// If AI says "NONE" or no match, return null
|
|
if (suggestedName === 'NONE' || !suggestedNotebook) {
|
|
return null
|
|
}
|
|
|
|
return suggestedNotebook as Notebook
|
|
} catch (error) {
|
|
console.error('Failed to suggest notebook:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the AI prompt for notebook suggestion (always in French - interface language)
|
|
*/
|
|
private buildPrompt(noteContent: string, notebooks: any[]): string {
|
|
const notebookList = notebooks
|
|
.map(nb => {
|
|
const labels = nb.labels.map((l: any) => l.name).join(', ')
|
|
const count = nb._count?.notes || 0
|
|
return `- ${nb.name} (${count} notes)${labels ? ` [labels: ${labels}]` : ''}`
|
|
})
|
|
.join('\n')
|
|
|
|
return `
|
|
Tu es un assistant qui suggère à quel carnet une note devrait appartenir.
|
|
|
|
CONTENU DE LA NOTE :
|
|
${noteContent.substring(0, 500)}
|
|
|
|
CARNETS DISPONIBLES :
|
|
${notebookList}
|
|
|
|
TÂCHE :
|
|
Analyse le contenu de la note (peu importe la langue) et suggère le carnet le PLUS approprié pour cette note.
|
|
Considère :
|
|
1. Le sujet/thème de la note (LE PLUS IMPORTANT)
|
|
2. Les labels existants dans chaque carnet
|
|
3. Le nombre de notes (préfère les carnets avec du contenu connexe)
|
|
|
|
GUIDES DE CLASSIFICATION :
|
|
- SPORT/EXERCICE/ACHATS/COURSSES → Carnet Personnel
|
|
- LOISIRS/PASSIONS/SORTIES → Carnet Personnel
|
|
- SANTÉ/FITNESS/MÉDECIN → Carnet Personnel ou Santé
|
|
- FAMILLE/AMIS → Carnet Personnel
|
|
- TRAVAIL/RÉUNIONS/PROJETS/CLIENTS → Carnet Travail
|
|
- CODING/TECH/DÉVELOPPEMENT → Carnet Travail ou Code
|
|
- FINANCES/FACTURES/BANQUE → Carnet Personnel ou Finances
|
|
|
|
RÈGLES :
|
|
- Retourne SEULEMENT le nom du carnet, EXACTEMENT comme indiqué ci-dessus (insensible à la casse)
|
|
- Si aucune bonne correspondance n'existe, retourne "NONE"
|
|
- Si la note est trop générique/vague, retourne "NONE"
|
|
- N'inclus pas d'explications ou de texte supplémentaire
|
|
|
|
Exemples :
|
|
- "Réunion avec Jean sur le planning du projet" → carnet "Travail"
|
|
- "Liste de courses ou achat de vêtements" → carnet "Personnel"
|
|
- "Script Python pour analyse de données" → carnet "Code"
|
|
- "Séance de sport ou fitness" → carnet "Personnel"
|
|
- "Achat d'une chemise et d'un jean" → carnet "Personnel"
|
|
|
|
Ta suggestion :
|
|
`.trim()
|
|
}
|
|
|
|
/**
|
|
* Batch suggest notebooks for multiple notes (IA3)
|
|
* @param noteContents - Array of note contents
|
|
* @param userId - User ID
|
|
* @returns Map of note index -> suggested notebook
|
|
*/
|
|
async suggestNotebooksBatch(
|
|
noteContents: string[],
|
|
userId: string
|
|
): Promise<Map<number, Notebook | null>> {
|
|
const results = new Map<number, Notebook | null>()
|
|
|
|
// For efficiency, we could batch this into a single AI call
|
|
// For now, process sequentially (could be parallelized)
|
|
for (let i = 0; i < noteContents.length; i++) {
|
|
const suggestion = await this.suggestNotebook(noteContents[i], userId)
|
|
results.set(i, suggestion)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* Get notebook suggestion confidence score
|
|
* (For future UI enhancement: show confidence level)
|
|
*/
|
|
async suggestNotebookWithConfidence(
|
|
noteContent: string,
|
|
userId: string
|
|
): Promise<{ notebook: Notebook | null; confidence: number }> {
|
|
// This could use logprobs from OpenAI API to calculate confidence
|
|
// For now, return binary confidence
|
|
const notebook = await this.suggestNotebook(noteContent, userId)
|
|
return {
|
|
notebook,
|
|
confidence: notebook ? 0.8 : 0, // Fixed confidence for now
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const notebookSuggestionService = new NotebookSuggestionService()
|