Keep/keep-notes/lib/ai/services/batch-organization.service.ts
2026-02-15 17:38:16 +01:00

987 lines
30 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<OrganizationPlan> {
// 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<OrganizationPlan> {
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<string, string> = {
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<number> {
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()