From 88a7d2ad0aa4f90c2ca40424bba5d7b778e019d6 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Fri, 17 Jul 2026 18:47:44 +0000 Subject: [PATCH] =?UTF-8?q?fix(audit):=20nettoyage=20acc=C3=A8s,=20garde-f?= =?UTF-8?q?ous=20build,=20unification=20quotas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Semaine 1 — fuites et accès : - /archive rendu accessible via sidebar (était invisible) - Suppression pages orphelines : /chat, /graph, /support, test-title-suggestions - Fixes i18n : search-modal (clé non interpolée), sidebar tooltips, export PDF - 15 locales mises à jour Semaine 2 — garde-fous : - 8 erreurs TS fixees → ignoreBuildErrors: false (build type-safe) - reactStrictMode: true (dev-only, zero impact prod) - reserveUsageOrThrow → wrapper deprecie vers reserveAiUsageOrThrow - auth-guard.ts : requireAuthOrResponse() + requireAdminOrResponse() Tests 212/212 | Lint 0 erreur | Build OK | tsc 0 erreur --- AGENTS.md | 6 +- memento-note/app/(main)/chat/page.tsx | 44 - memento-note/app/(main)/graph/page.tsx | 17 - memento-note/app/(main)/insights/page.tsx | 5 - memento-note/app/(main)/support/page.tsx | 156 ---- memento-note/app/actions/chat-actions.ts | 2 - memento-note/app/api/ai/auto-labels/route.ts | 5 +- .../app/api/ai/suggest-notebook/route.ts | 5 +- memento-note/app/api/graph/route.ts | 195 ----- memento-note/app/api/graph/sync-all/route.ts | 84 -- memento-note/app/layout.tsx | 2 +- .../app/test-title-suggestions/page.tsx | 61 -- memento-note/auth.config.ts | 5 +- .../components/chat/chat-container.tsx | 222 ----- memento-note/components/chat/chat-input.tsx | 159 ---- .../components/chat/chat-messages.tsx | 84 -- memento-note/components/chat/chat-sidebar.tsx | 127 --- .../note-editor/note-editor-toolbar.tsx | 8 +- memento-note/components/note-graph-view.tsx | 789 ------------------ memento-note/components/search-modal.tsx | 4 +- memento-note/components/sidebar.tsx | 20 +- .../ai/services/notebook-wizard.service.ts | 4 +- memento-note/lib/auth-guard.ts | 60 +- memento-note/lib/credits.ts | 1 + memento-note/lib/entitlements.ts | 23 +- memento-note/locales/ar.json | 7 +- memento-note/locales/de.json | 7 +- memento-note/locales/en.json | 7 +- memento-note/locales/es.json | 7 +- memento-note/locales/fa.json | 7 +- memento-note/locales/fr.json | 7 +- memento-note/locales/hi.json | 7 +- memento-note/locales/it.json | 7 +- memento-note/locales/ja.json | 7 +- memento-note/locales/ko.json | 7 +- memento-note/locales/nl.json | 7 +- memento-note/locales/pl.json | 7 +- memento-note/locales/pt.json | 7 +- memento-note/locales/ru.json | 7 +- memento-note/locales/zh.json | 7 +- memento-note/next.config.ts | 12 +- .../tests/unit/brainstorm-billing.test.ts | 92 +- 42 files changed, 216 insertions(+), 2081 deletions(-) delete mode 100644 memento-note/app/(main)/chat/page.tsx delete mode 100644 memento-note/app/(main)/graph/page.tsx delete mode 100644 memento-note/app/(main)/support/page.tsx delete mode 100644 memento-note/app/api/graph/route.ts delete mode 100644 memento-note/app/api/graph/sync-all/route.ts delete mode 100644 memento-note/app/test-title-suggestions/page.tsx delete mode 100644 memento-note/components/chat/chat-container.tsx delete mode 100644 memento-note/components/chat/chat-input.tsx delete mode 100644 memento-note/components/chat/chat-messages.tsx delete mode 100644 memento-note/components/chat/chat-sidebar.tsx delete mode 100644 memento-note/components/note-graph-view.tsx diff --git a/AGENTS.md b/AGENTS.md index 85d58cc..d01e2f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,8 @@ - Flux Excalidraw / diagrammes générés : accès via notification en plus d'une simple redirection ; priorité à la mise en page et au texte contenu dans les formes ; proposer des modes visuels (ex. coloré vs plus austère) tout en visant un rendu proche du style Excalidraw (polices, look). - **Interdiction d'écrire des tests** sauf demande explicite ; en CI, seul `npm run test:unit` (`tests/unit/**`) — pas `tests/migration/` ; ne jamais générer de code superflu. - Déploiement : privilégier le chemin rapide (artifact Next.js en CI + `Dockerfile.prebuilt`) ; CI/CD très robuste (pas d'image Docker obsolète en prod, pas de migrations/schéma DB via le workflow deploy) ; éviter les rebuild Docker complets inutiles (~15 min par itération) ; **ne pas pousser un déploiement quand des features clés sont inachevées** ; ne pas insister sur le déploiement tant que le produit n'est pas fini ou meilleur ; **ne jamais hardcoder d'IP/URL** dans le repo ou les défauts CI — uniquement variables d'env (ex. `GRAFANA_BIND`, `GRAFANA_URL`) ; la config runtime reste sur le serveur dans `.env.docker`. **Machines** : agent/dev = **192.168.1.83** (devSanbox) ; prod = **192.168.1.190** (`/opt/memento`) — **ne jamais confondre** les deux au diagnostic services. **CI/CD Gitea spécifique** : (1) CI `ubuntu-24.04` = lint + tests + build (validation) ; deploy `docker-host` = build sur le serveur → `/tmp/web-artifact.tgz` → `deploy-prod.sh` — **sans** upload/download-artifact inter-runners (stockage Gitea instable ; si artifacts utilisés, **@v3** uniquement — @v4 → `GHESNotSupportedError`) ; (2) `Dockerfile.socket.prebuilt` doit utiliser `--legacy-peer-deps` dans `npm install` (conflit TipTap 3.22.5 vs 3.23.6) ; (3) le serveur de prod (192.168.1.190) **ne peut pas pull Docker Hub** (DNS cassé) — le build Docker complet échoue, seul le chemin prebuilt artifact fonctionne ; (4) `docker-entrypoint.sh` applique les migrations Prisma **avant** de démarrer le serveur Next.js (ordre correct) ; (5) rollback d'urgence : `docker tag memento-memento-note:rollback memento-memento-note:latest && docker compose up -d --force-recreate memento-note` ; (6) `scripts/deploy-prod.sh` charge `/opt/memento/.env.docker` via un **parseur robuste ligne-par-ligne** (jamais `source` — crash `unexpected EOF` sur guillemet non fermé) et teste Postgres avec `pg_isready` + credentials **du conteneur** (`$POSTGRES_USER` interne) ; (7) lint CI = `eslint --max-warnings 9999` : les **warnings ne bloquent pas**, seules les **erreurs** bloquent (ex. `no-html-link-for-pages` → `` obligatoire pour la navigation interne, pas ``) ; **TRAVAILLER SUR UNE BRANCHE** pendant le dev, ne push sur `main` que quand le code est testé et fonctionnel — **avant push `main`** : exécuter localement `npm run lint`, `npm run test:unit`, `npm run build` (même enchaînement que CI) ; chaque push sur `main` déclenche un déploiement automatique en production. -- Authentification : priorité à l'inscription/connexion via **Google OAuth** (plutôt qu'un compte email/mot de passe) ; exiger une vraie déconnexion (invalidation session/cookies — pas de reconnexion implicite, y compris en navigation privée) ; prod : `AUTH_GOOGLE_ID`/`AUTH_GOOGLE_SECRET` dans `/opt/memento/.env.docker` **et** variables/secrets Gitea Actions (workflow deploy n'écrit pas les valeurs vides) — absence → `invalid_client` ; redirect URI exacte `https://memento-note.com/api/auth/callback/google` ; config via **Google Auth Platform** (Branding → Audience **In production**, pas Testing → Clients Web) ; `ALLOW_REGISTRATION=true` requis pour inscription email (env ou `SystemConfig` en base — la DB peut primer sur l'env). -- Priorité absolue à la qualité UX, même si l'implémentation est complexe (« je m'en fous si c'est complexe ») ; préférer les **solutions long terme** aux rustines ; si l'utilisateur demande explicitement d'**analyser sans coder**, expliquer d'abord et **ne pas modifier le code** tant qu'il n'a pas validé ; pour les **tâches ops** (config OAuth prod, serveur), préfère un **guidage interactif étape par étape** plutôt qu'un bloc d'instructions monolithique ; **ne jamais affirmer qu'un correctif ou une feature est fait sans vérification réelle** (app, prototype `architectural-grid`, ou test), **notamment navigation recherche/liste notes et vue `/insights` vs fichiers prototype** — l'utilisateur sanctionne fermement les fausses déclarations ; **ouverture note liée depuis l'éditeur** (ex. bloc live « Ouvrir ») : **split peek inline** animé (`lib/note-peek-sync.ts`, `note-editor-split-peek.tsx` — éditeur courant à **gauche**, note liée en lecture seule à **droite** en LTR ; **inversé en RTL** `fa`/`ar`), **pas nouvel onglet** ; **ne jamais annuler du code non commité** (`git checkout`, reset fichier) **sans demande explicite** (perte de travail documentée, ex. drag handle éditeur) ; **ne jamais remettre du code que l'utilisateur a explicitement retiré sans demander d'abord** (ex. `reserveUsageOrThrow` retiré intentionnellement de `organize-notebook.ts` — agent l'a remis sans demander → user mécontent) ; **correction i18n ou spec doc** : **ne pas refondre logique/UI** hors scope (ex. US-4 `structuredViewBlock` — garder le dual-mode base locale + lien carnet, pas de suppression du mode local) ; en frustration ou pour déléguer, **prévoir des prompts / briefs d'implémentation détaillés** (autre modèle ou dev), en plus des briefs outil de design. +- Authentification : priorité à l'inscription/connexion via **Google OAuth** (plutôt qu'un compte email/mot de passe) ; exiger une vraie déconnexion (invalidation session/cookies — pas de reconnexion implicite, y compris en navigation privée) ; prod : `AUTH_GOOGLE_ID`/`AUTH_GOOGLE_SECRET` dans `/opt/memento/.env.docker` **et** variables/secrets Gitea Actions (workflow deploy n'écrit pas les valeurs vides) — absence → `invalid_client` ; redirect URI exacte `https://memento-note.com/api/auth/callback/google` ; config via **Google Auth Platform** (Branding → Audience **In production**, pas Testing → Clients Web) ; `ALLOW_REGISTRATION=true` requis pour inscription email (env ou `SystemConfig` en base — la DB peut primer sur l'env) ; **navigateur intégré Cursor peu fiable pour le login** (focus volé, cookies isolés, OAuth Google souvent bloqué) — préférer un vrai navigateur pour la connexion manuelle, et pour l'automation (démos/Playwright) une **session JWT/cookies stockée** plutôt que de rejouer le login dans le browser Cursor. +- Priorité absolue à la qualité UX, même si l'implémentation est complexe (« je m'en fous si c'est complexe ») ; préférer les **solutions long terme** aux rustines ; si l'utilisateur demande explicitement d'**analyser sans coder**, expliquer d'abord et **ne pas modifier le code** tant qu'il n'a pas validé ; pour les **tâches ops** (config OAuth prod, serveur), préfère un **guidage interactif étape par étape** plutôt qu'un bloc d'instructions monolithique ; **ne jamais affirmer qu'un correctif ou une feature est fait sans vérification réelle** (app, prototype `architectural-grid`, ou test), **notamment navigation recherche/liste notes et vue `/insights` vs fichiers prototype** — l'utilisateur sanctionne fermement les fausses déclarations ; **ouverture note liée depuis l'éditeur** (ex. bloc live « Ouvrir ») : **split peek inline** animé (`lib/note-peek-sync.ts`, `note-editor-split-peek.tsx` — éditeur courant à **gauche**, note liée en lecture seule à **droite** en LTR ; **inversé en RTL** `fa`/`ar`), **pas nouvel onglet** ; **ne jamais annuler du code non commité** (`git checkout`, reset fichier) **sans demande explicite** (perte de travail documentée, ex. drag handle éditeur) ; **ne jamais remettre du code que l'utilisateur a explicitement retiré sans demander d'abord** (ex. `reserveUsageOrThrow` retiré intentionnellement de `organize-notebook.ts` — agent l'a remis sans demander → user mécontent) ; **correction i18n ou spec doc** : **ne pas refondre logique/UI** hors scope (ex. US-4 `structuredViewBlock` — garder le dual-mode base locale + lien carnet, pas de suppression du mode local) ; en frustration ou pour déléguer, **prévoir des prompts / briefs d'implémentation détaillés** (autre modèle ou dev), en plus des briefs outil de design ; **vidéos promo** : VO **anglaise** ; montrer de **vraies features authentifiées** (dashboard, Memory Echo, Insights, Revision, Agents…) — pas un slideshow landing seul ; **interdit Ken Burns / `zoompan`** (surtout sur fonds pointillés → moiré / tremblement) — images fixes, fond uni stable à la capture. - Livraison : **une user story à la fois**, tester et valider avec l'utilisateur avant la suivante (pas d'auto-validation ni d'enchaînement de code non demandé) ; suivi dans `docs/user-stories.md` ; briefs pour outil de design externe sur demande ; **avant de développer une story, vérifier d'abord dans le code si la feature existe déjà** — les docs de stories (`docs/story-nextgen-editor.md`, `docs/user-stories.md`) sont **souvent périmés** (features livrées mais marquées « à faire ») ; **dashboard Second Brain** : même rythme — **une feature à la fois**, validation explicite avant la suivante. - **Facturation & quotas IA** : limites mensuelles, tiers (BASIC/PRO/BUSINESS/ENTERPRISE) et Price IDs Stripe via **Admin > Facturation & quotas** (`/admin/billing`) — pas via `.env` pour le métier ; secrets Stripe (`STRIPE_SECRET_KEY`, webhook) restent en env serveur ; doc `memento-note/docs/admin-billing-quotas-guide.md` ; **tier BASIC sans accès MCP** ; chaque usage IA (dashboard, agents, MCP si autorisé, etc.) doit **décompter le quota** — mécanisme = **réservation atomique upfront** (Redis/Postgres) **avant** l'appel IA, sans rollback si l'appel échoue ensuite ; BYOK peut contourner selon config — ne pas affirmer un déploiement prod sans vérif réelle (ex. travail MCP/quotas encore local). @@ -28,4 +28,4 @@ - Migrations prebuilt + vérif deploy : `docker compose exec memento-note node ./node_modules/prisma/build/index.js migrate deploy` (pas `npx prisma`) ; helper `scripts/migrate-docker.sh` ; `GET /api/build-info` (SHA Git) ; comparer `127.0.0.1:3000` et domaine Cloudflare — purger cache si versions divergent ; 403 `/api/manifest` côté domaine = souvent Cloudflare. - Éditeur riche : `rich-text-editor.tsx` — `immediatelyRender: false` ; activer **`shouldRerenderOnTransaction: false`** (quick win perf TipTap 2.5) ; **drag handle / menu bloc** via **`@tiptap/extension-drag-handle-react`** (spec officielle — pas de double plugin `DragHandleExtension` + composant React, pas de repositionnement maison) ; poignée dans **colonne gutter fixe** du wrapper (padding + `getReferencedVirtualElement`), pas sur le bord des listes/numéros ; CSS : **pas `opacity:0` sur `.drag-handle`** (visibilité gérée par le plugin) ; config/callbacks **stables hors composant** ; fondation blocs : `tiptap-unique-id-extension.ts` / **`data-id` persisté à la sauvegarde** (références « Copier la référence ») ; **Smart Paste** : `lib/editor/smart-paste-extension.ts` ; **peek split** note source : `lib/note-peek-sync.ts` + `note-editor-split-peek.tsx` ; **conversion markdown → texte enrichi** : un **seul** convertisseur `lib/markdown-to-html.ts` (`markdownToHtml`, gfm+breaks) + une **action atomique** `convertToRichText(html)` dans `note-editor-context.tsx` (applique le HTML **immédiatement** via `setContentImmediate` — pas le `setContent` débouncé 800ms — et bascule `isMarkdown=false`) ; toolbar (`handleConvertToRichtext`) **et** chat IA contextuel (`contextual-ai-chat.tsx`, action `toRichText` → `/api/ai/convert-markdown`) passent par cette action unique (ne pas dupliquer la conversion ni oublier de basculer `isMarkdown`) ; **US-4 `structuredViewBlock`** (`tiptap-structured-view-block-extension.tsx`, `structured-view-block-embed.tsx`) : **dual-mode** — base locale autonome par défaut (`/database`, `/vue`, `isLocal: true`) + option « Lier à un carnet » (Structured Views) ; i18n `structuredViewBlock.*` ; **rejeté** : ancien `databaseBlock` « Auteurs & Œuvres » et spec embed-only `docs/story-nextgen-editor-us4-redesign.md` ; epic active `docs/story-nextgen-editor.md` — priorité **PERF > NEXTGEN > UX > MOBILE > MARKDOWN**. - Sync mutations notes entre composants : `memento-note/lib/note-change-sync.ts` (`emitNoteChange`, événement `NOTE_CHANGE_EVENT`) ; tracking quotas IA : événement `ai-usage-changed` (window) — doit être dispatché depuis **tous** les points d'appel IA (recherche sémantique, auto-tag, auto-title, chat, reformulation, analyse carnet, etc.) ; UI quotas : `usage-meter.tsx` (polling 5s + écoute événement). -- Roadmap / écart prototype vs prod : Web Clipper — **`ClipperSimulator.tsx` = référence design uniquement** (pas de simulateur en prod) ; extension **`memento-note/extension/`** v0.3 **Side Panel** (clip page/sélection/lien ; popup Chrome se ferme au clic page — Side Panel pour la sélection) ; i18n extension **15 langues** (`_locales/`, détection locale navigateur ; script `extension/i18n/generate-translations.cjs`) ; **`host_permissions`** incl. LAN ; **URL serveur configurable en dev**, adresse prod figée en release ; cookies/session alignés avec l'instance cible ; **Flashcards IA SM-2 livrées** : `/revision`, `/api/flashcards/*`, génération depuis l'éditeur (GraduationCap) — réf. prototype `RevisionView.tsx` ; **Structured Views partiellement livrées** : schéma par carnet, Table/Kanban, champs partagés et valeurs par note (`/home` + toolbar carnet) — **suivi de tâches par carnet via Kanban structuré** (pas de vue agrégée Notes/Tâches sur la home ; cases à cocher inline dans les notes) ; **Éditeur Next-Gen livré** (doc `docs/story-nextgen-editor.md` **périmé** — vérifier le code) : US-1 poignée gutter unique (`editor-block-drag-handle.tsx` + `lib/editor/global-drag-handle-extension.ts`, extension Novel `tiptap-extension-global-drag-handle`), US-2 menu action bloc (`block-action-menu.tsx`), US-3 Smart Paste transclusion (`smart-paste-extension.ts` + `smart-paste-menu.tsx`), US-4 database inline (`structuredViewBlock`), plus `data-id` / `liveBlock` / peek split ; **`/insights` livré** : `app/(main)/insights/page.tsx` + `network-graph.tsx` (clusters sémantiques, bridge notes ; **≠ `/graph`**) ; **US-TEMPORAL reporté** ; encore en gap : transclusion bidirectionnelle complète, graphe knowledge enrichi (`GraphKnowledgeMap.tsx`) ; publication **Chrome Web Store** : icônes 16/48/128, privacy policy, `host_permissions` prod restreints vs build dev ; **Wizards** : `NotebookOrganizerDialog` (tags/doublons) branché via bouton "Tags IA" dans `home-client.tsx` — `StructuredViewsWizard` encore **orphelin** (pas de point d'entrée UI) ; **Publication web** (`note-editor-toolbar.tsx`) : deux modes — simple (copie directe) + IA (2-3 templates **visuellement distincts**, reformulation/mise en page **adaptée au contenu** — exercices, toggles, callouts — **images incluses**, KaTeX pour équations) — quota IA consommé uniquement sur publication IA ; l'utilisateur rejette les rendus « copie du texte » / mise en page nulle ; **champs DB publication** sur modèle `Note` : `publicSlug` (pas `publishedSlug`), `publishedContent` (pas `publishedHtml`) — `publishMode` est paramètre API uniquement (`mode: 'simple' | 'ai'`), **pas** un champ en base ; **Second Brain dashboard** (`/home`, `dashboard-view.tsx`) **livré** : layout v5 configurable (~15 widgets, `/api/dashboard/layout`), briefing agrégé (`/api/briefing`), pistes fast+enrich (`/api/briefing/paths`, `paths-fast.ts`), suggestions agents, scan Gmail (`GmailScanHistory`), navigation sidebar `/home` ; bento interactif — `MindMapCard` (clusters), suggestions agents (`AgentSuggestion`, cron), Memory Echo, sentiment, inbox/révisions ; **Gmail OAuth** (`/api/integrations/gmail/*`, pattern Calendar) — scan vols/colis/abonnements → notes + rappels ; crons entrypoint : agents, agent-suggestions, clusters, reminders, gmail-scan, sync-usage. +- Roadmap / écart prototype vs prod : Web Clipper — **`ClipperSimulator.tsx` = référence design uniquement** (pas de simulateur en prod) ; extension **`memento-note/extension/`** v0.3 **Side Panel** (clip page/sélection/lien ; popup Chrome se ferme au clic page — Side Panel pour la sélection) ; i18n extension **15 langues** (`_locales/`, détection locale navigateur ; script `extension/i18n/generate-translations.cjs`) ; **`host_permissions`** incl. LAN ; **URL serveur configurable en dev**, adresse prod figée en release ; cookies/session alignés avec l'instance cible ; **Flashcards IA SM-2 livrées** : `/revision`, `/api/flashcards/*`, génération depuis l'éditeur (GraduationCap) — réf. prototype `RevisionView.tsx` ; **Structured Views partiellement livrées** : schéma par carnet, Table/Kanban, champs partagés et valeurs par note (`/home` + toolbar carnet) — **suivi de tâches par carnet via Kanban structuré** (pas de vue agrégée Notes/Tâches sur la home ; cases à cocher inline dans les notes) ; **Éditeur Next-Gen livré** (doc `docs/story-nextgen-editor.md` **périmé** — vérifier le code) : US-1 poignée gutter unique (`editor-block-drag-handle.tsx` + `lib/editor/global-drag-handle-extension.ts`, extension Novel `tiptap-extension-global-drag-handle`), US-2 menu action bloc (`block-action-menu.tsx`), US-3 Smart Paste transclusion (`smart-paste-extension.ts` + `smart-paste-menu.tsx`), US-4 database inline (`structuredViewBlock`), plus `data-id` / `liveBlock` / peek split ; **`/insights` livré** : `app/(main)/insights/page.tsx` + `network-graph.tsx` (clusters sémantiques, bridge notes ; **≠ `/graph`**) ; **US-TEMPORAL reporté** ; encore en gap : transclusion bidirectionnelle complète, graphe knowledge enrichi (`GraphKnowledgeMap.tsx`) ; publication **Chrome Web Store** : icônes 16/48/128, privacy policy, `host_permissions` prod restreints vs build dev ; **Wizards** : `NotebookOrganizerDialog` (tags/doublons) branché via bouton "Tags IA" dans `home-client.tsx` — `StructuredViewsWizard` encore **orphelin** (pas de point d'entrée UI) ; **Publication web** (`note-editor-toolbar.tsx`) : deux modes — simple (copie directe) + IA (2-3 templates **visuellement distincts**, reformulation/mise en page **adaptée au contenu** — exercices, toggles, callouts — **images incluses**, KaTeX pour équations) — quota IA consommé uniquement sur publication IA ; l'utilisateur rejette les rendus « copie du texte » / mise en page nulle ; **champs DB publication** sur modèle `Note` : `publicSlug` (pas `publishedSlug`), `publishedContent` (pas `publishedHtml`) — `publishMode` est paramètre API uniquement (`mode: 'simple' | 'ai'`), **pas** un champ en base ; **Second Brain dashboard** (`/home`, `dashboard-view.tsx`) **livré** : layout v5 configurable (~15 widgets, `/api/dashboard/layout`), briefing agrégé (`/api/briefing`), pistes fast+enrich (`/api/briefing/paths`, `paths-fast.ts`), suggestions agents, scan Gmail (`GmailScanHistory`), navigation sidebar `/home` ; bento interactif — `MindMapCard` (clusters), suggestions agents (`AgentSuggestion`, cron), Memory Echo, sentiment, inbox/révisions ; **Gmail OAuth** (`/api/integrations/gmail/*`, pattern Calendar) — scan vols/colis/abonnements → notes + rappels ; crons entrypoint : agents, agent-suggestions, clusters, reminders, gmail-scan, sync-usage ; **vidéos promo** : pipeline `promo-video/` (`make_promo.py`, `make_promo_features.py`, `SCENARIO.md`) — captures app authentifiée (Playwright + session JWT), VO EN (edge-tts), montage stable sans zoom. diff --git a/memento-note/app/(main)/chat/page.tsx b/memento-note/app/(main)/chat/page.tsx deleted file mode 100644 index 510d251..0000000 --- a/memento-note/app/(main)/chat/page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Metadata } from 'next' -import { auth } from '@/auth' -import { redirect } from 'next/navigation' -import { prisma } from '@/lib/prisma' -import { ChatContainer } from '@/components/chat/chat-container' -import { getConversations } from '@/app/actions/chat-actions' -import { getSystemConfig } from '@/lib/config' - -export const metadata: Metadata = { - title: 'Chat IA | Memento', - description: 'Discutez avec vos notes et vos agents IA', -} - -export default async function ChatPage() { - const session = await auth() - if (!session?.user?.id) redirect('/login') - - const userId = session.user.id - - // Fetch initial data - const [conversations, notebooks, config] = await Promise.all([ - getConversations(), - prisma.notebook.findMany({ - where: { userId }, - orderBy: { order: 'asc' } - }), - getSystemConfig(), - ]) - - // Check if web search tools are configured - const webSearchAvailable = !!( - config.WEB_SEARCH_PROVIDER || config.BRAVE_SEARCH_API_KEY || config.SEARXNG_URL || config.JINA_API_KEY - ) - - return ( -
- -
- ) -} diff --git a/memento-note/app/(main)/graph/page.tsx b/memento-note/app/(main)/graph/page.tsx deleted file mode 100644 index 4519ffa..0000000 --- a/memento-note/app/(main)/graph/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client' - -import dynamic from 'next/dynamic' - -// D3 uses browser APIs — must be loaded client-side only -const NoteGraphView = dynamic( - () => import('@/components/note-graph-view').then(m => m.NoteGraphView), - { ssr: false } -) - -export default function GraphPage() { - return ( -
- -
- ) -} diff --git a/memento-note/app/(main)/insights/page.tsx b/memento-note/app/(main)/insights/page.tsx index 3435f0a..cda27c7 100644 --- a/memento-note/app/(main)/insights/page.tsx +++ b/memento-note/app/(main)/insights/page.tsx @@ -19,7 +19,6 @@ import { ChevronRight, ChevronDown, Database, - ArrowRight, Menu, Network, List, @@ -28,7 +27,6 @@ import { X, } from 'lucide-react' import { toast } from 'sonner' -import Link from 'next/link' import { useNotePeek, NotePeekPanel } from '@/components/note-peek' import { createNote } from '@/app/actions/notes' import { emitNoteChange } from '@/lib/note-change-sync' @@ -455,9 +453,6 @@ export default function InsightsPage() {

{t('insightsView.semanticGraphLegend')} - - {t('insightsView.openGraphMap')} -
diff --git a/memento-note/app/(main)/support/page.tsx b/memento-note/app/(main)/support/page.tsx deleted file mode 100644 index 36050d0..0000000 --- a/memento-note/app/(main)/support/page.tsx +++ /dev/null @@ -1,156 +0,0 @@ -'use client' - -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { useLanguage } from '@/lib/i18n'; - -export default function SupportPage() { - const { t } = useLanguage(); - - return ( -
-
-

- {t('support.title')} -

-

- {t('support.description')} -

-
- -
- - - - - {t('support.buyMeACoffee')} - - - -

- {t('support.donationDescription')} -

-
-

- {t('support.kofiDescription')} -

- - - - - - - 💚 - {t('support.sponsorOnGithub')} - - - -

- {t('support.sponsorDescription')} -

- -

- {t('support.githubDescription')} -

-
-
-
- - - - {t('support.howSupportHelps')} - - -
-
-

💰 {t('support.directImpact')}

-
    -
  • ☕ Keeps me fueled with coffee
  • -
  • 🐛 Covers hosting and server costs
  • -
  • ✨ Funds development of new features
  • -
  • 📚 Improves documentation
  • -
  • 🌍 Keeps Memento 100% open-source
  • -
-
-
-

🎁 {t('support.sponsorPerks')}

-
    -
  • 🥉 $5/month - Bronze: Name in supporters list
  • -
  • 🥈 $15/month - Silver: Priority feature requests
  • -
  • 🥇 $50/month - Gold: Logo in footer, priority support
  • -
  • 💎 $100/month - Platinum: Custom features, consulting
  • -
-
-
-
-
- - - - 💡 {t('support.transparency')} - - -

- {t('support.transparencyDescription')} -

-
-
- {t('support.hostingServers')} - ~$20/month -
-
- {t('support.domainSSL')} - ~$15/year -
-
- {t('support.aiApiCosts')} - ~$30/month -
-
- {t('support.totalExpenses')} - ~$50/month -
-
-

- Any amount beyond these costs goes directly into improving Memento - and funding new features. Thank you for your support! 💚 -

-
-
- -
-

{t('support.otherWaysTitle')}

-
- - - - -
-
-
- ); -} diff --git a/memento-note/app/actions/chat-actions.ts b/memento-note/app/actions/chat-actions.ts index d26eb9a..6530840 100644 --- a/memento-note/app/actions/chat-actions.ts +++ b/memento-note/app/actions/chat-actions.ts @@ -2,7 +2,6 @@ import { chatService } from '@/lib/ai/services' import { auth } from '@/auth' -import { revalidatePath } from 'next/cache' import { prisma } from '@/lib/prisma' /** @@ -38,7 +37,6 @@ export async function sendChatMessage( try { const result = await chatService.chat(message, conversationId, notebookId) - revalidatePath('/chat') return { success: true, ...result } } catch (error: any) { console.error('[ChatAction] Error:', error) diff --git a/memento-note/app/api/ai/auto-labels/route.ts b/memento-note/app/api/ai/auto-labels/route.ts index 09db4a5..08e20f9 100644 --- a/memento-note/app/api/ai/auto-labels/route.ts +++ b/memento-note/app/api/ai/auto-labels/route.ts @@ -19,6 +19,7 @@ export async function POST(request: NextRequest) { { status: 401 } ) } + const userId = session.user.id // GDPR AI Consent check if (!(await hasUserAiConsent())) { @@ -61,12 +62,12 @@ export async function POST(request: NextRequest) { } const suggestions = await withAiQuota( - session.user.id, + userId, 'auto_tag', () => autoLabelCreationService.suggestLabels( notebookId, - session.user!.id, + userId, language, ), { lane: 'tags' }, diff --git a/memento-note/app/api/ai/suggest-notebook/route.ts b/memento-note/app/api/ai/suggest-notebook/route.ts index c8296c0..d7c207f 100644 --- a/memento-note/app/api/ai/suggest-notebook/route.ts +++ b/memento-note/app/api/ai/suggest-notebook/route.ts @@ -10,6 +10,7 @@ export async function POST(req: NextRequest) { if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } + const userId = session.user.id if (!(await hasUserAiConsent())) { return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 }) @@ -32,11 +33,11 @@ export async function POST(req: NextRequest) { } const suggestedNotebook = await withAiQuota( - session.user.id, + userId, 'auto_tag', () => notebookSuggestionService.suggestNotebook( noteContent, - session.user!.id, + userId, language ), { lane: 'tags' }, diff --git a/memento-note/app/api/graph/route.ts b/memento-note/app/api/graph/route.ts deleted file mode 100644 index 28a077a..0000000 --- a/memento-note/app/api/graph/route.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import prisma from '@/lib/prisma' -import { auth } from '@/auth' - -// ── Stopwords FR + EN ───────────────────────────────────────────────────────── -const STOPWORDS = new Set([ - 'le','la','les','de','du','des','un','une','et','en','au','aux','ce','se', - 'sa','son','ses','mon','ma','mes','ton','ta','tes','que','qui','quoi','dont', - 'il','elle','ils','elles','nous','vous','je','tu','on','par','pour','sur', - 'sous','avec','dans','est','sont','pas','ne','plus','très','tout','comme', - 'mais','donc','car','cet','cette','ces','leur','leurs','note','notes', - 'the','a','an','and','or','but','in','on','at','to','for','of','with','by', - 'from','is','are','was','were','be','been','have','has','had','do','does', - 'did','will','would','could','should','may','might','this','that','these', - 'those','it','its','they','them','their','he','she','we','you','not','no', - 'so','if','as','up','out','about','also','just','can','all','any','get', -]) - -function stripHtml(html: string): string { - return html.replace(/<[^>]+>/g, ' ').replace(/&\w+;/g, ' ') -} - -function extractKeywords(text: string): Set { - return new Set( - stripHtml(text) - .toLowerCase() - .split(/[\s\p{P}]+/u) - .filter(w => w.length >= 3 && !STOPWORDS.has(w) && !/^\d+$/.test(w)) - ) -} - -function jaccardSimilarity(a: Set, b: Set): number { - if (a.size === 0 || b.size === 0) return 0 - let intersection = 0 - for (const w of a) if (b.has(w)) intersection++ - return intersection / (a.size + b.size - intersection) -} - -type EdgeType = 'title_mention' | 'shared_label' | 'jaccard' | 'explicit_link' | 'semantic_echo' - -interface GraphEdge { source: string; target: string; weight: number; type: EdgeType } - -// GET /api/graph — connexions automatiques à 3 niveaux -export async function GET(request: NextRequest) { - const session = await auth() - if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - - const userId = session.user.id - const { searchParams } = new URL(request.url) - const notebookId = searchParams.get('notebookId') || undefined - - const notes = await prisma.note.findMany({ - where: { userId, trashedAt: null, ...(notebookId ? { notebookId } : {}) }, - select: { - id: true, title: true, content: true, notebookId: true, createdAt: true, - labelRelations: { select: { id: true } }, - notebook: { select: { id: true, name: true } }, - }, - take: 500, - orderBy: { updatedAt: 'desc' }, - }) - - if (notes.length === 0) return NextResponse.json({ nodes: [], edges: [] }) - - const ids = notes.map(n => n.id) - - // Query NoteLink manually created relationships - const noteLinks = await (prisma as any).noteLink.findMany({ - where: { - sourceNoteId: { in: ids }, - targetNoteId: { in: ids } - }, - select: { - sourceNoteId: true, - targetNoteId: true, - contextSnippet: true - } - }) - - // Query MemoryEchoInsight semantic relationships - const echoInsights = await (prisma as any).memoryEchoInsight.findMany({ - where: { - userId, - dismissed: false, - note1Id: { in: ids }, - note2Id: { in: ids } - }, - select: { - note1Id: true, - note2Id: true, - similarityScore: true, - insight: true - } - }) - - // Pré-calcul - const keywordsMap = new Map>() - const labelMap = new Map>() - for (const note of notes) { - keywordsMap.set(note.id, extractKeywords(`${note.title ?? ''} ${note.content}`)) - labelMap.set(note.id, new Set(note.labelRelations.map((l: any) => l.id))) - } - - const EDGE_TYPE_PRIORITY: Record = { - explicit_link: 5, - semantic_echo: 4, - title_mention: 3, - shared_label: 2, - jaccard: 1, - } - - const edgeMap = new Map() - function upsertEdge(a: string, b: string, weight: number, type: EdgeType) { - const key = a < b ? `${a}--${b}` : `${b}--${a}` - const ex = edgeMap.get(key) - if (!ex) { - edgeMap.set(key, { source: a < b ? a : b, target: a < b ? b : a, weight, type }) - } else { - const exPriority = EDGE_TYPE_PRIORITY[ex.type] || 0 - const curPriority = EDGE_TYPE_PRIORITY[type] || 0 - if (curPriority > exPriority || (curPriority === exPriority && weight > ex.weight)) { - edgeMap.set(key, { source: a < b ? a : b, target: a < b ? b : a, weight, type }) - } - } - } - - // ── Niveau 1 : Title Mention (comme Obsidian "unlinked mentions") ────────── - for (const noteA of notes) { - const title = (noteA.title ?? '').trim().toLowerCase() - if (title.length < 3) continue - const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const re = new RegExp(`(? lb.has(l)).length - if (shared > 0) upsertEdge(ids[i], ids[j], Math.min(0.5 + shared * 0.15, 0.9), 'shared_label') - } - } - - // ── Niveau 3 : Jaccard (désactivé > 500 notes) ─────────────────────────── - if (notes.length <= 500) { - for (let i = 0; i < ids.length; i++) { - const kwI = keywordsMap.get(ids[i])! - const candidates: { j: number; score: number }[] = [] - for (let j = i + 1; j < ids.length; j++) { - const score = jaccardSimilarity(kwI, keywordsMap.get(ids[j])!) - if (score >= 0.12) candidates.push({ j, score }) - } - candidates.sort((a, b) => b.score - a.score).slice(0, 10) - .forEach(({ j, score }) => upsertEdge(ids[i], ids[j], score * 0.8, 'jaccard')) - } - } - - // ── Niveau 4 : WikiLinks explicites (NoteLink) ───────────────────────────── - for (const link of noteLinks) { - upsertEdge(link.sourceNoteId, link.targetNoteId, 1.0, 'explicit_link') - } - - // ── Niveau 5 : Échos sémantiques IA (MemoryEchoInsight) ──────────────────── - for (const echo of echoInsights) { - upsertEdge(echo.note1Id, echo.note2Id, echo.similarityScore, 'semantic_echo') - } - - const degreeMap = new Map() - for (const e of edgeMap.values()) { - degreeMap.set(e.source, (degreeMap.get(e.source) ?? 0) + 1) - degreeMap.set(e.target, (degreeMap.get(e.target) ?? 0) + 1) - } - - const nodes = notes.map(n => ({ - id: n.id, - title: n.title || 'Sans titre', - notebookId: n.notebookId, - createdAt: n.createdAt, - degree: degreeMap.get(n.id) ?? 0, - })) - - // Build clusters (notebooks) - const notebookMap = new Map() - for (const n of notes) { - if (n.notebook) notebookMap.set(n.notebook.id, n.notebook.name) - } - const clusters = [...notebookMap.entries()].map(([id, name]) => ({ id, name })) - - return NextResponse.json({ nodes, edges: Array.from(edgeMap.values()), clusters }) -} diff --git a/memento-note/app/api/graph/sync-all/route.ts b/memento-note/app/api/graph/sync-all/route.ts deleted file mode 100644 index 8b719d4..0000000 --- a/memento-note/app/api/graph/sync-all/route.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { NextResponse } from 'next/server' -import prisma from '@/lib/prisma' -import { auth } from '@/auth' - -const WIKILINK_RE = /\[\[([^\]|#]+?)(?:[|#][^\]]+)?\]\]/g - -function extractWikilinks(content: string): { title: string; snippet: string }[] { - const plain = content.replace(/<[^>]+>/g, ' ') - const results: { title: string; snippet: string }[] = [] - const seen = new Set() - let match: RegExpExecArray | null - - WIKILINK_RE.lastIndex = 0 - while ((match = WIKILINK_RE.exec(plain)) !== null) { - const title = match[1].trim() - if (!title || seen.has(title.toLowerCase())) continue - seen.add(title.toLowerCase()) - const start = Math.max(0, match.index - 50) - const end = Math.min(plain.length, match.index + match[0].length + 50) - const snippet = plain.slice(start, end).replace(/\s+/g, ' ').trim() - results.push({ title, snippet }) - } - - return results -} - -/** - * POST /api/graph/sync-all - * Batch-sync [[wikilinks]] for ALL notes of the authenticated user. - * Call once to populate the NoteLink table from existing notes. - */ -export async function POST() { - const session = await auth() - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - const userId = session.user.id - - // Get all non-trashed notes with content - const notes = await prisma.note.findMany({ - where: { userId, trashedAt: null, content: { not: '' } }, - select: { id: true, content: true, notebookId: true }, - }) - - let totalLinks = 0 - - for (const note of notes) { - if (!note.content.includes('[[')) continue - - const wikilinks = extractWikilinks(note.content) - if (wikilinks.length === 0) continue - - for (const { title, snippet } of wikilinks) { - const targetNote = await prisma.note.findFirst({ - where: { - userId, - title: { equals: title, mode: 'insensitive' }, - trashedAt: null, - }, - select: { id: true }, - }) - - if (!targetNote) { - // Skip stubs in batch sync — we only link existing notes - continue - } - - if (targetNote.id === note.id) continue - - try { - await (prisma as any).noteLink.upsert({ - where: { sourceNoteId_targetNoteId: { sourceNoteId: note.id, targetNoteId: targetNote.id } }, - update: { contextSnippet: snippet }, - create: { sourceNoteId: note.id, targetNoteId: targetNote.id, contextSnippet: snippet }, - }) - totalLinks++ - } catch { - // ignore duplicate constraint errors - } - } - } - - return NextResponse.json({ synced: notes.length, links: totalLinks }) -} diff --git a/memento-note/app/layout.tsx b/memento-note/app/layout.tsx index 7483568..57143ad 100644 --- a/memento-note/app/layout.tsx +++ b/memento-note/app/layout.tsx @@ -101,7 +101,7 @@ export default async function RootLayout({ const htmlStyle = { '--color-brand-accent': serverAccent, // Aide le navigateur à peindre scrollbars/inputs dans le bon mode - colorScheme: wantsDarkClass || resolvedTheme === 'dark' || resolvedTheme === 'midnight' ? 'dark' : resolvedTheme === 'light' ? 'light' : undefined, + colorScheme: wantsDarkClass ? 'dark' : resolvedTheme === 'light' ? 'light' : undefined, } as CSSProperties return ( diff --git a/memento-note/app/test-title-suggestions/page.tsx b/memento-note/app/test-title-suggestions/page.tsx deleted file mode 100644 index 6956af3..0000000 --- a/memento-note/app/test-title-suggestions/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -'use client' - -import { useState } from 'react' -import { useTitleSuggestions } from '@/hooks/use-title-suggestions' - -export default function TestTitleSuggestionsPage() { - const [content, setContent] = useState('') - - const { suggestions, isAnalyzing, error } = useTitleSuggestions({ - content, - enabled: true // Always enabled for testing - }) - - const wordCount = content.split(/\s+/).filter(w => w.length > 0).length - - return ( -
-

Test Title Suggestions

- -
- -