feat(ai): localize AI features

This commit is contained in:
Sepehr Ramezani
2026-02-15 17:38:16 +01:00
parent 8f9031f076
commit 9eb3bd912a
72 changed files with 17098 additions and 7759 deletions

View File

@@ -28,7 +28,7 @@ export class AutoLabelCreationService {
* @param userId - User ID (for authorization)
* @returns Suggested labels or null if not enough notes/no patterns found
*/
async suggestLabels(notebookId: string, userId: string): Promise<AutoLabelSuggestion | null> {
async suggestLabels(notebookId: string, userId: string, language: string = 'en'): Promise<AutoLabelSuggestion | null> {
// 1. Get notebook with existing labels
const notebook = await prisma.notebook.findFirst({
where: {
@@ -84,7 +84,7 @@ export class AutoLabelCreationService {
}
// 2. Use AI to detect recurring themes
const suggestions = await this.detectRecurringThemes(notes, notebook)
const suggestions = await this.detectRecurringThemes(notes, notebook, language)
return suggestions
}
@@ -94,13 +94,14 @@ export class AutoLabelCreationService {
*/
private async detectRecurringThemes(
notes: any[],
notebook: any
notebook: any,
language: string
): Promise<AutoLabelSuggestion | null> {
const existingLabelNames = new Set<string>(
notebook.labels.map((l: any) => l.name.toLowerCase())
)
const prompt = this.buildPrompt(notes, existingLabelNames)
const prompt = this.buildPrompt(notes, existingLabelNames, language)
try {
const config = await getSystemConfig()
@@ -128,9 +129,9 @@ export class AutoLabelCreationService {
}
/**
* Build prompt for AI (always in French - interface language)
* Build prompt for AI (localized)
*/
private buildPrompt(notes: any[], existingLabelNames: Set<string>): string {
private buildPrompt(notes: any[], existingLabelNames: Set<string>, language: string = 'en'): string {
const notesSummary = notes
.map((note, index) => {
const title = note.title || 'Sans titre'
@@ -141,7 +142,8 @@ export class AutoLabelCreationService {
const existingLabels = Array.from(existingLabelNames).join(', ')
return `
const instructions: Record<string, string> = {
fr: `
Tu es un assistant qui détecte les thèmes récurrents dans des notes pour suggérer de nouvelles étiquettes.
CARNET ANALYSÉ :
@@ -182,7 +184,178 @@ Exemples de bonnes étiquettes :
- "marie", "jean", "équipe" (personnes)
Ta réponse (JSON seulement) :
`.trim(),
en: `
You are an assistant that detects recurring themes in notes to suggest new labels.
ANALYZED NOTEBOOK:
${notes.length} notes
EXISTING LABELS (do not suggest these):
${existingLabels || 'None'}
NOTEBOOK NOTES:
${notesSummary}
TASK:
Analyze the notes and detect recurring themes (keywords, subjects, places, people).
A theme must appear in at least 5 different notes to be suggested.
RESPONSE FORMAT (JSON):
{
"labels": [
{
"nom": "label_name",
"note_indices": [0, 5, 12, 23, 45],
"confiance": 0.85
}
]
}
RULES:
- Label name must be short (max 1-2 words)
- A theme must appear in 5+ notes to be suggested
- Confidence must be > 0.60
- Do not suggest labels that already exist
- Prioritize places, people, clear categories
- Maximum 5 suggestions
Examples of good labels:
- "tokyo", "kyoto", "osaka" (places)
- "hotels", "restaurants", "flights" (categories)
- "mary", "john", "team" (people)
Your response (JSON only):
`.trim(),
fa: `
شما یک دستیار هستید که تم‌های تکرارشونده در یادداشت‌ها را برای پیشنهاد برچسب‌های جدید شناسایی می‌کنید.
دفترچه‌ تحلیل شده:
${notes.length} یادداشت
برچسب‌های موجود (این‌ها را پیشنهاد ندهید):
${existingLabels || 'هیچ'}
یادداشت‌های دفترچه:
${notesSummary}
وظیفه:
یادداشت‌ها را تحلیل کنید و تم‌های تکرارشونده (کلمات کلیدی، موضوعات، مکان‌ها، افراد) را شناسایی کنید.
یک تم باید حداقل در ۵ یادداشت مختلف ظاهر شود تا پیشنهاد داده شود.
فرمت پاسخ (JSON):
{
"labels": [
{
"nom": "نام_برچسب",
"note_indices": [0, 5, 12, 23, 45],
"confiance": 0.85
}
]
}
قوانین:
- نام برچسب باید کوتاه باشد (حداکثر ۱-۲ کلمه)
- یک تم باید در ۵+ یادداشت ظاهر شود تا پیشنهاد داده شود
- اطمینان باید > 0.60 باشد
- برچسب‌هایی که قبلاً وجود دارند را پیشنهاد ندهید
- اولویت با مکان‌ها، افراد، دسته‌بندی‌های واضح است
- حداکثر ۵ پیشنهاد
مثال‌های برچسب خوب:
- "توکیو"، "کیوتو"، "اوزاکا" (مکان‌ها)
- "هتل‌ها"، "رستوران‌ها"، "پروازها" (دسته‌بندی‌ها)
- "مریم"، "علی"، "تیم" (افراد)
پاسخ شما (فقط JSON):
`.trim(),
es: `
Eres un asistente que detecta temas recurrentes en notas para sugerir nuevas etiquetas.
CUADERNO ANALIZADO:
${notes.length} notas
ETIQUETAS EXISTENTES (no sugerir estas):
${existingLabels || 'Ninguna'}
NOTAS DEL CUADERNO:
${notesSummary}
TAREA:
Analiza las notas y detecta temas recurrentes (palabras clave, temas, lugares, personas).
Un tema debe aparecer en al menos 5 notas diferentes para ser sugerido.
FORMATO DE RESPUESTA (JSON):
{
"labels": [
{
"nom": "nombre_etiqueta",
"note_indices": [0, 5, 12, 23, 45],
"confiance": 0.85
}
]
}
REGLAS:
- El nombre de la etiqueta debe ser corto (máx 1-2 palabras)
- Un tema debe aparecer en 5+ notas para ser sugerido
- La confianza debe ser > 0.60
- No sugieras etiquetas que ya existen
- Prioriza lugares, personas, categorías claras
- Máximo 5 sugerencias
Ejemplos de buenas etiquetas:
- "tokio", "kyoto", "osaka" (lugares)
- "hoteles", "restaurantes", "vuelos" (categorías)
- "maría", "juan", "equipo" (personas)
Tu respuesta (solo JSON):
`.trim(),
de: `
Du bist ein Assistent, der wiederkehrende Themen in Notizen erkennt, um neue Labels vorzuschlagen.
ANALYSIERTES NOTIZBUCH:
${notes.length} Notizen
VORHANDENE LABELS (schlage diese nicht vor):
${existingLabels || 'Keine'}
NOTIZBUCH-NOTIZEN:
${notesSummary}
AUFGABE:
Analysiere die Notizen und erkenne wiederkehrende Themen (Schlüsselwörter, Themen, Orte, Personen).
Ein Thema muss in mindestens 5 verschiedenen Notizen erscheinen, um vorgeschlagen zu werden.
ANTWORTFORMAT (JSON):
{
"labels": [
{
"nom": "label_name",
"note_indices": [0, 5, 12, 23, 45],
"confiance": 0.85
}
]
}
REGELN:
- Der Labelname muss kurz sein (max 1-2 Wörter)
- Ein Thema muss in 5+ Notizen erscheinen, um vorgeschlagen zu werden
- Konfidenz muss > 0.60 sein
- Schlage keine Labels vor, die bereits existieren
- Priorisiere Orte, Personen, klare Kategorien
- Maximal 5 Vorschläge
Beispiele für gute Labels:
- "tokio", "kyoto", "osaka" (Orte)
- "hotels", "restaurants", "flüge" (Kategorien)
- "maria", "johannes", "team" (Personen)
Deine Antwort (nur JSON):
`.trim()
}
return instructions[language] || instructions['en'] || instructions['fr']
}
/**

View File

@@ -36,9 +36,10 @@ 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): Promise<OrganizationPlan> {
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: {
@@ -86,7 +87,7 @@ export class BatchOrganizationService {
}
// 3. Call AI to create organization plan
const plan = await this.aiOrganizeNotes(notesWithoutNotebook, notebooks)
const plan = await this.aiOrganizeNotes(notesWithoutNotebook, notebooks, language)
return plan
}
@@ -96,9 +97,10 @@ export class BatchOrganizationService {
*/
private async aiOrganizeNotes(
notes: NoteForOrganization[],
notebooks: any[]
notebooks: any[],
language: string
): Promise<OrganizationPlan> {
const prompt = this.buildPrompt(notes, notebooks)
const prompt = this.buildPrompt(notes, notebooks, language)
try {
const config = await getSystemConfig()
@@ -121,9 +123,9 @@ export class BatchOrganizationService {
}
/**
* Build prompt for AI (always in French - interface language)
* Build prompt for AI (localized)
*/
private buildPrompt(notes: NoteForOrganization[], notebooks: any[]): string {
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(', ')
@@ -140,7 +142,9 @@ export class BatchOrganizationService {
})
.join('\n')
return `
// 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 :
@@ -175,7 +179,7 @@ Pour chaque carnet, liste les notes qui lui appartiennent :
{
"index": 0,
"confiance": 0.95,
"raison": "Courte explication"
"raison": "Courte explication en français"
}
]
}
@@ -186,9 +190,684 @@ 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.
Ta réponse (JSON seulement) :
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']
}
/**

View File

@@ -26,7 +26,8 @@ export class ContextualAutoTagService {
async suggestLabels(
noteContent: string,
notebookId: string | null,
userId: string
userId: string,
language: string = 'en'
): Promise<LabelSuggestion[]> {
// If no notebook, return empty (no context)
if (!notebookId) {
@@ -54,11 +55,11 @@ export class ContextualAutoTagService {
// CASE 1: Notebook has existing labels → suggest from them (IA2)
if (notebook.labels.length > 0) {
return await this.suggestFromExistingLabels(noteContent, notebook)
return await this.suggestFromExistingLabels(noteContent, notebook, language)
}
// CASE 2: Notebook has NO labels → suggest NEW labels to create
return await this.suggestNewLabels(noteContent, notebook)
return await this.suggestNewLabels(noteContent, notebook, language)
}
/**
@@ -66,12 +67,13 @@ export class ContextualAutoTagService {
*/
private async suggestFromExistingLabels(
noteContent: string,
notebook: any
notebook: any,
language: string
): Promise<LabelSuggestion[]> {
const availableLabels = notebook.labels.map((l: any) => l.name)
// Build prompt with available labels
const prompt = this.buildPrompt(noteContent, notebook.name, availableLabels)
const prompt = this.buildPrompt(noteContent, notebook.name, availableLabels, language)
try {
const config = await getSystemConfig()
@@ -151,10 +153,11 @@ export class ContextualAutoTagService {
*/
private async suggestNewLabels(
noteContent: string,
notebook: any
notebook: any,
language: string
): Promise<LabelSuggestion[]> {
// Build prompt to suggest NEW labels based on content
const prompt = this.buildNewLabelsPrompt(noteContent, notebook.name)
const prompt = this.buildNewLabelsPrompt(noteContent, notebook.name, language)
try {
const config = await getSystemConfig()
@@ -229,12 +232,13 @@ export class ContextualAutoTagService {
}
/**
* Build the AI prompt for contextual label suggestion
* Build the AI prompt for contextual label suggestion (localized)
*/
private buildPrompt(noteContent: string, notebookName: string, availableLabels: string[]): string {
private buildPrompt(noteContent: string, notebookName: string, availableLabels: string[], language: string = 'en'): string {
const labelList = availableLabels.map(l => `- ${l}`).join('\n')
return `
const instructions: Record<string, string> = {
fr: `
Tu es un assistant qui suggère les labels les plus appropriés pour une note.
CONTENU DE LA NOTE :
@@ -267,14 +271,154 @@ FORMAT DE RÉPONSE (JSON uniquement) :
}
Ta réponse :
`.trim(),
en: `
You are an assistant that suggests the most appropriate labels for a note.
NOTE CONTENT:
${noteContent.substring(0, 1000)}
CURRENT NOTEBOOK:
${notebookName}
AVAILABLE LABELS IN THIS NOTEBOOK:
${labelList}
TASK:
Analyze the note content and suggest the MOST appropriate labels from the available labels above.
Consider:
1. Label relevance to content
2. Number of labels (maximum 3 suggestions)
3. Confidence (minimum threshold: 0.6)
RULES:
- Suggest ONLY labels that are in the available labels list
- Return maximum 3 suggestions
- Each suggestion must have confidence > 0.6
- If no label is relevant, return an empty array
RESPONSE FORMAT (JSON only):
{
"suggestions": [
{ "label": "label_name", "confidence": 0.85, "reasoning": "Why this label is relevant" }
]
}
Your response:
`.trim(),
fa: `
شما یک دستیار هستید که مناسب‌ترین برچسب‌ها را برای یک یادداشت پیشنهاد می‌دهید.
محتوای یادداشت:
${noteContent.substring(0, 1000)}
دفترچه فعلی:
${notebookName}
برچسب‌های موجود در این دفترچه:
${labelList}
وظیفه:
محتوای یادداشت را تحلیل کنید و مناسب‌ترین برچسب‌ها را از لیست برچسب‌های موجود در بالا پیشنهاد دهید.
در نظر بگیرید:
1. ارتباط برچسب با محتوا
2. تعداد برچسب‌ها (حداکثر ۳ پیشنهاد)
3. اطمینان (حداقل آستانه: 0.6)
قوانین:
- فقط برچسب‌هایی را پیشنهاد دهید که در لیست برچسب‌های موجود هستند
- حداکثر ۳ پیشنهاد برگردانید
- هر پیشنهاد باید دارای اطمینان > 0.6 باشد
- اگر هیچ برچسبی مرتبط نیست، یک اینرایه خالی برگردانید
فرمت پاسخ (فقط JSON):
{
"suggestions": [
{ "label": "نام_برچسب", "confidence": 0.85, "reasoning": "چرا این برچسب مرتبط است" }
]
}
پاسخ شما:
`.trim(),
es: `
Eres un asistente que sugiere las etiquetas más apropiadas para una nota.
CONTENIDO DE LA NOTA:
${noteContent.substring(0, 1000)}
CUADERNO ACTUAL:
${notebookName}
ETIQUETAS DISPONIBLES EN ESTE CUADERNO:
${labelList}
TAREA:
Analiza el contenido de la nota y sugiere las etiquetas MÁS apropiadas de las etiquetas disponibles arriba.
Considera:
1. Relevancia de la etiqueta para el contenido
2. Número de etiquetas (máximo 3 sugerencias)
3. Confianza (umbral mínimo: 0.6)
REGLAS:
- Sugiere SOLO etiquetas que estén en la lista de etiquetas disponibles
- Devuelve máximo 3 sugerencias
- Cada sugerencia debe tener confianza > 0.6
- Si ninguna etiqueta es relevante, devuelve un array vacío
FORMATO DE RESPUESTA (solo JSON):
{
"suggestions": [
{ "label": "nombre_etiqueta", "confidence": 0.85, "reasoning": "Por qué esta etiqueta es relevante" }
]
}
Tu respuesta:
`.trim(),
de: `
Du bist ein Assistent, der die passendsten Labels für eine Notiz vorschlägt.
NOTIZINHALT:
${noteContent.substring(0, 1000)}
AKTUELLES NOTIZBUCH:
${notebookName}
VERFÜGBARE LABELS IN DIESEM NOTIZBUCH:
${labelList}
AUFGABE:
Analysiere den Notizinhalt und schlage die AM BESTEN geeigneten Labels aus den oben verfügbaren Labels vor.
Berücksichtige:
1. Relevanz des Labels für den Inhalt
2. Anzahl der Labels (maximal 3 Vorschläge)
3. Konfidenz (Mindestschwellenwert: 0.6)
REGELN:
- Schlage NUR Labels vor, die in der Liste der verfügbaren Labels sind
- Gib maximal 3 Vorschläge zurück
- Jeder Vorschlag muss eine Konfidenz > 0.6 haben
- Wenn kein Label relevant ist, gib ein leeres Array zurück
ANTWORTFORMAT (nur JSON):
{
"suggestions": [
{ "label": "label_name", "confidence": 0.85, "reasoning": "Warum dieses Label relevant ist" }
]
}
Deine Antwort:
`.trim()
}
return instructions[language] || instructions['en'] || instructions['fr']
}
/**
* Build the AI prompt for NEW label suggestions (when notebook is empty)
* Build the AI prompt for NEW label suggestions (when notebook is empty) (localized)
*/
private buildNewLabelsPrompt(noteContent: string, notebookName: string): string {
return `
private buildNewLabelsPrompt(noteContent: string, notebookName: string, language: string = 'en'): string {
const instructions: Record<string, string> = {
fr: `
Tu es un assistant qui suggère de nouveaux labels pour organiser une note.
CONTENU DE LA NOTE :
@@ -306,7 +450,141 @@ FORMAT DE RÉPONSE (JSON brut, sans markdown) :
{"suggestions":[{"label":"nom_du_label","confidence":0.85,"reasoning":"Pourquoi ce label est pertinent"}]}
Ta réponse (JSON brut uniquement) :
`.trim(),
en: `
You are an assistant that suggests new labels to organize a note.
NOTE CONTENT:
${noteContent.substring(0, 1000)}
CURRENT NOTEBOOK:
${notebookName}
CONTEXT:
This notebook has no labels yet. You must suggest the FIRST appropriate labels for this note.
TASK:
Analyze the note content and suggest 1-3 labels that would be relevant to organize this note.
Consider:
1. Topics or themes covered
2. Content type (idea, task, reference, etc.)
3. Context of the notebook "${notebookName}"
RULES:
- Labels must be SHORT (max 1-2 words)
- Labels must be lowercase
- Avoid accents if possible
- Return maximum 3 suggestions
- Each suggestion must have confidence > 0.6
IMPORTANT: Respond ONLY with valid JSON, without text before or after. No markdown, no code blocks.
RESPONSE FORMAT (raw JSON, no markdown):
{"suggestions":[{"label":"label_name","confidence":0.85,"reasoning":"Why this label is relevant"}]}
Your response (raw JSON only):
`.trim(),
fa: `
شما یک دستیار هستید که برچسب‌های جدیدی برای سازماندهی یک یادداشت پیشنهاد می‌دهید.
محتوای یادداشت:
${noteContent.substring(0, 1000)}
دفترچه فعلی:
${notebookName}
زمینه:
این دفترچه هنوز هیچ برچسبی ندارد. شما باید اولین برچسب‌های مناسب را برای این یادداشت پیشنهاد دهید.
وظیفه:
محتوای یادداشت را تحلیل کنید و ۱-۳ برچسب پیشنهاد دهید که برای سازماندهی این یادداشت مرتبط باشند.
در نظر بگیرید:
1. موضوعات یا تم‌های پوشش داده شده
2. نوع محتوا (ایده، وظیفه، مرجع و غیره)
3. زمینه دفترچه "${notebookName}"
قوانین:
- برچسب‌ها باید کوتاه باشند (حداکثر ۱-۲ کلمه)
- برچسب‌ها باید با حروف کوچک باشند
- حداکثر ۳ پیشنهاد برگردانید
- هر پیشنهاد باید دارای اطمینان > 0.6 باشد
مهم: فقط با یک JSON معتبر پاسخ دهید، بدون متن قبل یا بعد. بدون مارک‌داون، بدون بلوک کد.
فرمت پاسخ (JSON خام، بدون مارک‌داون):
{"suggestions":[{"label":"نام_برچسب","confidence":0.85,"reasoning":"چرا این برچسب مرتبط است"}]}
پاسخ شما (فقط JSON خام):
`.trim(),
es: `
Eres un asistente que sugiere nuevas etiquetas para organizar una nota.
CONTENIDO DE LA NOTA:
${noteContent.substring(0, 1000)}
CUADERNO ACTUAL:
${notebookName}
CONTEXTO:
Este cuaderno aún no tiene etiquetas. Debes sugerir las PRIMERAS etiquetas apropiadas para esta nota.
TAREA:
Analiza el contenido de la nota y sugiere 1-3 etiquetas que serían relevantes para organizar esta nota.
Considera:
1. Temas o tópicos cubiertos
2. Tipo de contenido (idea, tarea, referencia, etc.)
3. Contexto del cuaderno "${notebookName}"
REGLAS:
- Las etiquetas deben ser CORTAS (máx 1-2 palabras)
- Las etiquetas deben estar en minúsculas
- Evita acentos si es posible
- Devuelve máximo 3 sugerencias
- Cada sugerencia debe tener confianza > 0.6
IMPORTANTE: Responde SOLO con JSON válido, sin texto antes o después. Sin markdown, sin bloques de código.
FORMATO DE RESPUESTA (JSON crudo, sin markdown):
{"suggestions":[{"label":"nombre_etiqueta","confidence":0.85,"reasoning":"Por qué esta etiqueta es relevante"}]}
Tu respuesta (solo JSON crudo):
`.trim(),
de: `
Du bist ein Assistent, der neue Labels vorschlägt, um eine Notiz zu organisieren.
NOTIZINHALT:
${noteContent.substring(0, 1000)}
AKTUELLES NOTIZBUCH:
${notebookName}
KONTEXT:
Dieses Notizbuch hat noch keine Labels. Du musst die ERSTEN passenden Labels für diese Notiz vorschlagen.
AUFGABE:
Analysiere den Notizinhalt und schlage 1-3 Labels vor, die relevant wären, um diese Notiz zu organisieren.
Berücksichtige:
1. Abgedeckte Themen oder Bereiche
2. Inhaltstyp (Idee, Aufgabe, Referenz, usw.)
3. Kontext des Notizbuchs "${notebookName}"
REGELN:
- Labels müssen KURZ sein (max 1-2 Wörter)
- Labels müssen kleingeschrieben sein
- Vermeide Akzente wenn möglich
- Gib maximal 3 Vorschläge zurück
- Jeder Vorschlag muss eine Konfidenz > 0.6 haben
WICHTIG: Antworte NUR mit gültigem JSON, ohne Text davor oder danach. Kein Markdown, keine Code-Blöcke.
ANTWORTFORMAT (rohes JSON, kein Markdown):
{"suggestions":[{"label":"label_name","confidence":0.85,"reasoning":"Warum dieses Label relevant ist"}]}
Deine Antwort (nur rohes JSON):
`.trim()
}
return instructions[language] || instructions['en'] || instructions['fr']
}
}

View File

@@ -10,7 +10,7 @@ export class NotebookSuggestionService {
* @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> {
async suggestNotebook(noteContent: string, userId: string, language: string = 'en'): Promise<Notebook | null> {
// 1. Get all notebooks for this user
const notebooks = await prisma.notebook.findMany({
where: { userId },
@@ -28,7 +28,7 @@ export class NotebookSuggestionService {
}
// 2. Build prompt for AI (always in French - interface language)
const prompt = this.buildPrompt(noteContent, notebooks)
const prompt = this.buildPrompt(noteContent, notebooks, language)
// 3. Call AI
try {
@@ -57,9 +57,9 @@ export class NotebookSuggestionService {
}
/**
* Build the AI prompt for notebook suggestion (always in French - interface language)
* Build the AI prompt for notebook suggestion (localized)
*/
private buildPrompt(noteContent: string, notebooks: any[]): string {
private buildPrompt(noteContent: string, notebooks: any[], language: string = 'en'): string {
const notebookList = notebooks
.map(nb => {
const labels = nb.labels.map((l: any) => l.name).join(', ')
@@ -68,7 +68,8 @@ export class NotebookSuggestionService {
})
.join('\n')
return `
const instructions: Record<string, string> = {
fr: `
Tu es un assistant qui suggère à quel carnet une note devrait appartenir.
CONTENU DE LA NOTE :
@@ -107,7 +108,148 @@ Exemples :
- "Achat d'une chemise et d'un jean" → carnet "Personnel"
Ta suggestion :
`.trim(),
en: `
You are an assistant that suggests which notebook a note should belong to.
NOTE CONTENT:
${noteContent.substring(0, 500)}
AVAILABLE NOTEBOOKS:
${notebookList}
TASK:
Analyze the note content (regardless of language) and suggest the MOST appropriate notebook for this note.
Consider:
1. The subject/theme of the note (MOST IMPORTANT)
2. Existing labels in each notebook
3. The number of notes (prefer notebooks with related content)
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
RULES:
- Return ONLY the notebook name, EXACTLY as listed above (case insensitive)
- If no good match exists, return "NONE"
- If the note is too generic/vague, return "NONE"
- Do not include explanations or extra text
Examples:
- "Meeting with John about project planning" → notebook "Work"
- "Grocery list or buying clothes" → notebook "Personal"
- "Python script for data analysis" → notebook "Code"
- "Gym session or fitness" → notebook "Personal"
Your suggestion:
`.trim(),
fa: `
شما یک دستیار هستید که پیشنهاد می‌دهد یک یادداشت به کدام دفترچه تعلق داشته باشد.
محتوای یادداشت:
${noteContent.substring(0, 500)}
دفترچه‌های موجود:
${notebookList}
وظیفه:
محتوای یادداشت را تحلیل کنید (صرف نظر از زبان) و مناسب‌ترین دفترچه را برای این یادداشت پیشنهاد دهید.
در نظر بگیرید:
1. موضوع/تم یادداشت (مهم‌ترین)
2. برچسب‌های موجود در هر دفترچه
3. تعداد یادداشت‌ها (دفترچه‌های با محتوای مرتبط را ترجیح دهید)
راهنمای طبقه‌بندی:
- ورزش/تمرین/خرید → دفترچه شخصی
- سرگرمی‌ها/علایق/گردش → دفترچه شخصی
- سلامت/تناسب اندام/پزشک → دفترچه شخصی یا سلامت
- خانواده/دوستان → دفترچه شخصی
- کار/جلسات/پروژه‌ها/مشتریان → دفترچه کار
- کدنویسی/تکنولوژی/توسعه → دفترچه کار یا کد
- مالی/قبض‌ها/بانک → دفترچه شخصی یا مالی
قوانین:
- فقط نام دفترچه را برگردانید، دقیقاً همانطور که در بالا ذکر شده است (بدون حساسیت به حروف بزرگ و کوچک)
- اگر تطابق خوبی وجود ندارد، "NONE" را برگردانید
- اگر یادداشت خیلی کلی/مبهم است، "NONE" را برگردانید
- توضیحات یا متن اضافی را شامل نکنید
پیشناد شما:
`.trim(),
es: `
Eres un asistente que sugiere a qué cuaderno debería pertenecer una nota.
CONTENIDO DE LA NOTA:
${noteContent.substring(0, 500)}
CUADERNOS DISPONIBLES:
${notebookList}
TAREA:
Analiza el contenido de la nota (independientemente del idioma) y sugiere el cuaderno MÁS apropiado para esta nota.
Considera:
1. El tema/asunto de la nota (LO MÁS IMPORTANTE)
2. Etiquetas existentes en cada cuaderno
3. El número de notas (prefiere cuadernos con contenido relacionado)
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
REGLAS:
- Devuelve SOLO el nombre del cuaderno, EXACTAMENTE como se lista arriba (insensible a mayúsculas/minúsculas)
- Si no existe una buena coincidencia, devuelve "NONE"
- Si la nota es demasiado genérica/vaga, devuelve "NONE"
- No incluyas explicaciones o texto extra
Tu sugerencia:
`.trim(),
de: `
Du bist ein Assistent, der vorschlägt, zu welchem Notizbuch eine Notiz gehören sollte.
NOTIZINHALT:
${noteContent.substring(0, 500)}
VERFÜGBARE NOTIZBÜCHER:
${notebookList}
AUFGABE:
Analysiere den Notizinhalt (unabhängig von der Sprache) und schlage das AM BESTEN geeignete Notizbuch für diese Notiz vor.
Berücksichtige:
1. Das Thema/den Inhalt der Notiz (AM WICHTIGSTEN)
2. Vorhandene Labels in jedem Notizbuch
3. Die Anzahl der Notizen (bevorzuge Notizbücher mit verwandtem Inhalt)
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
REGELN:
- Gib NUR den Namen des Notizbuchs zurück, GENAU wie oben aufgeführt (Groß-/Kleinschreibung egal)
- Wenn keine gute Übereinstimmung existiert, gib "NONE" zurück
- Wenn die Notiz zu allgemein/vage ist, gib "NONE" zurück
- Füge keine Erklärungen oder zusätzlichen Text hinzu
Dein Vorschlag:
`.trim()
}
return instructions[language] || instructions['en'] || instructions['fr']
}
/**
@@ -118,14 +260,15 @@ Ta suggestion :
*/
async suggestNotebooksBatch(
noteContents: string[],
userId: string
userId: string,
language: string = 'en'
): 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)
const suggestion = await this.suggestNotebook(noteContents[i], userId, language)
results.set(i, suggestion)
}

View File

@@ -26,7 +26,7 @@ export class NotebookSummaryService {
* @param userId - User ID (for authorization)
* @returns Notebook summary or null
*/
async generateSummary(notebookId: string, userId: string): Promise<NotebookSummary | null> {
async generateSummary(notebookId: string, userId: string, language: string = 'en'): Promise<NotebookSummary | null> {
// 1. Get notebook with notes and labels
const notebook = await prisma.notebook.findFirst({
where: {
@@ -79,7 +79,7 @@ export class NotebookSummaryService {
}
// 2. Generate summary using AI
const summary = await this.generateAISummary(notes, notebook)
const summary = await this.generateAISummary(notes, notebook, language)
// 3. Get labels used in this notebook
const labelsUsed = Array.from(
@@ -107,7 +107,7 @@ export class NotebookSummaryService {
/**
* Use AI to generate notebook summary
*/
private async generateAISummary(notes: any[], notebook: any): Promise<string> {
private async generateAISummary(notes: any[], notebook: any, language: string): Promise<string> {
// Build notes summary for AI
const notesSummary = notes
.map((note, index) => {
@@ -122,7 +122,7 @@ ${content}...`
})
.join('\n\n')
const prompt = this.buildPrompt(notesSummary, notebook.name)
const prompt = this.buildPrompt(notesSummary, notebook.name, language)
try {
const config = await getSystemConfig()
@@ -136,10 +136,11 @@ ${content}...`
}
/**
* Build prompt for AI (always in French - interface language)
* Build prompt for AI (localized)
*/
private buildPrompt(notesSummary: string, notebookName: string): string {
return `
private buildPrompt(notesSummary: string, notebookName: string, language: string = 'en'): string {
const instructions: Record<string, string> = {
fr: `
Tu es un assistant qui génère des synthèses structurées de carnets de notes.
CARNET: ${notebookName}
@@ -181,9 +182,197 @@ RÈGLES:
- 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 DOIT ÊTRE EN FRANÇAIS
Ta réponse :
`.trim(),
en: `
You are an assistant that generates structured summaries of notebooks.
NOTEBOOK: ${notebookName}
NOTEBOOK NOTES:
${notesSummary}
TASK:
Generate a structured and organized summary of this notebook by analyzing all notes.
RESPONSE FORMAT (Markdown with emojis):
# 📊 Summary of Notebook ${notebookName}
## 🌍 Main Themes
• Identify 3-5 recurring themes or topics covered
## 📝 Statistics
• Total number of notes analyzed
• Main content categories
## 📅 Temporal Elements
• Important dates or periods mentioned
• Planned vs past events
## ⚠️ Action Items / Attention Points
• Tasks or actions identified in notes
• Important reminders or deadlines
• Items requiring special attention
## 💡 Key Insights
• Summary of most important information
• Observed trends or patterns
• Connections between different notes
RULES:
- Use Markdown format with emojis as in the example
- Be concise and organize information clearly
- Identify real trends, do not invent information
- If a section is not relevant, use "N/A" or omit it
- Tone: professional but accessible
- YOUR RESPONSE MUST BE IN ENGLISH
Your response:
`.trim(),
fa: `
شما یک دستیار هستید که خلاصه‌های ساختاریافته از دفترچه‌های یادداشت تولید می‌کنید.
دفترچه: ${notebookName}
یادداشت‌های دفترچه:
${notesSummary}
وظیفه:
یک خلاصه ساختاریافته و منظم از این دفترچه با تحلیل تمام یادداشت‌ها تولید کنید.
فرمت پاسخ (مارک‌داون با ایموجی):
# 📊 خلاصه دفترچه ${notebookName}
## 🌍 موضوعات اصلی
• ۳-۵ موضوع تکرارشونده یا مبحث پوشش داده شده را شناسایی کنید
## 📝 آمار
• تعداد کل یادداشت‌های تحلیل شده
• دسته‌بندی‌های اصلی محتوا
## 📅 عناصر زمانی
• تاریخ‌ها یا دوره‌های مهم ذکر شده
• رویدادهای برنامه‌ریزی شده در مقابل گذشته
## ⚠️ موارد اقدام / نقاط توجه
• وظایف یا اقدامات شناسایی شده در یادداشت‌ها
• یادآوری‌ها یا مهلت‌های مهم
• مواردی که نیاز به توجه ویژه دارند
## 💡 بینش‌های کلیدی
• خلاصه مهم‌ترین اطلاعات
• روندها یا الگوهای مشاهده شده
• ارتباطات بین یادداشت‌های مختلف
قوانین:
- از فرمت مارک‌داون با ایموجی مانند مثال استفاده کنید
- مختصر باشید و اطلاعات را به وضوح سازماندهی کنید
- روندهای واقعی را شناسایی کنید، اطلاعات اختراع نکنید
- اگر بخش مرتبط نیست، از "N/A" استفاده کنید یا آن را حذف کنید
- لحن: حرفه‌ای اما قابل دسترس
- پاسخ شما باید به زبان فارسی باشد
پاسخ شما:
`.trim(),
es: `
Eres un asistente que genera resúmenes estructurados de cuadernos de notas.
CUADERNO: ${notebookName}
NOTAS DEL CUADERNO:
${notesSummary}
TAREA:
Genera un resumen estructurado y organizado de este cuaderno analizando todas las notas.
FORMATO DE RESPUESTA (Markdown con emojis):
# 📊 Resumen del Cuaderno ${notebookName}
## 🌍 Temas Principales
• Identifica 3-5 temas recurrentes o tópicos cubiertos
## 📝 Estadísticas
• Número total de notas analizadas
• Categorías principales de contenido
## 📅 Elementos Temporales
• Fechas o periodos importantes mencionados
• Eventos planificados vs pasados
## ⚠️ Puntos de Atención / Acciones Requeridas
• Tareas o acciones identificadas en las notas
• Recordatorios o plazos importantes
• Elementos que requieren atención especial
## 💡 Insights Clave
• Resumen de la información más importante
• Tendencias o patrones observados
• Conexiones entre las diferentes notas
REGLAS:
- Usa formato Markdown con emojis como en el ejemplo
- Sé conciso y organiza la información claramente
- Identifica tendencias reales, no inventes información
- Si una sección no es relevante, usa "N/A" u omítela
- Tono: profesional pero accesible
- TU RESPUESTA DEBE SER EN ESPAÑOL
Tu respuesta:
`.trim(),
de: `
Du bist ein Assistent, der strukturierte Zusammenfassungen von Notizbüchern erstellt.
NOTIZBUCH: ${notebookName}
NOTIZBUCH-NOTIZEN:
${notesSummary}
AUFGABE:
Erstelle eine strukturierte und organisierte Zusammenfassung dieses Notizbuchs, indem du alle Notizen analysierst.
ANTWORTFORMAT (Markdown mit Emojis):
# 📊 Zusammenfassung des Notizbuchs ${notebookName}
## 🌍 Hauptthemen
• Identifiziere 3-5 wiederkehrende Themen
## 📝 Statistiken
• Gesamtzahl der analysierten Notizen
• Hauptinhaltkategorien
## 📅 Zeitliche Elemente
• Wichtige erwähnte Daten oder Zeiträume
• Geplante vs. vergangene Ereignisse
## ⚠️ Handlungspunkte / Aufmerksamkeitspunkte
• In Notizen identifizierte Aufgaben oder Aktionen
• Wichtige Erinnerungen oder Fristen
• Elemente, die besondere Aufmerksamkeit erfordern
## 💡 Wichtige Erkenntnisse
• Zusammenfassung der wichtigsten Informationen
• Beobachtete Trends oder Muster
• Verbindungen zwischen verschiedenen Notizen
REGELN:
- Verwende Markdown-Format mit Emojis wie im Beispiel
- Sei prägnant und organisiere Informationen klar
- Identifiziere echte Trends, erfinde keine Informationen
- Wenn ein Abschnitt nicht relevant ist, verwende "N/A" oder lass ihn weg
- Ton: professionell aber zugänglich
- DEINE ANTWORT MUSS AUF DEUTSCH SEIN
Deine Antwort:
`.trim()
}
return instructions[language] || instructions['en'] || instructions['fr']
}
}

View File

@@ -24,6 +24,15 @@ export interface Translations {
createAccount: string
rememberMe: string
orContinueWith: string
checkYourEmail: string
resetEmailSent: string
returnToLogin: string
forgotPasswordTitle: string
forgotPasswordDescription: string
sending: string
sendResetLink: string
backToLogin: string
signOut: string
}
sidebar: {
notes: string
@@ -65,6 +74,8 @@ export interface Translations {
invalidDateTime: string
reminderMustBeFuture: string
reminderSet: string
reminderPastError: string
reminderRemoved: string
addImage: string
addLink: string
linkAdded: string
@@ -92,6 +103,34 @@ export interface Translations {
noNotes: string
noNotesFound: string
createFirstNote: string
size: string
small: string
medium: string
large: string
shareWithCollaborators: string
view: string
edit: string
readOnly: string
preview: string
noContent: string
takeNote: string
takeNoteMarkdown: string
addItem: string
sharedReadOnly: string
makeCopy: string
saving: string
copySuccess: string
copyFailed: string
copy: string
markdownOn: string
markdownOff: string
undo: string
redo: string
}
pagination: {
previous: string
pageInfo: string
next: string
}
labels: {
title: string
@@ -110,9 +149,19 @@ export interface Translations {
labelName: string
labelColor: string
manageLabels: string
manageLabelsDescription: string
selectedLabels: string
allLabels: string
clearAll: string
filterByLabel: string
tagAdded: string
showLess: string
showMore: string
editLabels: string
editLabelsDescription: string
noLabelsFound: string
loading: string
notebookRequired: string
}
search: {
placeholder: string
@@ -133,6 +182,27 @@ export interface Translations {
canEdit: string
canView: string
shareNote: string
shareWithCollaborators: string
addCollaboratorDescription: string
viewerDescription: string
emailAddress: string
enterEmailAddress: string
invite: string
peopleWithAccess: string
noCollaborators: string
noCollaboratorsViewer: string
pendingInvite: string
pending: string
remove: string
unnamedUser: string
done: string
willBeAdded: string
alreadyInList: string
nowHasAccess: string
accessRevoked: string
errorLoading: string
failedToAdd: string
failedToRemove: string
}
ai: {
analyzing: string
@@ -143,6 +213,64 @@ export interface Translations {
poweredByAI: string
languageDetected: string
processing: string
tagAdded: string
titleGenerating: string
titleGenerateWithAI: string
titleGenerationMinWords: string
titleGenerationError: string
titlesGenerated: string
titleGenerationFailed: string
titleApplied: string
reformulationNoText: string
reformulationSelectionTooShort: string
reformulationMinWords: string
reformulationMaxWords: string
reformulationError: string
reformulationFailed: string
reformulationApplied: string
transformMarkdown: string
transforming: string
transformSuccess: string
transformError: string
assistant: string
generating: string
generateTitles: string
reformulateText: string
reformulating: string
clarify: string
shorten: string
improveStyle: string
reformulationComparison: string
original: string
reformulated: string
}
batchOrganization: {
error: string
noNotesSelected: string
title: string
description: string
analyzing: string
notesToOrganize: string
selected: string
noNotebooks: string
noSuggestions: string
confidence: string
unorganized: string
applying: string
apply: string
}
autoLabels: {
error: string
noLabelsSelected: string
created: string
analyzing: string
title: string
description: string
note: string
notes: string
typeContent: string
createNewLabel: string
new: string
}
titleSuggestions: {
available: string
@@ -169,6 +297,71 @@ export interface Translations {
description: string
dailyInsight: string
insightReady: string
viewConnection: string
helpful: string
notHelpful: string
dismiss: string
thanksFeedback: string
thanksFeedbackImproving: string
connections: string
connection: string
connectionsBadge: string
fused: string
overlay: {
title: string
searchPlaceholder: string
sortBy: string
sortSimilarity: string
sortRecent: string
sortOldest: string
viewAll: string
loading: string
noConnections: string
}
comparison: {
title: string
similarityInfo: string
highSimilarityInsight: string
untitled: string
clickToView: string
helpfulQuestion: string
helpful: string
notHelpful: string
}
editorSection: {
title: string
loading: string
view: string
compare: string
merge: string
compareAll: string
mergeAll: string
}
fusion: {
title: string
mergeNotes: string
notesToMerge: string
optionalPrompt: string
promptPlaceholder: string
generateFusion: string
generating: string
previewTitle: string
edit: string
modify: string
finishEditing: string
optionsTitle: string
archiveOriginals: string
keepAllTags: string
useLatestTitle: string
createBacklinks: string
cancel: string
confirmFusion: string
success: string
error: string
generateError: string
noContentReturned: string
unknownDate: string
}
}
nav: {
home: string
@@ -181,6 +374,29 @@ export interface Translations {
aiSettings: string
logout: string
login: string
adminDashboard: string
diagnostics: string
trash: string
support: string
reminders: string
userManagement: string
accountSettings: string
manageAISettings: string
configureAI: string
supportDevelopment: string
supportDescription: string
buyMeACoffee: string
donationDescription: string
donateOnKofi: string
donationNote: string
sponsorOnGithub: string
sponsorDescription: string
workspace: string
quickAccess: string
myLibrary: string
favorites: string
recent: string
proPlan: string
}
settings: {
title: string
@@ -230,6 +446,21 @@ export interface Translations {
profileError: string
accountSettings: string
manageAISettings: string
displaySettings: string
displaySettingsDescription: string
fontSize: string
selectFontSize: string
fontSizeSmall: string
fontSizeMedium: string
fontSizeLarge: string
fontSizeExtraLarge: string
fontSizeDescription: string
fontSizeUpdateSuccess: string
fontSizeUpdateFailed: string
showRecentNotes: string
showRecentNotesDescription: string
recentNotesUpdateSuccess: string
recentNotesUpdateFailed: string
}
aiSettings: {
title: string
@@ -287,6 +518,25 @@ export interface Translations {
save: string
cancel: string
}
notebook: {
create: string
createNew: string
createDescription: string
name: string
selectIcon: string
selectColor: string
cancel: string
creating: string
edit: string
editDescription: string
delete: string
deleteWarning: string
deleteConfirm: string
summary: string
summaryDescription: string
generating: string
summaryError: string
}
notebookSuggestion: {
title: string
description: string
@@ -296,6 +546,354 @@ export interface Translations {
moveToNotebook: string
generalNotes: string
}
admin: {
title: string
userManagement: string
aiTesting: string
settings: string
security: {
title: string
description: string
allowPublicRegistration: string
allowPublicRegistrationDescription: string
updateSuccess: string
updateFailed: string
}
ai: {
title: string
description: string
tagsGenerationProvider: string
tagsGenerationDescription: string
embeddingsProvider: string
embeddingsDescription: string
provider: string
baseUrl: string
model: string
apiKey: string
selectOllamaModel: string
openAIKeyDescription: string
modelRecommendations: string
commonModelsDescription: string
selectEmbeddingModel: string
commonEmbeddingModels: string
saving: string
saveSettings: string
openTestPanel: string
updateSuccess: string
updateFailed: string
providerTagsRequired: string
providerEmbeddingRequired: string
}
smtp: {
title: string
description: string
host: string
port: string
username: string
password: string
fromEmail: string
forceSSL: string
ignoreCertErrors: string
saveSettings: string
sending: string
testEmail: string
updateSuccess: string
updateFailed: string
testSuccess: string
testFailed: string
}
users: {
createUser: string
addUser: string
createUserDescription: string
name: string
email: string
password: string
role: string
createSuccess: string
createFailed: string
deleteSuccess: string
deleteFailed: string
roleUpdateSuccess: string
roleUpdateFailed: string
table: {
name: string
email: string
role: string
createdAt: string
actions: string
}
}
aiTest: {
title: string
description: string
tagsTestTitle: string
tagsTestDescription: string
embeddingsTestTitle: string
embeddingsTestDescription: string
howItWorksTitle: string
provider: string
model: string
testing: string
runTest: string
testPassed: string
testFailed: string
responseTime: string
generatedTags: string
embeddingDimensions: string
vectorDimensions: string
first5Values: string
error: string
testError: string
tipTitle: string
tipDescription: string
}
}
about: {
title: string
description: string
appName: string
appDescription: string
version: string
buildDate: string
platform: string
platformWeb: string
features: {
title: string
description: string
titleSuggestions: string
semanticSearch: string
paragraphReformulation: string
memoryEcho: string
notebookOrganization: string
dragDrop: string
labelSystem: string
multipleProviders: string
}
technology: {
title: string
description: string
frontend: string
backend: string
database: string
authentication: string
ai: string
ui: string
testing: string
}
support: {
title: string
description: string
documentation: string
reportIssues: string
feedback: string
}
}
support: {
title: string
description: string
buyMeACoffee: string
donationDescription: string
donateOnKofi: string
kofiDescription: string
sponsorOnGithub: string
sponsorDescription: string
githubDescription: string
howSupportHelps: string
directImpact: string
sponsorPerks: string
transparency: string
transparencyDescription: string
hostingServers: string
domainSSL: string
aiApiCosts: string
totalExpenses: string
otherWaysTitle: string
starGithub: string
reportBug: string
contributeCode: string
shareTwitter: string
}
demoMode: {
title: string
activated: string
deactivated: string
toggleFailed: string
description: string
parametersActive: string
similarityThreshold: string
delayBetweenNotes: string
unlimitedInsights: string
createNotesTip: string
}
resetPassword: {
title: string
description: string
invalidLinkTitle: string
invalidLinkDescription: string
requestNewLink: string
newPassword: string
confirmNewPassword: string
resetting: string
resetPassword: string
passwordMismatch: string
success: string
loading: string
}
dataManagement: {
title: string
toolsDescription: string
export: {
title: string
description: string
button: string
success: string
failed: string
}
import: {
title: string
description: string
button: string
success: string
failed: string
}
delete: {
title: string
description: string
button: string
confirm: string
success: string
failed: string
}
indexing: {
title: string
description: string
button: string
success: string
failed: string
}
cleanup: {
title: string
description: string
button: string
failed: string
}
}
appearance: {
title: string
description: string
}
generalSettings: {
title: string
description: string
}
toast: {
saved: string
saveFailed: string
operationSuccess: string
operationFailed: string
openingConnection: string
openConnectionFailed: string
thanksFeedback: string
thanksFeedbackImproving: string
feedbackFailed: string
notesFusionSuccess: string
}
testPages: {
titleSuggestions: {
title: string
contentLabel: string
placeholder: string
wordCount: string
status: string
analyzing: string
idle: string
error: string
suggestions: string
noSuggestions: string
}
}
trash: {
title: string
empty: string
restore: string
deletePermanently: string
}
footer: {
privacy: string
terms: string
openSource: string
}
connection: {
similarityInfo: string
clickToView: string
isHelpful: string
helpful: string
notHelpful: string
memoryEchoDiscovery: string
}
diagnostics: {
title: string
configuredProvider: string
apiStatus: string
testDetails: string
troubleshootingTitle: string
tip1: string
tip2: string
tip3: string
tip4: string
}
batch: {
organizeWithAI: string
organize: string
}
common: {
unknown: string
notAvailable: string
loading: string
error: string
success: string
confirm: string
cancel: string
close: string
save: string
delete: string
edit: string
add: string
remove: string
search: string
noResults: string
required: string
optional: string
}
time: {
justNow: string
minutesAgo: string
hoursAgo: string
daysAgo: string
yesterday: string
today: string
tomorrow: string
}
favorites: {
title: string
toggleSection: string
noFavorites: string
pinToFavorite: string
}
notebooks: {
create: string
allNotebooks: string
noNotebooks: string
createFirst: string
}
ui: {
close: string
open: string
expand: string
collapse: string
}
[key: string]: any
}
/**
@@ -304,12 +902,11 @@ export interface Translations {
export async function loadTranslations(language: SupportedLanguage): Promise<Translations> {
try {
const translations = await import(`@/locales/${language}.json`)
return translations.default as Translations
return translations.default as unknown as Translations
} catch (error) {
console.error(`Failed to load translations for ${language}:`, error)
// Fallback to English
const enTranslations = await import(`@/locales/en.json`)
return enTranslations.default as Translations
return enTranslations.default as unknown as Translations
}
}