diff --git a/.env.docker.example b/.env.docker.example
index c681954..f1f7ead 100644
--- a/.env.docker.example
+++ b/.env.docker.example
@@ -1,80 +1,101 @@
-# ============================================
+# =============================================================================
# Memento - Docker Environment Configuration
-# ============================================
+# =============================================================================
# Copy this file to .env.docker and update with your values.
+# This file is read by docker-compose.yml via env_file directive.
+# cp .env.docker.example .env.docker
-# ============================================
-# Application URL
-# ============================================
+# =============================================================================
+# APPLICATION URL (REQUIRED)
+# =============================================================================
# Change to your server IP or domain
+# Examples:
+# IP: http://192.168.1.190:3000
+# Domain: http://notes.yourdomain.com
+# HTTPS: https://notes.yourdomain.com
NEXTAUTH_URL="http://localhost:3000"
-# ============================================
-# Authentication Secret
-# ============================================
+# =============================================================================
+# AUTHENTICATION SECRET (REQUIRED)
+# =============================================================================
# Generate with: openssl rand -base64 32
NEXTAUTH_SECRET="changethisinproduction"
-# ============================================
-# PostgreSQL Configuration
-# ============================================
+# =============================================================================
+# REGISTRATION
+# =============================================================================
+# Set to "false" to disable public registration (default: true)
+# ALLOW_REGISTRATION=true
+
+# =============================================================================
+# POSTGRESQL CONFIGURATION
+# =============================================================================
POSTGRES_PORT=5432
POSTGRES_DB=memento
POSTGRES_USER=memento
POSTGRES_PASSWORD=memento
-# ============================================
-# MCP Server Configuration
-# ============================================
+# =============================================================================
+# MCP SERVER CONFIGURATION
+# =============================================================================
# Mode: 'stdio' (Claude Desktop, Cline) or 'sse' (N8N, HTTP)
MCP_MODE="stdio"
MCP_PORT="3001"
-# ============================================
-# AI Provider - Tags Generation
-# ============================================
+# =============================================================================
+# AI PROVIDER - TAGS GENERATION
+# =============================================================================
# Options: ollama, openai, custom
AI_PROVIDER_TAGS=ollama
AI_MODEL_TAGS="granite4:latest"
-# ============================================
-# AI Provider - Embeddings
-# ============================================
+# =============================================================================
+# AI PROVIDER - EMBEDDINGS
+# =============================================================================
# Options: ollama, openai, custom
AI_PROVIDER_EMBEDDING=ollama
AI_MODEL_EMBEDDING="embeddinggemma:latest"
-# ============================================
-# Ollama Configuration
-# ============================================
+# =============================================================================
+# AI PROVIDER - CHAT (optional, falls back to AI_PROVIDER_TAGS)
+# =============================================================================
+# AI_PROVIDER_CHAT=ollama
+# AI_MODEL_CHAT="granite4:latest"
+
+# =============================================================================
+# OLLAMA CONFIGURATION (if provider = ollama)
+# =============================================================================
# Docker service: http://ollama:11434
# Host machine: http://host.docker.internal:11434
# Remote server: http://YOUR_SERVER_IP:11434
OLLAMA_BASE_URL="http://ollama:11434"
-OLLAMA_MODEL="granite4:latest"
-# ============================================
-# OpenAI Configuration
-# ============================================
+# =============================================================================
+# OPENAI CONFIGURATION (if provider = openai)
+# =============================================================================
# OPENAI_API_KEY="sk-..."
-# ============================================
-# Custom OpenAI-Compatible Provider
-# ============================================
-# OpenRouter, Groq, Together AI, Mistral, etc.
+# =============================================================================
+# CUSTOM OPENAI-COMPATIBLE PROVIDER (if provider = custom)
+# =============================================================================
+# Compatible with: OpenRouter, Groq, Together AI, Mistral, etc.
+# OpenRouter: https://openrouter.ai/api/v1
+# Groq: https://api.groq.com/openai/v1
+# Together: https://api.together.xyz/v1
+# Mistral: https://api.mistral.ai/v1
# CUSTOM_OPENAI_API_KEY="your-api-key"
# CUSTOM_OPENAI_BASE_URL="https://openrouter.ai/api/v1"
-# ============================================
-# Email / SMTP Configuration
-# ============================================
+# =============================================================================
+# EMAIL / SMTP (optional, required for password reset)
+# =============================================================================
# SMTP_HOST="smtp.gmail.com"
# SMTP_PORT="587"
# SMTP_USER="your-email@gmail.com"
# SMTP_PASS="your-app-password"
# SMTP_FROM="noreply@memento.app"
-# ============================================
-# Application Settings
-# ============================================
-# ALLOW_REGISTRATION=true
+# =============================================================================
+# RESEND EMAIL (alternative to SMTP, optional)
+# =============================================================================
+# RESEND_API_KEY="re_..."
diff --git a/GUIDE.md b/GUIDE.md
new file mode 100644
index 0000000..9456e5d
--- /dev/null
+++ b/GUIDE.md
@@ -0,0 +1,725 @@
+# Memento - Guide d'utilisation complet
+
+## Table des matieres
+
+1. [Presentation](#presentation)
+2. [Architecture](#architecture)
+3. [Installation locale](#installation-locale)
+4. [Deploiement Docker](#deploiement-docker)
+5. [Configuration des providers IA](#configuration-des-providers-ia)
+6. [Serveur MCP (Model Context Protocol)](#serveur-mcp)
+7. [Integrations N8N](#integrations-n8n)
+8. [Configuration email (SMTP)](#configuration-email)
+9. [Administration](#administration)
+10. [Reference des variables d'environnement](#reference-des-variables-denvironnement)
+11. [Commandes utiles](#commandes-utiles)
+12. [Resolution des problemes](#resolution-des-problemes)
+
+---
+
+## Presentation
+
+**Memento** est une application de prise de notes intelligente, inspiree de Google Keep, construite avec Next.js 16, TypeScript, Tailwind CSS 4, Prisma et PostgreSQL.
+
+### Fonctionnalites principales
+
+- **Notes texte et checklists** avec couleurs personnalisees (10 themes pastel)
+- **Grille masonry responsive** avec drag-and-drop
+- **Upload d'images** avec preservation de la taille originale
+- **Notebooks et labels contextuels** pour organiser les notes
+- **Recherche semantique** (embeddings IA) + recherche plein texte
+- **Generation automatique de tags** via IA
+- **Suggestions de titre** par IA
+- **Systeme de rappels** avec notifications
+- **Mode sombre/clair** avec preference systeme
+- **Authentification** NextAuth.js (credentials, inscription, reset mot de passe)
+- **Panneau admin** pour configurer les providers IA, SMTP, etc.
+- **Serveur MCP** pour integrer avec Claude Desktop, N8N, ou tout client MCP
+- **Support i18n** (FR/EN)
+- **Export/import de donnees** (JSON)
+- **PWA** (Progressive Web App)
+
+### Stack technique
+
+| Composant | Technologie |
+|-----------|-------------|
+| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4 |
+| UI | shadcn/ui, Lucide React |
+| Backend | Next.js Server Actions, API Routes |
+| Base de donnees | PostgreSQL 16 via Prisma ORM 5 |
+| Authentification | NextAuth.js v5 |
+| IA | Vercel AI SDK (OpenAI, Ollama, custom) |
+| MCP | @modelcontextprotocol/sdk |
+| Email | Nodemailer (SMTP) ou Resend |
+| Docker | Docker Compose (postgres + memento-note + mcp-server + ollama) |
+
+---
+
+## Architecture
+
+```
+Momento/
+├── docker-compose.yml # Orchestration multi-conteneurs
+├── .env.docker.example # Template config Docker
+├── LICENSE # Apache 2.0 + Commons Clause
+├── memento-note/ # Application Next.js
+│ ├── app/ # App Router (pages, actions, API)
+│ ├── components/ # Composants React
+│ ├── lib/ # Logique metier
+│ │ ├── ai/ # Providers IA (factory pattern)
+│ │ ├── prisma.ts # Client DB
+│ │ ├── mail.ts # Envoi d'emails
+│ │ └── config.ts # Config depuis DB
+│ ├── prisma/ # Schema + migrations PostgreSQL
+│ ├── locales/ # Fichiers i18n (fr.json, en.json)
+│ ├── Dockerfile # Build multi-stage (node:22-bullseye)
+│ ├── docker-compose.yml # Standalone (postgres + app)
+│ └── .env.example # Template dev local
+├── mcp-server/ # Serveur MCP
+│ ├── index.js # Mode stdio (Claude Desktop)
+│ ├── index-sse.js # Mode SSE/HTTP (N8N, remote)
+│ ├── tools.js # Definitions des outils MCP
+│ ├── Dockerfile # Conteneur MCP (node:20-alpine)
+│ └── .env.example # Template MCP
+├── scripts/ # Scripts de migration
+└── n8n-memento-workflow.json # Workflow N8N preconfigure
+```
+
+### Flux de donnees
+
+```
+Navigateur → Next.js App Router
+ ├── Server Components (lecture)
+ ├── Server Actions (ecriture)
+ └── API Routes (REST)
+ ↓
+ Prisma ORM
+ ↓
+ PostgreSQL 16
+ ↑
+ MCP Server (stdio ou SSE)
+ ↑
+ Claude Desktop / N8N / Client MCP
+```
+
+---
+
+## Installation locale
+
+### Prerequis
+
+- Node.js 20+ (22 recommande)
+- PostgreSQL 16
+- npm
+
+### Etapes
+
+```bash
+# 1. Cloner le depot
+git clone https://github.com/votre-user/Momento.git
+cd Momento/memento-note
+
+# 2. Installer les dependances
+npm install --legacy-peer-deps
+
+# 3. Configurer l'environnement
+cp .env.example .env
+# Editer .env avec vos valeurs (DATABASE_URL, NEXTAUTH_SECRET, etc.)
+
+# 4. Creer la base de donnees et appliquer les migrations
+npx prisma migrate dev
+
+# 5. Lancer en developpement
+npm run dev
+
+# 6. Acceder a l'application
+# http://localhost:3000
+```
+
+### Premier lancement
+
+1. Creez un compte via la page d'inscription
+2. Le premier utilisateur inscrit devient automatiquement admin
+3. Accedez au panneau admin : `http://localhost:3000/admin/settings`
+
+---
+
+## Deploiement Docker
+
+### Quick Start
+
+```bash
+# 1. Cloner le depot
+git clone https://github.com/votre-user/Momento.git
+cd Momento
+
+# 2. Configurer l'environnement
+cp .env.docker.example .env.docker
+nano .env.docker
+# Modifier NEXTAUTH_URL et NEXTAUTH_SECRET (obligatoire)
+
+# 3. Lancer les conteneurs
+docker compose up -d
+
+# 4. Verifier que tout fonctionne
+docker compose ps
+docker compose logs -f memento-note
+```
+
+### Avec Ollama (IA locale)
+
+```bash
+# Ajouter le profile ollama
+docker compose --profile ollama up -d
+
+# Tirer un modele
+docker exec memento-ollama ollama pull granite4
+docker exec memento-ollama ollama pull embeddinggemma
+```
+
+### Variables Docker obligatoires
+
+| Variable | Description |
+|----------|-------------|
+| `NEXTAUTH_URL` | URL publique de l'app (ex: `http://192.168.1.190:3000`) |
+| `NEXTAUTH_SECRET` | Secret JWT - generer avec `openssl rand -base64 32` |
+
+### Ports utilises
+
+| Service | Port | Description |
+|---------|------|-------------|
+| memento-note | 3000 | Application web |
+| PostgreSQL | 5432 | Base de donnees |
+| MCP Server | 3001 | SSE mode uniquement |
+| Ollama | 11434 | IA locale (optionnel) |
+
+### Reverse Proxy (Nginx)
+
+```nginx
+server {
+ listen 80;
+ server_name notes.yourdomain.com;
+
+ location / {
+ proxy_pass http://localhost:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_cache_bypass $http_upgrade;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ client_max_body_size 10M;
+}
+```
+
+---
+
+## Configuration des providers IA
+
+Memento supporte 3 providers IA, configurables independamment pour chaque fonctionnalite (tags, embeddings, chat).
+
+### Provider 1 : Ollama (IA locale, gratuit)
+
+```bash
+# .env.docker
+AI_PROVIDER_TAGS=ollama
+AI_PROVIDER_EMBEDDING=ollama
+OLLAMA_BASE_URL="http://ollama:11434"
+AI_MODEL_TAGS="granite4:latest"
+AI_MODEL_EMBEDDING="embeddinggemma:latest"
+```
+
+Lancer avec le profile ollama :
+```bash
+docker compose --profile ollama up -d
+docker exec memento-ollama ollama pull granite4
+docker exec memento-ollama ollama pull embeddinggemma
+```
+
+**Ressources recommandees** : 8 Go RAM, 4 CPU
+
+### Provider 2 : OpenAI (cloud, payant)
+
+```bash
+# .env.docker
+AI_PROVIDER_TAGS=openai
+AI_PROVIDER_EMBEDDING=openai
+OPENAI_API_KEY="sk-votre-cle-ici"
+AI_MODEL_TAGS="gpt-4o-mini"
+AI_MODEL_EMBEDDING="text-embedding-3-small"
+```
+
+### Provider 3 : Custom (OpenRouter, Groq, Together, Mistral...)
+
+```bash
+# .env.docker
+AI_PROVIDER_TAGS=custom
+AI_PROVIDER_EMBEDDING=custom
+CUSTOM_OPENAI_API_KEY="votre-cle-api"
+CUSTOM_OPENAI_BASE_URL="https://openrouter.ai/api/v1"
+AI_MODEL_TAGS="openai/gpt-4o-mini"
+AI_MODEL_EMBEDDING="openai/text-embedding-3-small"
+```
+
+| Provider | URL de base |
+|----------|-------------|
+| OpenRouter | `https://openrouter.ai/api/v1` |
+| Groq | `https://api.groq.com/openai/v1` |
+| Together AI | `https://api.together.xyz/v1` |
+| Mistral | `https://api.mistral.ai/v1` |
+
+### Configuration mixte
+
+Vous pouvez utiliser des providers differents pour chaque fonctionnalite :
+
+```bash
+# Ollama pour les tags, OpenAI pour les embeddings
+AI_PROVIDER_TAGS=ollama
+AI_PROVIDER_EMBEDDING=openai
+OLLAMA_BASE_URL="http://ollama:11434"
+OPENAI_API_KEY="sk-..."
+AI_MODEL_TAGS="granite4:latest"
+AI_MODEL_EMBEDDING="text-embedding-3-small"
+```
+
+### Configuration via le panneau admin
+
+Les providers IA peuvent aussi etre configures depuis l'interface :
+1. Se connecter en admin
+2. Aller sur `/admin/settings`
+3. Section "AI Settings"
+4. Choisir le provider, le modele, et entrer la cle API
+5. Sauvegarder
+
+**Note** : Les parametres du panneau admin (stockes en DB) sont prioritaires sur les variables d'environnement.
+
+---
+
+## Serveur MCP
+
+Le serveur MCP (Model Context Protocol) permet aux agents IA d'interagir avec vos notes via un protocole standardise.
+
+### Outils disponibles (9 outils)
+
+| Outil | Description |
+|-------|-------------|
+| `create_note` | Creer une nouvelle note |
+| `get_notes` | Recuperer toutes les notes |
+| `get_note` | Recuperer une note specifique |
+| `update_note` | Modifier une note existante |
+| `delete_note` | Supprimer une note |
+| `search_notes` | Rechercher des notes par contenu |
+| `get_labels` | Lister les labels |
+| `toggle_pin` | Epingler/Depingler une note |
+| `toggle_archive` | Archiver/Desarchiver une note |
+
+### Mode stdio (Claude Desktop, Cline)
+
+Communication via stdin/stdout, ideal pour les clients locaux.
+
+**Configuration Claude Desktop** (`claude_desktop_config.json`) :
+```json
+{
+ "mcpServers": {
+ "memento": {
+ "command": "docker",
+ "args": ["exec", "-i", "memento-mcp", "node", "index.js"]
+ }
+ }
+}
+```
+
+### Mode SSE (N8N, HTTP)
+
+Communication via HTTP Server-Sent Events, accessible sur le reseau.
+
+```bash
+# .env.docker
+MCP_MODE="sse"
+MCP_PORT="3001"
+```
+
+Le serveur sera accessible sur `http://localhost:3001`.
+
+#### Endpoints SSE
+
+| Endpoint | Methode | Description |
+|----------|---------|-------------|
+| `/` | GET | Health check |
+| `/sse` | GET/POST | Endpoint MCP principal |
+| `/message` | POST | Envoi de messages |
+| `/sessions` | GET | Sessions actives |
+
+#### Verification
+
+```bash
+curl http://localhost:3001/
+# {"name":"Memento MCP SSE Server","version":"1.0.0","status":"running"}
+```
+
+### Configuration MCP avancee
+
+```bash
+# .env.docker
+MCP_LOG_LEVEL=info # debug, info, warn, error
+MCP_REQUEST_TIMEOUT=30000 # Timeout en ms
+MCP_REQUIRE_AUTH=false # Activer l'authentification
+MCP_API_KEY="votre-cle" # Cle API si auth active
+APP_BASE_URL="http://localhost:3000" # URL de l'app pour les liens
+```
+
+---
+
+## Integrations N8N
+
+### Configuration du noeud MCP Client
+
+1. Ajouter un noeud **"MCP Client"** dans N8N
+2. Selectionner **"HTTP Streamable"** comme transport
+3. Configurer l'endpoint : `http://memento-mcp:3001/sse` (Docker) ou `http://VOTRE_IP:3001/sse`
+
+### Workflows preconfigures
+
+Le fichier `n8n-memento-workflow.json` contient un workflow pret a l'emploi pour :
+- Creer des notes automatiquement
+- Rechercher des notes
+- Archiver les notes anciennes
+
+### Exemples d'utilisation
+
+#### Creer une note via curl (SSE mode)
+
+```bash
+curl -X POST http://localhost:3001/sse \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "name": "create_note",
+ "arguments": {
+ "title": "Ma note",
+ "content": "Contenu de la note"
+ }
+ }
+ }'
+```
+
+#### Lister les outils disponibles
+
+```bash
+curl -X POST http://localhost:3001/sse \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list",
+ "params": {}
+ }'
+```
+
+---
+
+## Configuration email
+
+L'email est necessaire pour la reinitialisation de mot de passe et les rappels.
+
+### Option 1 : SMTP
+
+```bash
+# .env.docker
+SMTP_HOST="smtp.gmail.com"
+SMTP_PORT="587"
+SMTP_USER="votre-email@gmail.com"
+SMTP_PASS="votre-mot-de-passe-app"
+SMTP_FROM="noreply@votre-domaine.com"
+```
+
+| Provider | Host | Port |
+|----------|------|------|
+| Gmail | smtp.gmail.com | 587 |
+| Outlook | smtp.office365.com | 587 |
+| SendGrid | smtp.sendgrid.net | 587 |
+| Mailgun | smtp.mailgun.org | 587 |
+
+### Option 2 : Resend
+
+```bash
+# .env.docker
+RESEND_API_KEY="re_votre-cle"
+```
+
+Les parametres `SMTP_SECURE` et `SMTP_IGNORE_CERT` sont configurables depuis le panneau admin.
+
+---
+
+## Administration
+
+### Panneau admin
+
+Accessibles uniquement par le premier utilisateur inscrit (ou les utilisateurs avec le role admin).
+
+**URL** : `/admin/settings`
+
+### Sections configurables
+
+1. **AI Settings** - Provider, modele, cle API pour tags, embeddings et chat
+2. **Email Settings** - Configuration SMTP
+3. **General Settings** - Inscription publique activee/desactivee
+
+### Gestion des utilisateurs
+
+- Les utilisateurs sont geres via `/admin`
+- Le premier utilisateur inscrit est automatiquement admin
+- L'inscription peut etre desactivee avec `ALLOW_REGISTRATION=false`
+
+---
+
+## Reference des variables d'environnement
+
+### Fichiers .env
+
+| Fichier | Usage |
+|---------|-------|
+| `.env.docker` | Configuration Docker (lu par docker-compose via env_file) |
+| `.env.docker.example` | Template pour `.env.docker` |
+| `memento-note/.env` | Configuration dev local |
+| `memento-note/.env.example` | Template pour dev local |
+| `mcp-server/.env` | Configuration du serveur MCP |
+| `mcp-server/.env.example` | Template pour MCP |
+
+### Variables completes
+
+#### Core (obligatoire)
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `DATABASE_URL` | - | Connection string PostgreSQL |
+| `NEXTAUTH_SECRET` | - | Secret JWT (`openssl rand -base64 32`) |
+| `NEXTAUTH_URL` | `http://localhost:3000` | URL publique de l'app |
+
+#### PostgreSQL (Docker)
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `POSTGRES_USER` | `memento` | Utilisateur PostgreSQL |
+| `POSTGRES_PASSWORD` | `memento` | Mot de passe PostgreSQL |
+| `POSTGRES_DB` | `memento` | Nom de la base |
+| `POSTGRES_PORT` | `5432` | Port expose |
+
+#### IA - Tags
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `AI_PROVIDER_TAGS` | - | Provider pour tags (`ollama`, `openai`, `custom`) |
+| `AI_MODEL_TAGS` | `granite4:latest` | Modele pour tags |
+
+#### IA - Embeddings
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `AI_PROVIDER_EMBEDDING` | - | Provider pour embeddings |
+| `AI_MODEL_EMBEDDING` | `embeddinggemma:latest` | Modele pour embeddings |
+
+#### IA - Chat
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `AI_PROVIDER_CHAT` | fallback Tags | Provider pour chat |
+| `AI_MODEL_CHAT` | fallback Tags | Modele pour chat |
+
+#### IA - OpenAI
+
+| Variable | Description |
+|----------|-------------|
+| `OPENAI_API_KEY` | Cle API OpenAI (`sk-...`) |
+
+#### IA - Ollama
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `OLLAMA_BASE_URL` | - | URL du service Ollama |
+
+#### IA - Custom provider
+
+| Variable | Description |
+|----------|-------------|
+| `CUSTOM_OPENAI_API_KEY` | Cle API du provider |
+| `CUSTOM_OPENAI_BASE_URL` | URL de base du provider |
+
+#### MCP Server
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `MCP_MODE` | `stdio` | Mode (`stdio` ou `sse`) |
+| `MCP_PORT` | `3001` | Port SSE |
+| `MCP_LOG_LEVEL` | `info` | Niveau de log |
+| `MCP_REQUEST_TIMEOUT` | `30000` | Timeout (ms) |
+| `MCP_REQUIRE_AUTH` | `false` | Activer authentification |
+| `MCP_API_KEY` | - | Cle API pour auth |
+| `APP_BASE_URL` | `http://localhost:3000` | URL de l'app |
+| `USER_ID` | - | Filtrer par utilisateur |
+
+#### Email
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `SMTP_HOST` | - | Serveur SMTP |
+| `SMTP_PORT` | `587` | Port SMTP |
+| `SMTP_USER` | - | Utilisateur SMTP |
+| `SMTP_PASS` | - | Mot de passe SMTP |
+| `SMTP_FROM` | `noreply@memento.app` | Email expediteur |
+| `RESEND_API_KEY` | - | Cle API Resend |
+
+#### Application
+
+| Variable | Defaut | Description |
+|----------|--------|-------------|
+| `ALLOW_REGISTRATION` | `true` | Autoriser l'inscription publique |
+| `NODE_ENV` | `development` | Environnement |
+| `PORT` | `3000` | Port de l'app |
+| `HOSTNAME` | `0.0.0.0` | Host d'ecoute |
+
+---
+
+## Commandes utiles
+
+### Docker
+
+```bash
+# Demarrer tous les services
+docker compose up -d
+
+# Avec Ollama
+docker compose --profile ollama up -d
+
+# Voir les logs
+docker compose logs -f
+docker compose logs -f memento-note # App seulement
+docker compose logs -f mcp-server # MCP seulement
+
+# Statut des services
+docker compose ps
+
+# Reconstruire apres modification
+docker compose down
+docker compose build --no-cache
+docker compose up -d
+
+# Acceder a un conteneur
+docker compose exec memento-note sh
+docker compose exec mcp-server sh
+
+# Arreter et supprimer les volumes (ATTENTION : perte de donnees)
+docker compose down -v
+```
+
+### Base de donnees
+
+```bash
+# Lancer Prisma Studio (GUI pour la DB)
+cd memento-note
+npx prisma studio
+
+# Creer une migration
+npx prisma migrate dev --name nom_de_la_migration
+
+# Appliquer les migrations en production
+npx prisma migrate deploy
+
+# Regenerer le client Prisma
+npx prisma generate
+```
+
+### Developpement
+
+```bash
+cd memento-note
+
+# Dev avec hot-reload
+npm run dev
+
+# Build production
+npm run build
+npm start
+
+# Linter
+npm run lint
+```
+
+### Deploy.sh (script de deploiement)
+
+```bash
+cd memento-note
+chmod +x deploy.sh
+
+./deploy.sh build # Construire l'image
+./deploy.sh start # Demarrer les conteneurs
+./deploy.sh logs # Voir les logs
+./deploy.sh backup # Sauvegarder la DB
+./deploy.sh update # Mettre a jour l'app
+```
+
+---
+
+## Resolution des problemes
+
+### Erreur : `ECONNREFUSED 127.0.0.1:11434`
+
+**Cause** : L'app essaie d'acceder a Ollama via localhost au lieu du service Docker.
+
+**Solution** : Verifier que `.env.docker` contient :
+```
+OLLAMA_BASE_URL="http://ollama:11434"
+```
+
+### Le provider IA ne change pas dans l'admin
+
+1. Sauvegarder les modifications dans l'admin
+2. Rafraichir la page (F5)
+3. Verifier la valeur affichee sous le dropdown
+
+### Docker ne demarre pas
+
+```bash
+# Verifier les logs
+docker compose logs memento-note
+
+# Verifier la DB
+docker compose exec postgres pg_isready -U memento
+
+# Reconstruire
+docker compose build --no-cache memento-note
+```
+
+### Mot de passe oublie sans SMTP
+
+Si vous n'avez pas configure l'email, vous pouvez reinitialiser le mot de passe manuellement :
+
+```bash
+# Se connecter a la DB
+docker compose exec postgres psql -U memento -d memento
+
+# ou en local
+cd memento-note
+npx prisma studio
+```
+
+### Les embeddings ne se generent pas
+
+1. Verifier que le provider d'embeddings est correctement configure
+2. Verifier que le modele d'embedding est disponible
+3. En admin, lancer la regeneration manuelle des embeddings
+
+---
+
+## Licence
+
+Apache License 2.0 avec Commons Clause Restriction.
+Usage personnel et educatif libre. Usage commercial interdit sans autorisation ecrite de l'auteur.
+Voir le fichier [LICENSE](LICENSE) pour les details complets.
diff --git a/docker-compose.yml b/docker-compose.yml
index 55d00bd..d3ae574 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,15 +7,15 @@ services:
container_name: memento-postgres
restart: unless-stopped
environment:
- POSTGRES_USER: keepnotes
- POSTGRES_PASSWORD: keepnotes
- POSTGRES_DB: keepnotes
+ POSTGRES_USER: ${POSTGRES_USER:-memento}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-memento}
+ POSTGRES_DB: ${POSTGRES_DB:-memento}
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
- test: ["CMD-SHELL", "pg_isready -U keepnotes"]
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-memento}"]
interval: 5s
timeout: 5s
retries: 5
@@ -35,7 +35,7 @@ services:
ports:
- "3000:3000"
environment:
- - DATABASE_URL=postgresql://keepnotes:keepnotes@postgres:5432/keepnotes
+ - DATABASE_URL=postgresql://${POSTGRES_USER:-memento}:${POSTGRES_PASSWORD:-memento}@postgres:5432/${POSTGRES_DB:-memento}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-changethisinproduction}
- NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000}
- NODE_ENV=production
@@ -51,11 +51,16 @@ services:
# AI Providers
- AI_PROVIDER_TAGS=${AI_PROVIDER_TAGS}
- AI_PROVIDER_EMBEDDING=${AI_PROVIDER_EMBEDDING}
+ - AI_PROVIDER_CHAT=${AI_PROVIDER_CHAT}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
- - OLLAMA_MODEL=${OLLAMA_MODEL}
- AI_MODEL_TAGS=${AI_MODEL_TAGS}
- AI_MODEL_EMBEDDING=${AI_MODEL_EMBEDDING}
+ - AI_MODEL_CHAT=${AI_MODEL_CHAT}
+ - CUSTOM_OPENAI_API_KEY=${CUSTOM_OPENAI_API_KEY}
+ - CUSTOM_OPENAI_BASE_URL=${CUSTOM_OPENAI_BASE_URL}
+ - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true}
+ - RESEND_API_KEY=${RESEND_API_KEY}
volumes:
- uploads-data:/app/public/uploads
depends_on:
@@ -97,7 +102,7 @@ services:
- MCP_MODE=${MCP_MODE:-stdio}
- PORT=${MCP_PORT:-3001}
# Database - connect to shared PostgreSQL
- - DATABASE_URL=postgresql://keepnotes:keepnotes@postgres:5432/keepnotes
+ - DATABASE_URL=postgresql://${POSTGRES_USER:-memento}:${POSTGRES_PASSWORD:-memento}@postgres:5432/${POSTGRES_DB:-memento}
- NODE_ENV=production
depends_on:
postgres:
diff --git a/mcp-server/.env.example b/mcp-server/.env.example
new file mode 100644
index 0000000..0bc30b6
--- /dev/null
+++ b/mcp-server/.env.example
@@ -0,0 +1,50 @@
+# =============================================================================
+# MCP Server - Environment Variables
+# =============================================================================
+# Copy this file to .env and fill in the values.
+# cp .env.example .env
+
+# =============================================================================
+# DATABASE (REQUIRED)
+# =============================================================================
+# Local development (if sharing SQLite with memento-note):
+# DATABASE_URL="file:../memento-note/prisma/dev.db"
+# PostgreSQL:
+DATABASE_URL="postgresql://memento:memento@localhost:5432/memento"
+
+# =============================================================================
+# MCP SERVER MODE
+# =============================================================================
+# 'stdio' = Standard I/O (Claude Desktop, Cline, etc.)
+# 'sse' = Server-Sent Events / HTTP (N8N, remote access, port 3001)
+MCP_MODE="stdio"
+
+# =============================================================================
+# SSE MODE SETTINGS (only if MCP_MODE="sse")
+# =============================================================================
+# Port for HTTP/SSE mode
+# PORT=3001
+
+# Request timeout in milliseconds (default: 30000)
+# MCP_REQUEST_TIMEOUT=30000
+
+# Log level: debug, info, warn, error (default: info)
+# MCP_LOG_LEVEL=info
+
+# =============================================================================
+# AUTHENTICATION (SSE mode only)
+# =============================================================================
+# Enable authentication (default: false in dev mode)
+# MCP_REQUIRE_AUTH=false
+
+# Static API key for authentication (if MCP_REQUIRE_AUTH=true)
+# MCP_API_KEY="your-secret-api-key"
+
+# =============================================================================
+# APPLICATION
+# =============================================================================
+# Base URL of the Memento web app (for generating links in responses)
+# APP_BASE_URL="http://localhost:3000"
+
+# Filter data to a specific user ID (optional, null = all users)
+# USER_ID=""
diff --git a/memento-note/.env.example b/memento-note/.env.example
new file mode 100644
index 0000000..4e567df
--- /dev/null
+++ b/memento-note/.env.example
@@ -0,0 +1,64 @@
+# =============================================================================
+# Memento Note - Environment Variables
+# Copy this file to .env and fill in the values
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Core (required)
+# -----------------------------------------------------------------------------
+DATABASE_URL="postgresql://user:password@localhost:5432/memento"
+NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32"
+NEXTAUTH_URL="http://localhost:3000"
+
+# -----------------------------------------------------------------------------
+# Registration
+# -----------------------------------------------------------------------------
+# Set to "false" to disable public registration (default: true)
+# ALLOW_REGISTRATION="true"
+
+# -----------------------------------------------------------------------------
+# AI Providers
+# -----------------------------------------------------------------------------
+# Main provider: "openai" | "ollama" | "deepseek" | "openrouter" | "custom-openai"
+# AI_PROVIDER="openai"
+
+# Per-feature provider overrides (optional, falls back to AI_PROVIDER)
+# AI_PROVIDER_CHAT="openai"
+# AI_PROVIDER_TAGS="openai"
+# AI_PROVIDER_EMBEDDING="openai"
+
+# Model names (optional, uses provider defaults)
+# AI_MODEL_CHAT="gpt-4o-mini"
+# AI_MODEL_TAGS="gpt-4o-mini"
+# AI_MODEL_EMBEDDING="text-embedding-3-small"
+
+# OpenAI
+# OPENAI_API_KEY="sk-..."
+
+# Ollama (local)
+# OLLAMA_BASE_URL="http://localhost:11434"
+
+# Custom OpenAI-compatible endpoint
+# CUSTOM_OPENAI_API_KEY="..."
+# CUSTOM_OPENAI_BASE_URL="https://your-provider.com/v1"
+
+# -----------------------------------------------------------------------------
+# Email (at least one provider required for password reset)
+# -----------------------------------------------------------------------------
+# Resend (https://resend.com)
+# RESEND_API_KEY="re_..."
+
+# SMTP
+# SMTP_HOST="smtp.example.com"
+# SMTP_PORT="587"
+# SMTP_USER=""
+# SMTP_PASS=""
+# SMTP_FROM="noreply@example.com"
+# SMTP_SECURE="false"
+# SMTP_IGNORE_CERT="false"
+
+# -----------------------------------------------------------------------------
+# MCP (Model Context Protocol)
+# -----------------------------------------------------------------------------
+# MCP_SERVER_MODE="disabled"
+# MCP_SERVER_URL=""
diff --git a/memento-note/.gitignore b/memento-note/.gitignore
index 86dde0f..9b1afb1 100644
--- a/memento-note/.gitignore
+++ b/memento-note/.gitignore
@@ -32,6 +32,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
+!.env.example
# vercel
.vercel
diff --git a/memento-note/app/(main)/settings/ai/page-new.tsx b/memento-note/app/(main)/settings/ai/page-new.tsx
deleted file mode 100644
index e44276b..0000000
--- a/memento-note/app/(main)/settings/ai/page-new.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-'use client'
-
-import { useState } from 'react'
-import { SettingsNav, SettingsSection, SettingToggle, SettingSelect, SettingInput } from '@/components/settings'
-import { updateAISettings } from '@/app/actions/ai-settings'
-import { toast } from 'sonner'
-import { useLanguage } from '@/lib/i18n'
-
-export default function AISettingsPage() {
- const { t } = useLanguage()
- const [apiKey, setApiKey] = useState('')
-
- // Mock settings state - in real implementation, load from server
- const [settings, setSettings] = useState({
- titleSuggestions: true,
- semanticSearch: true,
- paragraphRefactor: true,
- memoryEcho: true,
- memoryEchoFrequency: 'daily' as 'daily' | 'weekly' | 'custom',
- aiProvider: 'auto' as 'auto' | 'openai' | 'ollama',
- preferredLanguage: 'auto' as 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl',
- demoMode: false
- })
-
- const handleToggle = async (feature: string, value: boolean) => {
- setSettings(prev => ({ ...prev, [feature]: value }))
- try {
- await updateAISettings({ [feature]: value })
- } catch (error) {
- toast.error(t('aiSettings.error'))
- setSettings(settings) // Revert on error
- }
- }
-
- const handleFrequencyChange = async (value: string) => {
- setSettings(prev => ({ ...prev, memoryEchoFrequency: value as any }))
- try {
- await updateAISettings({ memoryEchoFrequency: value as any })
- } catch (error) {
- toast.error(t('aiSettings.error'))
- }
- }
-
- const handleProviderChange = async (value: string) => {
- setSettings(prev => ({ ...prev, aiProvider: value as any }))
- try {
- await updateAISettings({ aiProvider: value as any })
- } catch (error) {
- toast.error(t('aiSettings.error'))
- }
- }
-
- const handleApiKeyChange = async (value: string) => {
- setApiKey(value)
- // TODO: Implement API key persistence
-
- }
-
- return (
-
-
- {/* Sidebar Navigation */}
-
-
- {/* Main Content */}
-
-
-
{t('aiSettings.title')}
-
- {t('aiSettings.description')}
-
-
-
- {/* AI Provider */}
- 🤖}
- description={t('aiSettings.providerDesc')}
- >
-
-
- {settings.aiProvider === 'openai' && (
-
- )}
-
-
- {/* Feature Toggles */}
- ✨}
- description={t('aiSettings.description')}
- >
- handleToggle('titleSuggestions', checked)}
- />
-
- handleToggle('semanticSearch', checked)}
- />
-
- handleToggle('paragraphRefactor', checked)}
- />
-
- handleToggle('memoryEcho', checked)}
- />
-
- {settings.memoryEcho && (
-
- )}
-
-
- {/* Demo Mode */}
- 🎭}
- description={t('demoMode.description')}
- >
- handleToggle('demoMode', checked)}
- />
-
-
-
-
- )
-}
diff --git a/memento-note/app/(main)/settings/profile/page-new.tsx b/memento-note/app/(main)/settings/profile/page-new.tsx
deleted file mode 100644
index f81e852..0000000
--- a/memento-note/app/(main)/settings/profile/page-new.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-'use client'
-
-import { useState } from 'react'
-import { SettingsNav, SettingsSection, SettingToggle, SettingInput, SettingSelect } from '@/components/settings'
-import { updateAISettings } from '@/app/actions/ai-settings'
-import { toast } from 'sonner'
-import { useLanguage } from '@/lib/i18n'
-
-export default function ProfileSettingsPage() {
- const { t } = useLanguage()
-
- // Mock user data - in real implementation, load from server
- const [user, setUser] = useState({
- name: 'John Doe',
- email: 'john@example.com'
- })
-
- const [language, setLanguage] = useState('auto')
- const [showRecentNotes, setShowRecentNotes] = useState(false)
-
- const handleNameChange = async (value: string) => {
- setUser(prev => ({ ...prev, name: value }))
- // TODO: Implement profile update
-
- }
-
- const handleEmailChange = async (value: string) => {
- setUser(prev => ({ ...prev, email: value }))
- // TODO: Implement email update
-
- }
-
- const handleLanguageChange = async (value: string) => {
- setLanguage(value)
- try {
- await updateAISettings({ preferredLanguage: value as any })
- } catch (error) {
- console.error('Error updating language:', error)
- toast.error(t('aiSettings.error'))
- }
- }
-
- const handleRecentNotesChange = async (enabled: boolean) => {
- setShowRecentNotes(enabled)
- try {
- await updateAISettings({ showRecentNotes: enabled })
- } catch (error) {
- console.error('Error updating recent notes setting:', error)
- toast.error(t('aiSettings.error'))
- }
- }
-
- return (
-
-
- {/* Sidebar Navigation */}
-
-
- {/* Main Content */}
-
-
-
{t('profile.title')}
-
- {t('profile.description')}
-
-
-
- {/* Profile Information */}
- 👤}
- description={t('profile.description')}
- >
-
-
-
-
-
- {/* Preferences */}
- ⚙️}
- description={t('profile.languagePreferencesDescription')}
- >
-
-
-
-
-
- {/* AI Settings Link */}
-
-
-
✨
-
-
{t('aiSettings.title')}
-
- {t('aiSettings.description')}
-
-
-
-
-
-
-
-
- )
-}
diff --git a/memento-note/app/actions/auth-reset.ts b/memento-note/app/actions/auth-reset.ts
index e9498fb..f4ba1a4 100644
--- a/memento-note/app/actions/auth-reset.ts
+++ b/memento-note/app/actions/auth-reset.ts
@@ -6,7 +6,7 @@ import { getSystemConfig } from '@/lib/config'
import bcrypt from 'bcryptjs'
import { getEmailTemplate } from '@/lib/email-template'
-// Helper simple pour générer un token sans dépendance externe lourde
+// Simple helper to generate a token without heavy external dependencies
function generateToken() {
const array = new Uint8Array(32);
globalThis.crypto.getRandomValues(array);
@@ -19,7 +19,7 @@ export async function forgotPassword(email: string) {
try {
const user = await prisma.user.findUnique({ where: { email: email.toLowerCase() } });
if (!user) {
- // Pour des raisons de sécurité, on ne dit pas si l'email existe ou pas
+ // For security reasons, don't reveal whether the email exists
return { success: true };
}
@@ -34,7 +34,7 @@ export async function forgotPassword(email: string) {
}
});
- const resetLink = `${process.env.NEXTAUTH_URL || 'http://localhost:3000'}/reset-password?token=${token}`;
+ const resetLink = `${process.env.NEXTAUTH_URL}/reset-password?token=${token}`;
const html = getEmailTemplate(
"Reset your Password",
diff --git a/memento-note/app/actions/profile-broken.ts b/memento-note/app/actions/profile-broken.ts
deleted file mode 100644
index 4e0da9a..0000000
--- a/memento-note/app/actions/profile-broken.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-'use server'
-
-import { revalidatePath } from 'next/cache'
-import prisma from '@/lib/prisma'
-import { auth } from '@/auth'
-import bcrypt from 'bcryptjs'
-import { z } from 'zod'
-
-const ProfileSchema = z.object({
- name: z.string().min(2, "Name must be at least 2 characters"),
- email: z.string().email().optional(), // Email change might require verification logic, keeping it simple for now or read-only
-})
-
-const PasswordSchema = z.object({
- currentPassword: z.string().min(1, "Current password is required"),
- newPassword: z.string().min(6, "New password must be at least 6 characters"),
- confirmPassword: z.string().min(6, "Confirm password must be at least 6 characters"),
-}).refine((data) => data.newPassword === data.confirmPassword, {
- message: "Passwords don't match",
- path: ["confirmPassword"],
-})
-
-export async function updateProfile(data: { name: string }) {
- const session = await auth()
- if (!session?.user?.id) throw new Error('Unauthorized')
-
- const validated = ProfileSchema.safeParse(data)
- if (!validated.success) {
- return { error: validated.error.flatten().fieldErrors }
- }
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { name: validated.data.name },
- })
- revalidatePath('/settings/profile')
- return { success: true }
- } catch (error) {
- return { error: { _form: ['Failed to update profile'] } }
- }
-}
-
-export async function changePassword(formData: FormData) {
- const session = await auth()
- if (!session?.user?.id) throw new Error('Unauthorized')
-
- const rawData = {
- currentPassword: formData.get('currentPassword'),
- newPassword: formData.get('newPassword'),
- confirmPassword: formData.get('confirmPassword'),
- }
-
- const validated = PasswordSchema.safeParse(rawData)
- if (!validated.success) {
- return { error: validated.error.flatten().fieldErrors }
- }
-
- const { currentPassword, newPassword } = validated.data
-
- const user = await prisma.user.findUnique({
- where: { id: session.user.id },
- })
-
- if (!user || !user.password) {
- return { error: { _form: ['User not found'] } }
- }
-
- const passwordsMatch = await bcrypt.compare(currentPassword, user.password)
- if (!passwordsMatch) {
- return { error: { currentPassword: ['Incorrect current password'] } }
- }
-
- const hashedPassword = await bcrypt.hash(newPassword, 10)
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { password: hashedPassword },
- })
- return { success: true }
- } catch (error) {
- return { error: { _form: ['Failed to change password'] } }
- }
-}
-
-export async function updateTheme(theme: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { theme },
- })
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true }
- } catch (error) {
- return { error: 'Failed to update theme' }
- }
-}
-
-export async function updateLanguage(language: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Update or create UserAISettings with the preferred language
- await prisma.userAISettings.upsert({
- where: { userId: session.user.id },
- create: {
- userId: session.user.id,
- preferredLanguage: language,
- },
- update: {
- preferredLanguage: language,
- },
- })
-
- // Note: The language will be applied on next page load
- // The client component should handle updating localStorage and reloading
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, language }
- } catch (error) {
- console.error('Failed to update language:', error)
- return { error: 'Failed to update language' }
- }
-}
-
-export async function updateFontSize(fontSize: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Check if UserAISettings exists
- const existing = await prisma.userAISettings.findUnique({
- where: { userId: session.user.id }
- })
-
- let result
- if (existing) {
- // Update existing - only update fontSize field
- result = await prisma.userAISettings.update({
- where: { userId: session.user.id },
- data: { fontSize: fontSize }
- })
- } else {
- // Create new with all required fields
- result = await prisma.userAISettings.create({
- data: {
- userId: session.user.id,
- fontSize: fontSize,
- // Set default values for required fields
- titleSuggestions: true,
- semanticSearch: true,
- paragraphRefactor: true,
- memoryEcho: true,
- memoryEchoFrequency: 'daily',
- aiProvider: 'auto',
- preferredLanguage: 'auto',
- showRecentNotes: false
- }
- })
- }
-
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, fontSize }
- } catch (error) {
- console.error('[updateFontSize] Failed to update font size:', error)
- return { error: 'Failed to update font size' }
- }
-}
-
-export async function updateShowRecentNotes(showRecentNotes: boolean) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Use EXACT same pattern as updateFontSize which works
- const existing = await prisma.userAISettings.findUnique({
- where: { userId: session.user.id }
- })
-
- if (existing) {
- // Try Prisma client first, fallback to raw SQL if field doesn't exist in client
- try {
- await prisma.userAISettings.update({
- where: { userId: session.user.id },
- data: { showRecentNotes: showRecentNotes } as any
- })
- } catch (prismaError: any) {
- // If Prisma client doesn't know about showRecentNotes, use raw SQL
- if (prismaError?.message?.includes('Unknown argument') || prismaError?.code === 'P2009') {
- const value = showRecentNotes ? 1 : 0
- await prisma.$executeRaw`
- UPDATE UserAISettings
- SET showRecentNotes = ${value}
- WHERE userId = ${session.user.id}
- `
- } else {
- throw prismaError
- }
- }
- } else {
- // Create new - same as updateFontSize
- await prisma.userAISettings.create({
- data: {
- userId: session.user.id,
- titleSuggestions: true,
- semanticSearch: true,
- paragraphRefactor: true,
- memoryEcho: true,
- memoryEchoFrequency: 'daily',
- aiProvider: 'auto',
- preferredLanguage: 'auto',
- fontSize: 'medium',
- showRecentNotes: showRecentNotes
- } as any
- })
- }
-
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, showRecentNotes }
- } catch (error) {
- console.error('[updateShowRecentNotes] Failed:', error)
- return { error: 'Failed to update show recent notes setting' }
- }
-}
diff --git a/memento-note/app/actions/profile-old.ts b/memento-note/app/actions/profile-old.ts
deleted file mode 100644
index 4e0da9a..0000000
--- a/memento-note/app/actions/profile-old.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-'use server'
-
-import { revalidatePath } from 'next/cache'
-import prisma from '@/lib/prisma'
-import { auth } from '@/auth'
-import bcrypt from 'bcryptjs'
-import { z } from 'zod'
-
-const ProfileSchema = z.object({
- name: z.string().min(2, "Name must be at least 2 characters"),
- email: z.string().email().optional(), // Email change might require verification logic, keeping it simple for now or read-only
-})
-
-const PasswordSchema = z.object({
- currentPassword: z.string().min(1, "Current password is required"),
- newPassword: z.string().min(6, "New password must be at least 6 characters"),
- confirmPassword: z.string().min(6, "Confirm password must be at least 6 characters"),
-}).refine((data) => data.newPassword === data.confirmPassword, {
- message: "Passwords don't match",
- path: ["confirmPassword"],
-})
-
-export async function updateProfile(data: { name: string }) {
- const session = await auth()
- if (!session?.user?.id) throw new Error('Unauthorized')
-
- const validated = ProfileSchema.safeParse(data)
- if (!validated.success) {
- return { error: validated.error.flatten().fieldErrors }
- }
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { name: validated.data.name },
- })
- revalidatePath('/settings/profile')
- return { success: true }
- } catch (error) {
- return { error: { _form: ['Failed to update profile'] } }
- }
-}
-
-export async function changePassword(formData: FormData) {
- const session = await auth()
- if (!session?.user?.id) throw new Error('Unauthorized')
-
- const rawData = {
- currentPassword: formData.get('currentPassword'),
- newPassword: formData.get('newPassword'),
- confirmPassword: formData.get('confirmPassword'),
- }
-
- const validated = PasswordSchema.safeParse(rawData)
- if (!validated.success) {
- return { error: validated.error.flatten().fieldErrors }
- }
-
- const { currentPassword, newPassword } = validated.data
-
- const user = await prisma.user.findUnique({
- where: { id: session.user.id },
- })
-
- if (!user || !user.password) {
- return { error: { _form: ['User not found'] } }
- }
-
- const passwordsMatch = await bcrypt.compare(currentPassword, user.password)
- if (!passwordsMatch) {
- return { error: { currentPassword: ['Incorrect current password'] } }
- }
-
- const hashedPassword = await bcrypt.hash(newPassword, 10)
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { password: hashedPassword },
- })
- return { success: true }
- } catch (error) {
- return { error: { _form: ['Failed to change password'] } }
- }
-}
-
-export async function updateTheme(theme: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- await prisma.user.update({
- where: { id: session.user.id },
- data: { theme },
- })
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true }
- } catch (error) {
- return { error: 'Failed to update theme' }
- }
-}
-
-export async function updateLanguage(language: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Update or create UserAISettings with the preferred language
- await prisma.userAISettings.upsert({
- where: { userId: session.user.id },
- create: {
- userId: session.user.id,
- preferredLanguage: language,
- },
- update: {
- preferredLanguage: language,
- },
- })
-
- // Note: The language will be applied on next page load
- // The client component should handle updating localStorage and reloading
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, language }
- } catch (error) {
- console.error('Failed to update language:', error)
- return { error: 'Failed to update language' }
- }
-}
-
-export async function updateFontSize(fontSize: string) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Check if UserAISettings exists
- const existing = await prisma.userAISettings.findUnique({
- where: { userId: session.user.id }
- })
-
- let result
- if (existing) {
- // Update existing - only update fontSize field
- result = await prisma.userAISettings.update({
- where: { userId: session.user.id },
- data: { fontSize: fontSize }
- })
- } else {
- // Create new with all required fields
- result = await prisma.userAISettings.create({
- data: {
- userId: session.user.id,
- fontSize: fontSize,
- // Set default values for required fields
- titleSuggestions: true,
- semanticSearch: true,
- paragraphRefactor: true,
- memoryEcho: true,
- memoryEchoFrequency: 'daily',
- aiProvider: 'auto',
- preferredLanguage: 'auto',
- showRecentNotes: false
- }
- })
- }
-
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, fontSize }
- } catch (error) {
- console.error('[updateFontSize] Failed to update font size:', error)
- return { error: 'Failed to update font size' }
- }
-}
-
-export async function updateShowRecentNotes(showRecentNotes: boolean) {
- const session = await auth()
- if (!session?.user?.id) return { error: 'Unauthorized' }
-
- try {
- // Use EXACT same pattern as updateFontSize which works
- const existing = await prisma.userAISettings.findUnique({
- where: { userId: session.user.id }
- })
-
- if (existing) {
- // Try Prisma client first, fallback to raw SQL if field doesn't exist in client
- try {
- await prisma.userAISettings.update({
- where: { userId: session.user.id },
- data: { showRecentNotes: showRecentNotes } as any
- })
- } catch (prismaError: any) {
- // If Prisma client doesn't know about showRecentNotes, use raw SQL
- if (prismaError?.message?.includes('Unknown argument') || prismaError?.code === 'P2009') {
- const value = showRecentNotes ? 1 : 0
- await prisma.$executeRaw`
- UPDATE UserAISettings
- SET showRecentNotes = ${value}
- WHERE userId = ${session.user.id}
- `
- } else {
- throw prismaError
- }
- }
- } else {
- // Create new - same as updateFontSize
- await prisma.userAISettings.create({
- data: {
- userId: session.user.id,
- titleSuggestions: true,
- semanticSearch: true,
- paragraphRefactor: true,
- memoryEcho: true,
- memoryEchoFrequency: 'daily',
- aiProvider: 'auto',
- preferredLanguage: 'auto',
- fontSize: 'medium',
- showRecentNotes: showRecentNotes
- } as any
- })
- }
-
- revalidatePath('/')
- revalidatePath('/settings/profile')
- return { success: true, showRecentNotes }
- } catch (error) {
- console.error('[updateShowRecentNotes] Failed:', error)
- return { error: 'Failed to update show recent notes setting' }
- }
-}
diff --git a/memento-note/auth.config.ts b/memento-note/auth.config.ts
index b5b668a..865f49b 100644
--- a/memento-note/auth.config.ts
+++ b/memento-note/auth.config.ts
@@ -5,7 +5,7 @@ export const authConfig = {
signIn: '/login',
newUser: '/register',
},
- secret: "csQFtfYvQ8YtatEYSUFyslXdk2vJhZFt9D5gav/RJQg=",
+ secret: process.env.NEXTAUTH_SECRET,
trustHost: true,
session: {
strategy: 'jwt',
diff --git a/memento-note/components/note-editor.tsx b/memento-note/components/note-editor.tsx
index 3744536..32f93fa 100644
--- a/memento-note/components/note-editor.tsx
+++ b/memento-note/components/note-editor.tsx
@@ -113,25 +113,25 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
const [comparisonNotes, setComparisonNotes] = useState>>([])
const [fusionNotes, setFusionNotes] = useState>>([])
- // Tags rejetés par l'utilisateur pour cette session
+ // Tags dismissed by the user for this session
const [dismissedTags, setDismissedTags] = useState([])
const colorClasses = NOTE_COLORS[color as NoteColor] || NOTE_COLORS.default
const handleSelectGhostTag = async (tag: string) => {
- // Vérification insensible à la casse
+ // Case-insensitive check
const tagExists = labels.some(l => l.toLowerCase() === tag.toLowerCase())
if (!tagExists) {
setLabels(prev => [...prev, tag])
- // Créer le label globalement s'il n'existe pas
+ // Create the label globally if it doesn't exist
const globalExists = globalLabels.some(l => l.name.toLowerCase() === tag.toLowerCase())
if (!globalExists) {
try {
await addLabel(tag)
} catch (err) {
- console.error('Erreur création label auto:', err)
+ console.error('Error creating auto-label:', err)
}
}
toast.success(t('ai.tagAdded', { tag }))
@@ -142,8 +142,8 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
setDismissedTags(prev => [...prev, tag])
}
- // Filtrer les suggestions pour ne pas afficher celles rejetées par l'utilisateur
- // ni celles déjà présentes sur la note
+ // Filter suggestions to exclude dismissed ones
+ // and those already present on the note
const existingLabelsLower = (note.labels || []).map((l) => l.toLowerCase())
const filteredSuggestions = suggestions.filter(s => {
if (!s || !s.tag) return false
@@ -241,7 +241,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
setTitleSuggestions(data.suggestions || [])
toast.success(t('ai.titlesGenerated', { count: data.suggestions.length }))
} catch (error: any) {
- console.error('Erreur génération titres:', error)
+ console.error('Error generating titles:', error)
toast.error(error.message || t('ai.titleGenerationFailed'))
} finally {
setIsGeneratingTitles(false)
@@ -311,7 +311,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
option: data.option
})
} catch (error: any) {
- console.error('Erreur reformulation:', error)
+ console.error('Error reformulating:', error)
toast.error(error.message || t('ai.reformulationFailed'))
} finally {
setIsReformulating(false)
@@ -494,10 +494,10 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
cleanupOrphanedImages(removedImageUrls, note.id).catch(() => {})
}
- // Rafraîchir les labels globaux pour refléter les suppressions éventuelles (orphans)
+ // Refresh global labels to reflect any deletions (orphans)
await refreshLabels()
- // Rafraîchir la liste des notes
+ // Refresh the notes list
triggerRefresh()
onClose()
diff --git a/memento-note/hooks/use-auto-tagging.ts b/memento-note/hooks/use-auto-tagging.ts
index 932718a..f2336a2 100644
--- a/memento-note/hooks/use-auto-tagging.ts
+++ b/memento-note/hooks/use-auto-tagging.ts
@@ -15,14 +15,14 @@ export function useAutoTagging({ content, notebookId, enabled = true }: UseAutoT
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [error, setError] = useState(null);
- // Debounce le contenu de 1.5s
+ // Debounce content by 1.5s
const debouncedContent = useDebounce(content, 1500);
// Track previous notebookId to detect when note is moved to a notebook
const previousNotebookId = useRef(notebookId);
const analyzeContent = async (contentToAnalyze: string) => {
- // CRITICAL: Don't suggest labels in "Notes générales" (notebookId is null)
+ // CRITICAL: Don't suggest labels in "General Notes" (notebookId is null)
// Labels should ONLY appear within notebooks, not in the general notes section
if (!notebookId) {
setSuggestions([]);
@@ -49,13 +49,13 @@ export function useAutoTagging({ content, notebookId, enabled = true }: UseAutoT
});
if (!response.ok) {
- throw new Error('Erreur lors de l\'analyse');
+ throw new Error('Error during analysis');
}
const data = await response.json();
setSuggestions(data.tags || []);
} catch (err) {
- setError('Impossible de générer des suggestions');
+ setError('Failed to generate suggestions');
} finally {
setIsAnalyzing(false);
}
@@ -78,7 +78,7 @@ export function useAutoTagging({ content, notebookId, enabled = true }: UseAutoT
const prev = previousNotebookId.current;
previousNotebookId.current = notebookId;
- // Detect when note is moved FROM "Notes générales" (null) TO a notebook
+ // Detect when note is moved FROM "General Notes" (null) TO a notebook
const wasMovedToNotebook = (prev === null || prev === undefined) && notebookId;
if (wasMovedToNotebook && content && content.length >= 10) {
diff --git a/memento-note/hooks/use-title-suggestions.ts b/memento-note/hooks/use-title-suggestions.ts
index 7281aaa..b76fd20 100644
--- a/memento-note/hooks/use-title-suggestions.ts
+++ b/memento-note/hooks/use-title-suggestions.ts
@@ -17,7 +17,7 @@ export function useTitleSuggestions({ content, enabled = true }: UseTitleSuggest
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [error, setError] = useState(null)
- // Debounce le contenu de 2s pour éviter trop d'appels
+ // Debounce content by 2s to avoid excessive API calls
const debouncedContent = useDebounce(content, 2000)
useEffect(() => {
@@ -28,7 +28,7 @@ export function useTitleSuggestions({ content, enabled = true }: UseTitleSuggest
const wordCount = debouncedContent.split(/\s+/).length
- // Il faut au moins 10 mots
+ // Need at least 10 words
if (wordCount < 10) {
setSuggestions([])
return
@@ -49,14 +49,14 @@ export function useTitleSuggestions({ content, enabled = true }: UseTitleSuggest
if (!response.ok) {
const errorData = await response.json()
- throw new Error(errorData.error || 'Erreur lors de la génération des titres')
+ throw new Error(errorData.error || 'Error generating title suggestions')
}
const data = await response.json()
setSuggestions(data.suggestions || [])
} catch (err) {
console.error('❌ Title suggestions error:', err)
- setError('Impossible de générer des suggestions de titres')
+ setError('Failed to generate title suggestions')
} finally {
setIsAnalyzing(false)
}
diff --git a/memento-note/lib/ai/providers/custom-openai.ts b/memento-note/lib/ai/providers/custom-openai.ts
index fec4e4b..535256a 100644
--- a/memento-note/lib/ai/providers/custom-openai.ts
+++ b/memento-note/lib/ai/providers/custom-openai.ts
@@ -40,17 +40,17 @@ export class CustomOpenAIProvider implements AIProvider {
model: this.model,
schema: z.object({
tags: z.array(z.object({
- tag: z.string().describe('Le nom du tag, court et en minuscules'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ tag: z.string().describe('Short tag name in lowercase'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
- prompt: `Analyse la note suivante et suggère entre 1 et 5 tags pertinents.
- Contenu de la note: "${content}"`,
+ prompt: `Analyze the following note and suggest 1 to 5 relevant tags.
+ Note content: "${content}"`,
});
return object.tags;
} catch (e) {
- console.error('Erreur génération tags Custom OpenAI:', e);
+ console.error('Error generating tags (Custom OpenAI):', e);
return [];
}
}
@@ -63,7 +63,7 @@ export class CustomOpenAIProvider implements AIProvider {
});
return embedding;
} catch (e) {
- console.error('Erreur embeddings Custom OpenAI:', e);
+ console.error('Error generating embeddings (Custom OpenAI):', e);
return [];
}
}
@@ -74,8 +74,8 @@ export class CustomOpenAIProvider implements AIProvider {
model: this.model,
schema: z.object({
titles: z.array(z.object({
- title: z.string().describe('Le titre suggéré'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ title: z.string().describe('Suggested title'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
prompt: prompt,
@@ -83,7 +83,7 @@ export class CustomOpenAIProvider implements AIProvider {
return object.titles;
} catch (e) {
- console.error('Erreur génération titres Custom OpenAI:', e);
+ console.error('Error generating titles (Custom OpenAI):', e);
return [];
}
}
@@ -97,7 +97,7 @@ export class CustomOpenAIProvider implements AIProvider {
return text.trim();
} catch (e) {
- console.error('Erreur génération texte Custom OpenAI:', e);
+ console.error('Error generating text (Custom OpenAI):', e);
throw e;
}
}
@@ -112,7 +112,7 @@ export class CustomOpenAIProvider implements AIProvider {
return { text: text.trim() };
} catch (e) {
- console.error('Erreur chat Custom OpenAI:', e);
+ console.error('Error in chat (Custom OpenAI):', e);
throw e;
}
}
diff --git a/memento-note/lib/ai/providers/deepseek.ts b/memento-note/lib/ai/providers/deepseek.ts
index b4db2c9..d9abd51 100644
--- a/memento-note/lib/ai/providers/deepseek.ts
+++ b/memento-note/lib/ai/providers/deepseek.ts
@@ -24,17 +24,17 @@ export class DeepSeekProvider implements AIProvider {
model: this.model,
schema: z.object({
tags: z.array(z.object({
- tag: z.string().describe('Le nom du tag, court et en minuscules'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ tag: z.string().describe('Short tag name in lowercase'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
- prompt: `Analyse la note suivante et suggère entre 1 et 5 tags pertinents.
- Contenu de la note: "${content}"`,
+ prompt: `Analyze the following note and suggest 1 to 5 relevant tags.
+ Note content: "${content}"`,
});
return object.tags;
} catch (e) {
- console.error('Erreur génération tags DeepSeek:', e);
+ console.error('Error generating tags (DeepSeek):', e);
return [];
}
}
@@ -47,7 +47,7 @@ export class DeepSeekProvider implements AIProvider {
});
return embedding;
} catch (e) {
- console.error('Erreur embeddings DeepSeek:', e);
+ console.error('Error generating embeddings (DeepSeek):', e);
return [];
}
}
@@ -58,8 +58,8 @@ export class DeepSeekProvider implements AIProvider {
model: this.model,
schema: z.object({
titles: z.array(z.object({
- title: z.string().describe('Le titre suggéré'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ title: z.string().describe('Suggested title'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
prompt: prompt,
@@ -67,7 +67,7 @@ export class DeepSeekProvider implements AIProvider {
return object.titles;
} catch (e) {
- console.error('Erreur génération titres DeepSeek:', e);
+ console.error('Error generating titles (DeepSeek):', e);
return [];
}
}
@@ -81,7 +81,7 @@ export class DeepSeekProvider implements AIProvider {
return text.trim();
} catch (e) {
- console.error('Erreur génération texte DeepSeek:', e);
+ console.error('Error generating text (DeepSeek):', e);
throw e;
}
}
@@ -96,7 +96,7 @@ export class DeepSeekProvider implements AIProvider {
return { text: text.trim() };
} catch (e) {
- console.error('Erreur chat DeepSeek:', e);
+ console.error('Error in chat (DeepSeek):', e);
throw e;
}
}
diff --git a/memento-note/lib/ai/providers/ollama.ts b/memento-note/lib/ai/providers/ollama.ts
index c2ecc64..4923879 100644
--- a/memento-note/lib/ai/providers/ollama.ts
+++ b/memento-note/lib/ai/providers/ollama.ts
@@ -75,7 +75,7 @@ Note content: "${content}"`;
return JSON.parse(jsonMatch[0]);
}
- // Support pour le format { "tags": [...] }
+ // Support for { "tags": [...] } format
const objectMatch = text.match(/\{\s*"tags"\s*:\s*(\[[\s\S]*\])\s*\}/);
if (objectMatch && objectMatch[1]) {
return JSON.parse(objectMatch[1]);
@@ -83,7 +83,7 @@ Note content: "${content}"`;
return [];
} catch (e) {
- console.error('Erreur API directe Ollama:', e);
+ console.error('Error in Ollama API:', e);
return [];
}
}
@@ -104,7 +104,7 @@ Note content: "${content}"`;
const data = await response.json();
return data.embedding;
} catch (e) {
- console.error('Erreur embeddings directs Ollama:', e);
+ console.error('Error generating embeddings (Ollama):', e);
return [];
}
}
@@ -116,7 +116,7 @@ Note content: "${content}"`;
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.modelName,
- prompt: `${prompt}\n\nRéponds UNIQUEMENT sous forme de tableau JSON : [{"title": "string", "confidence": number}]`,
+ prompt: `${prompt}\n\nRespond ONLY as a JSON array: [{"title": "string", "confidence": number}]`,
stream: false,
}),
});
@@ -126,7 +126,7 @@ Note content: "${content}"`;
const data = await response.json();
const text = data.response;
- // Extraire le JSON de la réponse
+ // Extract JSON from response
const jsonMatch = text.match(/\[\s*\{[\s\S]*\}\s*\]/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
@@ -134,7 +134,7 @@ Note content: "${content}"`;
return [];
} catch (e) {
- console.error('Erreur génération titres Ollama:', e);
+ console.error('Error generating titles (Ollama):', e);
return [];
}
}
@@ -156,7 +156,7 @@ Note content: "${content}"`;
const data = await response.json();
return data.response.trim();
} catch (e) {
- console.error('Erreur génération texte Ollama:', e);
+ console.error('Error generating text (Ollama):', e);
throw e;
}
}
@@ -187,7 +187,7 @@ Note content: "${content}"`;
const data = await response.json();
return { text: data.message?.content?.trim() || '' };
} catch (e) {
- console.error('Erreur chat Ollama:', e);
+ console.error('Error in chat (Ollama):', e);
throw e;
}
}
diff --git a/memento-note/lib/ai/providers/openai.ts b/memento-note/lib/ai/providers/openai.ts
index 3bbd96b..4a4b447 100644
--- a/memento-note/lib/ai/providers/openai.ts
+++ b/memento-note/lib/ai/providers/openai.ts
@@ -24,17 +24,17 @@ export class OpenAIProvider implements AIProvider {
model: this.model,
schema: z.object({
tags: z.array(z.object({
- tag: z.string().describe('Le nom du tag, court et en minuscules'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ tag: z.string().describe('Short tag name in lowercase'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
- prompt: `Analyse la note suivante et suggère entre 1 et 5 tags pertinents.
- Contenu de la note: "${content}"`,
+ prompt: `Analyze the following note and suggest 1 to 5 relevant tags.
+ Note content: "${content}"`,
});
return object.tags;
} catch (e) {
- console.error('Erreur génération tags OpenAI:', e);
+ console.error('Error generating tags (OpenAI):', e);
return [];
}
}
@@ -47,7 +47,7 @@ export class OpenAIProvider implements AIProvider {
});
return embedding;
} catch (e) {
- console.error('Erreur embeddings OpenAI:', e);
+ console.error('Error generating embeddings (OpenAI):', e);
return [];
}
}
@@ -58,8 +58,8 @@ export class OpenAIProvider implements AIProvider {
model: this.model,
schema: z.object({
titles: z.array(z.object({
- title: z.string().describe('Le titre suggéré'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ title: z.string().describe('Suggested title'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
prompt: prompt,
@@ -67,7 +67,7 @@ export class OpenAIProvider implements AIProvider {
return object.titles;
} catch (e) {
- console.error('Erreur génération titres OpenAI:', e);
+ console.error('Error generating titles (OpenAI):', e);
return [];
}
}
@@ -81,7 +81,7 @@ export class OpenAIProvider implements AIProvider {
return text.trim();
} catch (e) {
- console.error('Erreur génération texte OpenAI:', e);
+ console.error('Error generating text (OpenAI):', e);
throw e;
}
}
@@ -96,7 +96,7 @@ export class OpenAIProvider implements AIProvider {
return { text: text.trim() };
} catch (e) {
- console.error('Erreur chat OpenAI:', e);
+ console.error('Error in chat (OpenAI):', e);
throw e;
}
}
diff --git a/memento-note/lib/ai/providers/openrouter.ts b/memento-note/lib/ai/providers/openrouter.ts
index 6018f52..f39616f 100644
--- a/memento-note/lib/ai/providers/openrouter.ts
+++ b/memento-note/lib/ai/providers/openrouter.ts
@@ -24,17 +24,17 @@ export class OpenRouterProvider implements AIProvider {
model: this.model,
schema: z.object({
tags: z.array(z.object({
- tag: z.string().describe('Le nom du tag, court et en minuscules'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ tag: z.string().describe('Short tag name in lowercase'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
- prompt: `Analyse la note suivante et suggère entre 1 et 5 tags pertinents.
- Contenu de la note: "${content}"`,
+ prompt: `Analyze the following note and suggest 1 to 5 relevant tags.
+ Note content: "${content}"`,
});
return object.tags;
} catch (e) {
- console.error('Erreur génération tags OpenRouter:', e);
+ console.error('Error generating tags (OpenRouter):', e);
return [];
}
}
@@ -47,7 +47,7 @@ export class OpenRouterProvider implements AIProvider {
});
return embedding;
} catch (e) {
- console.error('Erreur embeddings OpenRouter:', e);
+ console.error('Error generating embeddings (OpenRouter):', e);
return [];
}
}
@@ -58,8 +58,8 @@ export class OpenRouterProvider implements AIProvider {
model: this.model,
schema: z.object({
titles: z.array(z.object({
- title: z.string().describe('Le titre suggéré'),
- confidence: z.number().min(0).max(1).describe('Le niveau de confiance entre 0 et 1')
+ title: z.string().describe('Suggested title'),
+ confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
}))
}),
prompt: prompt,
@@ -67,7 +67,7 @@ export class OpenRouterProvider implements AIProvider {
return object.titles;
} catch (e) {
- console.error('Erreur génération titres OpenRouter:', e);
+ console.error('Error generating titles (OpenRouter):', e);
return [];
}
}
@@ -81,7 +81,7 @@ export class OpenRouterProvider implements AIProvider {
return text.trim();
} catch (e) {
- console.error('Erreur génération texte OpenRouter:', e);
+ console.error('Error generating text (OpenRouter):', e);
throw e;
}
}
@@ -96,7 +96,7 @@ export class OpenRouterProvider implements AIProvider {
return { text: text.trim() };
} catch (e) {
- console.error('Erreur chat OpenRouter:', e);
+ console.error('Error in chat (OpenRouter):', e);
throw e;
}
}
diff --git a/memento-note/lib/ai/types.ts b/memento-note/lib/ai/types.ts
index 66f9e15..6b1030c 100644
--- a/memento-note/lib/ai/types.ts
+++ b/memento-note/lib/ai/types.ts
@@ -29,32 +29,32 @@ export interface ToolCallResult {
export interface AIProvider {
/**
- * Analyse le contenu et suggère des tags pertinents.
+ * Analyze content and suggest relevant tags.
*/
generateTags(content: string, language?: string): Promise;
/**
- * Génère un vecteur d'embeddings pour la recherche sémantique.
+ * Generate an embedding vector for semantic search.
*/
getEmbeddings(text: string): Promise;
/**
- * Génère des suggestions de titres basées sur le contenu.
+ * Generate title suggestions based on content.
*/
generateTitles(prompt: string): Promise;
/**
- * Génère du texte basé sur un prompt.
+ * Generate text based on a prompt.
*/
generateText(prompt: string): Promise;
/**
- * Fournit une réponse de chat (utilisé pour le système agentique)
+ * Provide a chat response (used for the agentic system)
*/
chat(messages: any[], systemPrompt?: string): Promise;
/**
- * Retourne le modèle AI SDK pour le streaming direct (utilisé par l'API route)
+ * Return the AI SDK model for direct streaming (used by API route)
*/
getModel(): any;
@@ -69,7 +69,7 @@ export type AIProviderType = 'openai' | 'ollama';
export interface AIConfig {
provider: AIProviderType;
apiKey?: string;
- baseUrl?: string; // Utile pour Ollama
+ baseUrl?: string; // Used for Ollama
model?: string;
embeddingModel?: string;
}
diff --git a/memento-note/scripts/reset-password-auto.ts b/memento-note/scripts/reset-password-auto.ts
index 18059ce..bde20de 100644
--- a/memento-note/scripts/reset-password-auto.ts
+++ b/memento-note/scripts/reset-password-auto.ts
@@ -3,8 +3,19 @@ import { prisma } from '../lib/prisma'
import bcrypt from 'bcryptjs'
async function main() {
- const email = 'test@example.com'
- const newPassword = 'password123'
+ const email = process.argv[2]
+ const newPassword = process.argv[3]
+
+ if (!email || !newPassword) {
+ console.error('Usage: npx tsx scripts/reset-password-auto.ts ')
+ console.error('Example: npx tsx scripts/reset-password-auto.ts user@example.com mynewpassword')
+ process.exit(1)
+ }
+
+ if (newPassword.length < 6) {
+ console.error('Password must be at least 6 characters')
+ process.exit(1)
+ }
console.log(`Resetting password for ${email}...`)
@@ -19,9 +30,9 @@ async function main() {
resetTokenExpiry: null
},
})
- console.log(`✅ Password successfully reset for ${user.email}`)
+ console.log(`Password successfully reset for ${user.email}`)
} catch (error) {
- console.error('❌ Error resetting password:', error)
+ console.error('Error resetting password:', error)
process.exit(1)
}
}
diff --git a/memento-note/scripts/reset-password.js b/memento-note/scripts/reset-password.js
index a9acccc..2078b9d 100644
--- a/memento-note/scripts/reset-password.js
+++ b/memento-note/scripts/reset-password.js
@@ -1,25 +1,12 @@
/**
- * Script DIRECT de reset de mot de passe
- *
- * POURQUOI CE SCRIPT ?
- * -----------------
- * - Le compte `test@example.com` N'EXISTE PAS (vous avez raison !)
- * - L'envoi d'email nécessite une configuration SMTP complexe
- * - VOUS VOULEZ UNE SOLUTION DIRECTE, SANS PERDRE DE TEMPS
- *
- * CE QUE FAIT CE SCRIPT :
- * -------------------
- * - Réinitialise DIRECTEMENT le mot de passe d'un compte existant
- * - Sans avoir besoin d'email
- * - Sans avoir besoin d'interface graphique
- *
- * COMMENT UTILISER :
- * ---------------
- * 1. Ouvrez un terminal dans le dossier memento-note
- * 2. Exécutez: node scripts/reset-password.js
- * 3. Quand demandé, entrez l'email du compte à réinitialiser
- * 4. Entrez le nouveau mot de passe (2 fois pour confirmation)
- * 5. FINI ! Connectez-vous avec le nouveau mot de passe
+ * Interactive password reset script
+ *
+ * USAGE:
+ * 1. Open a terminal in the memento-note directory
+ * 2. Run: node scripts/reset-password.js
+ * 3. Enter the email of the account to reset
+ * 4. Enter the new password (twice for confirmation)
+ * 5. Done! Log in with the new password
*/
const readline = require('readline');
@@ -31,25 +18,25 @@ const rl = readline.createInterface({
output: process.stdout
});
-console.log('╔══════════════════════════════════════════════════════════════════╗');
-console.log('║ RESET DE MOT DE PASSE DIRECT ║');
-console.log('║ ║');
-console.log('║ ⚠️ ATTENTION : Utilisez seulement pour VOTRE propre compte ! ║');
-console.log('║ ║');
-console.log('╚══════════════════════════════════════════════════════════════════════╝');
+console.log('================================================================');
+console.log(' INTERACTIVE PASSWORD RESET');
+console.log('');
+console.log(' WARNING: Use only for your own account!');
+console.log('');
+console.log('================================================================');
console.log('');
-rl.question('Entrez l\'EMAIL du compte à réinitialiser : ', async (email) => {
+rl.question('Enter the EMAIL of the account to reset: ', async (email) => {
if (!email || !email.includes('@')) {
- console.log('❌ Erreur : Email invalide !');
+ console.log('Error: Invalid email!');
rl.close();
process.exit(1);
}
email = email.toLowerCase().trim();
-
- console.log(`🔍 Recherche du compte : ${email}...`);
-
+
+ console.log(`Looking up account: ${email}...`);
+
try {
const user = await prisma.user.findUnique({
where: { email: email }
@@ -57,56 +44,55 @@ rl.question('Entrez l\'EMAIL du compte à réinitialiser : ', async (email) => {
if (!user) {
console.log('');
- console.log('❌ ERREUR : AUCUN compte trouvé avec cet email !');
+ console.log('ERROR: No account found with this email!');
console.log('');
- console.log('📋 COMPTES DISPONIBLES (si existants) :');
- console.log('─────────────────────────────────────────');
-
- // Afficher tous les utilisateurs de la base de données
+ console.log('AVAILABLE ACCOUNTS (if any):');
+ console.log('-------------------------');
+
const allUsers = await prisma.user.findMany({
select: { email: true, name: true, role: true, createdAt: true },
orderBy: { createdAt: 'desc' }
});
-
+
if (allUsers.length > 0) {
console.log('');
allUsers.forEach((u, index) => {
- console.log(`${index + 1}. 📧 Email: ${u.email}`);
- console.log(` 👤 Nom: ${u.name || 'N/A'}`);
- console.log(` 🏷️ Rôle: ${u.role}`);
- console.log(` 📅 Créé: ${u.createdAt.toLocaleString('fr-FR')}`);
+ console.log(`${index + 1}. Email: ${u.email}`);
+ console.log(` Name: ${u.name || 'N/A'}`);
+ console.log(` Role: ${u.role}`);
+ console.log(` Created: ${u.createdAt.toLocaleString()}`);
console.log('');
});
} else {
- console.log(' (Aucun compte dans la base de données)');
+ console.log(' (No accounts in the database)');
}
-
- console.log('─────────────────────────────────────────');
+
+ console.log('-------------------------');
console.log('');
rl.close();
process.exit(1);
}
- console.log(`✅ Compte trouvé : ${user.email} (${user.name})`);
+ console.log(`Account found: ${user.email} (${user.name})`);
console.log('');
- rl.question('Entrez le NOUVEAU mot de passe (minimum 6 caractères) : ', async (newPassword) => {
+ rl.question('Enter the NEW password (minimum 6 characters): ', async (newPassword) => {
if (!newPassword || newPassword.length < 6) {
- console.log('❌ Erreur : Le mot de passe doit avoir au moins 6 caractères !');
+ console.log('Error: Password must be at least 6 characters!');
rl.close();
process.exit(1);
}
- rl.question('Confirmez le nouveau mot de passe : ', async (confirmPassword) => {
+ rl.question('Confirm the new password: ', async (confirmPassword) => {
if (newPassword !== confirmPassword) {
- console.log('❌ Erreur : Les mots de passe ne correspondent pas !');
+ console.log('Error: Passwords do not match!');
rl.close();
process.exit(1);
}
console.log('');
- console.log('🔄 Réinitialisation du mot de passe en cours...');
-
+ console.log('Resetting password...');
+
const hashedPassword = await bcrypt.hash(newPassword, 10);
await prisma.user.update({
@@ -118,17 +104,15 @@ rl.question('Entrez l\'EMAIL du compte à réinitialiser : ', async (email) => {
}
});
- console.log('✅ SUCCÈS ! Le mot de passe a été réinitialisé !');
+ console.log('SUCCESS! Password has been reset!');
console.log('');
- console.log('═══════════════════════════════════════════════════════════════════════');
- console.log('🎉 VOUS POUVEZ MAINTENANT VOUS CONNECTER !');
- console.log('═════════════════════════════════════════════════════════════════════');
+ console.log('================================================================');
+ console.log('YOU CAN NOW LOG IN!');
+ console.log('================================================================');
console.log('');
- console.log('📱 URL de connexion : http://localhost:3000/login');
- console.log('📧 Email :', email);
- console.log('🔑 Mot de passe :', newPassword);
- console.log('');
- console.log('⏩ Copiez ces informations et connectez-vous !');
+ console.log('Login URL: http://localhost:3000/login');
+ console.log('Email:', email);
+ console.log('Password:', newPassword);
console.log('');
rl.close();
@@ -137,7 +121,7 @@ rl.question('Entrez l\'EMAIL du compte à réinitialiser : ', async (email) => {
});
} catch (error) {
console.log('');
- console.log('❌ ERREUR lors de la réinitialisation :');
+ console.log('ERROR during password reset:');
console.error(error);
rl.close();
process.exit(1);