import { prisma } from '@/lib/prisma' import { getAIProvider } from '@/lib/ai/factory' import { getSystemConfig } from '@/lib/config' export interface NoteForOrganization { id: string title: string | null content: string } export interface NotebookOrganization { notebookId: string notebookName: string notebookIcon: string | null notebookColor: string | null notes: Array<{ noteId: string title: string | null content: string confidence: number reason: string }> } export interface OrganizationPlan { notebooks: NotebookOrganization[] totalNotes: number unorganizedNotes: number // Notes that couldn't be categorized } /** * Service for batch organizing notes from "Notes générales" into notebooks * (Story 5.3 - IA3) */ export class BatchOrganizationService { /** * Analyze all notes in "Notes générales" and create an organization plan * @param userId - User ID * @param language - User's preferred language (default: 'en') * @returns Organization plan with notebook assignments */ async createOrganizationPlan(userId: string, language: string = 'en'): Promise { // 1. Get all notes without notebook (Inbox/Notes générales) const notesWithoutNotebook = await prisma.note.findMany({ where: { userId, notebookId: null, }, select: { id: true, title: true, content: true, }, orderBy: { updatedAt: 'desc', }, take: 50, // Limit to 50 notes for AI processing }) if (notesWithoutNotebook.length === 0) { return { notebooks: [], totalNotes: 0, unorganizedNotes: 0, } } // 2. Get all user's notebooks const notebooks = await prisma.notebook.findMany({ where: { userId }, include: { labels: true, _count: { select: { notes: true }, }, }, orderBy: { order: 'asc' }, }) if (notebooks.length === 0) { // No notebooks to organize into return { notebooks: [], totalNotes: notesWithoutNotebook.length, unorganizedNotes: notesWithoutNotebook.length, } } // 3. Call AI to create organization plan const plan = await this.aiOrganizeNotes(notesWithoutNotebook, notebooks, language) return plan } /** * Use AI to analyze notes and create organization plan */ private async aiOrganizeNotes( notes: NoteForOrganization[], notebooks: any[], language: string ): Promise { const prompt = this.buildPrompt(notes, notebooks, language) try { const config = await getSystemConfig() const provider = getAIProvider(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, } } } /** * Build prompt for AI (localized) */ private buildPrompt(notes: NoteForOrganization[], notebooks: any[], language: string = 'en'): 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') const notesList = notes .map((note, index) => { const title = note.title || 'Sans titre' const content = note.content.substring(0, 200) return `[${index}] "${title}": ${content}` }) .join('\n') // System instructions based on language const instructions: Record = { fr: ` Tu es un assistant qui organise des notes en les regroupant par thématique dans des carnets. CARNETS DISPONIBLES : ${notebookList} NOTES À ORGANISER (Notes générales) : ${notesList} TÂCHE : Analyse chaque note et propose le carnet le PLUS approprié. Considère : 1. Le sujet/thème de la note (LE PLUS IMPORTANT) 2. Les labels existants dans chaque carnet 3. La cohérence thématique entre notes du même carnet 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 FORMAT DE RÉPONSE (JSON) : Pour chaque carnet, liste les notes qui lui appartiennent : { "carnets": [ { "nom": "Nom du carnet", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Courte explication en français" } ] } ] } RÈGLES : - Seules les notes avec confiance > 0.60 doivent être assignées - Si une note est trop générique, ne l'assigne pas - Sois précis dans tes regroupements thématiques - Ta réponse doit être uniquement un JSON valide `.trim(), en: ` You are an assistant that organizes notes by grouping them into notebooks based on their theme. AVAILABLE NOTEBOOKS: ${notebookList} NOTES TO ORGANIZE (Inbox): ${notesList} TASK: Analyze each note and suggest the MOST appropriate notebook. Consider: 1. The subject/theme of the note (MOST IMPORTANT) 2. Existing labels in each notebook 3. Thematic consistency between notes in the same notebook CLASSIFICATION GUIDE: - SPORT/EXERCISE/SHOPPING/GROCERIES → Personal Notebook - HOBBIES/PASSIONS/OUTINGS → Personal Notebook - HEALTH/FITNESS/DOCTOR → Personal Notebook or Health - FAMILY/FRIENDS → Personal Notebook - WORK/MEETINGS/PROJECTS/CLIENTS → Work Notebook - CODING/TECH/DEVELOPMENT → Work Notebook or Code - FINANCE/BILLS/BANKING → Personal Notebook or Finance RESPONSE FORMAT (JSON): For each notebook, list the notes that belong to it: { "carnets": [ { "nom": "Notebook Name", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Short explanation in English" } ] } ] } RULES: - Only assign notes with confidence > 0.60 - If a note is too generic, do not assign it - Be precise in your thematic groupings - Your response must be valid JSON only `.trim(), es: ` Eres un asistente que organiza notas agrupándolas por temática en cuadernos. CUADERNOS DISPONIBLES: ${notebookList} NOTAS A ORGANIZAR (Bandeja de entrada): ${notesList} TAREA: Analiza cada nota y sugiere el cuaderno MÁS apropiado. Considera: 1. El tema/asunto de la nota (LO MÁS IMPORTANTE) 2. Etiquetas existentes en cada cuaderno 3. Coherencia temática entre notas del mismo cuaderno GUÍA DE CLASIFICACIÓN: - DEPORTE/EJERCICIO/COMPRAS → Cuaderno Personal - HOBBIES/PASIONES/SALIDAS → Cuaderno Personal - SALUD/FITNESS/DOCTOR → Cuaderno Personal o Salud - FAMILIA/AMIGOS → Cuaderno Personal - TRABAJO/REUNIONES/PROYECTOS → Cuaderno Trabajo - CODING/TECH/DESARROLLO → Cuaderno Trabajo o Código - FINANZAS/FACTURAS/BANCO → Cuaderno Personal o Finanzas FORMATO DE RESPUESTA (JSON): Para cada cuaderno, lista las notas que le pertenecen: { "carnets": [ { "nom": "Nombre del cuaderno", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Breve explicación en español" } ] } ] } REGLAS: - Solo asigna notas con confianza > 0.60 - Si una nota es demasiado genérica, no la asignes - Sé preciso en tus agrupaciones temáticas - Tu respuesta debe ser únicamente un JSON válido `.trim(), de: ` Du bist ein Assistent, der Notizen organisiert, indem er sie thematisch in Notizbücher gruppiert. VERFÜGBARE NOTIZBÜCHER: ${notebookList} ZU ORGANISIERENDE NOTIZEN (Eingang): ${notesList} AUFGABE: Analysiere jede Notiz und schlage das AM BESTEN geeignete Notizbuch vor. Berücksichtige: 1. Das Thema/den Inhalt der Notiz (AM WICHTIGSTEN) 2. Vorhandene Labels in jedem Notizbuch 3. Thematische Konsistenz zwischen Notizen im selben Notizbuch KLASSIFIZIERUNGSLEITFADEN: - SPORT/ÜBUNG/EINKAUFEN → Persönliches Notizbuch - HOBBYS/LEIDENSCHAFTEN → Persönliches Notizbuch - GESUNDHEIT/FITNESS/ARZT → Persönliches Notizbuch oder Gesundheit - FAMILIE/FREUNDE → Persönliches Notizbuch - ARBEIT/MEETINGS/PROJEKTE → Arbeitsnotizbuch - CODING/TECH/ENTWICKLUNG → Arbeitsnotizbuch oder Code - FINANZEN/RECHNUNGEN/BANK → Persönliches Notizbuch oder Finanzen ANTWORTFORMAT (JSON): Für jedes Notizbuch, liste die zugehörigen Notizen auf: { "carnets": [ { "nom": "Name des Notizbuchs", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Kurze Erklärung auf Deutsch" } ] } ] } REGELN: - Ordne nur Notizen mit Konfidenz > 0.60 - Wenn eine Notiz zu allgemein ist, ordne sie nicht zu - Sei präzise in deinen thematischen Gruppierungen - Deine Antwort muss ein gültiges JSON sein `.trim(), it: ` Sei un assistente che organizza le note raggruppandole per tema nei taccuini. TACCUINI DISPONIBILI: ${notebookList} NOTE DA ORGANIZZARE (In arrivo): ${notesList} COMPITO: Analizza ogni nota e suggerisci il taccuino PIÙ appropriato. Considera: 1. L'argomento/tema della nota (PIÙ IMPORTANTE) 2. Etichette esistenti in ogni taccuino 3. Coerenza tematica tra le note dello stesso taccuino GUIDA ALLA CLASSIFICAZIONE: - SPORT/ESERCIZIO/SHOPPING → Taccuino Personale - HOBBY/PASSIONI/USCITE → Taccuino Personale - SALUTE/FITNESS/DOTTORE → Taccuino Personale o Salute - FAMIGLIA/AMIGOS → Taccuino Personale - LAVORO/RIUNIONI/PROGETTI → Taccuino Lavoro - CODING/TECH/SVILUPPO → Taccuino Lavoro o Codice - FINANZA/BILLS/BANCA → Taccuino Personale o Finanza FORMATO RISPOSTA (JSON): Per ogni taccuino, elenca le note che appartengono ad esso: { "carnets": [ { "nom": "Nome del taccuino", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Breve spiegazione in italiano" } ] } ] } REGOLE: - Assegna solo note con confidenza > 0.60 - Se una nota è troppo generica, non assegnarla - Sii preciso nei tuoi raggruppamenti tematici - La tua risposta deve essere solo un JSON valido `.trim(), pt: ` Você é um assistente que organiza notas agrupando-as por tema em cadernos. CADERNOS DISPONÍVEIS: ${notebookList} NOTAS A ORGANIZAR (Caixa de entrada): ${notesList} TAREFA: Analise cada nota e sugira o caderno MAIS apropriado. Considere: 1. O assunto/tema da nota (MAIS IMPORTANTE) 2. Etiquetas existentes em cada caderno 3. Coerência temática entre notas do mesmo caderno GUIA DE CLASSIFICAÇÃO: - ESPORTE/EXERCÍCIO/COMPRAS → Caderno Pessoal - HOBBIES/PAIXÕES/SAÍDAS → Caderno Pessoal - SAÚDE/FITNESS/MÉDICO → Caderno Pessoal ou Saúde - FAMÍLIA/AMIGOS → Caderno Pessoal - TRABALHO/REUNIÕES/PROJETOS → Caderno Trabalho - CODING/TECH/DESENVOLVIMENTO → Caderno Trabalho ou Código - FINANÇAS/CONTAS/BANCO → Caderno Pessoal ou Finanças FORMATO DE RESPOSTA (JSON): Para cada caderno, liste as notas que pertencem a ele: { "carnets": [ { "nom": "Nome do caderno", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Breve explicação em português" } ] } ] } REGRAS: - Apenas atribua notas com confiança > 0.60 - Se uma nota for muito genérica, não a atribua - Seja preciso em seus agrupamentos temáticos - Sua resposta deve ser apenas um JSON válido `.trim(), nl: ` Je bent een assistent die notities organiseert door ze thematisch in notitieboekjes te groeperen. BESCHIKBARE NOTITIEBOEKJES: ${notebookList} TE ORGANISEREN NOTITIES (Inbox): ${notesList} TAAK: Analyseer elke notitie en stel het MEEST geschikte notitieboek voor. Overweeg: 1. Het onderwerp/thema van de notitie (BELANGRIJKST) 2. Bestaande labels in elk notitieboekje 3. Thematische consistentie tussen notities in hetzelfde notitieboekje CLASSIFICATIEGIDS: - SPORT/OEFENING/WINKELEN → Persoonlijk Notitieboek - HOBBIES/PASSIES/UITJES → Persoonlijk Notitieboek - GEZONDHEID/FITNESS/DOKTER → Persoonlijk Notitieboek of Gezondheid - FAMILIE/VRIENDEN → Persoonlijk Notitieboek - WERK/VERGADERINGEN/PROJECTEN → Werk Notitieboek - CODING/TECH/ONTWIKKELING → Werk Notitieboek of Code - FINANCIËN/REKENINGEN/BANK → Persoonlijk Notitieboek of Financiën ANTWOORDFORMAAT (JSON): Voor elk notitieboekje, lijst de notities op die erbij horen: { "carnets": [ { "nom": "Naam van het notitieboekje", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Korte uitleg in het Nederlands" } ] } ] } REGELS: - Wijs alleen notities toe met vertrouwen > 0.60 - Als een notitie te generiek is, wijs deze dan niet toe - Wees nauwkeurig in je thematische groeperingen - Je antwoord moet alleen een geldige JSON zijn `.trim(), pl: ` Jesteś asystentem, który organizuje notatki, grupując je tematycznie w notatnikach. DOSTĘPNE NOTATNIKI: ${notebookList} NOTATKI DO ZORGANIZOWANIA (Skrzynka odbiorcza): ${notesList} ZADANIE: Przeanalizuj każdą notatkę i zasugeruj NAJBARDZIEJ odpowiedni notatnik. Rozważ: 1. Temat/treść notatki (NAJWAŻNIEJSZE) 2. Istniejące etykiety w każdym notatniku 3. Spójność tematyczna między notatkami w tym samym notatniku PRZEWODNIK KLASYFIKACJI: - SPORT/ĆWICZENIA/ZAKUPY → Notatnik Osobisty - HOBBY/PASJE/WYJŚCIA → Notatnik Osobisty - ZDROWIE/FITNESS/LEKARZ → Notatnik Osobisty lub Zdrowie - RODZINA/PRZYJACIELE → Notatnik Osobisty - PRACA/SPOTKANIA/PROJEKTY → Notatnik Praca - KODOWANIE/TECH/ROZWÓJ → Notatnik Praca lub Kod - FINANSE/RACHUNKI/BANK → Notatnik Osobisty lub Finanse FORMAT ODPOWIEDZI (JSON): Dla każdego notatnika wymień należące do niego notatki: { "carnets": [ { "nom": "Nazwa notatnika", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Krótkie wyjaśnienie po polsku" } ] } ] } ZASADY: - Przypisuj tylko notatki z pewnością > 0.60 - Jeśli notatka jest zbyt ogólna, nie przypisuj jej - Bądź precyzyjny w swoich grupach tematycznych - Twoja odpowiedź musi być tylko prawidłowym JSON `.trim(), ru: ` Вы помощник, который организует заметки, группируя их по темам в блокноты. ДОСТУПНЫЕ БЛОКНОТЫ: ${notebookList} ЗАМЕТКИ ДЛЯ ОРГАНИЗАЦИИ (Входящие): ${notesList} ЗАДАЧА: Проанализируйте каждую заметку и предложите САМЫЙ подходящий блокнот. Учитывайте: 1. Тему/предмет заметки (САМОЕ ВАЖНОЕ) 2. Существующие метки в каждом блокноте 3. Тематическую согласованность между заметками в одном блокноте РУКОВОДСТВО ПО КЛАССИФИКАЦИИ: - СПОРТ/УПРАЖНЕНИЯ/ПОКУПКИ → Личный блокнот - ХОББИ/УВЛЕЧЕНИЯ/ВЫХОДЫ → Личный блокнот - ЗДОРОВЬЕ/ФИТНЕС/ВРАЧ → Личный блокнот или Здоровье - СЕМЬЯ/ДРУЗЬЯ → Личный блокнот - РАБОТА/СОВЕЩАНИЯ/ПРОЕКТЫ → Рабочий блокнот - КОДИНГ/ТЕХНОЛОГИИ/РАЗРАБОТКА → Рабочий блокнот или Код - ФИНАНСЫ/СЧЕТА/БАНК → Личный блокнот или Финансы ФОРМАТ ОТВЕТА (JSON): Для каждого блокнота перечислите заметки, которые к нему относятся: { "carnets": [ { "nom": "Название блокнота", "notes": [ { "index": 0, "confiance": 0.95, "raison": "Краткое объяснение на русском" } ] } ] } ПРАВИЛА: - Назначайте только заметки с уверенностью > 0.60 - Если заметка слишком общая, не назначайте ее - Будьте точны в своих тематических группировках - Ваш ответ должен быть только валидным JSON `.trim(), ja: ` あなたは、テーマごとにノートを分類してノートブックに整理するアシスタントです。 利用可能なノートブック: ${notebookList} 整理するノート (受信トレイ): ${notesList} タスク: 各ノートを分析し、最も適切なノートブックを提案してください。 考慮事項: 1. ノートの主題/テーマ (最も重要) 2. 各ノートブックの既存のラベル 3. 同じノートブック内のノート間のテーマの一貫性 分類ガイド: - スポーツ/運動/買い物 → 個人用ノートブック - 趣味/情熱/外出 → 個人用ノートブック - 健康/フィットネス/医師 → 個人用ノートブックまたは健康 - 家族/友人 → 個人用ノートブック - 仕事/会議/プロジェクト → 仕事用ノートブック - コーディング/技術/開発 → 仕事用ノートブックまたはコード - 金融/請求書/銀行 → 個人用ノートブックまたは金融 回答形式 (JSON): 各ノートブックについて、それに属するノートをリストアップしてください: { "carnets": [ { "nom": "ノートブック名", "notes": [ { "index": 0, "confiance": 0.95, "raison": "日本語での短い説明" } ] } ] } ルール: - 信頼度が 0.60 を超えるノートのみを割り当ててください - ノートが一般的すぎる場合は、割り当てないでください - テーマ別のグループ化において正確であってください - 回答は有効な JSON のみにしてください `.trim(), ko: ` 당신은 주제별로 노트를 분류하여 노트북으로 정리하는 도우미입니다. 사용 가능한 노트북: ${notebookList} 정리할 노트 (받은 편지함): ${notesList} 작업: 각 노트를 분석하고 가장 적절한 노트북을 제안하십시오. 고려 사항: 1. 노트의 주제/테마 (가장 중요) 2. 각 노트북의 기존 라벨 3. 동일한 노트북 내 노트 간의 주제별 일관성 분류 가이드: - 스포츠/운동/쇼핑 → 개인 노트북 - 취미/열정/외출 → 개인 노트북 - 건강/피트니스/의사 → 개인 노트북 또는 건강 - 가족/친구 → 개인 노트북 - 업무/회의/프로젝트 → 업무 노트북 - 코딩/기술/개발 → 업무 노트북 또는 코드 - 금융/청구서/은행 → 개인 노트북 또는 금융 응답 형식 (JSON): 각 노트북에 대해 속한 노트를 나열하십시오: { "carnets": [ { "nom": "노트북 이름", "notes": [ { "index": 0, "confiance": 0.95, "raison": "한국어로 된 짧은 설명" } ] } ] } 규칙: - 신뢰도가 0.60을 초과하는 노트만 할당하십시오 - 노트가 너무 일반적인 경우 할당하지 마십시오 - 주제별 그룹화에서 정확해야 합니다 - 응답은 유효한 JSON이어야 합니다 `.trim(), zh: ` 你是一个助手,负责通过按主题将笔记分组到笔记本中来整理笔记。 可用笔记本: ${notebookList} 待整理笔记(收件箱): ${notesList} 任务: 分析每个笔记并建议最合适的笔记本。 考虑: 1. 笔记的主题/题材(最重要) 2. 每个笔记本中的现有标签 3. 同一笔记本中笔记之间的主题一致性 分类指南: - 运动/锻炼/购物 → 个人笔记本 - 爱好/激情/郊游 → 个人笔记本 - 健康/健身/医生 → 个人笔记本或健康 - 家庭/朋友 → 个人笔记本 - 工作/会议/项目 → 工作笔记本 - 编码/技术/开发 → 工作笔记本或代码 - 金融/账单/银行 → 个人笔记本或金融 响应格式 (JSON): 对于每个笔记本,列出属于它的笔记: { "carnets": [ { "nom": "笔记本名称", "notes": [ { "index": 0, "confiance": 0.95, "raison": "中文简短说明" } ] } ] } 规则: - 仅分配置信度 > 0.60 的笔记 - 如果笔记太普通,请勿分配 - 在主题分组中要精确 - 您的响应必须仅为有效的 JSON `.trim(), ar: ` أنت مساعد يقوم بتنظيم الملاحظات عن طريق تجميعها حسب الموضوع في دفاتر ملاحظات. دفاتر الملاحظات المتاحة: ${notebookList} ملاحظات للتنظيم (صندوق الوارد): ${notesList} المهمة: حلل كل ملاحظة واقترح دفتر الملاحظات الأكثر ملاءمة. اعتبار: 1. موضوع/مادة الملاحظة (الأهم) 2. التسميات الموجودة في كل دفتر ملاحظات 3. الاتساق الموضوعي بين الملاحظات في نفس دفتر الملاحظات دليل التصنيف: - الرياضة/التمرين/التسوق → دفتر ملاحظات شخصي - الهوايات/الشغف/النزهات → دفتر ملاحظات شخصي - الصحة/اللياقة البدنية/الطبيب → دفتر ملاحظات شخصي أو صحة - العائلة/الأصدقاء → دفتر ملاحظات شخصي - العمل/الاجتماعات/المشاريع → دفتر ملاحظات العمل - البرمجة/التقنية/التطوير → دفتر ملاحظات العمل أو الكود - المالية/الفواتير/البنك → دفتر ملاحظات شخصي أو مالية تنسيق الاستجابة (JSON): لكل دفتر ملاحظات، ضع قائمة بالملاحظات التي تنتمي إليه: { "carnets": [ { "nom": "اسم دفتر الملاحظات", "notes": [ { "index": 0, "confiance": 0.95, "raison": "شرح قصير باللغة العربية" } ] } ] } القواعد: - عيّن فقط الملاحظات ذات الثقة > 0.60 - إذا كانت الملاحظة عامة جدًا، فلا تقم بتعيينها - كن دقيقًا في مجموعاتك الموضوعية - يجب أن تكون إجابتك بتنسيق JSON صالح فقط `.trim(), hi: ` आप एक सहायक हैं जो नोटों को विषय के आधार पर नोटबुक में समूहित करके व्यवस्थित करते हैं। उपलब्ध नोटबुक: ${notebookList} व्यवस्थित करने के लिए नोट्स (इनबॉक्स): ${notesList} कार्य: प्रत्येक नोट का विश्लेषण करें और सबसे उपयुक्त नोटबुक का सुझाव दें। विचार करें: 1. नोट का विषय/थीम (सबसे महत्वपूर्ण) 2. प्रत्येक नोटबुक में मौजूदा लेबल 3. एक ही नोटबुक में नोटों के बीच विषयगत स्थिरता वर्गीकरण गाइड: - खेल/व्यायाम/खरीदारी → व्यक्तिगत नोटबुक - शौक/जुनून/बाहर जाना → व्यक्तिगत नोटबुक - स्वास्थ्य/फिटनेस/डॉक्टर → व्यक्तिगत नोटबुक या स्वास्थ्य - परिवार/मित्र → व्यक्तिगत नोटबुक - कार्य/बैठकें/परियोजनाएं → कार्य नोटबुक - कोडिंग/तकनीक/विकास → कार्य नोटबुक या कोड - वित्त/बिल/बैंक → व्यक्तिगत नोटबुक या वित्त प्रतिक्रिया प्रारूप (JSON): प्रत्येक नोटबुक के लिए, उन नोटों को सूचीबद्ध करें जो उससे संबंधित हैं: { "carnets": [ { "nom": "नोटबुक का नाम", "notes": [ { "index": 0, "confiance": 0.95, "raison": "हिंदी में संक्षिप्त स्पष्टीकरण" } ] } ] } नियम: - केवल > 0.60 आत्मविश्वास वाले नोट्स असाइन करें - यदि कोई नोट बहुत सामान्य है, तो उसे असाइन न करें - अपने विषयगत समूहों में सटीक रहें - आपकी प्रतिक्रिया केवल वैध JSON होनी चाहिए `.trim(), fa: ` شما دستیاری هستید که یادداشت‌ها را با گروه‌بندی موضوعی در دفترچه‌ها سازماندهی می‌کنید. دفترچه‌های موجود: ${notebookList} یادداشت‌های برای سازماندهی (صندوق ورودی): ${notesList} وظیفه: هر یادداشت را تحلیل کنید و مناسب‌ترین دفترچه را پیشنهاد دهید. در نظر بگیرید: 1. موضوع/تم یادداشت (مهم‌ترین) 2. برچسب‌های موجود در هر دفترچه 3. سازگاری موضوعی بین یادداشت‌ها در همان دفترچه راهنمای طبقه‌بندی: - ورزش/تمرین/خرید → دفترچه شخصی - سرگرمی‌ها/علایق/گردش → دفترچه شخصی - سلامت/تناسب اندام/پزشک → دفترچه شخصی یا سلامت - خانواده/دوستان → دفترچه شخصی - کار/جلسات/پروژه‌ها → دفترچه کار - کدنویسی/تکنولوژی/توسعه → دفترچه کار یا کد - مالی/قبض‌ها/بانک → دفترچه شخصی یا مالی فرمت پاسخ (JSON): برای هر دفترچه، یادداشت‌هایی که به آن تعلق دارند را لیست کنید: { "carnets": [ { "nom": "نام دفترچه", "notes": [ { "index": 0, "confiance": 0.95, "raison": "توضیح کوتاه به فارسی" } ] } ] } قوانین: - فقط یادداشت‌های با اطمینان > 0.60 را اختصاص دهید - اگر یادداشتی خیلی کلی است، آن را اختصاص ندهید - در گروه‌بندی‌های موضوعی خود دقیق باشید - پاسخ شما باید فقط یک JSON معتبر باشد `.trim() } // Return instruction for requested language, fallback to English return instructions[language] || instructions['en'] || instructions['fr'] } /** * Parse AI response into OrganizationPlan */ private parseAIResponse( response: string, notes: NoteForOrganization[], 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') } const aiData = JSON.parse(jsonMatch[0]) const notebookOrganizations: NotebookOrganization[] = [] // Process each notebook in AI response for (const aiNotebook of aiData.carnets || []) { const notebook = notebooks.find(nb => nb.name === aiNotebook.nom) if (!notebook) continue const noteAssignments = aiNotebook.notes .filter((n: any) => n.confiance > 0.60) // Only high confidence .map((n: any) => { const note = notes[n.index] if (!note) return null return { noteId: note.id, title: note.title, content: note.content, confidence: n.confiance, reason: n.raison || '', } }) .filter(Boolean) if (noteAssignments.length > 0) { notebookOrganizations.push({ notebookId: notebook.id, notebookName: notebook.name, notebookIcon: notebook.icon, notebookColor: notebook.color, notes: noteAssignments, }) } } // Count unorganized notes const organizedNoteIds = new Set( notebookOrganizations.flatMap(nb => nb.notes.map(n => n.noteId)) ) const unorganizedCount = notes.length - organizedNoteIds.size return { notebooks: notebookOrganizations, totalNotes: notes.length, unorganizedNotes: unorganizedCount, } } catch (error) { console.error('Failed to parse AI response:', error) return { notebooks: [], totalNotes: notes.length, unorganizedNotes: notes.length, } } } /** * Apply the organization plan (move notes to notebooks) * @param userId - User ID * @param plan - Organization plan to apply * @param selectedNoteIds - Specific note IDs to organize (user can deselect) * @returns Number of notes moved */ async applyOrganizationPlan( userId: string, plan: OrganizationPlan, selectedNoteIds: string[] ): Promise { let movedCount = 0 for (const notebookOrg of plan.notebooks) { // Filter notes that are selected const notesToMove = notebookOrg.notes.filter(n => selectedNoteIds.includes(n.noteId) ) if (notesToMove.length === 0) continue // Move notes to notebook await prisma.note.updateMany({ where: { id: { in: notesToMove.map(n => n.noteId) }, userId, }, data: { notebookId: notebookOrg.notebookId, }, }) movedCount += notesToMove.length } return movedCount } } // Export singleton instance export const batchOrganizationService = new BatchOrganizationService()