fix: brainstorm infinite loop, ghost cursor, embedding ::vector cast, semantic search, billing stats, usage meter accordion
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 5s

- Fix useBrainstormSocket: stable guestId via useRef, remove setState in cleanup
- Fix GhostCursor: direct DOM manipulation via refs, no useState re-renders
- Fix all SQL embedding queries: add ::vector cast on text columns
- Fix embedding truncation to 15000 chars (under 8192 token limit)
- Fix NoteEmbedding INSERT: remove non-existent updatedAt column
- Fix billing page: show all quota stats in grid instead of single metric
- Fix usage meter: accordion expand/collapse, per-feature detail
- Fix semantic search: rebuild 103 note embeddings, ::vector cast on vectorSearch
- Fix brainstorm expand/manual-idea/create: ::vector cast on embedding SQL
This commit is contained in:
Antigravity
2026-05-16 18:50:34 +00:00
parent ee8e2bda59
commit 8c7ca69640
117 changed files with 11732 additions and 834 deletions

View File

@@ -1,8 +1,8 @@
{
"version": 1,
"lastRunAtMs": 1778916916450,
"turnsSinceLastRun": 6,
"turnsSinceLastRun": 9,
"lastTranscriptMtimeMs": 1778916916347.422,
"lastProcessedGenerationId": "6a9dfdc8-438b-497e-831b-ad0079ebea2a",
"lastProcessedGenerationId": "309e0c67-c6f3-45de-b400-cc4455d26b28",
"trialStartedAtMs": null
}

View File

@@ -2,7 +2,7 @@
Une application de prise de notes intelligente et powered by IA. Comme Google Keep, mais avec des notebooks, la recherche semantique, des agents IA et un serveur MCP integre.
**[Read in English](README.md)** | **[Guide complet](GUIDE.md)**
**[Read in English](README.md)** | **[Guide complet](GUIDE.md)** | **[Guide utilisateur (captures)](docs/guide-utilisateur/README.md)**
---

View File

@@ -0,0 +1,307 @@
# Guide utilisateur Momento
Documentation produit illustrée du SaaS **Momento** — second cerveau augmenté par lIA (notes, recherche sémantique, agents, brainstorm collaboratif, BYOK).
> **Sources internes** : ce guide synthétise le [PRD](../prd.md), les [fonctionnalités IA](../fonctionnalites-ia.md), la [doc brainstorm](../../memento-note/docs/brainstorm-documentation.md) et le [GUIDE technique](../../GUIDE.md) (installation / admin).
---
## Sommaire
1. [Vue densemble](#1-vue-densemble)
2. [Premiers pas](#2-premiers-pas)
3. [Page daccueil (marketing)](#3-page-daccueil-marketing)
4. [Espace de travail](#4-espace-de-travail)
5. [Intelligence artificielle](#5-intelligence-artificielle)
6. [Agents autonomes](#6-agents-autonomes)
7. [Brainstorm radial](#7-brainstorm-radial)
8. [Paramètres et facturation](#8-paramètres-et-facturation)
9. [Administration](#9-administration)
10. [Captures décran](#10-captures-décran)
11. [Aller plus loin](#11-aller-plus-loin)
---
## 1. Vue densemble
Momento est une application de prise de notes qui combine :
- **Organisation** : carnets, labels, grille masonry, archive, corbeille, partage.
- **Recherche sémantique** : trouver par idée, pas seulement par mot-clé (vecteurs + plein texte).
- **IA intégrée** : chat RAG sur vos notes, reformulation, tags et titres suggérés, Memory Echo.
- **Agents** : Scraper, Researcher, Monitor, générateur de slides/diagrammes, agents personnalisés.
- **Brainstorm** : canvas radial D3 en temps réel (vagues Variations / Analogies / Disruptions).
- **BYOK** : connecter vos propres clés API (OpenAI, Anthropic, Google, etc.) pour maîtriser les coûts.
- **Modèle commercial** : pack découverte IA, abonnements Pro / Business / Enterprise, facturation « host-pays » en session partagée.
**Publics visés** : créateurs, consultants, équipes R&D, organisations qui veulent une mémoire de travail partagée et sécurisée.
---
## 2. Premiers pas
### Créer un compte
1. Ouvrir lURL de votre instance (ex. `https://votre-domaine.com`).
2. Cliquer sur **Get started** ou **Sign up**.
3. Renseigner email, nom et mot de passe.
![Inscription](screenshots/08-register.png)
### Se connecter
![Connexion](screenshots/07-login.png)
En cas doubli : **Forgot password?** envoie un lien de réinitialisation par email (SMTP configuré côté serveur).
![Mot de passe oublié](screenshots/09-forgot-password.png)
### Après connexion
Vous arrivez sur **laccueil notes** (`/home`) : sidebar (carnets, navigation), zone centrale (grille ou liste de notes), actions de création.
> Les captures de lapplication connectée (`10-app-*.png`, etc.) nécessitent une session active. Voir [Régénérer les captures](#régénérer-les-captures).
---
## 3. Page daccueil (marketing)
La landing publique présente la proposition de valeur avant inscription.
### Hero — second cerveau amplifié
![Landing — hero](screenshots/01-landing-hero.png)
Message clé : Momento relie, analyse et développe vos idées avec **6 types dagents IA** et une **recherche sémantique** avancée. Exemple produit : *Memory Echo* qui signale un lien avec un projet passé.
### Capacités IA
![Landing — fonctionnalités IA](screenshots/02-landing-features.png)
| Bloc | Ce que lutilisateur y gagne |
|------|------------------------------|
| **Semantic Search** | Retrouver une note par le sens, pas seulement les mots exacts. |
| **Contextual RAG Chat** | Dialoguer avec tout votre corpus (notes + web + documents). |
| **Augmented Writing** | Reformulation, titres, tags et résumés en arrière-plan. |
### Agents spécialisés
![Landing — agents](screenshots/03-landing-agents.png)
Six rôles mis en avant : **Scraper**, **Researcher**, **Slide Gen**, **Monitor**, **Diagram Gen**, et agents **Custom** (rôles et sources de données définis par vous).
### Brainstorm — vagues de pensée
![Landing — brainstorm](screenshots/04-landing-brainstorm.png)
Brainstorming radial temps réel : génération par vagues, collaboration (curseurs, avatars), export sémantique vers des notes structurées.
### Tarifs
![Landing — tarifs](screenshots/05-landing-pricing.png)
| Offre | Positionnement |
|-------|----------------|
| **Basic** | Découverte (notes limitées, crédits IA découverte). |
| **Pro** | Créateurs / consultants (BYOK OpenAI/Anthropic, agents, historique étendu). |
| **Business** | Équipes (collaborateurs, 13 fournisseurs BYOK, API). |
| **Enterprise** | SSO/SAML, agents illimités, audit, support dédié. |
### BYOK — votre propre fournisseur IA
![Landing — BYOK](screenshots/06-landing-byok.png)
Si vous avez déjà des clés **OpenAI**, **Anthropic** ou **Google**, vous les connectez à Momento : pas de plafond de crédits imposé par la plateforme, facturation directe chez le fournisseur, changement de provider en un clic.
---
## 4. Espace de travail
### Notes et carnets
- **Types de notes** : texte riche, checklist, Markdown, texte brut.
- **Carnets** : regroupement thématique ; **labels** contextuels (y compris suggérés par lIA).
- **Vues** : grille masonry (drag-and-drop) ou onglets.
- **Cycle de vie** : archive, corbeille, historique des versions, partage avec permissions.
### Recherche
Barre de recherche globale : mode **plein texte** et mode **sémantique** (embeddings). Idéal pour « retrouver cette idée sur larchitecture modulaire » sans se souvenir du titre exact.
### Chat IA (`/chat`)
Conversations persistées, alimentées par vos notes (RAG), avec outils : recherche de notes, lecture, recherche web, scrape. Le système peut cibler un carnet ou la note ouverte (mode Copilot).
### Lab (`/lab`)
Tableau blanc **Excalidraw** intégré pour schémas, mindmaps et croquis libres.
---
## 5. Intelligence artificielle
Détail technique : [fonctionnalites-ia.md](../fonctionnalites-ia.md).
### Trois fournisseurs configurables
| Tier | Usage |
|------|--------|
| **Tags** | Tags, reformulation, suggestions de titre |
| **Embeddings** | Recherche sémantique, Memory Echo |
| **Chat** | Chat RAG, agents, brainstorm, vision |
Chacun peut pointer vers un modèle différent (OpenAI, Ollama local, Anthropic, DeepSeek, OpenRouter, etc.).
### Dans léditeur de note
- **Suggestions de titre** (3 styles).
- **Tags contextuels** parmi les labels du carnet ou nouveaux.
- **Reformulation** : clarifier, raccourcir, style, grammaire, traduire.
- **Description dimages** (vision) pour notes illustrées.
### Memory Echo
Détection proactive de liens entre notes (similarité vectorielle) avec explication en une phrase ; feedback pouce haut/bas pour affiner la sensibilité.
### Quotas et pack découverte
Indicateur de consommation IA (sidebar). À lépuisement : proposition d**upgrade** ou dajout dune **clé BYOK** — pas de blocage brutal sans issue.
---
## 6. Agents autonomes
Page **Agents** (`/agents`) : créer, planifier et suivre des agents.
| Type | Rôle |
|------|------|
| **Scraper** | URLs + flux RSS → synthèse et note avec images |
| **Researcher** | Requêtes web → note de recherche structurée |
| **Monitor** | Surveillance dun carnet → tendances et insights |
| **Custom** | Rôle libre + sources optionnelles |
| **Slide Generator** | PowerPoint ou slides HTML Reveal.js |
| **Diagram Gen** | Diagrammes Excalidraw (mindmap, flowchart, etc.) |
Planification : manuel, horaire, quotidien, hebdomadaire, mensuel (fuseau IANA).
Résultats : notification in-app et par email selon configuration.
---
## 7. Brainstorm radial
Documentation technique : [brainstorm-documentation.md](../../memento-note/docs/brainstorm-documentation.md).
### Parcours type
1. Saisir une **idée graine** (ou partir dune note existante).
2. LIA génère **3 vagues** : Variations → Analogies → Disruptions (9 idées).
3. Sur le **canvas D3** : zoom, drag, approfondir, rejeter, convertir en note.
4. **Partager** la session (lien invité ; lhôte paie les tokens en mode host-pays).
5. **Exporter** en note structurée ou formats de présentation.
### Collaboration
Socket.io : curseurs en direct, déplacement de nœuds, présence des participants (host / editor / viewer).
---
## 8. Paramètres et facturation
Accessible via licône paramètres (profil utilisateur).
| Section | Contenu |
|---------|---------|
| **Profil** | Identité, email, mot de passe |
| **Apparence** | Thème clair/sombre, langue (15 locales) |
| **IA** | Préférences utilisateur ; clés **BYOK** personnelles (chiffrées AES-256-GCM) |
| **Facturation** | Abonnement Stripe, usage, portail client |
| **Données** | Export / import JSON |
| **MCP** | Clés API pour le serveur MCP (Claude Desktop, N8N, etc.) |
### BYOK côté utilisateur
Coller la clé du fournisseur choisi → validation en direct → badge « mode BYOK » ; le routeur LLM bascule automatiquement sans choix manuel à chaque requête.
### Facturation host-pays
Dans une session brainstorm partagée, cest l**hôte** qui consomme le quota / la clé BYOK pour les invités — flux fluide pour les équipes.
---
## 9. Administration
Réservé aux comptes **ADMIN** (premier email `ADMIN_EMAIL` à linscription ou promotion manuelle).
| Zone | Rôle |
|------|------|
| **Utilisateurs** | Gestion des comptes et rôles |
| **IA** | Fournisseurs système (tags, embeddings, chat), **fournisseur de secours** optionnel (erreurs 429/5xx) |
| **Tests IA** | Valider tags et embeddings |
| **Sécurité** | Inscription publique on/off |
| **SMTP** | Emails transactionnels |
Guide déploiement et variables : [GUIDE.md](../../GUIDE.md).
---
## 10. Captures décran
| Fichier | Description |
|---------|-------------|
| `01-landing-hero.png` | Landing — accroche |
| `02-landing-features.png` | Capacités IA |
| `03-landing-agents.png` | Agents |
| `04-landing-brainstorm.png` | Brainstorm |
| `05-landing-pricing.png` | Tarifs |
| `06-landing-byok.png` | BYOK |
| `07-login.png` | Connexion |
| `08-register.png` | Inscription |
| `09-forgot-password.png` | Mot de passe oublié |
| `10-app-home.png` | Accueil (connecté) |
| `11-app-chat.png` | Chat IA |
| `12-app-agents.png` | Agents |
| `13-app-brainstorm.png` | Brainstorm app |
| `14-app-lab.png` | Lab Excalidraw |
| `15-settings-ai.png` | Paramètres IA / BYOK |
| `16-settings-billing.png` | Facturation |
| `17-settings-profile.png` | Profil *(si capture auth)* |
| `18-admin-dashboard.png` | Admin *(si capture auth)* |
### Régénérer les captures
Depuis le dossier **`memento-note/`** (Playwright y est installé) :
```bash
cd memento-note
npx playwright install chromium # une seule fois
# Pages publiques uniquement
node ../docs/guide-utilisateur/capture-screenshots.mjs
# + application connectée (recommandé pour 1018)
MOMENTO_DOC_EMAIL=votre@email.com \
MOMENTO_DOC_PASSWORD='votre-mot-de-passe' \
node ../docs/guide-utilisateur/capture-screenshots.mjs
```
- **URL** : par défaut lue depuis `memento-note/.env``NEXTAUTH_URL` (ex. `http://192.168.1.83:3000`). Surcharge : `MOMENTO_DOC_BASE_URL`.
- En cas déchec de connexion, les captures **0109** sont quand même produites ; **1018** sont ignorées.
---
## 11. Aller plus loin
| Document | Public | Sujet |
|----------|--------|--------|
| [GUIDE.md](../../GUIDE.md) | Admin / DevOps | Installation, Docker, env, MCP |
| [fonctionnalites-ia.md](../fonctionnalites-ia.md) | Produit / Dev | Détail des capacités IA |
| [prd.md](../prd.md) | Produit | Vision, parcours, exigences |
| [ux-design-specification.md](../ux-design-specification.md) | Design | Quotas, BYOK, RGPD |
| [epics.md](../epics.md) | Engineering | Stories commerciales V3 |
| [brainstorm-documentation.md](../../memento-note/docs/brainstorm-documentation.md) | Dev | Architecture brainstorm |
| [gtm-pricing-strategy.md](../gtm-pricing-strategy.md) | Business | Tarification GTM |
---
*Dernière mise à jour des captures : génération automatique sur instance locale. Pour une doc production, régénérer les PNG après connexion avec un compte de démonstration.*

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env node
/**
* Capture les captures d'écran pour docs/guide-utilisateur/screenshots/
*
* Pages publiques : sans identifiants.
* App connectée : définir MOMENTO_DOC_EMAIL et MOMENTO_DOC_PASSWORD
*
* Usage (depuis memento-note/ — Playwright est installé là) :
* node ../docs/guide-utilisateur/capture-screenshots.mjs
* MOMENTO_DOC_EMAIL=you@example.com MOMENTO_DOC_PASSWORD=secret node ../docs/guide-utilisateur/capture-screenshots.mjs
*/
import { createRequire } from 'module'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const appRoot = path.resolve(__dirname, '../../memento-note')
const require = createRequire(path.join(appRoot, 'package.json'))
const { chromium } = require('playwright')
const OUT = path.join(__dirname, 'screenshots')
function resolveBaseUrl() {
if (process.env.MOMENTO_DOC_BASE_URL) return process.env.MOMENTO_DOC_BASE_URL.replace(/\/$/, '')
try {
const envPath = path.join(appRoot, '.env')
const env = require('fs').readFileSync(envPath, 'utf8')
const m = env.match(/^NEXTAUTH_URL=["']?([^"'\n]+)["']?/m)
if (m) return m[1].replace(/\/$/, '')
} catch {
/* ignore */
}
return 'http://localhost:3000'
}
const BASE = resolveBaseUrl()
async function shot(page, url, name, opts = {}) {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
await page.waitForTimeout(opts.wait ?? 1000)
if (opts.scrollId) {
const el = page.locator(`#${opts.scrollId}`).first()
if (await el.count()) await el.scrollIntoViewIfNeeded()
}
await page.waitForTimeout(400)
await page.screenshot({ path: path.join(OUT, name), fullPage: !!opts.fullPage })
console.log('✓', name)
}
async function loginIfConfigured(page) {
const email = process.env.MOMENTO_DOC_EMAIL
const password = process.env.MOMENTO_DOC_PASSWORD
if (!email || !password) {
console.log(' Connexion ignorée (MOMENTO_DOC_EMAIL / MOMENTO_DOC_PASSWORD non définis)')
return false
}
console.log(` Connexion sur ${BASE}/login`)
await page.goto(`${BASE}/login`, { waitUntil: 'networkidle' })
await page.locator('#email').fill(email)
await page.locator('#password').fill(password)
await page.locator('form button[type="submit"], form button').first().click()
try {
await page.waitForURL((u) => !u.pathname.includes('/login'), { timeout: 20000 })
} catch {
const err = await page.locator('.text-red-500, [class*="error"]').first().textContent().catch(() => '')
console.warn(
`⚠ Connexion échouée (${err || 'timeout'}). Vérifiez MOMENTO_DOC_EMAIL, MOMENTO_DOC_PASSWORD et NEXTAUTH_URL=${BASE}`,
)
return false
}
console.log('✓ Session connectée →', page.url())
return true
}
console.log(` Base URL: ${BASE}`)
const browser = await chromium.launch({ headless: true })
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } })
await page.goto(`${BASE}/`, { waitUntil: 'networkidle' })
for (const [id, name] of [
[null, '01-landing-hero.png'],
['features', '02-landing-features.png'],
['agents', '03-landing-agents.png'],
['brainstorm', '04-landing-brainstorm.png'],
['pricing', '05-landing-pricing.png'],
['tech', '06-landing-byok.png'],
]) {
if (id) await page.goto(`${BASE}/#${id}`, { waitUntil: 'networkidle' })
else await page.evaluate(() => window.scrollTo(0, 0))
await page.waitForTimeout(600)
if (id) {
const el = page.locator(`#${id}`).first()
if (await el.count()) await el.scrollIntoViewIfNeeded()
}
await page.waitForTimeout(400)
await page.screenshot({ path: path.join(OUT, name) })
console.log('✓', name)
}
await shot(page, `${BASE}/login`, '07-login.png')
await shot(page, `${BASE}/register`, '08-register.png')
await shot(page, `${BASE}/forgot-password`, '09-forgot-password.png')
const loggedIn = await loginIfConfigured(page)
if (loggedIn) {
for (const [route, name] of [
['/home', '10-app-home.png'],
['/chat', '11-app-chat.png'],
['/agents', '12-app-agents.png'],
['/brainstorm', '13-app-brainstorm.png'],
['/lab', '14-app-lab.png'],
['/settings/ai', '15-settings-ai.png'],
['/settings/billing', '16-settings-billing.png'],
['/settings/profile', '17-settings-profile.png'],
['/admin', '18-admin-dashboard.png'],
]) {
await shot(page, `${BASE}${route}`, name, { wait: 1500 })
}
}
await browser.close()
console.log('\nCaptures enregistrées dans', OUT)

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,21 +1,103 @@
'use client'
import { useState } from 'react'
import { useState, useRef, useEffect, useCallback } from 'react'
import { createPortal } from 'react-dom'
import { Button } from '@/components/ui/button'
import { deleteUser, updateUserRole } from '@/app/actions/admin'
import { deleteUser, updateUserRole, updateUserSubscription } from '@/app/actions/admin'
import { toast } from 'sonner'
import { Trash2, Shield, ShieldOff } from 'lucide-react'
import { Trash2, Shield, ShieldOff, Crown, ChevronDown } from 'lucide-react'
import { format } from 'date-fns'
import { useLanguage } from '@/lib/i18n'
const TIERS = ['BASIC', 'PRO', 'BUSINESS', 'ENTERPRISE'] as const
const tierColors: Record<string, string> = {
BASIC: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300',
PRO: 'bg-brand-accent/10 text-brand-accent',
BUSINESS: 'bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
ENTERPRISE: 'bg-purple-50 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
}
function TierDropdown({ userId, tier, isOpen, onToggle, onClose, onChange }: {
userId: string
tier: string
isOpen: boolean
onToggle: () => void
onClose: () => void
onChange: (tier: string) => void
}) {
const btnRef = useRef<HTMLButtonElement>(null)
const [pos, setPos] = useState({ top: 0, left: 0 })
const updatePos = useCallback(() => {
if (btnRef.current) {
const r = btnRef.current.getBoundingClientRect()
setPos({ top: r.bottom + 4, left: r.left })
}
}, [])
useEffect(() => {
if (isOpen) {
updatePos()
window.addEventListener('scroll', updatePos, true)
window.addEventListener('resize', updatePos)
}
return () => {
window.removeEventListener('scroll', updatePos, true)
window.removeEventListener('resize', updatePos)
}
}, [isOpen, updatePos])
return (
<td className="p-4 align-middle">
<button
ref={btnRef}
onClick={(e) => { e.stopPropagation(); onToggle() }}
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors cursor-pointer hover:opacity-80 ${tierColors[tier] || tierColors.BASIC}`}
>
{tier === 'ENTERPRISE' && <Crown size={10} />}
{tier}
<ChevronDown size={10} />
</button>
{isOpen && typeof document !== 'undefined' && createPortal(
<>
<div className="fixed inset-0 z-[9998]" onClick={onClose} />
<div
className="fixed z-[9999] bg-white dark:bg-zinc-900 border border-border rounded-xl shadow-xl py-1 min-w-[140px]"
style={{ top: pos.top, left: pos.left }}
>
{TIERS.map((t) => (
<button
key={t}
onClick={(e) => { e.stopPropagation(); onChange(t) }}
className={`w-full text-left px-4 py-2 text-xs font-medium transition-colors hover:bg-muted flex items-center gap-2 ${tier === t ? 'text-brand-accent font-bold' : 'text-foreground'}`}
>
{tier === t && '✓'}
<span className={`inline-flex items-center gap-1 ${tier === t ? '' : 'ml-4'}`}>
{t === 'ENTERPRISE' && <Crown size={10} />}
{t}
</span>
</button>
))}
</div>
</>,
document.body
)}
</td>
)
}
export function UserList({ initialUsers }: { initialUsers: any[] }) {
const { t } = useLanguage()
const [users, setUsers] = useState(initialUsers)
const [openDropdown, setOpenDropdown] = useState<string | null>(null)
const handleDelete = async (id: string) => {
if (!confirm(t('admin.users.confirmDelete'))) return
try {
await deleteUser(id)
toast.success(t('admin.users.deleteSuccess'))
setUsers(prev => prev.filter(u => u.id !== id))
} catch (e) {
toast.error(t('admin.users.deleteFailed'))
}
@@ -26,11 +108,25 @@ export function UserList({ initialUsers }: { initialUsers: any[] }) {
try {
await updateUserRole(user.id, newRole)
toast.success(t('admin.users.roleUpdateSuccess', { role: newRole }))
setUsers(prev => prev.map(u => u.id === user.id ? { ...u, role: newRole } : u))
} catch (e) {
toast.error(t('admin.users.roleUpdateFailed'))
}
}
const handleTierChange = async (userId: string, tier: string) => {
setOpenDropdown(null)
try {
await updateUserSubscription(userId, tier)
toast.success(t('admin.users.tierUpdateSuccess', { tier }))
setUsers(prev => prev.map(u => u.id === userId ? { ...u, subscription: { tier, status: 'ACTIVE' } } : u))
} catch (e) {
toast.error(t('admin.users.tierUpdateFailed'))
}
}
const getUserTier = (user: any) => user.subscription?.tier || 'BASIC'
return (
<div className="w-full overflow-auto">
<table className="w-full caption-bottom text-sm text-left">
@@ -39,12 +135,15 @@ export function UserList({ initialUsers }: { initialUsers: any[] }) {
<th className="h-12 px-4 align-middle font-medium text-muted-foreground">{t('admin.users.table.name')}</th>
<th className="h-12 px-4 align-middle font-medium text-muted-foreground">{t('admin.users.table.email')}</th>
<th className="h-12 px-4 align-middle font-medium text-muted-foreground">{t('admin.users.table.role')}</th>
<th className="h-12 px-4 align-middle font-medium text-muted-foreground">{t('admin.users.table.subscription') || 'Abonnement'}</th>
<th className="h-12 px-4 align-middle font-medium text-muted-foreground">{t('admin.users.table.createdAt')}</th>
<th className="h-12 px-4 align-middle font-medium text-muted-foreground text-right">{t('admin.users.table.actions')}</th>
</tr>
</thead>
<tbody className="[&_tr:last-child]:border-0">
{initialUsers.map((user) => (
{users.map((user) => {
const tier = getUserTier(user)
return (
<tr key={user.id} className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td className="p-4 align-middle font-medium">{user.name || t('common.notAvailable')}</td>
<td className="p-4 align-middle">{user.email}</td>
@@ -53,6 +152,14 @@ export function UserList({ initialUsers }: { initialUsers: any[] }) {
{user.role === 'ADMIN' ? t('admin.users.roles.admin') : t('admin.users.roles.user')}
</span>
</td>
<TierDropdown
userId={user.id}
tier={tier}
isOpen={openDropdown === user.id}
onToggle={() => setOpenDropdown(openDropdown === user.id ? null : user.id)}
onClose={() => setOpenDropdown(null)}
onChange={(t) => handleTierChange(user.id, t)}
/>
<td className="p-4 align-middle">{format(new Date(user.createdAt), 'PP')}</td>
<td className="p-4 align-middle text-right">
<div className="flex justify-end gap-2">
@@ -75,7 +182,8 @@ export function UserList({ initialUsers }: { initialUsers: any[] }) {
</div>
</td>
</tr>
))}
)
})}
</tbody>
</table>
</div>

View File

@@ -2,7 +2,7 @@
import { useState, useCallback, useMemo, useEffect, useRef } from 'react'
import { motion } from 'motion/react'
import { PageEntry } from '@/components/page-entry'
import { Plus, Bot, Search, LifeBuoy } from 'lucide-react'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
@@ -205,7 +205,6 @@ export function AgentsPageClient({
const showDetail = selectedAgent !== null || isNewAgent
return (
<PageEntry>
<>
{showDetail ? (
<AgentDetailView
@@ -334,6 +333,5 @@ export function AgentsPageClient({
<AgentHelp onClose={() => setShowHelp(false)} />
)}
</>
</PageEntry>
)
}

View File

@@ -7,7 +7,7 @@ import { detectUserLanguage, parseAcceptLanguage } from "@/lib/i18n/detect-user-
import { loadTranslations } from "@/lib/i18n/load-translations";
import { getAISettings } from "@/app/actions/ai-settings";
import { AIChatLayoutBridge } from "@/components/ai-chat-layout-bridge";
import { PageTransition } from "@/components/page-transition";
export default async function MainLayout({
children,
@@ -38,9 +38,7 @@ export default async function MainLayout({
</Suspense>
<main className="flex min-h-0 flex-1 flex-col overflow-y-auto scroll-smooth bg-memento-paper dark:bg-background">
<PageTransition>
{children}
</PageTransition>
</main>
{showAIAssistant && <AIChatLayoutBridge />}

View File

@@ -7,7 +7,7 @@ import { toast } from 'sonner'
import { useRouter } from 'next/navigation'
import { Globe, Bell } from 'lucide-react'
import { motion } from 'motion/react'
import { PageEntry } from '@/components/page-entry'
interface GeneralSettingsClientProps {
initialSettings: {

View File

@@ -0,0 +1,16 @@
'use client'
import { motion } from 'motion/react'
export default function MainTemplate({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
className="flex-1 flex flex-col min-h-0"
>
{children}
</motion.div>
)
}

View File

@@ -3,7 +3,7 @@
import { useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import { motion, AnimatePresence } from 'motion/react'
import { PageEntry } from '@/components/page-entry'
import {
Trash2,
RotateCcw,
@@ -138,7 +138,6 @@ export function TrashClient({
}
return (
<PageEntry>
<div className="h-full flex flex-col bg-[#F9F8F6] dark:bg-[#1a1a1a]">
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-[#F9F8F6]/80 dark:bg-[#1a1a1a]/80 backdrop-blur-md z-30 border-b border-border/20">
<div className="flex items-center justify-between">
@@ -289,6 +288,5 @@ export function TrashClient({
</p>
</footer>
</div>
</PageEntry>
)
}

View File

@@ -0,0 +1,17 @@
import { headers } from 'next/headers'
import { detectUserLanguage, parseAcceptLanguage } from '@/lib/i18n/detect-user-language'
import { loadTranslations } from '@/lib/i18n/load-translations'
import { PublicProviders } from '@/components/public-providers'
export default async function PublicLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers()
const browserLang = parseAcceptLanguage(headersList.get('accept-language'))
const initialLanguage = await detectUserLanguage(browserLang)
const initialTranslations = await loadTranslations(initialLanguage)
return (
<PublicProviders initialLanguage={initialLanguage} initialTranslations={initialTranslations}>
{children}
</PublicProviders>
)
}

View File

@@ -0,0 +1,5 @@
import { LandingPage } from '@/components/landing-page'
export default function PublicHomePage() {
return <LandingPage />
}

View File

@@ -5,6 +5,7 @@ import prisma from '@/lib/prisma'
import { auth } from '@/auth'
import bcrypt from 'bcryptjs'
import { z } from 'zod'
import { SubscriptionTier, SubscriptionStatus } from '@prisma/client'
// Schema pour la création d'utilisateur
const CreateUserSchema = z.object({
@@ -33,6 +34,13 @@ export async function getUsers() {
email: true,
role: true,
createdAt: true,
subscription: {
select: {
tier: true,
status: true,
currentPeriodEnd: true,
}
}
}
})
return users
@@ -118,3 +126,39 @@ export async function updateUserRole(userId: string, newRole: string) {
throw new Error('Failed to update role')
}
}
export async function updateUserSubscription(userId: string, tier: string) {
await checkAdmin()
const validTiers: string[] = ['BASIC', 'PRO', 'BUSINESS', 'ENTERPRISE']
if (!validTiers.includes(tier)) {
throw new Error('Invalid tier')
}
try {
const now = new Date()
const periodEnd = new Date(now)
periodEnd.setFullYear(periodEnd.getFullYear() + 1)
await prisma.subscription.upsert({
where: { userId },
update: {
tier: tier as SubscriptionTier,
status: 'ACTIVE',
currentPeriodStart: now,
currentPeriodEnd: periodEnd,
},
create: {
userId,
tier: tier as SubscriptionTier,
status: 'ACTIVE' as SubscriptionStatus,
currentPeriodStart: now,
currentPeriodEnd: periodEnd,
},
})
revalidatePath('/admin')
return { success: true }
} catch (error) {
throw new Error('Failed to update subscription')
}
}

View File

@@ -11,7 +11,7 @@ export async function authenticate(
await signIn('credentials', {
email: formData.get('email'),
password: formData.get('password'),
redirectTo: '/',
redirectTo: '/home',
});
} catch (error) {
if (error instanceof AuthError) {

View File

@@ -128,7 +128,7 @@ export async function respondToBrainstormShare(
})
}
revalidatePath('/')
revalidatePath('/home')
return { success: true }
}
@@ -193,6 +193,6 @@ export async function removeBrainstormShare(sessionId: string) {
data: { status: 'removed' },
})
revalidatePath('/')
revalidatePath('/home')
return { success: true }
}

View File

@@ -3,7 +3,7 @@
import DOMPurify from 'isomorphic-dompurify'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { getAIProvider } from '@/lib/ai/factory'
import { getChatProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
import { getAISettings } from '@/app/actions/ai-settings'
import { revalidatePath } from 'next/cache'
@@ -58,18 +58,47 @@ export async function generateNoteIllustrationSvg(noteId: string): Promise<{ ok:
}
const config = await getSystemConfig()
const provider = getAIProvider(config)
const provider = getChatProvider(config)
const prompt = `Tu es un designer minimaliste. Produis UN SEUL document SVG valide pour une vignette de carte note.
Contraintes strictes:
- viewBox="0 0 224 168" (rapport 4:3), pas de width/height fixes en px sur la racine ou width="100%" height="100%"
- Style architectural / papier, 24 formes géométriques ou lignes, palette sobre (noir/gris/une couleur douce), pas de texte lisible
- AUCUN script, AUCUNE balise foreignObject, AUCUN lien externe, AUCUN attribut on*
- Réponds UNIQUEMENT avec le fragment SVG (commence par <svg ...> et finit par </svg>), sans markdown ni commentaire.
const prompt = `Create a small SVG thumbnail that VISUALLY REPRESENTS this note's topic.
Thème à suggérer visuellement (abstrait, pas littéral):
Titre: ${plainTitle || '(sans titre)'}
Extrait: ${plainBody.slice(0, 400)}`
OUTPUT: Only raw SVG markup. No markdown, no code fences, no comments. Start with <svg and end with </svg>.
SPECIFICATIONS:
- viewBox="0 0 224 168", NO fixed width/height attributes
- Maximum 1000 bytes
- Background: soft warm beige (#F5F0E8) or transparent
- Color palette (pick 2-3): warm charcoal (#2C2C2C), slate gray (#6B7280), soft sage (#A8B5A0), muted ochre (#C4A882), dusty rose (#C9A9A6), teal (#5F9EA0), burgundy (#8B4513)
- NO text, NO scripts, NO foreignObject, NO external links
CRITICAL: The illustration MUST be recognizably related to the topic.
Think of it like an ICON or PICTOGRAM for the title. Not abstract random shapes.
TOPIC: "${plainTitle || 'untitled'}"
How to illustrate this topic (pick the BEST match):
- If the topic is about CODE/DEV: Show angle brackets <>, curly braces {}, a terminal window shape, or circuit-like lines
- If the topic is about MUSIC: Show sound waves, musical notes shapes, or speaker icon
- If the topic is about FOOD/COOKING: Show a pot shape, utensils, or plate
- If the topic is about TRAVEL: Show a path/road, mountain peaks, or compass
- If the topic is about SCIENCE: Show atom orbits, flask/beaker, or molecule bonds
- If the topic is about BUSINESS/FINANCE: Show ascending chart lines, coins, or briefcase
- If the topic is about HEALTH: Show heart shape, pulse line, or leaf
- If the topic is about EDUCATION: Show book shape, graduation cap, or pencil
- If the topic is about NATURE: Show tree, mountain, water wave, or sun
- If the topic is about DESIGN/ART: Show palette, brush stroke, or frame
- If the topic is about PEOPLE/TEAM: Show overlapping circles, handshake, or connected nodes
- If the topic is about ARCHITECTURE: Show building outline, blueprint grid, or columns
- Otherwise: Extract the KEY CONCEPT from the title and draw its SIMPLEST iconic representation
TECHNICAL RULES:
- Use simple shapes: <circle>, <rect>, <line>, <path>, <ellipse>, <polygon>, <g>
- Keep it FLAT and MINIMAL — 2-4 elements max
- Use opacity for depth (0.3-0.8)
- The icon should be immediately recognizable even at small size
Additional context from the note:
${plainBody.slice(0, 200)}`
const raw = await provider.generateText(prompt)
const extracted = extractSvgSnippet(raw)
@@ -90,7 +119,7 @@ Extrait: ${plainBody.slice(0, 400)}`
},
})
revalidatePath('/')
revalidatePath('/home')
return { ok: true }
} catch (e) {
console.error('[note-illustration]', e)

View File

@@ -434,7 +434,7 @@ export async function restoreNoteVersion(noteId: string, historyEntryId: string)
},
})
revalidatePath('/')
revalidatePath('/home')
return parseNote(restored)
}
@@ -603,7 +603,7 @@ export async function createNote(data: {
if (!data.skipRevalidation) {
// Revalidate main page (handles both inbox and notebook views via query params)
revalidatePath('/')
revalidatePath('/home')
}
// Fire-and-forget: run AI operations in background without blocking the response
@@ -690,7 +690,7 @@ export async function createNote(data: {
const merged = [...new Set([...existingNames, ...appliedLabels])]
await syncNoteLabels(noteId, merged, notebookId ?? null, userId)
if (!data.skipRevalidation) {
revalidatePath('/')
revalidatePath('/home')
}
}
}
@@ -853,7 +853,7 @@ export async function updateNote(id: string, data: {
if (!options?.skipRevalidation) {
try { revalidatePath(`/note/${id}`) } catch {}
try { revalidatePath('/') } catch {}
try { revalidatePath('/home') } catch {}
}
if (isStructuralChange) {
@@ -895,7 +895,7 @@ export async function deleteNote(id: string, options?: { skipRevalidation?: bool
})
if (!options?.skipRevalidation) {
revalidatePath('/')
revalidatePath('/home')
}
return { success: true }
} catch (error) {
@@ -915,7 +915,7 @@ export async function trashNote(id: string, options?: { skipRevalidation?: boole
data: { trashedAt: new Date() }
})
if (!options?.skipRevalidation) {
revalidatePath('/')
revalidatePath('/home')
}
return { success: true }
} catch (error) {
@@ -933,7 +933,7 @@ export async function restoreNote(id: string) {
where: { id, userId: session.user.id },
data: { trashedAt: null }
})
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/trash')
return { success: true }
} catch (error) {
@@ -984,7 +984,7 @@ export async function permanentDeleteNote(id: string) {
await syncLabels(session.user.id, [])
revalidatePath('/trash')
revalidatePath('/')
revalidatePath('/home')
return { success: true }
} catch (error) {
console.error('Error permanently deleting note:', error)
@@ -1028,7 +1028,7 @@ export async function emptyTrash() {
await syncLabels(session.user.id, [])
revalidatePath('/trash')
revalidatePath('/')
revalidatePath('/home')
return { success: true }
} catch (error) {
console.error('Error emptying trash:', error)
@@ -1182,7 +1182,7 @@ export async function reorderNotes(draggedId: string, targetId: string) {
prisma.note.update({ where: { id: note.id }, data: { order: index } })
)
await prisma.$transaction(updates)
revalidatePath('/')
revalidatePath('/home')
return { success: true }
} catch (error) {
throw new Error('Failed to reorder notes')
@@ -1198,7 +1198,7 @@ export async function updateFullOrder(ids: string[]) {
prisma.note.update({ where: { id, userId }, data: { order: index } })
)
await prisma.$transaction(updates)
revalidatePath('/')
revalidatePath('/home')
return { success: true }
} catch (error) {
throw new Error('Failed to update order')
@@ -1314,7 +1314,7 @@ export async function cleanupAllOrphans() {
}
}
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings')
return {
success: true,
@@ -1487,7 +1487,7 @@ export async function dismissFromRecent(id: string) {
data: { dismissedFromRecent: true }
})
// revalidatePath('/') // Removed to prevent immediate refill of the list
// revalidatePath('/home') // Removed to prevent immediate refill of the list
return { success: true }
} catch (error) {
console.error('Error dismissing note from recent:', error)
@@ -1895,7 +1895,7 @@ export async function respondToShareRequest(shareId: string, action: 'accept' |
});
// Revalidate all relevant cache tags
revalidatePath('/');
revalidatePath('/home');
return { success: true, share: updatedShare };
} catch (error: any) {
@@ -1957,7 +1957,7 @@ export async function removeSharedNoteFromView(shareId: string) {
}
});
revalidatePath('/');
revalidatePath('/home');
return { success: true };
} catch (error: any) {
console.error('Error removing shared note from view:', error);
@@ -2018,7 +2018,7 @@ export async function leaveSharedNote(noteId: string) {
}
});
revalidatePath('/');
revalidatePath('/home');
return { success: true };
} catch (error: any) {
console.error('Error leaving shared note:', error);

View File

@@ -271,7 +271,7 @@ export async function executeNotebookOrganization(plan: OrganizationPlan): Promi
}
}
revalidatePath('/')
revalidatePath('/home')
return { success: true, created, moved }
} catch (err) {
console.error('[organize-notebook] Execute error:', err)

View File

@@ -96,7 +96,7 @@ export async function updateTheme(theme: string) {
where: { id: session.user.id },
data: { theme: normalized },
})
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/profile')
return { success: true }
} catch (error) {
@@ -123,7 +123,7 @@ export async function updateLanguage(language: string) {
// Note: The language will be applied on next page load
// The client component should handle updating localStorage and reloading
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/profile')
return { success: true, language }
} catch (error) {
@@ -168,7 +168,7 @@ export async function updateFontSize(fontSize: string) {
})
}
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/profile')
return { success: true, fontSize }
} catch (error) {
@@ -194,7 +194,7 @@ export async function updateShowRecentNotes(showRecentNotes: boolean) {
},
})
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/profile')
return { success: true, showRecentNotes }
} catch (error) {

View File

@@ -99,7 +99,7 @@ export async function applyTitleSuggestion(
console.error('[HISTORY] Failed to create snapshot after title suggestion:', snapshotError)
}
revalidatePath('/')
revalidatePath('/home')
revalidatePath(`/note/${noteId}`)
} catch (error) {
console.error('Error applying title suggestion:', error)

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { paragraphRefactorService } from '@/lib/ai/services/paragraph-refactor.service'
import { getAISettings } from '@/app/actions/ai-settings'
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
export async function POST(request: NextRequest) {
try {
@@ -52,9 +53,27 @@ export async function POST(request: NextRequest) {
}, { status: 400 })
}
// Check quota
try {
await checkEntitlementOrThrow(session.user.id, 'reformulate')
} catch (err) {
if (err instanceof QuotaExceededError) {
const isTierLocked = err.currentQuota === 0
return NextResponse.json({
error: isTierLocked ? 'feature_locked' : 'quota_exceeded',
errorKey: isTierLocked ? 'ai.featureLocked' : 'ai.quotaExceeded',
upgradeTier: err.upgradeTier,
quotaExceeded: true,
}, { status: 402 })
}
throw err
}
// Use the ParagraphRefactorService
const result = await paragraphRefactorService.refactor(text, mode, format === 'html' ? 'html' : 'markdown', language)
incrementUsageAsync(session.user.id, 'reformulate')
return NextResponse.json({
originalText: result.original,
reformulatedText: result.refactored,

View File

@@ -67,7 +67,7 @@ async function getParentContext(
FROM "NoteEmbedding" e
JOIN "Note" n ON n.id = e."noteId"
WHERE n."userId" = $1 AND n."trashedAt" IS NULL AND n.id NOT IN (${excludeList})
ORDER BY e.embedding <=> $2::vector
ORDER BY e.embedding::vector <=> $2::vector
LIMIT 5`,
hostUserId, vectorStr
) as any[]

View File

@@ -131,7 +131,7 @@ export async function POST(
FROM "NoteEmbedding" e
JOIN "Note" n ON n.id = e."noteId"
WHERE n.id IN (${idList}) AND n."trashedAt" IS NULL
ORDER BY e.embedding <=> $1::vector
ORDER BY e.embedding::vector <=> $1::vector
LIMIT 3`,
vectorStr
) as any[]
@@ -142,7 +142,7 @@ export async function POST(
FROM "NoteEmbedding" e
JOIN "Note" n ON n.id = e."noteId"
WHERE n."userId" = $1 AND n."trashedAt" IS NULL
ORDER BY e.embedding <=> $2::vector
ORDER BY e.embedding::vector <=> $2::vector
LIMIT 3`,
aiUserId, vectorStr
) as any[]

View File

@@ -44,7 +44,7 @@ async function autoContextSearch(
FROM "NoteEmbedding" e
JOIN "Note" n ON n.id = e."noteId"
WHERE n."userId" = $1 AND n."trashedAt" IS NULL
ORDER BY e.embedding <=> $2::vector
ORDER BY e.embedding::vector <=> $2::vector
LIMIT 8`,
userId, vectorStr
) as any[]

View File

@@ -146,7 +146,7 @@ export async function PUT(
})
// Revalidate to refresh UI
revalidatePath('/')
revalidatePath('/home')
return NextResponse.json({
success: true,
@@ -253,7 +253,7 @@ export async function DELETE(
})
// Revalidate to refresh UI
revalidatePath('/')
revalidatePath('/home')
return NextResponse.json({
success: true,

View File

@@ -6,7 +6,7 @@ export async function GET() {
name: "Memento Notes",
short_name: "Memento",
description: "A smart, local-first note taking app with AI capabilities.",
start_url: "/",
start_url: "/home",
display: "standalone",
background_color: "#F2F0E9",
theme_color: "#1C1C1C",

View File

@@ -98,7 +98,7 @@ export async function PATCH(
})
}
try { revalidatePath('/') } catch {}
try { revalidatePath('/home') } catch {}
return NextResponse.json({ success: true })
} catch (error) {
@@ -143,7 +143,7 @@ export async function DELETE(
await prisma.notebook.delete({ where: { id } })
try { revalidatePath('/') } catch {}
try { revalidatePath('/home') } catch {}
return NextResponse.json({
success: true,

View File

@@ -47,7 +47,7 @@ export async function POST(request: NextRequest) {
await prisma.$transaction(updates)
revalidatePath('/')
revalidatePath('/home')
return NextResponse.json({
success: true,

View File

@@ -105,7 +105,7 @@ export async function POST(request: NextRequest) {
}
})
try { revalidatePath('/') } catch {}
try { revalidatePath('/home') } catch {}
return NextResponse.json({
success: true,

View File

@@ -90,7 +90,7 @@ export async function POST(
console.error('[HISTORY] Failed to create snapshot after notebook move:', snapshotError)
}
// No revalidatePath('/') here — the client-side triggerRefresh() in
// No revalidatePath('/home') here — the client-side triggerRefresh() in
// notebooks-context.tsx handles the refresh. Avoiding server-side
// revalidation prevents a double-refresh (server + client).

View File

@@ -50,7 +50,7 @@ export async function POST(req: NextRequest) {
})
// Revalidate paths
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/data')
// Await cleanup in background (don't block response)

View File

@@ -222,7 +222,7 @@ export async function POST(req: NextRequest) {
}
}
revalidatePath('/')
revalidatePath('/home')
revalidatePath('/settings/data')
return NextResponse.json({ success: true, ...stats })

View File

@@ -1,6 +1,6 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
import { getUserQuotas } from '@/lib/entitlements';
import { getUserQuotas, getEffectiveTier } from '@/lib/entitlements';
export async function GET() {
const session = await auth();
@@ -10,8 +10,11 @@ export async function GET() {
}
try {
const quotas = await getUserQuotas(session.user.id);
return NextResponse.json({ quotas });
const [quotas, tier] = await Promise.all([
getUserQuotas(session.user.id),
getEffectiveTier(session.user.id),
]);
return NextResponse.json({ quotas, tier });
} catch (error) {
console.error('[usage/current] Failed to fetch quotas:', error);
return NextResponse.json(

View File

@@ -8,6 +8,10 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if ((session.user as any)?.role !== 'ADMIN') {
return NextResponse.json({ users: [] })
}
const q = request.nextUrl.searchParams.get('q') || ''
if (q.length < 2) {
return NextResponse.json({ users: [] })

View File

@@ -14,7 +14,7 @@ export const authConfig = {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isAdmin = (auth?.user as any)?.role === 'ADMIN';
const isDashboardPage = nextUrl.pathname === '/' ||
const isDashboardPage = nextUrl.pathname === '/home' ||
nextUrl.pathname.startsWith('/reminders') ||
nextUrl.pathname.startsWith('/archive') ||
nextUrl.pathname.startsWith('/trash') ||
@@ -24,8 +24,14 @@ export const authConfig = {
nextUrl.pathname.startsWith('/chat') ||
nextUrl.pathname.startsWith('/canvas') ||
nextUrl.pathname.startsWith('/notebooks') ||
nextUrl.pathname.startsWith('/note/');
nextUrl.pathname.startsWith('/note/') ||
nextUrl.pathname.startsWith('/brainstorm');
const isAdminPage = nextUrl.pathname.startsWith('/admin');
const isPublicPage = nextUrl.pathname === '/' ||
nextUrl.pathname === '/login' ||
nextUrl.pathname === '/register' ||
nextUrl.pathname === '/forgot-password' ||
nextUrl.pathname.startsWith('/reset-password');
if (isAdminPage) {
return isLoggedIn && isAdmin;
@@ -34,9 +40,12 @@ export const authConfig = {
if (isDashboardPage) {
if (isLoggedIn) return true;
return false;
} else if (isLoggedIn && (nextUrl.pathname === '/login' || nextUrl.pathname === '/register')) {
return Response.redirect(new URL('/', nextUrl));
}
if (isLoggedIn && (nextUrl.pathname === '/login' || nextUrl.pathname === '/register')) {
return Response.redirect(new URL('/home', nextUrl));
}
return true;
},
async jwt({ token, user }) {

View File

@@ -122,7 +122,7 @@ export function AdminSidebar({ className }: { className?: string }) {
</DropdownMenu>
<a
href="/"
href="/home"
className={cn(
'flex min-w-0 flex-1 items-center gap-2 rounded-xl px-2 py-1.5 transition-colors',
'hover:bg-white/40 dark:hover:bg-white/10'
@@ -143,7 +143,7 @@ export function AdminSidebar({ className }: { className?: string }) {
</div>
<a
href="/"
href="/home"
className={cn(
'flex items-center gap-3 rounded-xl px-4 py-2.5 text-[13px] font-medium transition-all',
'text-muted-foreground hover:bg-white/40 hover:text-foreground dark:hover:bg-white/10'

View File

@@ -205,7 +205,7 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<div className="p-3 bg-muted rounded-xl group-hover:bg-foreground group-hover:text-background transition-all">
<div className="p-3 bg-muted rounded-xl group-hover:bg-brand-accent group-hover:text-white transition-all">
<Icon className="w-5 h-5" />
</div>
<div className="space-y-1">
@@ -224,7 +224,7 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
>
<div className="relative inline-flex items-center cursor-pointer">
<div className={`w-8 h-4 rounded-full transition-colors ${
agent.isEnabled ? 'bg-primary' : 'bg-muted-foreground/30'
agent.isEnabled ? 'bg-brand-accent' : 'bg-muted-foreground/30'
}`}>
<span className={`absolute top-0.5 left-[2px] bg-background border border-muted-foreground/30 rounded-full h-3 w-3 transition-all ${
agent.isEnabled ? 'translate-x-4' : ''
@@ -260,9 +260,9 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
<span className="uppercase tracking-tight">{t('agents.status.lastStatus')}</span>
{lastAction ? (
<span className={`flex items-center gap-1 ${
lastAction.status === 'success' ? 'text-primary'
lastAction.status === 'success' ? 'text-brand-accent'
: lastAction.status === 'failure' ? 'text-destructive'
: lastAction.status === 'running' ? 'text-primary'
: lastAction.status === 'running' ? 'text-brand-accent'
: 'text-muted-foreground'
}`}>
{lastAction.status === 'success' && <Activity className="w-2 h-2" />}

View File

@@ -353,7 +353,7 @@ export function AgentDetailView({
<div className="flex items-end justify-between">
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="p-4 bg-foreground text-background rounded-2xl shadow-xl shadow-foreground/10">
<div className="p-4 bg-brand-accent text-white rounded-2xl shadow-xl shadow-brand-accent/10">
<Icon className="w-8 h-8" />
</div>
<div className="space-y-1">
@@ -369,7 +369,7 @@ export function AgentDetailView({
{isNew ? t('agents.newBadge') : `ID: ${agent?.id?.slice(0, 8)}`}
</span>
{!isNew && agent?.isEnabled && (
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-primary/10 text-primary rounded-md">
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-brand-accent/10 text-brand-accent rounded-md">
{t('agents.actions.toggleOn')}
</span>
)}
@@ -386,7 +386,7 @@ export function AgentDetailView({
{successRate !== null && (
<div className="flex flex-col items-end gap-1">
<span className="opacity-40">Succès</span>
<span className="text-primary">{successRate}%</span>
<span className="text-brand-accent">{successRate}%</span>
</div>
)}
</div>
@@ -751,7 +751,7 @@ export function AgentDetailView({
<div className="space-y-10">
<section className="space-y-6">
<h3 className={sectionTitleCls}>{t('agents.form.frequency')}<FieldHelp tooltip={t('agents.help.tooltips.frequency')} /></h3>
<div className="bg-foreground rounded-3xl p-8 space-y-8 text-background shadow-2xl shadow-foreground/20 relative overflow-hidden">
<div className="bg-ink rounded-3xl p-8 space-y-8 text-paper shadow-2xl shadow-ink/20 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10">
<Clock className="w-24 h-24" />
</div>

View File

@@ -6,9 +6,8 @@ import { DefaultChatTransport } from 'ai'
import type { UIMessage } from 'ai'
import { cn } from '@/lib/utils'
import {
X, Bot, Sparkles, History, Send, Globe, Briefcase, Palette, GraduationCap, Coffee,
Loader2, Layers, Square, Plus, ChevronRight, MessageSquare, FileCode,
Zap, Network, Clock, Scissors, Languages, Layout, ArrowRightLeft, BookOpen,
X, Bot, Sparkles, History, Send, Globe,
Loader2, Square, Plus, MessageSquare, FileCode,
Maximize2, Minimize2
} from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
@@ -28,14 +27,7 @@ function getTextContent(msg: UIMessage): string {
return ''
}
const TONE_IDS = [
{ id: 'professional', icon: Briefcase },
{ id: 'creative', icon: Palette },
{ id: 'academic', icon: GraduationCap },
{ id: 'casual', icon: Coffee },
] as const
type AITab = 'discussion' | 'actions' | 'explore' | 'resources'
type AITab = 'discussion' | 'history'
export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: boolean } = {}) {
const { t, language } = useLanguage()
@@ -45,7 +37,6 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
const [isOpen, setIsOpen] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
const [aiTab, setAiTab] = useState<AITab>('discussion')
const [selectedTone, setSelectedTone] = useState('professional')
const [webSearch, setWebSearch] = useState(false)
const [chatScope, setChatScope] = useState<'all' | string>('all')
const [input, setInput] = useState('')
@@ -55,9 +46,6 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
const [history, setHistory] = useState<any[]>([])
const [historyLoading, setHistoryLoading] = useState(false)
const [insights, setInsights] = useState<string>('')
const [insightsLoading, setInsightsLoading] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const transport = useRef(new DefaultChatTransport({ api: '/api/chat' })).current
@@ -65,6 +53,23 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
transport,
onError: (error) => {
console.error('Chat error:', error)
try {
const parsed = JSON.parse((error as Error).message || '{}')
if (parsed.error === 'QUOTA_EXCEEDED') {
const isBasic = (parsed.currentTier || 'BASIC') === 'BASIC'
toast.error(
language === 'fr'
? isBasic
? 'Le chat IA est réservé au plan PRO et supérieur.'
: `Limite mensuelle atteinte pour le plan ${parsed.currentTier}. Elle se réinitialise le mois prochain.`
: isBasic
? 'AI Chat is available from the PRO plan onwards.'
: `Monthly quota reached for ${parsed.currentTier} plan. It will reset next month.`,
{ duration: 8000 }
)
return
}
} catch {}
toast.error(t('chat.assistantError') || 'Chat error')
}
})
@@ -91,7 +96,6 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
{ text },
{
body: {
tone: selectedTone,
chatScope,
notebookId: chatScope !== 'all' ? chatScope : undefined,
webSearch: webSearch && webSearchAvailable,
@@ -115,24 +119,6 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
setHistoryLoading(false)
}
const fetchInsights = async () => {
setInsightsLoading(true)
try {
const res = await fetch('/api/chat/insights')
if (res.ok) {
const data = await res.json()
setInsights(data.insight)
}
} catch (e) { console.error(e) }
setInsightsLoading(false)
}
useEffect(() => {
if (aiTab === 'discussion') {
// history is loaded on demand via insights tab
}
}, [aiTab])
useEffect(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
@@ -203,6 +189,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
<button
onClick={() => setIsOpen(false)}
className="p-1.5 hover:bg-slate-100 dark:hover:bg-white/10 rounded-lg transition-colors text-concrete"
title={t('general.close') || 'Fermer'}
>
<X size={18} />
</button>
@@ -215,22 +202,28 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
{/* Tabs */}
<div className="flex border-b border-border px-2 shrink-0">
{(['discussion', 'actions', 'explore', 'resources'] as AITab[]).map((tab) => {
{(['discussion', 'history'] as AITab[]).map((tab) => {
const labels: Record<AITab, string> = {
discussion: t('ai.chatTab') || 'Discussion',
actions: t('ai.actionsTab') || 'Actions',
explore: t('ai.exploreTab') || 'Explorer',
resources: t('ai.resourcesTab') || 'Ressources',
history: t('ai.historyTab') || 'Historique',
}
const icons: Record<AITab, React.ReactNode> = {
discussion: <MessageSquare size={12} />,
history: <History size={12} />,
}
return (
<button
key={tab}
onClick={() => setAiTab(tab)}
onClick={() => {
setAiTab(tab)
if (tab === 'history') fetchHistory()
}}
className={cn(
"flex-1 py-3 text-[10px] uppercase tracking-[0.2em] font-bold transition-all relative",
"flex-1 py-3 text-[10px] uppercase tracking-[0.2em] font-bold transition-all relative flex items-center justify-center gap-1.5",
aiTab === tab ? 'text-brand-accent' : 'text-concrete hover:text-ink/60'
)}
>
{icons[tab]}
{labels[tab]}
{aiTab === tab && (
<motion.div
@@ -248,38 +241,15 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
<AnimatePresence mode="wait">
{aiTab === 'discussion' && (
<motion.div key="discussion" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="space-y-6">
{/* Context selector */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-concrete">{t('ai.writingTone')}</label>
<div className="flex items-center gap-1">
{TONE_IDS.map((tone) => {
const Icon = tone.icon
return (
<button
key={tone.id}
onClick={() => setSelectedTone(tone.id)}
title={t(`ai.tones.${tone.id}`)}
className={cn(
'w-8 h-8 rounded-lg flex items-center justify-center text-[9px] font-bold transition-all border',
selectedTone === tone.id
? 'bg-brand-accent text-white border-brand-accent shadow-sm'
: 'bg-white/50 dark:bg-white/5 border-border/40 text-concrete hover:border-brand-accent/40'
)}
>
<Icon className="h-3.5 w-3.5" />
</button>
)
})}
</div>
</div>
{/* Scope selector */}
<div className="space-y-2">
<button
onClick={() => setChatScope('all')}
className={cn(
'w-full p-2.5 border rounded-xl text-[11px] flex items-center justify-between transition-all',
chatScope === 'all' ? 'bg-brand-accent/5 border-brand-accent/30' : 'bg-white/50 dark:bg-white/5 border-border/40 hover:border-ink/20'
)}
title={t('ai.allMyNotes') || 'Toutes mes notes'}
>
<div className="flex items-center gap-2.5">
<FileCode size={14} className="text-brand-accent/60" />
@@ -324,14 +294,14 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
<div key={msg.id} className={cn('flex gap-3', msg.role === 'user' && 'flex-row-reverse')}>
<div className={cn(
'w-8 h-8 rounded-xl flex items-center justify-center flex-shrink-0',
msg.role === 'user' ? 'bg-ink text-paper' : 'bg-brand-accent/10 text-brand-accent',
msg.role === 'user' ? 'bg-brand-accent text-white' : 'bg-brand-accent/10 text-brand-accent',
)}>
{msg.role === 'user' ? 'U' : <Bot className="h-4 w-4" />}
</div>
<div className={cn(
'max-w-[85%] p-3.5 rounded-2xl text-sm leading-relaxed',
msg.role === 'user'
? 'bg-ink text-paper rounded-tr-sm'
? 'bg-brand-accent text-white rounded-tr-sm'
: 'bg-white/60 dark:bg-white/5 border border-border rounded-tl-sm text-ink',
)}>
{msg.role === 'assistant' ? <MarkdownContent content={text} /> : <p>{text}</p>}
@@ -354,187 +324,36 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
</motion.div>
)}
{aiTab === 'actions' && (
<motion.div key="actions" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="space-y-8">
<div className="flex items-center gap-2 mb-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-concrete whitespace-nowrap">Transformations</h4>
<div className="h-px flex-1 bg-border/40" />
{aiTab === 'history' && (
<motion.div key="history" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="space-y-3">
{historyLoading ? (
<div className="flex justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-concrete" />
</div>
<div className="grid grid-cols-2 gap-2">
{[
{ icon: <Sparkles size={14} />, label: t('ai.actionClarify') || 'Clarifier' },
{ icon: <Scissors size={14} />, label: t('ai.actionShorten') || 'Raccourcir' },
{ icon: <Zap size={14} />, label: t('ai.actionImprove') || 'Améliorer' },
{ icon: <Languages size={14} />, label: t('ai.actionTranslate') || 'Traduire' },
].map((action, i) => (
<button key={i} className="flex flex-col items-center gap-3 p-4 bg-white/50 dark:bg-white/5 border border-border rounded-xl transition-all group hover:border-ink/20">
<div className="p-2 rounded-lg bg-slate-50 dark:bg-white/10 transition-colors group-hover:bg-brand-accent group-hover:text-white shadow-sm text-concrete">
{action.icon}
) : history.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-concrete/30">
<History size={24} />
<p className="text-[11px] mt-3 italic">{t('ai.noHistory') || 'Aucun historique'}</p>
</div>
<span className="text-[10px] font-bold text-ink/80 uppercase tracking-widest">{action.label}</span>
</button>
))}
<button className="col-span-2 flex items-center justify-center gap-3 py-3 px-4 bg-white/50 dark:bg-white/5 border border-border rounded-xl text-[10px] font-bold text-ink/80 hover:bg-white dark:hover:bg-white/10 transition-colors hover:border-ink/20 uppercase tracking-widest">
<FileCode size={14} className="text-concrete" />
{t('ai.actionMarkdown') || 'Convertir en Markdown'}
</button>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2 mb-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-concrete whitespace-nowrap">Generation Tools</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-brand-accent/30 transition-all duration-500 overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Layout size={80} className="text-brand-accent" />
</div>
<div className="relative space-y-5">
) : (
history.map((conv: any) => (
<button
key={conv.id}
onClick={() => { setConversationId(conv.id); setMessages(conv.messages || []); setAiTab('discussion') }}
className="w-full text-left p-4 bg-white/50 dark:bg-white/5 border border-border rounded-xl hover:border-brand-accent/30 transition-all group"
>
<div className="flex items-center gap-3">
<div className="p-2 bg-slate-50 rounded-lg text-brand-accent"><Layout size={18} /></div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-ink leading-none">{t('ai.slides') || 'Présentation'}</h5>
<p className="text-[10px] text-concrete uppercase tracking-tight">{t('ai.slidesDesc') || 'Convertir en slides interactives'}</p>
<div className="p-2 rounded-lg bg-brand-accent/10 text-brand-accent group-hover:bg-brand-accent group-hover:text-white transition-colors">
<MessageSquare size={14} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-ink truncate">{conv.title || conv.id}</p>
<p className="text-[10px] text-concrete mt-0.5">{new Date(conv.createdAt).toLocaleDateString()}</p>
</div>
</div>
<button className="w-full py-3.5 bg-brand-accent text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-brand-accent/20 uppercase tracking-[0.2em]">
{t('ai.generate') || 'Générer'} <ArrowRightLeft size={14} className="opacity-60" />
</button>
</div>
</div>
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-emerald-500/30 transition-all duration-500 overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<BookOpen size={80} className="text-emerald-500" />
</div>
<div className="relative space-y-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-slate-50 rounded-lg text-emerald-500"><BookOpen size={18} /></div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-ink leading-none">{t('ai.diagram') || 'Diagramme'}</h5>
<p className="text-[10px] text-concrete uppercase tracking-tight">{t('ai.diagramDesc') || 'Visualisation de structure'}</p>
</div>
</div>
<button className="w-full py-3.5 bg-emerald-600 text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-emerald-600/20 uppercase tracking-[0.2em]">
{t('ai.trace') || 'Tracer'} <ArrowRightLeft size={14} className="opacity-60" />
</button>
</div>
</div>
</div>
<div className="flex flex-col items-center gap-2 opacity-20 py-4">
<History size={16} />
<span className="text-[10px] font-bold uppercase tracking-widest whitespace-nowrap">Auto-Save Enabled</span>
</div>
</motion.div>
))
)}
{aiTab === 'explore' && (
<motion.div key="explore" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="space-y-6">
<div className="flex items-center gap-2 mb-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-concrete whitespace-nowrap">Intelligence Modules</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<div className="space-y-3">
<button className="w-full group relative p-5 rounded-2xl bg-white border border-border hover:border-brand-accent/30 transition-all text-left overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Zap size={60} className="text-brand-accent" />
</div>
<div className="relative flex items-center gap-4">
<div className="p-3 bg-brand-accent/10 rounded-xl text-brand-accent group-hover:bg-brand-accent group-hover:text-white transition-colors">
<Zap size={20} />
</div>
<div>
<h5 className="font-bold text-ink text-sm">{t('ai.brainstormWave') || 'Brainstorm Wave'}</h5>
<p className="text-[10px] text-concrete uppercase tracking-tight">{t('ai.brainstormWaveDesc') || 'Unfold dimensions of thought'}</p>
</div>
</div>
</button>
<button className="w-full group relative p-5 rounded-2xl bg-white border border-border hover:border-indigo-500/30 transition-all text-left overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Network size={60} className="text-indigo-500" />
</div>
<div className="relative flex items-center gap-4">
<div className="p-3 bg-indigo-500/10 rounded-xl text-indigo-500 group-hover:bg-indigo-500 group-hover:text-white transition-colors">
<Network size={20} />
</div>
<div>
<h5 className="font-bold text-ink text-sm">{t('ai.semanticNetwork') || 'Réseau Sémantique'}</h5>
<p className="text-[10px] text-concrete uppercase tracking-tight">{t('ai.semanticNetworkDesc') || 'Detect clusters and bridges'}</p>
</div>
</div>
</button>
<button className="w-full group relative p-5 rounded-2xl bg-white border border-border hover:border-rose-500/30 transition-all text-left overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Clock size={60} className="text-rose-500" />
</div>
<div className="relative flex items-center gap-4">
<div className="p-3 bg-rose-500/10 rounded-xl text-rose-500 group-hover:bg-rose-500 group-hover:text-white transition-colors">
<Clock size={20} />
</div>
<div>
<h5 className="font-bold text-ink text-sm">{t('ai.temporalForecast') || 'Prévision Temporelle'}</h5>
<p className="text-[10px] text-concrete uppercase tracking-tight">{t('ai.temporalForecastDesc') || 'Predict relevance recurrence'}</p>
</div>
</div>
</button>
</div>
<div className="p-6 rounded-2xl bg-slate-50 dark:bg-white/5 border border-dashed border-border">
<p className="text-[10px] text-concrete leading-relaxed font-medium italic text-center">
{t('ai.modulesHint') || 'Ces modules utilisent les embeddings pour analyser vos pensées.'}
</p>
</div>
</motion.div>
)}
{aiTab === 'resources' && (
<motion.div key="resources" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-concrete">{t('ai.resourceUrl') || 'URL (Optionnel)'}</label>
<div className="relative">
<input type="text" placeholder="https://..." className="w-full bg-white/50 dark:bg-white/5 border border-border rounded-xl pl-4 pr-10 py-3 text-xs outline-none focus:border-brand-accent transition-colors text-ink" />
<Globe size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-concrete/40" />
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-concrete">{t('ai.resourceText') || 'Texte de la ressource'}</label>
<textarea
rows={8}
placeholder={t('ai.resourcePlaceholder') || 'Collez votre texte ici...'}
className="w-full bg-white/50 dark:bg-white/5 border border-border rounded-xl p-4 text-xs outline-none focus:border-brand-accent transition-colors resize-none leading-relaxed text-ink"
/>
</div>
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-concrete">{t('ai.integrationMode') || "Mode d'intégration"}</label>
<div className="grid grid-cols-3 gap-2">
{[
{ id: 'replace', label: t('ai.modeReplace') || 'Remplacer', sub: 'Direct' },
{ id: 'append', label: t('ai.modeAppend') || 'Compléter', sub: 'Ajoute' },
{ id: 'merge', label: t('ai.modeMerge') || 'Fusionner', sub: 'Intègre' },
].map((mode) => (
<button key={mode.id} className="flex flex-col items-center justify-center p-3 rounded-xl border transition-all text-center bg-white border-border hover:bg-slate-50 dark:hover:bg-white/5">
<span className="text-[10px] font-bold text-ink">{mode.label}</span>
<span className="text-[8px] text-concrete opacity-60 leading-tight mt-1 font-medium">{mode.sub}</span>
</button>
))}
</div>
</div>
<button className="w-full py-4 bg-brand-accent text-white rounded-xl text-[11px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-3 hover:opacity-90 transition-opacity shadow-lg shadow-brand-accent/20">
<Sparkles size={18} />
{t('ai.generatePreview') || "Générer l'aperçu"}
</button>
</motion.div>
)}
</AnimatePresence>
@@ -561,6 +380,15 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
}}
disabled={isLoading}
/>
<div className="absolute left-6 bottom-4 flex gap-3 text-concrete/40">
<button
onClick={() => webSearchAvailable && setWebSearch(!webSearch)}
className={cn("hover:text-brand-accent transition-colors", webSearch && "text-brand-accent")}
title={webSearch ? (t('ai.webSearchEnabled') || 'Recherche web activée') : (t('ai.webSearchDisabled') || 'Activer la recherche web')}
>
<Globe size={14} />
</button>
</div>
<div className="absolute right-4 bottom-4 flex flex-col gap-2">
{isLoading ? (
<button onClick={() => stop()} className="p-2.5 bg-rose-500 text-white rounded-xl transition-all hover:scale-110 active:scale-95 shadow-lg shadow-rose-500/20">
@@ -572,18 +400,6 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
</button>
)}
</div>
<div className="absolute left-6 bottom-4 flex gap-3 text-concrete/40">
<button
onClick={() => webSearchAvailable && setWebSearch(!webSearch)}
className={cn("hover:text-brand-accent transition-colors", webSearch && "text-brand-accent")}
>
<Globe size={14} />
</button>
<button className="hover:text-brand-accent transition-colors"><Network size={14} /></button>
</div>
</div>
<div className="flex justify-center mt-4">
<p className="text-[9px] text-concrete/40 uppercase tracking-[0.3em] font-bold">Shift+Enter for new line</p>
</div>
</motion.div>
)}

View File

@@ -1,7 +1,7 @@
'use client'
import { useState, useCallback } from 'react'
import { PageEntry } from '@/components/page-entry'
import dynamic from 'next/dynamic'
import type { Note } from '@/lib/types'
import { NotesEditorialView } from '@/components/notes-editorial-view'
@@ -41,11 +41,9 @@ export function ArchiveClient({ notes }: ArchiveClientProps) {
}
return (
<PageEntry>
<NotesEditorialView
notes={notes}
onOpen={handleOpen}
/>
</PageEntry>
)
}

View File

@@ -213,7 +213,7 @@ export function BrainstormPage() {
setConvertToast({ noteTitle: result.title || idea.title, noteId: result.id })
setTimeout(() => {
setConvertToast(null)
router.push(`/?openNote=${result.id}`)
router.push(`/home?openNote=${result.id}`)
}, 2000)
}
} catch {}
@@ -228,7 +228,7 @@ export function BrainstormPage() {
setExportToast({ noteTitle: result.title || t('brainstorm.exportDefaultNoteTitle'), notebookName })
setTimeout(() => {
setExportToast(null)
router.push(`/?openNote=${result.id}`)
router.push(`/home?openNote=${result.id}`)
}, 2000)
return
}
@@ -634,7 +634,7 @@ export function BrainstormPage() {
</div>
{ref.noteId && (
<button
onClick={() => router.push(`/?openNote=${ref.noteId}`)}
onClick={() => router.push(`/home?openNote=${ref.noteId}`)}
className="shrink-0 px-2 py-1 text-[9px] font-bold uppercase tracking-wider rounded-lg bg-foreground/5 hover:bg-foreground/10 text-muted-foreground hover:text-foreground transition-all"
>
{t('brainstorm.viewNote') || 'View'}

View File

@@ -137,8 +137,8 @@ export function BrainstormShareDialog({
<DialogContent className="sm:max-w-md bg-white dark:bg-[#1A1A1A] border-border rounded-2xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-foreground">
<div className="w-7 h-7 rounded-lg bg-orange-500/10 flex items-center justify-center">
<UserPlus size={14} className="text-orange-500" />
<div className="w-7 h-7 rounded-lg bg-brand-accent/10 flex items-center justify-center">
<UserPlus size={14} className="text-brand-accent" />
</div>
<span className="font-serif">{t('brainstorm.shareDialogTitle')}</span>
</DialogTitle>
@@ -159,7 +159,7 @@ export function BrainstormShareDialog({
onFocus={() => results.length > 0 && setShowDropdown(true)}
onBlur={() => setTimeout(() => setShowDropdown(false), 150)}
placeholder={t('brainstorm.shareNameOrEmailPlaceholder')}
className="w-full px-4 py-3 text-sm border border-border rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-orange-500/20 focus:border-orange-500/40 transition-all"
className="w-full px-4 py-3 text-sm border border-border rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-brand-accent/20 focus:border-brand-accent/40 transition-all"
autoFocus
/>
@@ -173,9 +173,9 @@ export function BrainstormShareDialog({
e.preventDefault()
selectUser(user)
}}
className="w-full px-4 py-2.5 flex items-center gap-3 hover:bg-orange-500/5 transition-colors text-left"
className="w-full px-4 py-2.5 flex items-center gap-3 hover:bg-brand-accent/5 transition-colors text-left"
>
<div className="w-8 h-8 rounded-full bg-orange-500/10 flex items-center justify-center text-xs font-bold text-orange-600 shrink-0">
<div className="w-8 h-8 rounded-full bg-brand-accent/10 flex items-center justify-center text-xs font-bold text-brand-accent shrink-0">
{(user.name || user.email).charAt(0).toUpperCase()}
</div>
<div className="min-w-0 flex-1">
@@ -214,7 +214,7 @@ export function BrainstormShareDialog({
<button
type="submit"
disabled={!query.trim() || isPending}
className="w-full py-3 bg-orange-500 hover:bg-orange-600 text-white text-[10px] font-bold uppercase tracking-[0.15em] rounded-xl disabled:opacity-50 transition-all flex items-center justify-center gap-1.5"
className="w-full py-3 bg-brand-accent hover:bg-brand-accent/90 text-white text-[10px] font-bold uppercase tracking-[0.15em] rounded-xl disabled:opacity-50 transition-all flex items-center justify-center gap-1.5"
>
<UserPlus size={12} />
{isPending ? t('brainstorm.shareSubmitting') : t('brainstorm.shareSubmit')}

View File

@@ -1,6 +1,6 @@
'use client'
import React, { useEffect, useState, useRef } from 'react'
import React, { useEffect, useRef } from 'react'
import { motion, AnimatePresence } from 'motion/react'
interface GhostCursorProps {
@@ -10,99 +10,119 @@ interface GhostCursorProps {
}
export function GhostCursor({ isActive, containerRef, targetId }: GhostCursorProps) {
const [position, setPosition] = useState({ x: 0, y: 0 })
const [visible, setVisible] = useState(false)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const positionRef = useRef({ x: 0, y: 0 })
const visibleRef = useRef(false)
const elRef = useRef<HTMLDivElement>(null)
const rafRef = useRef<number | null>(null)
const targetRef = useRef({ x: 0, y: 0 })
const initializedRef = useRef(false)
const targetIdRef = useRef(targetId)
targetIdRef.current = targetId
const isActiveRef = useRef(isActive)
isActiveRef.current = isActive
useEffect(() => {
if (!isActive) {
setVisible(false)
if (intervalRef.current) clearInterval(intervalRef.current)
visibleRef.current = false
initializedRef.current = false
if (elRef.current) elRef.current.style.opacity = '0'
if (rafRef.current) cancelAnimationFrame(rafRef.current)
return
}
let angle = Math.random() * Math.PI * 2
const init = () => {
const container = containerRef.current
if (!container) { rafRef.current = requestAnimationFrame(init); return }
const rect = container.getBoundingClientRect()
if (rect.width === 0 || rect.height === 0) { rafRef.current = requestAnimationFrame(init); return }
const cx = rect.width / 2
const cy = rect.height / 2
targetRef.current = { x: cx + (Math.random() - 0.5) * 200, y: cy + (Math.random() - 0.5) * 200 }
positionRef.current = { ...targetRef.current }
initializedRef.current = true
visibleRef.current = true
if (elRef.current) {
elRef.current.style.opacity = '1'
elRef.current.style.transform = `translate(${positionRef.current.x}px, ${positionRef.current.y}px)`
}
}
rafRef.current = requestAnimationFrame(init)
const tick = () => {
if (!isActiveRef.current || !initializedRef.current) {
rafRef.current = requestAnimationFrame(tick)
return
}
const container = containerRef.current
if (!container) return
if (!container) { rafRef.current = requestAnimationFrame(tick); return }
const containerRect = container.getBoundingClientRect()
if (containerRect.width === 0 || containerRect.height === 0) { rafRef.current = requestAnimationFrame(tick); return }
setVisible(true)
// Initialisation sécurisée
const initialRect = container.getBoundingClientRect()
const initialCx = initialRect.width > 0 ? initialRect.width / 2 : window.innerWidth / 2
const initialCy = initialRect.height > 0 ? initialRect.height / 2 : window.innerHeight / 2
setPosition({
x: initialCx + (Math.random() - 0.5) * 200,
y: initialCy + (Math.random() - 0.5) * 200,
})
let angle = Math.random() * Math.PI * 2
let targetX = initialCx + Math.cos(angle) * 250
let targetY = initialCy + Math.sin(angle) * 250
intervalRef.current = setInterval(() => {
// Recalculer les dimensions à chaque tick pour s'adapter aux redimensionnements
const currentContainer = containerRef.current
if (!currentContainer) return
const containerRect = currentContainer.getBoundingClientRect()
const cx = containerRect.width / 2
const cy = containerRect.height / 2
let tx = targetRef.current.x
let ty = targetRef.current.y
let currentTargetX = targetX;
let currentTargetY = targetY;
if (targetId) {
const nodeElement = document.querySelector(`[data-id="${targetId}"]`);
const currentTargetId = targetIdRef.current
if (currentTargetId) {
const nodeElement = container.querySelector(`[data-id="${currentTargetId}"]`) as HTMLElement | null
if (nodeElement) {
const nodeRect = nodeElement.getBoundingClientRect();
currentTargetX = nodeRect.left - containerRect.left + nodeRect.width / 2;
currentTargetY = nodeRect.top - containerRect.top + nodeRect.height / 2;
const nodeRect = nodeElement.getBoundingClientRect()
tx = nodeRect.left - containerRect.left + nodeRect.width / 2
ty = nodeRect.top - containerRect.top + nodeRect.height / 2
}
}
setPosition(prev => {
const dx = currentTargetX - prev.x
const dy = currentTargetY - prev.y
const prev = positionRef.current
const dx = tx - prev.x
const dy = ty - prev.y
const dist = Math.sqrt(dx * dx + dy * dy)
if (!targetId && dist < 20) {
if (!currentTargetId && dist < 20) {
angle = Math.random() * Math.PI * 2
const radius = 150 + Math.random() * 200
targetX = cx + Math.cos(angle) * radius
targetY = cy + Math.sin(angle) * radius
tx = cx + Math.cos(angle) * radius
ty = cy + Math.sin(angle) * radius
targetRef.current = { x: tx, y: ty }
}
const speed = targetId ? 0.15 : 0.06;
const speed = currentTargetId ? 0.12 : 0.04
let newX = prev.x + (tx - prev.x) * speed
let newY = prev.y + (ty - prev.y) * speed
if (isNaN(newX)) newX = cx
if (isNaN(newY)) newY = cy
let newX = prev.x + dx * speed;
let newY = prev.y + dy * speed;
positionRef.current = { x: newX, y: newY }
// Protection Anti-NaN qui bloquait le curseur en haut à gauche
if (isNaN(newX)) newX = cx;
if (isNaN(newY)) newY = cy;
return {
x: newX,
y: newY,
if (elRef.current) {
elRef.current.style.transform = `translate(${newX}px, ${newY}px)`
}
})
}, 50)
rafRef.current = requestAnimationFrame(tick)
}
rafRef.current = requestAnimationFrame(tick)
return () => {
if (intervalRef.current) clearInterval(intervalRef.current)
if (rafRef.current) cancelAnimationFrame(rafRef.current)
}
}, [isActive, containerRef, targetId])
}, [isActive, containerRef])
return (
<AnimatePresence>
{visible && (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
<div
ref={elRef}
className="absolute pointer-events-none z-50"
style={{ transform: `translate(${position.x}px, ${position.y}px)` }}
style={{
left: 0,
top: 0,
opacity: 0,
transition: 'opacity 0.3s ease',
}}
>
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
@@ -116,8 +136,6 @@ export function GhostCursor({ isActive, containerRef, targetId }: GhostCursorPro
AI
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}

View File

@@ -564,16 +564,16 @@ export function ContextualAIChat({
<div className="p-6 border-b border-border/60 space-y-1.5 bg-white/50 dark:bg-black/20 backdrop-blur-md shrink-0">
<div className="flex items-center justify-between">
<div>
<div className="min-w-0 flex-1">
<h3 className="flex items-center gap-2 font-serif text-xl font-medium text-ink">
<Sparkles size={18} className="text-brand-accent" />
<Sparkles size={18} className="text-brand-accent shrink-0" />
{t('ai.assistantTitle') || 'IA Assistant'}
</h3>
<p className="text-[11px] text-concrete uppercase tracking-wider font-medium opacity-60 truncate">
"{noteTitle || t('ai.currentNote')}"
</p>
</div>
<div className="flex items-center gap-1 shrink-0">
<div className="flex items-center gap-1 shrink-0 ml-2">
<button
onClick={() => setExpanded(e => !e)}
className="p-1.5 hover:bg-slate-100 dark:hover:bg-white/10 rounded-lg transition-colors text-concrete"
@@ -584,6 +584,7 @@ export function ContextualAIChat({
<button
onClick={onClose}
className="p-1.5 hover:bg-slate-100 dark:hover:bg-white/10 rounded-lg transition-colors text-concrete"
title={t('general.close') || 'Fermer'}
>
<X size={18} />
</button>

View File

@@ -40,7 +40,10 @@ export function HierarchicalNotebookSelector({
const getDropdownStyle = useCallback((): React.CSSProperties => {
if (!triggerRef.current) return { position: 'fixed', top: 0, left: 0, width: 280, zIndex: 9999 }
const rect = triggerRef.current.getBoundingClientRect()
if (dropUp) {
const dropdownHeight = 350
const viewportHeight = window.innerHeight
const wouldOverflowBottom = rect.bottom + 8 + dropdownHeight > viewportHeight
if (dropUp || wouldOverflowBottom) {
return {
position: 'fixed',
bottom: window.innerHeight - rect.top + 8,

View File

@@ -23,7 +23,7 @@ import { NoteHistoryModal } from '@/components/note-history-modal'
import { CreateNotebookDialog } from '@/components/create-notebook-dialog'
import { toast } from 'sonner'
import { AnimatePresence, motion } from 'motion/react'
import { PageEntry } from '@/components/page-entry'
type SortOrder = 'newest' | 'oldest' | 'alpha' | 'manual'
@@ -111,7 +111,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
setEditingNote(null)
const params = new URLSearchParams(searchParams.toString())
params.delete('forceList')
const newUrl = params.toString() ? `/?${params.toString()}` : '/'
const newUrl = params.toString() ? `/home?${params.toString()}` : '/home'
router.replace(newUrl, { scroll: false })
}
}, [searchParams, router])
@@ -441,7 +441,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const params = new URLSearchParams(searchParams.toString())
params.delete('openNote')
const qs = params.toString()
router.replace(qs ? `/?${qs}` : '/', { scroll: false })
router.replace(qs ? `/home?${qs}` : '/home', { scroll: false })
// Invalidate notes cache and trigger refresh
refreshNotes(searchParams.get('notebook') || null)
}, [refreshNotes, router, searchParams])
@@ -457,7 +457,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
}, [refreshNotes])
return (
<PageEntry>
<div
className={cn(
'flex w-full min-h-0 flex-1 flex-col gap-3 py-1'
@@ -543,7 +542,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
} else {
params.delete('search')
}
router.push(`/?${params.toString()}`)
router.push(`/home?${params.toString()}`)
}}
onBlur={() => {
if (!inlineSearchQuery) {
@@ -556,7 +555,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
setInlineSearchQuery('')
const params = new URLSearchParams(searchParams.toString())
params.delete('search')
router.push(`/?${params.toString()}`)
router.push(`/home?${params.toString()}`)
}
}}
placeholder={t('search.placeholder') || 'Rechercher...'}
@@ -569,7 +568,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
setInlineSearchQuery('')
const params = new URLSearchParams(searchParams.toString())
params.delete('search')
router.push(`/?${params.toString()}`)
router.push(`/home?${params.toString()}`)
}}
className="text-muted-foreground hover:text-foreground transition-colors"
>
@@ -837,6 +836,5 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
/>
)}
</div>
</PageEntry>
)
}

View File

@@ -100,10 +100,10 @@ export function LabHeader({ canvases, currentCanvasId, onCreateCanvas }: LabHead
<ChevronDown className="h-4 w-4 text-muted-foreground ms-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-[280px] p-2 rounded-2xl shadow-xl border-muted/20">
<DropdownMenuContent align="start" className="w-[280px] p-2 rounded-2xl shadow-xl border-border/40 bg-paper dark:bg-dark-paper">
<DropdownMenuLabel className="text-xs text-muted-foreground px-2 py-1.5 flex justify-between items-center">
{t('labHeader.yourSpaces')}
<span className="text-[10px] bg-muted px-1.5 py-0.5 rounded-full font-mono">{canvases.length}</span>
<span className="text-[10px] bg-brand-accent/10 text-brand-accent px-1.5 py-0.5 rounded-full font-mono">{canvases.length}</span>
</DropdownMenuLabel>
<div className="space-y-1 mt-1">
{canvases.map(c => (
@@ -111,13 +111,13 @@ export function LabHeader({ canvases, currentCanvasId, onCreateCanvas }: LabHead
<DropdownMenuItem
className={cn(
"flex-1 flex flex-col items-start gap-0.5 rounded-xl cursor-pointer p-3 transition-all",
c.id === currentCanvasId ? "bg-primary/5 text-primary border border-primary/20" : "hover:bg-muted"
c.id === currentCanvasId ? "bg-brand-accent/5 text-brand-accent border border-brand-accent/20" : "hover:bg-muted/50"
)}
onClick={() => router.push(`/lab?id=${c.id}`)}
>
<div className="flex items-center gap-2 w-full justify-between">
<span className="font-semibold text-sm">{c.name}</span>
{c.id === currentCanvasId && <span className="w-2 h-2 rounded-full bg-primary" />}
{c.id === currentCanvasId && <span className="w-2 h-2 rounded-full bg-brand-accent" />}
</div>
<span className="text-[10px] text-muted-foreground">{t('labHeader.updated')} {new Date(c.updatedAt).toLocaleDateString()}</span>
</DropdownMenuItem>
@@ -139,7 +139,7 @@ export function LabHeader({ canvases, currentCanvasId, onCreateCanvas }: LabHead
<DropdownMenuItem
onClick={handleCreate}
disabled={isPending}
className="flex items-center gap-2 text-primary font-medium p-3 rounded-xl cursor-pointer hover:bg-primary/5"
className="flex items-center gap-2 text-brand-accent font-medium p-3 rounded-xl cursor-pointer hover:bg-brand-accent/5"
>
<Plus className="h-4 w-4" />
{t('labHeader.newSpace')}

View File

@@ -0,0 +1,412 @@
'use client'
import { motion } from 'motion/react'
import {
BrainCircuit, Search, MessageSquare, Zap, Cpu, Workflow,
Globe, Shield, ArrowRight, Sparkles, Layers, Activity,
Box
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import { useLanguage } from '@/lib/i18n'
import { useState } from 'react'
export function LandingPage() {
const { t } = useLanguage()
const router = useRouter()
const [billingInterval, setBillingInterval] = useState<'monthly' | 'annual'>('monthly')
const AGENTS = [
{ key: 'scraper', icon: <Globe size={24} /> },
{ key: 'researcher', icon: <Search size={24} /> },
{ key: 'slideGen', icon: <Layers size={24} /> },
{ key: 'monitor', icon: <Activity size={24} /> },
{ key: 'diagramGen', icon: <Box size={24} /> },
{ key: 'custom', icon: <Workflow size={24} /> },
]
const BRAINSTORM_ITEMS = ['waveGeneration', 'collaboration', 'export']
const PLANS = [
{ key: 'basic', popular: false, price: t('landing.pricing.basicPrice'), period: '' },
{ key: 'pro', popular: true, price: billingInterval === 'monthly' ? '9,90€' : '7,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perMonth') : t('landing.pricing.perMonthAnnual') },
{ key: 'business', popular: false, price: billingInterval === 'monthly' ? '29,90€' : '23,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perMonth') : t('landing.pricing.perMonthAnnual') },
{ key: 'enterprise', popular: false, price: billingInterval === 'monthly' ? '49,90€' : '39,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perUser') : t('landing.pricing.perUserAnnual') },
]
const TECH_TIERS = [
{ key: 'tags', color: 'bg-brand-accent' },
{ key: 'embeddings', color: 'bg-ochre' },
{ key: 'chatRag', color: 'bg-ink' },
]
const FOOTER_SECTIONS = ['product', 'community', 'legal'] as const
return (
<div className="min-h-screen bg-paper text-ink font-sans selection:bg-ochre/30 selection:text-ink">
{/* Navigation */}
<nav className="fixed top-0 left-0 right-0 z-[100] bg-paper/80 backdrop-blur-md border-b border-border px-8 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-ink flex items-center justify-center rounded-xl shadow-lg rotate-3 group hover:rotate-0 transition-transform cursor-pointer">
<span className="text-paper font-serif text-2xl font-bold">M</span>
</div>
<span className="font-serif text-2xl font-medium tracking-tight">Momento</span>
</div>
<div className="hidden md:flex items-center gap-10">
<a href="#features" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.features')}</a>
<a href="#agents" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.agents')}</a>
<a href="#brainstorm" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.brainstorm')}</a>
<a href="#pricing" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.pricing')}</a>
<a href="#tech" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.tech')}</a>
</div>
<div className="flex items-center gap-4">
<button onClick={() => router.push('/login')} className="hidden md:block px-6 py-2.5 text-concrete hover:text-ink text-[11px] font-bold uppercase tracking-widest transition-colors">
{t('landing.nav.login')}
</button>
<button onClick={() => router.push('/register')} className="px-6 py-2.5 bg-ink text-paper rounded-full text-[11px] font-bold uppercase tracking-widest hover:opacity-90 transition-all flex items-center gap-2 group shadow-xl shadow-ink/10">
{t('landing.nav.cta')}
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
</button>
</div>
</nav>
{/* Hero */}
<section className="relative pt-40 pb-32 px-8 overflow-hidden">
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-ochre/5 rounded-full blur-[120px] -translate-y-1/2 translate-x-1/4 -z-10" />
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-brand-accent/5 rounded-full blur-[100px] translate-y-1/2 -translate-x-1/4 -z-10" />
<div className="max-w-6xl mx-auto text-center">
<motion.div initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, ease: [0.23, 1, 0.32, 1] }}>
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-ochre/10 border border-ochre/20 text-ochre text-[10px] font-bold uppercase tracking-[0.2em] mb-8">
<Sparkles size={12} />
{t('landing.hero.badge')}
</div>
<h1 className="text-6xl md:text-8xl font-serif font-medium tracking-tight text-ink mb-8 leading-[1.1]">
{t('landing.hero.title1')} <br />
<span className="italic">{t('landing.hero.title2')}</span>
</h1>
<p className="max-w-2xl mx-auto text-lg md:text-xl text-concrete font-light leading-relaxed mb-12">
{t('landing.hero.subtitle')}
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<button onClick={() => router.push('/register')} className="px-10 py-5 bg-ink text-paper rounded-2xl text-sm font-bold uppercase tracking-[0.2em] hover:opacity-95 transition-all shadow-2xl shadow-ink/20 flex items-center gap-4 group">
{t('landing.hero.cta')}
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
</button>
<a href="#features" className="px-10 py-5 border border-border rounded-2xl text-sm font-bold uppercase tracking-[0.2em] hover:bg-slate-50 transition-all">
{t('landing.hero.secondary')}
</a>
</div>
</motion.div>
{/* App Preview Mockup */}
<motion.div initial={{ opacity: 0, y: 100 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 1, delay: 0.2, ease: [0.23, 1, 0.32, 1] }} className="mt-24 relative">
<div className="relative mx-auto max-w-5xl aspect-[16/10] bg-white rounded-[32px] shadow-[0_40px_100px_-20px_rgba(0,0,0,0.15)] border border-border p-4 overflow-hidden group">
<img src="/images/workspace-hero.jpg" alt="Momento Workspace" className="w-full h-full object-cover rounded-2xl filter saturate-[0.8]" />
<div className="absolute inset-0 bg-ink/10 group-hover:bg-ink/0 transition-colors duration-500" />
<div className="absolute top-10 right-10 w-64 bg-paper/90 backdrop-blur-xl border border-border p-6 rounded-2xl shadow-2xl">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 rounded-full bg-brand-accent/20 flex items-center justify-center text-brand-accent">
<BrainCircuit size={16} />
</div>
<span className="text-[10px] font-bold uppercase tracking-widest">{t('landing.hero.memoryEcho')}</span>
</div>
<p className="text-xs font-serif italic text-ink/70">{t('landing.hero.memoryEchoText')}</p>
</div>
<div className="absolute bottom-10 left-10 w-72 bg-ink text-paper p-6 rounded-2xl shadow-2xl">
<div className="flex items-center gap-3 mb-4">
<Activity size={16} className="text-ochre" />
<span className="text-[10px] font-bold uppercase tracking-widest text-ochre">{t('landing.hero.brainstormLive')}</span>
</div>
<div className="flex items-center -space-x-2">
{[1, 2, 3].map(i => (
<div key={i} className="w-6 h-6 rounded-full border-2 border-ink bg-concrete text-[8px] flex items-center justify-center font-bold">JD</div>
))}
<span className="text-[10px] ml-4 text-paper/60">{t('landing.hero.ideasGenerated')}</span>
</div>
</div>
</div>
</motion.div>
</div>
</section>
{/* Features */}
<section id="features" className="py-32 px-8 bg-paper">
<div className="max-w-6xl mx-auto">
<div className="mb-24 flex flex-col md:flex-row md:items-end justify-between gap-8">
<div className="max-w-2xl">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.features.label')}</span>
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink">{t('landing.features.title')} <br />{t('landing.features.title2')}</h2>
</div>
<div className="text-concrete font-light">{t('landing.features.desc')}</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
{['f1', 'f2', 'f3'].map((f, i) => {
const icons = [
<Search key="s" className="text-brand-accent" />,
<MessageSquare key="m" className="text-ochre" />,
<Zap key="z" className="text-ink" />
]
return (
<div key={f} className="group">
<div className="w-14 h-14 bg-slate-50 border border-border rounded-2xl flex items-center justify-center mb-8 group-hover:bg-ink group-hover:text-paper transition-all duration-300">
{icons[i]}
</div>
<h3 className="text-xl font-serif font-medium mb-4">{t(`landing.features.${f}Title`)}</h3>
<p className="text-sm text-concrete leading-relaxed font-light">{t(`landing.features.${f}Desc`)}</p>
</div>
)
})}
</div>
</div>
</section>
{/* Agents */}
<section id="agents" className="py-32 px-8 bg-ink text-paper overflow-hidden relative">
<div className="absolute top-0 right-0 w-[1000px] h-[1000px] bg-brand-accent/10 rounded-full blur-[150px] -translate-y-1/2 translate-x-1/2" />
<div className="max-w-6xl mx-auto relative z-10">
<div className="text-center mb-24">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.agents.label')}</span>
<h2 className="text-4xl md:text-6xl font-serif tracking-tight mb-8">{t('landing.agents.title')}</h2>
<p className="text-paper/60 max-w-xl mx-auto font-light">{t('landing.agents.desc')}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{AGENTS.map((agent, i) => (
<div key={i} className="p-8 rounded-3xl bg-white/5 border border-white/10 hover:bg-white/10 transition-all cursor-default group">
<div className="text-ochre mb-6 transition-transform group-hover:scale-110 duration-300">{agent.icon}</div>
<h4 className="text-xl font-serif font-medium mb-4">{t(`landing.agents.${agent.key}.title`)}</h4>
<p className="text-sm text-paper/50 leading-relaxed font-light">{t(`landing.agents.${agent.key}.desc`)}</p>
</div>
))}
</div>
</div>
</section>
{/* Brainstorm */}
<section id="brainstorm" className="py-32 px-8 bg-paper">
<div className="max-w-6xl mx-auto flex flex-col md:flex-row items-center gap-24">
<div className="flex-1">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.brainstorm.label')}</span>
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink mb-8 leading-tight">{t('landing.brainstorm.title')}</h2>
<div className="space-y-8">
{BRAINSTORM_ITEMS.map((item, i) => (
<div key={item} className="flex gap-6">
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-ochre/10 text-ochre flex items-center justify-center font-bold text-xs">{i + 1}</div>
<div>
<h5 className="font-bold text-sm mb-1">{t(`landing.brainstorm.${item}.title`)}</h5>
<p className="text-sm text-concrete font-light leading-relaxed">{t(`landing.brainstorm.${item}.desc`)}</p>
</div>
</div>
))}
</div>
</div>
<div className="flex-1 relative">
<div className="w-[450px] h-[450px] border-2 border-dashed border-border rounded-full flex items-center justify-center relative">
<div className="absolute top-0 right-1/2 translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-ink rounded-full" />
<div className="absolute bottom-0 right-1/2 translate-x-1/2 translate-y-1/2 w-4 h-4 bg-ochre rounded-full" />
<div className="w-[300px] h-[300px] border-2 border-dashed border-border rounded-full flex items-center justify-center">
<div className="w-[150px] h-[150px] border-2 border-dashed border-border rounded-full flex items-center justify-center">
<div className="w-12 h-12 bg-ink rounded-xl shadow-2xl flex items-center justify-center text-paper font-serif text-xl">M</div>
</div>
</div>
</div>
<div className="absolute top-10 right-0 p-4 bg-white border border-border rounded-xl shadow-xl">
<p className="text-[10px] font-bold text-ochre">{t('landing.brainstorm.disruptionLabel')}</p>
<p className="text-xs font-serif italic text-ink">{t('landing.brainstorm.disruptionText')}</p>
</div>
<div className="absolute bottom-20 -left-10 p-4 bg-white border border-border rounded-xl shadow-xl">
<p className="text-[10px] font-bold text-brand-accent">{t('landing.brainstorm.analogyLabel')}</p>
<p className="text-xs font-serif italic text-ink">{t('landing.brainstorm.analogyText')}</p>
</div>
</div>
</div>
</section>
{/* Tech */}
<section id="tech" className="py-32 px-8 bg-slate-50 border-y border-border">
<div className="max-w-6xl mx-auto text-center">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.tech.label')}</span>
<h2 className="text-4xl font-serif tracking-tight mb-16">{t('landing.tech.title')}</h2>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-8 grayscale opacity-50 hover:grayscale-0 hover:opacity-100 transition-all duration-700">
{['OpenAI', 'Google', 'Anthropic', 'DeepSeek', 'Mistral', 'Meta', 'Ollama', 'Groq', 'X.AI', 'Custom'].map((brand, i) => (
<div key={i} className="flex flex-col items-center gap-3">
<div className="w-12 h-12 bg-white rounded-xl border border-border flex items-center justify-center text-xs font-black tracking-tighter">
{brand.slice(0, 2).toUpperCase()}
</div>
<span className="text-[10px] font-bold uppercase tracking-widest">{brand}</span>
</div>
))}
</div>
<div className="mt-24 max-w-2xl mx-auto p-1 bg-white rounded-3xl border border-border shadow-sm flex flex-col md:flex-row gap-0.5">
{TECH_TIERS.map((tier, i) => (
<div key={i} className="flex-1 p-6 text-left">
<div className={`w-1.5 h-1.5 rounded-full ${tier.color} mb-4`} />
<h6 className="text-[10px] font-bold uppercase tracking-widest text-concrete mb-2">{t(`landing.tech.${tier.key}.title`)}</h6>
<p className="text-xs font-light text-concrete">{t(`landing.tech.${tier.key}.desc`)}</p>
</div>
))}
</div>
</div>
</section>
{/* Pricing */}
<section id="pricing" className="py-32 px-8 bg-paper">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-12">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.pricing.label')}</span>
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink mb-6">{t('landing.pricing.title')}</h2>
<p className="text-concrete font-light max-w-xl mx-auto mb-12">{t('landing.pricing.desc')}</p>
<div className="flex items-center justify-center gap-10 mb-8">
<button onClick={() => setBillingInterval('monthly')} className={`group relative py-2 px-1 transition-all ${billingInterval === 'monthly' ? 'text-ink' : 'text-concrete/40 hover:text-concrete'}`}>
<span className="text-xs font-black uppercase tracking-[0.2em]">{t('landing.pricing.monthly')}</span>
{billingInterval === 'monthly' && (
<motion.div layoutId="interval-active" className="absolute -inset-x-1 -inset-y-0.5 border border-ochre/60" transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }} />
)}
</button>
<div className="relative">
<button onClick={() => setBillingInterval('annual')} className={`group relative py-2 px-1 transition-all ${billingInterval === 'annual' ? 'text-ink' : 'text-concrete/40 hover:text-concrete'}`}>
<span className="text-xs font-black uppercase tracking-[0.2em]">{t('landing.pricing.annual')}</span>
{billingInterval === 'annual' && (
<motion.div layoutId="interval-active" className="absolute -inset-x-1 -inset-y-0.5 border border-ochre/60" transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }} />
)}
</button>
<div className="absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
<span className="text-[9px] font-bold text-ochre uppercase tracking-widest italic animate-pulse">(-20%)</span>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch">
{PLANS.map((plan, i) => (
<div key={plan.key} className={`relative p-8 rounded-[32px] border flex flex-col transition-all duration-300 hover:shadow-2xl hover:shadow-ink/5 ${plan.popular ? 'bg-ink text-paper border-ink ring-4 ring-ochre/20' : 'bg-white border-border text-ink'}`}>
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-ochre text-ink text-[10px] font-bold uppercase tracking-widest rounded-full">
{t('landing.pricing.popular')}
</div>
)}
<div className="mb-8">
<h4 className="text-[11px] font-bold uppercase tracking-widest mb-2 opacity-60">{t(`landing.pricing.${plan.key}.name`)}</h4>
<div className="flex items-baseline gap-1 mb-4">
<span className="text-4xl font-serif font-medium">{plan.price}</span>
{plan.period && <span className="text-xs opacity-60">{plan.period}</span>}
</div>
<p className="text-sm font-light leading-relaxed opacity-80">{t(`landing.pricing.${plan.key}.desc`)}</p>
</div>
<div className="flex-1 space-y-4 mb-10">
{[0, 1, 2, 3, 4, 5].map(j => {
const feat = t(`landing.pricing.${plan.key}.feature${j}`)
if (!feat || feat === `landing.pricing.${plan.key}.feature${j}`) return null
return (
<div key={j} className="flex items-start gap-3">
<div className={`mt-1 rounded-full p-0.5 ${plan.popular ? 'bg-ochre text-ink' : 'bg-brand-accent/10 text-brand-accent'}`}>
<Shield size={10} fill="currentColor" />
</div>
<span className="text-xs font-light">{feat}</span>
</div>
)
})}
</div>
<button onClick={() => router.push('/register')} className={`w-full py-4 rounded-2xl text-xs font-bold uppercase tracking-widest transition-all ${plan.popular ? 'bg-ochre text-ink hover:opacity-90' : 'bg-ink text-paper hover:bg-ink/90'}`}>
{t(`landing.pricing.${plan.key}.cta`)}
</button>
</div>
))}
</div>
{/* BYOK */}
<div className="mt-20 p-12 bg-slate-50 border border-border rounded-[40px] flex flex-col md:flex-row items-center gap-12">
<div className="flex-1">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-accent/10 text-brand-accent text-[9px] font-bold uppercase tracking-widest mb-6">
<Cpu size={12} />
{t('landing.byok.label')}
</div>
<h3 className="text-3xl font-serif font-medium mb-4">{t('landing.byok.title')}</h3>
<p className="text-concrete font-light leading-relaxed mb-6">{t('landing.byok.desc')}</p>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-white rounded-2xl border border-border">
<h5 className="text-[10px] font-bold uppercase tracking-widest mb-2">{t('landing.byok.noLockin')}</h5>
<p className="text-[10px] text-concrete font-light">{t('landing.byok.noLockinDesc')}</p>
</div>
<div className="p-4 bg-white rounded-2xl border border-border">
<h5 className="text-[10px] font-bold uppercase tracking-widest mb-2">{t('landing.byok.cost')}</h5>
<p className="text-[10px] text-concrete font-light">{t('landing.byok.costDesc')}</p>
</div>
</div>
</div>
<div className="w-full md:w-[400px] bg-ink rounded-3xl p-8 relative overflow-hidden group">
<div className="absolute inset-0 bg-brand-accent/10 blur-[50px] group-hover:bg-ochre/10 transition-colors" />
<div className="relative z-10 font-mono text-[10px] text-paper/40 space-y-2">
<p className="text-ochre">{"{"}</p>
<p className="pl-4">"provider": "anthropic",</p>
<p className="pl-4">"model": "claude-3-opus",</p>
<p className="pl-4 border-l border-brand-accent/30 bg-brand-accent/5">"apiKey": "sk-ant-at03-..."</p>
<p className="pl-4">"useSystemKey": false</p>
<p className="text-ochre">{"}"}</p>
</div>
<div className="mt-8 flex items-center justify-between relative z-10">
<span className="text-[10px] font-bold text-paper uppercase tracking-widest">{t('landing.byok.configLabel')}</span>
<div className="flex gap-1">{[1, 2, 3].map(i => <div key={i} className="w-1 h-1 rounded-full bg-paper/20" />)}</div>
</div>
</div>
</div>
</div>
</section>
{/* Final CTA */}
<section className="py-40 px-8 text-center bg-paper relative overflow-hidden">
<div className="max-w-3xl mx-auto relative z-10">
<h2 className="text-5xl md:text-7xl font-serif tracking-tight mb-8 leading-tight">{t('landing.cta.title1')} <br /><span className="italic">{t('landing.cta.title2')}</span></h2>
<p className="text-lg text-concrete font-light mb-12">{t('landing.cta.desc')}</p>
<button onClick={() => router.push('/register')} className="px-16 py-6 bg-ink text-paper rounded-[32px] text-lg font-bold uppercase tracking-[0.2em] hover:scale-105 transition-all shadow-[0_30px_60px_-15px_rgba(0,0,0,0.3)]">
{t('landing.cta.button')}
</button>
</div>
</section>
{/* Footer */}
<footer className="py-20 px-8 border-t border-border bg-paper">
<div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between gap-12">
<div className="space-y-6">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-ink flex items-center justify-center rounded-lg">
<span className="text-paper font-serif text-lg font-bold">M</span>
</div>
<span className="font-serif text-xl font-medium tracking-tight">Momento</span>
</div>
<p className="text-sm text-concrete font-light max-w-xs">{t('landing.footer.desc')}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-16">
{FOOTER_SECTIONS.map(section => (
<div key={section} className="space-y-4">
<h6 className="text-[10px] font-bold uppercase tracking-widest text-ink">{t(`landing.footer.${section}.title`)}</h6>
<ul className="space-y-2 text-sm text-concrete font-light">
{[0, 1, 2].map(j => {
const label = t(`landing.footer.${section}.link${j}`)
const href = t(`landing.footer.${section}.link${j}Href`)
if (!label || label.startsWith('landing.')) return null
return <li key={j}><a href={href} className="hover:text-ink">{label}</a></li>
})}
</ul>
</div>
))}
</div>
</div>
<div className="max-w-6xl mx-auto mt-20 pt-10 border-t border-border flex flex-col md:flex-row justify-between items-center gap-4 text-[10px] uppercase font-bold tracking-widest text-concrete">
<div>© 2026 MOMENTO LABS. ALL RIGHTS RESERVED.</div>
<div className="flex gap-8"><span>DESIGNED BY ANTIGRAVITY</span></div>
</div>
</footer>
</div>
)
}

View File

@@ -116,7 +116,7 @@ export function NoteEditorDialog({ onClose }: NoteEditorDialogProps) {
noteId={note.id}
onOpenNote={(noteId: string) => {
onClose()
window.location.href = `/?note=${noteId}`
window.location.href = `/home?note=${noteId}`
}}
onCompareNotes={(noteIds: string[]) => {
Promise.all(noteIds.map(async (id: string) => {
@@ -296,7 +296,7 @@ export function NoteEditorDialog({ onClose }: NoteEditorDialogProps) {
notes={state.comparisonNotes}
onOpenNote={(noteId: string) => {
onClose()
window.location.href = `/?note=${noteId}`
window.location.href = `/home?note=${noteId}`
}}
/>
)}

View File

@@ -177,23 +177,23 @@ export function NotificationPanel() {
// ── icon bg/color per notification type ──────────────────────────────────
const notifIconStyle = (type: string) => {
if (type === 'agent_success') return { bg: `${C.gold}20`, color: C.gold }
if (type === 'agent_slides_ready') return { bg: `${C.gold}20`, color: C.gold }
if (type === 'agent_canvas_ready') return { bg: `${C.gold}20`, color: C.gold }
if (type === 'agent_failure') return { bg: '#EF444420', color: '#EF4444' }
if (type === 'brainstorm_invite') return { bg: '#10b98120', color: '#10b981' }
if (type === 'brainstorm_joined') return { bg: '#60a5fa20', color: '#60a5fa' }
return { bg: `${C.green}20`, color: C.green }
if (type === 'agent_success') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' }
if (type === 'agent_slides_ready') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' }
if (type === 'agent_canvas_ready') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' }
if (type === 'agent_failure') return { bg: 'rgba(239,68,68,0.12)', color: '#EF4444' }
if (type === 'brainstorm_invite') return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' }
if (type === 'brainstorm_joined') return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' }
return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' }
}
const notifLabelColor = (type: string) => {
if (type.startsWith('agent')) {
if (type === 'agent_failure') return '#EF4444'
if (type === 'brainstorm_invite') return '#10b981'
if (type === 'brainstorm_joined') return '#60a5fa'
return C.gold
if (type === 'brainstorm_invite') return '#A3B18A'
if (type === 'brainstorm_joined') return '#A3B18A'
return '#A47148'
}
return C.green
return '#A3B18A'
}
return (
@@ -205,8 +205,7 @@ export function NotificationPanel() {
<Bell className="h-4 w-4 transition-transform duration-200 hover:scale-110" />
{pendingCount > 0 && (
<span
className="absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center rounded-full text-white text-[9px] font-bold border border-white shadow-sm"
style={{ background: C.green }}
className="absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center rounded-full text-white text-[9px] font-bold border border-white shadow-sm bg-brand-accent"
>
{pendingCount > 9 ? '9+' : pendingCount}
</span>
@@ -235,8 +234,7 @@ export function NotificationPanel() {
)}
{pendingCount > 0 && (
<span
className="h-5 px-1.5 flex items-center justify-center rounded-full text-white text-[9px] font-bold"
style={{ background: C.green }}
className="h-5 px-1.5 flex items-center justify-center rounded-full text-white text-[9px] font-bold bg-brand-accent"
>
{pendingCount}
</span>
@@ -405,14 +403,14 @@ export function NotificationPanel() {
<div className="flex items-start gap-3">
<div
className="h-8 w-8 rounded-full flex items-center justify-center text-white font-bold text-[11px] shrink-0 shadow-sm"
style={{ background: `linear-gradient(135deg, #fb923c, #f97316)` }}
style={{ background: `linear-gradient(135deg, #A47148, #A47148)` }}
>
{(share.sharer?.name || share.sharer?.email || '?')[0].toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 mb-0.5">
<Wind className="w-3 h-3" style={{ color: '#fb923c' }} />
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: '#fb923c' }}>
<Wind className="w-3 h-3" style={{ color: '#A47148' }} />
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: '#A47148' }}>
Brainstorm
</span>
</div>
@@ -434,8 +432,7 @@ export function NotificationPanel() {
</button>
<button
onClick={() => handleAcceptBrainstorm(share.id)}
className="flex-1 h-7 px-3 text-[11px] font-bold rounded-lg text-white transition-all active:scale-95 flex items-center justify-center gap-1 shadow-sm hover:opacity-90"
style={{ background: '#fb923c' }}
className="flex-1 h-7 px-3 text-[11px] font-bold rounded-lg text-white transition-all active:scale-95 flex items-center justify-center gap-1 shadow-sm hover:opacity-90 bg-brand-accent"
>
<Check className="h-3 w-3" />
{t('notification.accept') || 'Accept'}

View File

@@ -1,16 +0,0 @@
'use client'
import { motion } from 'motion/react'
export function PageEntry({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<motion.div
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, ease: [0.23, 1, 0.32, 1] }}
className={className}
>
{children}
</motion.div>
)
}

View File

@@ -1,9 +0,0 @@
'use client'
export function PageTransition({ children }: { children: React.ReactNode }) {
return (
<div className="flex-1 flex flex-col min-h-0">
{children}
</div>
)
}

View File

@@ -0,0 +1,41 @@
'use client'
import { LanguageProvider, useLanguage } from '@/lib/i18n/LanguageProvider'
import { QueryProvider } from '@/components/query-provider'
import type { Translations } from '@/lib/i18n/load-translations'
import type { ReactNode } from 'react'
import { useEffect } from 'react'
const RTL_LANGUAGES = ['ar', 'fa']
function DirWrapper({ children }: { children: ReactNode }) {
const { language } = useLanguage()
const dir = RTL_LANGUAGES.includes(language) ? 'rtl' : 'ltr'
useEffect(() => {
document.documentElement.lang = language
document.documentElement.dir = dir
}, [language, dir])
return <div dir={dir} className="contents">{children}</div>
}
export function PublicProviders({
children,
initialLanguage = 'en',
initialTranslations
}: {
children: ReactNode
initialLanguage?: string
initialTranslations?: Translations
}) {
return (
<QueryProvider>
<LanguageProvider initialLanguage={initialLanguage as any} initialTranslations={initialTranslations}>
<DirWrapper>
{children}
</DirWrapper>
</LanguageProvider>
</QueryProvider>
)
}

View File

@@ -150,7 +150,22 @@ async function aiReformulate(text: string, option: string, language?: string): P
})
const data = await res.json()
if (!res.ok) {
const serverMsg = typeof data?.error === 'string' && data.error.trim() ? data.error.trim() : AI_REFORMULATE_FALLBACK
if (data?.errorKey === 'ai.wordCountMin') {
throw new Error(t('ai.wordCountMin') || `Minimum ${data?.params?.min || 10} mots requis (${data?.params?.current || 0} actuels)`)
}
if (data?.errorKey === 'ai.wordCountMax') {
throw new Error(t('ai.wordCountMax') || `Maximum ${data?.params?.max || 500} mots (${data?.params?.current || 0} actuels)`)
}
if (data?.errorKey === 'ai.featureLocked') {
throw new Error(t('ai.featureLocked') || 'Cette fonctionnalité nécessite le plan PRO.')
}
if (data?.errorKey === 'ai.quotaExceeded') {
throw new Error(t('ai.quotaExceeded') || 'Limite mensuelle atteinte.')
}
if (data?.quotaExceeded) {
throw new Error(t('ai.quotaExceeded') || 'Limite mensuelle atteinte.')
}
const serverMsg = typeof data?.error === 'string' && !data.error.includes(':') ? data.error.trim() : AI_REFORMULATE_FALLBACK
throw new Error(serverMsg)
}
return data.reformulatedText || data.text || text
@@ -385,6 +400,7 @@ function BubbleToolbar({ editor }: { editor: Editor | null }) {
try {
const lang = option === 'translate' ? (targetLang || language) : language
const result = await aiReformulate(text, option, lang)
window.dispatchEvent(new Event('ai-usage-changed'))
if (option === 'explain') {
setAiModal({ type: 'explain', origText: text, html: result, from, to })
} else {

View File

@@ -28,12 +28,12 @@ export function SettingsNav({ className }: SettingsNavProps) {
const isActive = (href: string) => pathname === href || pathname.startsWith(href + '/')
return (
<nav className={`flex items-center gap-1 border-b border-border/40 pb-px ${className || ''}`}>
<nav className={`flex flex-wrap items-center gap-1 border-b border-border/40 pb-px ${className || ''}`}>
{tabs.map((tab) => (
<Link
key={tab.id}
href={tab.href}
className="flex items-center gap-2.5 px-6 py-5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all relative whitespace-nowrap text-concrete hover:text-ink/60"
className="flex items-center gap-2.5 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.18em] transition-all relative whitespace-nowrap text-concrete hover:text-ink/60"
style={{ color: isActive(tab.href) ? 'var(--ink)' : undefined }}
>
<span style={{ color: isActive(tab.href) ? 'var(--ink)' : 'var(--concrete)' }}>{tab.icon}</span>

View File

@@ -234,7 +234,7 @@ export function BillingPlans() {
{/* Usage Overview */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-3xl p-8 space-y-6">
<div className="md:col-span-2 bg-white/40 dark:bg-white/5 border border-border rounded-3xl p-8 space-y-6">
<div className="flex items-center gap-4">
<div className="p-2 bg-brand-accent/10 text-brand-accent rounded-xl">
<Activity size={20} />
@@ -243,17 +243,56 @@ export function BillingPlans() {
<h4 className="text-sm font-bold text-ink">{t('billing.currentUsage') || 'Utilisation actuelle'}</h4>
<p className="text-[10px] text-concrete uppercase tracking-widest">{t('billing.currentPeriod') || 'Période en cours'}</p>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between text-[11px] font-medium text-concrete uppercase tracking-wider">
<span>{t('billing.aiCredits') || 'Crédits IA'}</span>
<span>{aiUsed} / {aiLimit} {t('billing.used') || 'utilisés'}</span>
</div>
<div className="h-2 w-full bg-slate-100 dark:bg-white/5 rounded-full overflow-hidden">
<div className="h-full bg-brand-accent rounded-full" style={{ width: `${aiPct}%` }} />
<div className="ml-auto">
<span className={cn(
'px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest',
isPaid ? 'bg-brand-accent/10 text-brand-accent' : 'bg-concrete/10 text-concrete'
)}>
{effectiveTier === 'BASIC' ? 'Discovery' : effectiveTier}
</span>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{!quotas && (
<div className="col-span-full flex items-center justify-center py-8">
<Loader2 className="h-5 w-5 animate-spin text-concrete" />
</div>
)}
{quotas && Object.entries(quotas).filter(([_, q]) => q.limit > 0).length === 0 && (
<p className="col-span-full text-xs text-concrete text-center py-4">Aucune donnée d'utilisation disponible</p>
)}
{quotas && Object.entries(quotas).filter(([_, q]) => q.limit > 0).map(([key, q]) => {
const pct = q.limit > 0 ? (q.used / q.limit) * 100 : 0
const featureLabels: Record<string, string> = {
semantic_search: t('usageMeter.featureSearch'),
auto_tag: t('usageMeter.featureTags'),
auto_title: t('usageMeter.featureTitles'),
reformulate: t('usageMeter.featureReformulate'),
chat: t('usageMeter.featureChat'),
brainstorm_create: t('usageMeter.featureBrainstormCreate'),
brainstorm_expand: t('usageMeter.featureBrainstormExpand'),
brainstorm_enrich: t('usageMeter.featureBrainstormEnrich'),
}
return (
<div key={key} className="space-y-2 p-3 rounded-xl bg-paper/50 dark:bg-white/5">
<div className="flex justify-between items-center">
<span className="text-[10px] font-bold text-concrete uppercase tracking-wider truncate">
{featureLabels[key] || key}
</span>
</div>
<div className="flex items-baseline gap-1">
<span className="text-lg font-bold text-ink">{q.remaining === Infinity ? '' : q.remaining}</span>
<span className="text-[10px] text-concrete">/ {q.limit === Infinity ? '' : q.limit}</span>
</div>
<div className="h-1.5 w-full bg-slate-100 dark:bg-white/5 rounded-full overflow-hidden">
<div
className={cn('h-full rounded-full transition-all', pct >= 90 ? 'bg-rose-500' : pct >= 70 ? 'bg-amber-500' : 'bg-brand-accent')}
style={{ width: `${Math.min(pct, 100)}%` }}
/>
</div>
</div>
)
})}
</div>
</div>

View File

@@ -469,7 +469,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
const currentNoteId = searchParams.get('openNote')
const isInboxActive =
pathname === '/' &&
pathname === '/home' &&
!searchParams.get('notebook') &&
!searchParams.get('labels') &&
!searchParams.get('archived') &&
@@ -534,11 +534,11 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
const params = new URLSearchParams()
params.set('notebook', notebookId)
params.set('forceList', '1')
router.push(`/?${params.toString()}`)
router.push(`/home?${params.toString()}`)
}
const handleInboxClick = () => {
router.push('/?forceList=1')
router.push('/home?forceList=1')
}
const handleNoteClick = (noteId: string, notebookId: string) => {
@@ -546,7 +546,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
params.set('notebook', notebookId)
params.set('openNote', noteId)
params.delete('forceList')
router.push(`/?${params.toString()}`)
router.push(`/home?${params.toString()}`)
}
// ── Drag state ──
@@ -706,7 +706,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
await trashNotebook(deletingNotebook.id)
setDeletingNotebook(null)
if (currentNotebookId === deletingNotebook.id) {
router.push('/')
router.push('/home')
}
} catch (err) {
console.error('Trash failed:', err)
@@ -871,7 +871,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<Settings size={14} />
</Link>
<button
onClick={() => { router.push('/') }}
onClick={() => { router.push('/home') }}
className="p-1.5 text-muted-ink hover:text-ink transition-all hover:bg-white/50 dark:hover:bg-white/10 rounded-lg border border-transparent hover:border-border"
title={t('nav.home') || 'Accueil'}
>
@@ -889,29 +889,29 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<div className="flex bg-white/50 dark:bg-white/10 p-1 rounded-xl border border-border dark:border-white/10">
<button
onClick={() => { setActiveView('notebooks'); if (pathname !== '/') router.push('/') }}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'notebooks' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
onClick={() => { setActiveView('notebooks'); if (pathname !== '/home') router.push('/home') }}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'notebooks' ? 'bg-brand-accent text-white shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
title={t('nav.notebooks')}
>
<BookOpen size={14} />
</button>
<button
onClick={() => setActiveView('reminders')}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'reminders' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'reminders' ? 'bg-brand-accent text-white shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
title={t('sidebar.reminders')}
>
<Clock size={14} />
</button>
<button
onClick={() => { setActiveView('agents'); router.push('/agents') }}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'agents' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'agents' ? 'bg-brand-accent text-white shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
title={t('nav.agents')}
>
<Bot size={14} />
</button>
<button
onClick={() => { setActiveView('brainstorms'); router.push('/brainstorm') }}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'brainstorms' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
className={cn('flex-1 flex items-center justify-center py-1.5 rounded-lg transition-all', activeView === 'brainstorms' ? 'bg-brand-accent text-white shadow-sm' : 'text-muted-ink hover:text-ink hover:bg-white/50')}
title={t('brainstorm.sessions')}
>
<Sparkles size={14} />
@@ -989,8 +989,8 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<div className={cn(
'w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium border shrink-0',
isInboxActive
? 'bg-ink text-paper border-ink'
: 'bg-white/60 dark:bg-white/5 text-ink dark:text-foreground border-border'
? 'bg-brand-accent text-white border-brand-accent'
: 'bg-paper dark:bg-white/5 text-muted-ink border-border group-hover:border-brand-accent/20'
)}>
<Inbox size={14} />
</div>
@@ -1067,8 +1067,8 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<div className={cn(
'w-8 h-8 rounded-full flex items-center justify-center border transition-colors shrink-0',
isActive
? 'bg-foreground text-background border-foreground'
: 'bg-paper border-border group-hover:border-foreground/20'
? 'bg-brand-accent text-white border-brand-accent'
: 'bg-paper border-border group-hover:border-brand-accent/20'
)}>
<item.icon size={16} />
</div>
@@ -1109,15 +1109,15 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<UsageMeter />
<div className="px-2 space-y-0.5">
<Link
href="/?shared=1&forceList=1"
href="/home?shared=1&forceList=1"
className={cn(
'w-full flex items-center gap-3 px-3 py-2 text-[12px] transition-all font-medium group rounded-xl',
searchParams.get('shared') === '1' && pathname === '/'
searchParams.get('shared') === '1' && pathname === '/home'
? 'bg-accent/5 text-accent'
: 'text-muted-ink hover:text-ink hover:bg-black/5'
)}
>
<Users size={14} className={searchParams.get('shared') === '1' && pathname === '/' ? 'text-accent' : 'text-muted-ink group-hover:text-ink'} />
<Users size={14} className={searchParams.get('shared') === '1' && pathname === '/home' ? 'text-accent' : 'text-muted-ink group-hover:text-ink'} />
<span>{t('sidebar.sharedWithMe') || 'Shared'}</span>
</Link>

View File

@@ -1,10 +1,11 @@
'use client';
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { cn } from '@/lib/utils';
import { Sparkles, X, Crown } from 'lucide-react';
import { Sparkles, ChevronDown, X, Crown } from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { useLanguage } from '@/lib/i18n';
interface QuotaData {
@@ -17,27 +18,31 @@ interface UsageMeterProps {
className?: string;
}
function formatRemaining(value: number): string {
if (!Number.isFinite(value)) return '∞';
return String(value);
}
export function UsageMeter({ className }: UsageMeterProps) {
const { t } = useLanguage();
const router = useRouter();
const queryClient = useQueryClient();
const [expanded, setExpanded] = useState(false);
const [showModal, setShowModal] = useState(false);
const { data, isLoading } = useQuery({
queryKey: ['usage', 'current'],
queryFn: async () => {
const res = await fetch('/api/usage/current');
if (!res.ok) throw new Error('Failed to fetch quotas');
const data = await res.json();
return data.quotas as Record<string, QuotaData>;
const json = await res.json();
return { quotas: json.quotas as Record<string, QuotaData>, tier: json.tier as string };
},
refetchInterval: 30000,
staleTime: 5000,
refetchInterval: 10000,
});
if (isLoading || !data) {
useEffect(() => {
const handler = () => queryClient.invalidateQueries({ queryKey: ['usage', 'current'] });
window.addEventListener('ai-usage-changed', handler);
return () => window.removeEventListener('ai-usage-changed', handler);
}, [queryClient]);
if (isLoading || !data || !data.quotas) {
return (
<div className={cn('px-2 py-2', className)}>
<div className="h-8 rounded-lg bg-paper/50 animate-pulse" />
@@ -45,97 +50,151 @@ export function UsageMeter({ className }: UsageMeterProps) {
);
}
const features = [
{ key: 'semantic_search', labelKey: 'usageMeter.featureSearch' as const },
{ key: 'auto_tag', labelKey: 'usageMeter.featureTags' as const },
{ key: 'auto_title', labelKey: 'usageMeter.featureTitles' as const },
] as const;
const isProPlus = data.tier && data.tier !== 'BASIC';
const featureQuotas = features.map((f) => {
const quota = data[f.key];
return {
...f,
label: t(f.labelKey),
used: quota?.used ?? 0,
limit: quota?.limit ?? 0,
remaining: quota?.remaining ?? 0,
const featureLabels: Record<string, string> = {
semantic_search: t('usageMeter.featureSearch'),
auto_tag: t('usageMeter.featureTags'),
auto_title: t('usageMeter.featureTitles'),
reformulate: t('usageMeter.featureReformulate'),
chat: t('usageMeter.featureChat'),
brainstorm_create: t('usageMeter.featureBrainstormCreate'),
brainstorm_expand: t('usageMeter.featureBrainstormExpand'),
brainstorm_enrich: t('usageMeter.featureBrainstormEnrich'),
};
});
const featureQuotas = Object.entries(data.quotas)
.filter(([_, q]) => q.limit > 0)
.map(([key, quota]) => ({
key,
label: featureLabels[key] || key,
used: quota.used,
limit: quota.limit,
remaining: quota.remaining,
}));
const isUnlimited = featureQuotas.every((f) => !Number.isFinite(f.limit));
const totalRemaining = featureQuotas.reduce(
(sum, f) => sum + (Number.isFinite(f.remaining) ? f.remaining : 0),
0,
const totalUsed = featureQuotas.reduce(
(sum, f) => sum + (Number.isFinite(f.used) ? f.used : 0), 0,
);
const totalLimit = featureQuotas.reduce(
(sum, f) => sum + (Number.isFinite(f.limit) ? f.limit : 0),
0,
(sum, f) => sum + (Number.isFinite(f.limit) ? f.limit : 0), 0,
);
const used = totalLimit - totalRemaining;
const percentage = totalLimit > 0 ? Math.min((used / totalLimit) * 100, 100) : 0;
const totalRemaining = totalLimit - totalUsed;
const totalPct = totalLimit > 0 ? (totalUsed / totalLimit) * 100 : 0;
const isExhausted = !isUnlimited && totalRemaining <= 0;
const isLow = !isUnlimited && percentage >= 75 && !isExhausted;
return (
<>
<div className="px-2 py-2 w-full">
<div className="p-4 bg-slate-50 dark:bg-white/5 border border-border rounded-2xl space-y-3 group hover:shadow-lg transition-all duration-300">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="bg-slate-50 dark:bg-white/5 border border-border rounded-2xl overflow-hidden">
<button
type="button"
onClick={() => setExpanded(!expanded)}
className="w-full p-3 flex items-center gap-2 hover:bg-slate-100 dark:hover:bg-white/5 transition-colors"
>
<Sparkles
size={12}
className={
className={cn(
'shrink-0',
isExhausted
? 'text-rose-400 fill-rose-400/20'
: isUnlimited
? 'text-emerald-400 fill-emerald-400/20'
: 'text-brand-accent fill-brand-accent/20'
}
)}
/>
<span className="text-[11px] font-bold text-ink/70">{t('usageMeter.packName')}</span>
</div>
<span
className={cn(
'text-[10px] font-medium',
isExhausted
? 'text-rose-500'
: isUnlimited
? 'text-emerald-500'
: isLow
? 'text-amber-500'
: 'text-concrete',
)}
>
{isUnlimited
? t('usageMeter.unlimited')
: t('usageMeter.remaining', { count: totalRemaining })}
</span>
</div>
{!isUnlimited && (
<div className="h-1.5 w-full bg-slate-200 dark:bg-white/10 rounded-full overflow-hidden">
<>
<div className="flex-1 h-1 bg-slate-200 dark:bg-white/10 rounded-full overflow-hidden mx-2">
<div
className={cn(
'h-full rounded-full',
totalPct >= 90 ? 'bg-rose-400' : totalPct >= 70 ? 'bg-amber-400' : 'bg-brand-accent',
)}
style={{ width: `${Math.min(totalPct, 100)}%` }}
/>
</div>
<span className={cn(
'text-[10px] font-medium tabular-nums shrink-0',
totalPct >= 90 ? 'text-rose-500' : totalPct >= 70 ? 'text-amber-500' : 'text-concrete'
)}>
{totalRemaining}
</span>
</>
)}
{isUnlimited && (
<span className="text-[10px] font-medium text-emerald-500 ml-auto">{t('usageMeter.unlimited')}</span>
)}
{isProPlus && !isUnlimited && (
<span className="text-[9px] font-bold text-brand-accent uppercase tracking-widest ml-auto">{data.tier}</span>
)}
<ChevronDown
size={12}
className={cn(
'text-concrete shrink-0 transition-transform duration-200',
expanded && 'rotate-180',
)}
/>
</button>
<AnimatePresence initial={false}>
{expanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: 'easeInOut' }}
className="overflow-hidden"
>
<div className="px-3 pb-3 space-y-2">
<div className="border-t border-border/40 pt-2" />
{featureQuotas.map((f) => {
const fPct = Number.isFinite(f.limit) && f.limit > 0 ? (f.used / f.limit) * 100 : 0
return (
<div key={f.key} className="space-y-1">
<div className="flex justify-between text-[10px]">
<span className="text-concrete truncate">{f.label}</span>
<span className={cn(
'font-medium tabular-nums',
fPct >= 90 ? 'text-rose-500' : fPct >= 70 ? 'text-amber-500' : 'text-ink/60'
)}>
{!Number.isFinite(f.remaining) ? '∞' : f.remaining}/{!Number.isFinite(f.limit) ? '∞' : f.limit}
</span>
</div>
{Number.isFinite(f.limit) && (
<div className="h-1 w-full bg-slate-200 dark:bg-white/10 rounded-full overflow-hidden">
<div
className={cn(
'h-full rounded-full transition-all duration-300',
isExhausted
? 'bg-rose-400'
: isLow
? 'bg-amber-400'
: 'bg-brand-accent',
fPct >= 90 ? 'bg-rose-400' : fPct >= 70 ? 'bg-amber-400' : 'bg-brand-accent',
)}
style={{ width: `${percentage}%` }}
style={{ width: `${Math.min(fPct, 100)}%` }}
/>
</div>
)}
</div>
)
})}
{!isProPlus && (
<button
onClick={() => router.push('/settings/billing')}
className="w-full py-2 bg-brand-accent/10 hover:bg-brand-accent text-brand-accent hover:text-white text-[9px] font-bold uppercase tracking-widest rounded-xl transition-all duration-300 border border-brand-accent/20"
onClick={(e) => { e.stopPropagation(); router.push('/settings/billing'); }}
className="w-full mt-2 py-2 bg-brand-accent/10 hover:bg-brand-accent text-brand-accent hover:text-white text-[9px] font-bold uppercase tracking-widest rounded-xl transition-all duration-300 border border-brand-accent/20"
>
{t('usageMeter.upgradePricing') || 'Passer à Pro'}
</button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -32,7 +32,11 @@ export function useBrainstormSocket(
const [activities, setActivities] = useState<ActivityEvent[]>([])
const [aiProcessingNodeId, setAiProcessingNodeId] = useState<string | null>(null)
const effectiveUserId = userId || `guest_${Math.random().toString(36).slice(2, 10)}`
const guestIdRef = useRef<string | null>(null)
if (!guestIdRef.current && !userId) {
guestIdRef.current = `guest_${Math.random().toString(36).slice(2, 10)}`
}
const effectiveUserId = userId || guestIdRef.current!
useEffect(() => {
if (!sessionId) return
@@ -85,8 +89,6 @@ export function useBrainstormSocket(
return () => {
socket.disconnect()
socketRef.current = null
setOthers([])
setAiProcessingNodeId(null)
}
}, [sessionId, effectiveUserId, userName])

View File

@@ -15,16 +15,24 @@ export interface EmbeddingResult {
export class EmbeddingService {
private readonly EMBEDDING_DIMENSION = 1536
private readonly MAX_CHARS = 15000
private truncateForEmbedding(text: string): string {
if (text.length <= this.MAX_CHARS) return text
return text.slice(0, this.MAX_CHARS)
}
async generateEmbedding(text: string): Promise<EmbeddingResult> {
if (!text || text.trim().length === 0) {
throw new Error('Cannot generate embedding for empty text')
}
const truncated = this.truncateForEmbedding(text)
try {
const config = await getSystemConfig()
const embedding = await withAiProviderFallback('embedding', config, (provider) =>
provider.getEmbeddings(text)
provider.getEmbeddings(truncated)
)
return {
@@ -41,7 +49,7 @@ export class EmbeddingService {
async generateBatchEmbeddings(texts: string[]): Promise<EmbeddingResult[]> {
if (!texts || texts.length === 0) return []
const validTexts = texts.filter(t => t && t.trim().length > 0)
const validTexts = texts.filter(t => t && t.trim().length > 0).map(t => this.truncateForEmbedding(t))
if (validTexts.length === 0) return []
try {

View File

@@ -77,14 +77,14 @@ export class MemoryEchoService {
for (const note of notesWithoutEmbeddings) {
if (!note.content || note.content.trim().length === 0) continue
try {
const embedding = await provider.getEmbeddings(note.content)
const embedding = await provider.getEmbeddings(note.content.slice(0, 15000))
if (embedding && embedding.length > 0) {
const vecStr = `[${embedding.join(',')}]`
await prisma.$executeRawUnsafe(
`INSERT INTO "NoteEmbedding" ("id", "noteId", "embedding", "createdAt", "updatedAt")
VALUES (gen_random_uuid(), $1, $2::vector, now(), now())
`INSERT INTO "NoteEmbedding" ("id", "noteId", "embedding", "createdAt")
VALUES (gen_random_uuid(), $1, $2::vector, now())
ON CONFLICT ("noteId")
DO UPDATE SET "embedding" = $2::vector, "updatedAt" = now()`,
DO UPDATE SET "embedding" = $2::vector`,
note.id,
vecStr
)

View File

@@ -286,11 +286,15 @@ Only return the translated text, nothing else.`,
Keep the explanation clear, educational, and concise.`
}
const systemSuffix = mode === 'explain'
? `CRITICAL: Respond ONLY with your explanation in ${language}. No meta-commentary, no English. Format strictly as ${format === 'html' ? 'HTML tags' : 'Markdown'}.`
: `CRITICAL: Respond ONLY with the refactored text in ${language}. No explanations, no meta-commentary, no English. Format strictly as ${format === 'html' ? 'HTML tags' : 'Markdown'}.`
return `${instructions[mode]}
${content}
CRITICAL: Respond ONLY with the refactored text in ${language}. No explanations, no meta-commentary, no English. Format strictly as ${format === 'html' ? 'HTML tags' : 'Markdown'}.`
${systemSuffix}`
}
/**

View File

@@ -235,15 +235,15 @@ export class SemanticSearchService {
const rows: Array<{ noteId: string; similarity: number }> = await prisma.$queryRawUnsafe(
`SELECT n.id AS "noteId",
1 - (e."embedding" <=> ${vecParam}) AS similarity
1 - (e."embedding"::vector <=> ${vecParam}) AS similarity
FROM "Note" n
INNER JOIN "NoteEmbedding" e ON e."noteId" = n.id
WHERE n."trashedAt" IS NULL
AND n."isArchived" = false
${userClause}
${notebookClause}
AND 1 - (e."embedding" <=> ${vecParam}) >= ${thresholdParam}
ORDER BY e."embedding" <=> ${vecParam} ASC
AND 1 - (e."embedding"::vector <=> ${vecParam}) >= ${thresholdParam}
ORDER BY e."embedding"::vector <=> ${vecParam} ASC
LIMIT ${limitParam}`,
...params
)
@@ -356,10 +356,10 @@ export class SemanticSearchService {
const vecStr = embeddingService.toVectorString(embedding)
await prisma.$queryRawUnsafe(
`INSERT INTO "NoteEmbedding" ("id", "noteId", "embedding", "createdAt", "updatedAt")
VALUES (gen_random_uuid(), $1, $2::vector, now(), now())
`INSERT INTO "NoteEmbedding" ("id", "noteId", "embedding", "createdAt")
VALUES (gen_random_uuid(), $1, $2::vector, now())
ON CONFLICT ("noteId")
DO UPDATE SET "embedding" = $2::vector, "updatedAt" = now()`,
DO UPDATE SET "embedding" = $2::vector`,
noteId,
vecStr
)
@@ -427,7 +427,7 @@ export class SemanticSearchService {
AND n."trashedAt" IS NULL
AND n."userId" = $3
${noteFilter}
ORDER BY dc."embedding" <=> $1::vector
ORDER BY dc."embedding"::vector <=> $1::vector
LIMIT $2`,
...params
) as any[]

View File

@@ -31,6 +31,7 @@ export class QuotaExceededError extends Error {
billingOwnerId?: string;
triggeredByUserId?: string;
isGuestActor?: boolean;
currentTier?: string;
constructor(
upgradeTier: 'PRO' | 'BUSINESS',
@@ -42,6 +43,7 @@ export class QuotaExceededError extends Error {
billingOwnerId?: string;
triggeredByUserId?: string;
isGuestActor?: boolean;
currentTier?: string;
},
) {
super(`Quota exceeded for ${feature}`);
@@ -53,6 +55,7 @@ export class QuotaExceededError extends Error {
this.billingOwnerId = sessionMeta?.billingOwnerId;
this.triggeredByUserId = sessionMeta?.triggeredByUserId;
this.isGuestActor = sessionMeta?.isGuestActor;
this.currentTier = sessionMeta?.currentTier;
}
toJSON() {
@@ -62,6 +65,7 @@ export class QuotaExceededError extends Error {
upgradeTier: this.upgradeTier,
byokConfigured: this.byokConfigured,
isGuestActor: this.isGuestActor ?? false,
currentTier: this.currentTier,
};
}
}
@@ -121,6 +125,18 @@ local newCount = tonumber(redis.call('GET', KEYS[1]))
return newCount
`;
const INCREMENT_BY_LUA = `
local count = tonumber(ARGV[1]) or 1
local ttl = tonumber(ARGV[2])
redis.call('INCRBY', KEYS[1], count)
local ttlResult = redis.call('TTL', KEYS[1])
if ttlResult == -1 then
redis.call('EXPIRE', KEYS[1], ttl)
end
local newCount = tonumber(redis.call('GET', KEYS[1]))
return newCount
`;
const RESERVE_LUA = `
local limit = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])
@@ -252,6 +268,7 @@ export async function checkEntitlementOrThrow(
result.limit,
result.limit > 0 ? result.limit - result.remaining : 0,
result.byokConfigured ?? false,
{ currentTier: result.tier },
);
}
}
@@ -261,7 +278,7 @@ export async function reserveUsageOrThrow(
feature: string,
): Promise<void> {
if (!isValidFeature(feature)) {
throw new QuotaExceededError('PRO', feature, 0, 0, false);
throw new QuotaExceededError('PRO', feature, 0, 0, false, { currentTier: 'BASIC' });
}
const tier = await getEffectiveTier(userId);
@@ -274,6 +291,7 @@ export async function reserveUsageOrThrow(
0,
0,
false,
{ currentTier: tier },
);
}
@@ -297,6 +315,7 @@ export async function reserveUsageOrThrow(
limit,
limit,
byokConfigured,
{ currentTier: tier },
);
}
} catch (err) {
@@ -324,11 +343,11 @@ export async function checkSessionEntitlementOrThrow(
}
}
export function incrementUsageAsync(userId: string, feature: string): Promise<void> {
export function incrementUsageAsync(userId: string, feature: string, count: number = 1): Promise<void> {
if (!isValidFeature(feature)) return Promise.resolve();
const key = getRedisKey(userId, feature);
return redis.eval(INCREMENT_LUA, 1, key, String(TTL_SECONDS)).then(() => {}).catch((err) => {
return redis.eval(INCREMENT_BY_LUA, 1, key, String(count), String(TTL_SECONDS)).then(() => {}).catch((err) => {
console.error('[entitlements] Async increment failed:', err);
});
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "فشل تحديث إعدادات الملاحظات الحديثة"
},
"aiSettings": {
"title": "إعدادات الذكاء الاصطناعي",
"title": "AI",
"description": "تكوين ميزاتك وتفضيلاتك المدعومة بالذكاء الاصطناعي",
"features": "ميزات الذكاء الاصطناعي",
"provider": "مزود الذكاء الاصطناعي",
@@ -917,7 +917,8 @@
"autoLabeling": "اقتراحات التسميات",
"autoLabelingDesc": "يقترح ويطبق التسميات تلقائياً على ملاحظاتك",
"noteHistory": "سجل الملاحظة",
"noteHistoryDesc": "تفعيل لقطات النسخ والاستعادة من السجل"
"noteHistoryDesc": "تفعيل لقطات النسخ والاستعادة من السجل",
"titleSuggestions": "اقتراحات العناوين"
},
"general": {
"loading": "جاري التحميل...",
@@ -1115,7 +1116,13 @@
"languageDetection": "اكتشاف اللغة",
"languageDetectionDesc": "يكتشف تلقائياً لغة كل ملاحظة",
"autoLabeling": "التصنيف التلقائي",
"autoLabelingDesc": "يقترح ويطبق التصنيفات تلقائياً"
"autoLabelingDesc": "يقترح ويطبق التصنيفات تلقائياً",
"fallbackSectionTitle": "مزود احتياطي (اختياري)",
"fallbackSectionDescription": "يُستخدم تلقائياً عند أخطاء المزود (429، 5xx). محاولة واحدة خلال 1,5 ثانية.",
"fallbackProvider": "مزود احتياطي",
"fallbackModel": "نموذج احتياطي",
"fallbackNone": "لا شيء (معطّل)",
"fallbackModelPlaceholder": "مثال: gpt-4o-mini"
},
"resend": {
"title": "Resend (موصى به)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "فشل الحذف",
"roleUpdateSuccess": "تم تحديث دور المستخدم إلى {role}",
"roleUpdateFailed": "فشل تحديث الدور",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "تخفيض إلى مستخدم",
"promote": "ترقية إلى مشرف",
"confirmDelete": "هل أنت متأكد؟ لا يمكن التراجع عن هذا الإجراء.",
@@ -1180,6 +1189,7 @@
"name": "الاسم",
"email": "البريد الإلكتروني",
"role": "الدور",
"subscription": "Subscription",
"createdAt": "تاريخ الإنشاء",
"actions": "الإجراءات"
},
@@ -1371,7 +1381,7 @@
"loading": "جاري التحميل..."
},
"dataManagement": {
"title": "إدارة البيانات",
"title": "Data",
"toolsDescription": "أدوات للحفاظ على صحة قاعدة البيانات",
"exporting": "جاري التصدير...",
"importing": "جاري الاستيراد...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "الإعدادات العامة",
"title": "General",
"description": "إعدادات التطبيق العامة"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "طي"
},
"mcpSettings": {
"title": "إعدادات MCP",
"title": "MCP",
"description": "إدارة مفاتيح API وتكوين الأدوات الخارجية",
"whatIsMcp": {
"title": "ما هو MCP؟",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "استنفد مضيف الجلسة حدّ الذكاء الاصطناعي. اطلب منه ترقية خطته.",
"quotaHost": "لقد وصلت إلى حدّ الذكاء الاصطناعي لهذه الجلسة. رقِّ خطتك للمتابعة."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "مرحبًا في {tier}. ميزاتك متاحة الآن.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "الاستخدام الحالي",
"currentPeriod": "الفترة الحالية",
"aiCredits": "أرصدة الذكاء الاصطناعي",
"used": "مستخدم",
"billing": "الفواتير",
"renewal": "التجديد",
"paidPlanDesc": "يتم تجديد اشتراكك تلقائيًا.",
"businessDescription": "للفرق وقادة المنتجات."
},
"landing": {
"nav": {
"features": "الميزات",
"agents": "وكلاء الذكاء الاصطناعي",
"brainstorm": "العصف الذهني",
"pricing": "الأسعار",
"tech": "البنية",
"login": "تسجيل الدخول",
"cta": "ابدأ الآن"
},
"hero": {
"badge": "مدعوم بالذكاء الاصطناعي",
"title1": "عقلك الثاني،",
"title2": "مُعزَّز أخيرًا.",
"subtitle": "Momento ليست مجرد تطبيق للملاحظات. إنها منظومة ذكية تربط أفكارك وتحللها وتطورها في الوقت الفعلي بفضل 6 أنواع من وكلاء الذكاء الاصطناعي والبحث الدلالي المتقدم.",
"cta": "سجّل الآن",
"secondary": "استكشف الميزات",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"تم رصد ارتباط بمشروع التصميم المستدام الخاص بك من مارس 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 فكرة مُولَّدة"
},
"features": {
"label": "قدرات الذكاء الاصطناعي",
"title": "ذكاء سلس،",
"title2": "منسوج في كل كلمة.",
"desc": "تنسّق Momento أفكارك عبر بنية متعددة المزوّدين.",
"f1Title": "البحث الدلالي",
"f1Desc": "توقّف عن البحث بالكلمات المفتاحية. ابحث بالمفهوم. محركنا الهجين Vector + FTS يفهم القصد وراء ملاحظاتك.",
"f2Title": "دردشة RAG السياقية",
"f2Desc": "تحدّث مع معرفتك. يقرأ وكلاؤنا ملاحظاتك ويستكشفون الويب ويحللون مستنداتك للإجابة بدقة.",
"f3Title": "كتابة معزّزة",
"f3Desc": "إعادة الصياغة واقتراح العناوين والوسم التلقائي والملخصات. يعمل الذكاء الاصطناعي في الخلفية لتنظيم تفكيرك."
},
"agents": {
"label": "وكلاء متخصصون",
"title": "فوّض العمل المعقّد.",
"desc": "6 أنواع من وكلاء الذكاء الاصطناعي المستقلين لأتمتة أبحاثك وملخصاتك وعروضك.",
"scraper": {
"title": "Scraper",
"desc": "يجمع الروابط ويحلل خلاصات RSS ويلخّص المعلومات مع وضع ذكي للصور."
},
"researcher": {
"title": "Researcher",
"desc": "يولّد استعلامات معقدة ويستكشف مصادر الويب ويكتب ملاحظات بحث منظمة."
},
"slideGen": {
"title": "Slide Gen",
"desc": "يحوّل ملاحظاتك إلى عروض PowerPoint احترافية أو شرائح HTML تفاعلية."
},
"monitor": {
"title": "Monitor",
"desc": "يحلل دفاترك باستمرار لاكتشاف الاتجاهات والرؤى الجديدة."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "يحوّل أفكارك إلى مخططات Excalidraw سلسة (خرائط ذهنية، مخططات انسيابية) بتخطيط تلقائي."
},
"custom": {
"title": "Custom",
"desc": "عرّف وكلاءك بأدوار ومصادر بيانات مخصّصة."
}
},
"brainstorm": {
"label": "موجات الفكر",
"title": "عصف ذهني شعاعي في الوقت الفعلي.",
"waveGeneration": {
"title": "توليد بالموجات",
"desc": "تنويعات وتشبيهات ثم اختراقات. يدفع الذكاء الاصطناعي مفهومك الأولي إلى أقصى حدوده."
},
"collaboration": {
"title": "تعاون أصيل",
"desc": "مؤشرات شبحية للذكاء الاصطناعي وأفاتار متزامنة وتحريك العقد في الوقت الفعلي."
},
"export": {
"title": "تصدير دلالي",
"desc": "حوّل جلسة العصف الذهني بالكامل إلى ملاحظات منظمة بنقرة واحدة."
},
"disruptionLabel": "اختراق",
"disruptionText": "بنية معيارية 2.0",
"analogyLabel": "تشبيه",
"analogyText": "دورة المد والجزر"
},
"tech": {
"label": "البنية والمزوّدون",
"title": "اربط نموذج الذكاء الاصطناعي الخاص بك.",
"tags": {
"title": "الوسوم",
"desc": "قابل للإعداد بشكل مستقل مع أي نموذج."
},
"embeddings": {
"title": "Embeddings",
"desc": "قابل للإعداد بشكل مستقل مع أي نموذج."
},
"chatRag": {
"title": "Chat RAG",
"desc": "قابل للإعداد بشكل مستقل مع أي نموذج."
}
},
"pricing": {
"label": "الخطط والأسعار",
"title": "اختر مستوى التعزيز المناسب لك.",
"desc": "خيارات مرنة للعقول المبدعة، من الاستخدام الفردي إلى المؤسسات الكبيرة.",
"monthly": "شهري",
"annual": "سنوي",
"perMonth": "/شهر",
"perMonthAnnual": "/شهر، يُفوتر سنويًا",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "الأكثر شعبية",
"basic": {
"name": "Basic",
"desc": "اكتشف سحر Momento.",
"cta": "ابدأ",
"feature0": "100 ملاحظة كحد أقصى",
"feature1": "3 دفاتر",
"feature2": "50 رصيد ذكاء اصطناعي (مدى الحياة)",
"feature3": "بحث دلالي",
"feature4": "سجل 7 أيام"
},
"pro": {
"name": "Pro",
"desc": "للمستشارين والمبدعين الطموحين.",
"cta": "الترقية إلى Pro",
"feature0": "ملاحظات غير محدودة",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 بحث دلالي",
"feature3": "الوكلاء (12 تشغيل/شهر)",
"feature4": "سجل 30 يومًا",
"feature5": "دعم عبر البريد"
},
"business": {
"name": "Business",
"desc": "للفرق ومديري المنتجات.",
"cta": "اختر Business",
"feature0": "10 متعاونين مشمولين",
"feature1": "BYOK (13 مزوّدًا)",
"feature2": "1000 بحث دلالي",
"feature3": "الوكلاء (60 تشغيل/شهر)",
"feature4": "عصف ذهني غير محدود",
"feature5": "وصول API"
},
"enterprise": {
"name": "Enterprise",
"desc": "ذاكرة مؤسسية آمنة.",
"cta": "تواصل مع المبيعات",
"feature0": "كل ما في Business",
"feature1": "وكلاء غير محدودين",
"feature2": "SSO / SAML",
"feature3": "سجلات تدقيق وSLA",
"feature4": "دعم مخصص",
"feature5": "إعداد مباشر"
}
},
"byok": {
"label": "تقنية سحابية مفتوحة",
"title": "استراتيجية BYOK",
"desc": "لديك مفاتيح API من OpenAI أو Anthropic أو Google؟ اربطها مباشرة بـ Momento. استخدم الذكاء الاصطناعي دون حدود ائتمان مفروضة، وادفع فقط ما تستهلكه لدى مزوّدك المفضل.",
"noLockin": "بدون قفل",
"noLockinDesc": "بدّل المزوّد بنقرة واحدة.",
"cost": "تكاليف محسّنة",
"costDesc": "ادفع سعر API المباشر.",
"configLabel": "إعداد متعدد المزوّدين"
},
"cta": {
"title1": "مستعد لإطلاق",
"title2": "إمكاناتك الكاملة؟",
"desc": "انضم إلى آلاف الباحثين والمصممين والمفكرين الذين يستخدمون Momento لبناء مستقبلهم.",
"button": "تشغيل Momento"
},
"footer": {
"desc": "العقل الثاني المعزّز بالذكاء الاصطناعي. صُمم للعقول المبدعة.",
"product": {
"title": "المنتج",
"link0": "سجل التغييرات",
"link1": "التوثيق",
"link2": "خارطة الطريق",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "المجتمع",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "قانوني",
"link0": "سياسة الخصوصية",
"link1": "شروط الخدمة",
"link2": "سياسة ملفات تعريف الارتباط",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Failed to update recent notes setting"
},
"aiSettings": {
"title": "KI-Einstellungen",
"title": "AI",
"description": "Konfigurieren Sie Ihre KI-gesteuerten Funktionen und Präferenzen",
"features": "KI-Funktionen",
"provider": "KI-Anbieter",
@@ -917,7 +917,8 @@
"autoLabeling": "Etikettenvorschläge",
"autoLabelingDesc": "Schlagt automatisch Beschriftungen für Ihre Notizen vor und wendet diese an",
"noteHistory": "Notizverlauf",
"noteHistoryDesc": "Aktivieren Sie Versions-Snapshots und die Wiederherstellung aus dem Verlauf"
"noteHistoryDesc": "Aktivieren Sie Versions-Snapshots und die Wiederherstellung aus dem Verlauf",
"titleSuggestions": "Titelvorschläge"
},
"general": {
"loading": "Wird geladen...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Spracherkennung",
"languageDetectionDesc": "Erkennt automatisch die Sprache jeder Notiz",
"autoLabeling": "Automatische Beschriftung",
"autoLabelingDesc": "Schlägt Labels vor und wendet sie automatisch an"
"autoLabelingDesc": "Schlägt Labels vor und wendet sie automatisch an",
"fallbackSectionTitle": "Ausweich-Anbieter (optional)",
"fallbackSectionDescription": "Wird bei Anbieterfehlern automatisch genutzt (429, 5xx). Ein erneuter Versuch innerhalb von 1,5 s.",
"fallbackProvider": "Ausweich-Anbieter",
"fallbackModel": "Ausweich-Modell",
"fallbackNone": "Keiner (deaktiviert)",
"fallbackModelPlaceholder": "z. B. gpt-4o-mini"
},
"resend": {
"title": "Resend (Empfohlen)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Fehler beim Löschen",
"roleUpdateSuccess": "Benutzerrolle zu {role} aktualisiert",
"roleUpdateFailed": "Fehler beim Aktualisieren der Rolle",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Zurückstufen",
"promote": "Befördern",
"confirmDelete": "Möchten Sie diesen Benutzer wirklich löschen?",
@@ -1180,6 +1189,7 @@
"name": "Name",
"email": "E-Mail",
"role": "Rolle",
"subscription": "Subscription",
"createdAt": "Erstellt am",
"actions": "Aktionen"
},
@@ -1371,7 +1381,7 @@
"loading": "Wird geladen..."
},
"dataManagement": {
"title": "Datenverwaltung",
"title": "Data",
"toolsDescription": "Werkzeuge zur Pflege Ihrer Datenbankgesundheit",
"exporting": "Wird exportiert...",
"importing": "Wird importiert...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Allgemeine Einstellungen",
"title": "General",
"description": "Allgemeine Anwendungseinstellungen"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Zusammenklappen"
},
"mcpSettings": {
"title": "MCP-Einstellungen",
"title": "MCP",
"description": "API-Schlüssel verwalten und externe Tools konfigurieren",
"whatIsMcp": {
"title": "Was ist MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "Der Gastgeber der Sitzung hat sein KI-Kontingent aufgebraucht. Bitte ihn, seinen Tarif zu erweitern.",
"quotaHost": "Sie haben Ihr KI-Kontingent für dieses Brainstorming erreicht. Wechseln Sie den Tarif, um fortzufahren."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Willkommen bei {tier}. Ihre Funktionen sind jetzt freigeschaltet.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Aktuelle Nutzung",
"currentPeriod": "Aktueller Zeitraum",
"aiCredits": "KI-Guthaben",
"used": "verwendet",
"billing": "Abrechnung",
"renewal": "Verlängerung",
"paidPlanDesc": "Ihr Abonnement verlängert sich automatisch.",
"businessDescription": "Für Teams und Produktverantwortliche."
},
"landing": {
"nav": {
"features": "Funktionen",
"agents": "KI-Agenten",
"brainstorm": "Brainstorm",
"pricing": "Preise",
"tech": "Architektur",
"login": "Anmelden",
"cta": "Jetzt starten"
},
"hero": {
"badge": "Angetrieben von Künstlicher Intelligenz",
"title1": "Ihr zweites Gehirn,",
"title2": "endlich verstärkt.",
"subtitle": "Momento ist mehr als eine Notizen-App. Es ist ein intelligentes Ökosystem, das Ihre Ideen in Echtzeit verbindet, analysiert und weiterentwickelt mit 6 Arten von KI-Agenten und modernster semantischer Suche.",
"cta": "Jetzt registrieren",
"secondary": "Funktionen ansehen",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Verbindung erkannt mit Ihrem Nachhaltigkeitsdesign-Projekt von März 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 Ideen generiert"
},
"features": {
"label": "KI-Fähigkeiten",
"title": "Fließende Intelligenz,",
"title2": "in jedes Wort eingewoben.",
"desc": "Momento orchestriert Ihre Ideen über eine Multi-Provider-Architektur.",
"f1Title": "Semantische Suche",
"f1Desc": "Schluss mit der Stichwortsuche. Finden Sie nach Konzept. Unsere hybride Vector- und FTS-Engine versteht die Absicht hinter Ihren Notizen.",
"f2Title": "Kontextueller RAG-Chat",
"f2Desc": "Sprechen Sie mit Ihrem Wissen. Unsere Agenten lesen Ihre Notizen, erkunden das Web und analysieren Ihre Dokumente für präzise Antworten.",
"f3Title": "Erweitertes Schreiben",
"f3Desc": "Umformulierung, Titelvorschläge, automatisches Tagging und Zusammenfassungen. Die KI strukturiert Ihr Denken im Hintergrund."
},
"agents": {
"label": "Spezialisierte Agenten",
"title": "Delegieren Sie die komplexe Arbeit.",
"desc": "6 Arten autonomer KI-Agenten für Recherche, Zusammenfassungen und Präsentationen.",
"scraper": {
"title": "Scraper",
"desc": "Erfasst URLs, parst RSS-Feeds und fasst Informationen mit intelligentem Bild-Layout zusammen."
},
"researcher": {
"title": "Researcher",
"desc": "Erstellt komplexe Abfragen, erkundet Webquellen und schreibt strukturierte Recherchenotizen."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Verwandelt Ihre Notizen in professionelle PowerPoint-Präsentationen oder interaktive HTML-Slides."
},
"monitor": {
"title": "Monitor",
"desc": "Analysiert Ihre Notizbücher fortlaufend, um Trends und neue Erkenntnisse zu erkennen."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Wandelt Ideen in flüssige Excalidraw-Diagramme (Mindmaps, Flowcharts) mit Auto-Layout um."
},
"custom": {
"title": "Custom",
"desc": "Definieren Sie eigene Agenten mit spezifischen Rollen und Datenquellen."
}
},
"brainstorm": {
"label": "Gedankenwellen",
"title": "Radiales Brainstorming in Echtzeit.",
"waveGeneration": {
"title": "Wellengenerierung",
"desc": "Variationen, Analogien, dann Disruptionen. Die KI treibt Ihr Ausgangskonzept an seine Grenzen."
},
"collaboration": {
"title": "Native Zusammenarbeit",
"desc": "KI-Geistercursor, synchronisierte Avatare und Knotenbewegung in Echtzeit."
},
"export": {
"title": "Semantischer Export",
"desc": "Wandeln Sie Ihr gesamtes Brainstorming mit einem Klick in strukturierte Notizen um."
},
"disruptionLabel": "DISRUPTION",
"disruptionText": "Modulare Architektur 2.0",
"analogyLabel": "ANALOGIE",
"analogyText": "Der Gezeitenzyklus"
},
"tech": {
"label": "Architektur & Anbieter",
"title": "Verbinden Sie Ihr eigenes KI-Modell.",
"tags": {
"title": "Tags",
"desc": "Unabhängig mit jedem Modell konfigurierbar."
},
"embeddings": {
"title": "Embeddings",
"desc": "Unabhängig mit jedem Modell konfigurierbar."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Unabhängig mit jedem Modell konfigurierbar."
}
},
"pricing": {
"label": "Pläne & Preise",
"title": "Wählen Sie Ihr Verstärkungsniveau.",
"desc": "Flexible Optionen für kreative Köpfe vom Einzelgebrauch bis zu großen Organisationen.",
"monthly": "Monatlich",
"annual": "Jährlich",
"perMonth": "/Monat",
"perMonthAnnual": "/Monat, jährlich abgerechnet",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Am beliebtesten",
"basic": {
"name": "Basic",
"desc": "Entdecken Sie die Magie von Momento.",
"cta": "Loslegen",
"feature0": "Max. 100 Notizen",
"feature1": "3 Notizbücher",
"feature2": "50 KI-Credits (lebenslang)",
"feature3": "Semantische Suche",
"feature4": "7-Tage-Verlauf"
},
"pro": {
"name": "Pro",
"desc": "Für anspruchsvolle Berater und Kreative.",
"cta": "Auf Pro upgraden",
"feature0": "Unbegrenzte Notizen",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 semantische Suchen",
"feature3": "Agenten (12 Läufe/Monat)",
"feature4": "30-Tage-Verlauf",
"feature5": "E-Mail-Support"
},
"business": {
"name": "Business",
"desc": "Für Teams und Produktmanager.",
"cta": "Business wählen",
"feature0": "10 Mitarbeitende inklusive",
"feature1": "BYOK (13 Anbieter)",
"feature2": "1000 semantische Suchen",
"feature3": "Agenten (60 Läufe/Monat)",
"feature4": "Unbegrenztes Brainstorming",
"feature5": "API-Zugang"
},
"enterprise": {
"name": "Enterprise",
"desc": "Sicheres organisationales Gedächtnis.",
"cta": "Vertrieb kontaktieren",
"feature0": "Alles aus Business",
"feature1": "Unbegrenzte Agenten",
"feature2": "SSO / SAML",
"feature3": "Audit-Logs & SLA",
"feature4": "Dedizierter Support",
"feature5": "Live-Onboarding"
}
},
"byok": {
"label": "Offene Cloud-Technologie",
"title": "Die BYOK-Strategie",
"desc": "Sie haben bereits API-Schlüssel von OpenAI, Anthropic oder Google? Verbinden Sie sie direkt mit Momento. Nutzen Sie KI ohne erzwungene Credit-Limits und zahlen Sie nur, was Sie beim Anbieter Ihrer Wahl tatsächlich verbrauchen.",
"noLockin": "Kein Lock-in",
"noLockinDesc": "Anbieter mit einem Klick wechseln.",
"cost": "Optimierte Kosten",
"costDesc": "Zahlen Sie den direkten API-Preis.",
"configLabel": "Multi-Provider-Konfiguration"
},
"cta": {
"title1": "Bereit, Ihr",
"title2": "volles Potenzial freizusetzen?",
"desc": "Schließen Sie sich Tausenden von Forschern, Designern und Denkern an, die Momento bereits nutzen, um ihre Zukunft zu gestalten.",
"button": "Momento starten"
},
"footer": {
"desc": "Das KI-verstärkte zweite Gehirn. Für kreative Köpfe entwickelt.",
"product": {
"title": "Produkt",
"link0": "Changelog",
"link1": "Dokumentation",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Community",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Rechtliches",
"link0": "Datenschutz",
"link1": "Nutzungsbedingungen",
"link2": "Cookie-Richtlinie",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -394,7 +394,7 @@
"transformError": "Error during transformation",
"convertToRichtext": "Convert to Rich Text",
"convertingToRichtext": "Converting...",
"assistant": "AI Assistant",
"assistant": "AI Note",
"generating": "Generating...",
"generateTitles": "Generate titles",
"reformulateText": "Reformulate text",
@@ -457,8 +457,8 @@
"undoAI": "Undo AI transformation",
"undoApplied": "Original text restored",
"minWordsError": "Note must contain at least 5 words to use AI actions.",
"wordCountMin": "Please select at least {min} words to reformulate (currently {current} words)",
"wordCountMax": "Please select at most {max} words to reformulate (currently {current} words)",
"wordCountMin": "Minimum {min} words required ({current} current)",
"wordCountMax": "Maximum {max} words allowed ({current} current)",
"genericError": "AI error",
"actionError": "Error during AI action",
"appliedToNote": "Applied to note",
@@ -467,7 +467,7 @@
"selectContext": "Select context...",
"selectNotebook": "Select notebook",
"chatPlaceholder": "Ask AI to edit, summarize, or draft...",
"assistantTitle": "AI Assistant",
"assistantTitle": "AI Note",
"currentNote": "Current note",
"shrinkPanel": "Shrink panel",
"expandPanel": "Expand panel",
@@ -552,7 +552,7 @@
"insertedInNote": "Diagram inserted in note",
"insertExportError": "Error exporting/uploading diagram"
},
"openAssistant": "Open AI Assistant",
"openAssistant": "Open AI Note",
"poweredByMomento": "Powered by Momento AI",
"welcomeMsg": "Hello! I'm your AI assistant. How can I help you with your notes today? I can help refine tone, expand messaging, or summarize content.",
"summaryLast5": "Summary of your last 5 notes",
@@ -624,7 +624,9 @@
"presentationReadyBadge": "Presentation ready",
"openInLabTitle": "Open in Lab",
"inlineSummaryMarkdown": "**Summary:**",
"networkErrorShort": "Network error."
"networkErrorShort": "Network error.",
"featureLocked": "This feature requires the PRO plan or higher.",
"quotaExceeded": "Monthly limit reached. Resets next month."
},
"titleSuggestions": {
"available": "Title suggestions",
@@ -881,10 +883,11 @@
"showRecentNotes": "Show Recent Notes Section",
"showRecentNotesDescription": "Display recent notes (last 7 days) on the main page",
"recentNotesUpdateSuccess": "Recent notes setting updated successfully",
"recentNotesUpdateFailed": "Failed to update recent notes setting"
"recentNotesUpdateFailed": "Failed to update recent notes setting",
"tab": "Profile"
},
"aiSettings": {
"title": "AI Settings",
"title": "AI",
"description": "Configure your AI-powered features and preferences",
"features": "AI Features",
"provider": "AI Provider",
@@ -911,7 +914,8 @@
"autoLabeling": "Label suggestions",
"autoLabelingDesc": "Automatically suggests and applies labels to your notes",
"noteHistory": "Note history",
"noteHistoryDesc": "Enable version snapshots and restoration from History"
"noteHistoryDesc": "Enable version snapshots and restoration from History",
"titleSuggestions": "Title suggestions"
},
"general": {
"loading": "Loading...",
@@ -1173,6 +1177,8 @@
"deleteFailed": "Failed to delete",
"roleUpdateSuccess": "User role updated to {role}",
"roleUpdateFailed": "Failed to update role",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Demote to User",
"promote": "Promote to Admin",
"confirmDelete": "Are you sure? This action cannot be undone.",
@@ -1180,6 +1186,7 @@
"name": "Name",
"email": "Email",
"role": "Role",
"subscription": "Subscription",
"createdAt": "Created At",
"actions": "Actions"
},
@@ -1317,7 +1324,8 @@
"documentation": "Documentation",
"reportIssues": "Report Issues",
"feedback": "Feedback"
}
},
"tab": "About"
},
"support": {
"title": "Support Memento Development",
@@ -1371,7 +1379,7 @@
"loading": "Loading..."
},
"dataManagement": {
"title": "Data Management",
"title": "Data",
"toolsDescription": "Tools to maintain your database health",
"exporting": "Exporting...",
"importing": "Importing...",
@@ -1433,7 +1441,8 @@
"fontSystem": "System Default",
"fontInterDefault": "Inter (default)",
"fontPlayfairDisplay": "Playfair Display",
"fontJetBrainsMono": "JetBrains Mono"
"fontJetBrainsMono": "JetBrains Mono",
"tab": "Appearance"
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -1452,10 +1461,15 @@
"proChat": "100 chat messages / month",
"later": "Later",
"upgradePricing": "Upgrade to Pro",
"addApiKey": "Use your own API key (BYOK)"
"addApiKey": "Use your own API key (BYOK)",
"featureReformulate": "Reformulations",
"featureChat": "AI Messages",
"featureBrainstormCreate": "Brainstorm creations",
"featureBrainstormExpand": "Brainstorm expansions",
"featureBrainstormEnrich": "Brainstorm enrichments"
},
"generalSettings": {
"title": "General Settings",
"title": "General",
"description": "General application settings"
},
"toast": {
@@ -1641,7 +1655,7 @@
"collapse": "Collapse"
},
"mcpSettings": {
"title": "MCP Settings",
"title": "MCP",
"description": "Manage API keys and configure external tools",
"whatIsMcp": {
"title": "What is MCP?",
@@ -2325,6 +2339,216 @@
"checkoutSuccessBody": "Welcome to {tier}. Your features are now unlocked.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"tab": "Billing",
"currentUsage": "Current usage",
"currentPeriod": "Current period",
"aiCredits": "AI credits",
"used": "used",
"billing": "Billing",
"renewal": "Renewal",
"paidPlanDesc": "Your subscription renews automatically.",
"businessDescription": "For teams and product leaders."
},
"landing": {
"nav": {
"features": "Features",
"agents": "AI Agents",
"brainstorm": "Brainstorm",
"pricing": "Pricing",
"tech": "Architecture",
"login": "Log in",
"cta": "Get started"
},
"hero": {
"badge": "Powered by Artificial Intelligence",
"title1": "Your second brain,",
"title2": "finally amplified.",
"subtitle": "Momento is more than a note-taking app. It's an intelligent ecosystem that connects, analyzes and develops your ideas in real time with 6 types of AI agents and cutting-edge semantic search.",
"cta": "Sign up now",
"secondary": "See features",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Connection detected with your sustainable design project from March 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 ideas generated"
},
"features": {
"label": "AI Capabilities",
"title": "Fluid intelligence,",
"title2": "woven into every word.",
"desc": "Momento orchestrates your ideas through a multi-provider architecture.",
"f1Title": "Semantic Search",
"f1Desc": "Stop searching by keywords. Find by concept. Our hybrid Vector + FTS engine understands the intent behind your notes.",
"f2Title": "Contextual RAG Chat",
"f2Desc": "Chat with your knowledge. Our agents read your notes, explore the web and analyze your documents to respond with precision.",
"f3Title": "Augmented Writing",
"f3Desc": "Reformulation, title suggestions, auto-tagging and summaries. AI works in the background to structure your thinking."
},
"agents": {
"label": "Specialized Agents",
"title": "Delegate the complex work.",
"desc": "6 types of autonomous AI agents to automate your research, summaries and presentations.",
"scraper": {
"title": "Scraper",
"desc": "Scrapes URLs, parses RSS feeds and synthesizes information with smart image placement."
},
"researcher": {
"title": "Researcher",
"desc": "Generates complex queries, explores web sources and writes structured research notes."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Transforms your notes into professional PowerPoint presentations or interactive HTML Slides."
},
"monitor": {
"title": "Monitor",
"desc": "Continuously analyzes your notebooks to detect trends and new insights."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Converts your ideas into fluid Excalidraw diagrams (Mindmaps, Flowcharts) with auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Define your own agents with specific roles and data sources."
}
},
"brainstorm": {
"label": "Thought Waves",
"title": "Real-time radial brainstorming.",
"waveGeneration": {
"title": "Wave Generation",
"desc": "Variations, Analogies, then Disruptions. AI pushes your initial concept to its limits."
},
"collaboration": {
"title": "Native Collaboration",
"desc": "AI ghost cursors, synced avatars and real-time node movement."
},
"export": {
"title": "Semantic Export",
"desc": "Convert your entire brainstorm into structured notes in one click."
},
"disruptionLabel": "DISRUPTION",
"disruptionText": "Modular Architecture 2.0",
"analogyLabel": "ANALOGY",
"analogyText": "The tidal cycle"
},
"tech": {
"label": "Architecture & Providers",
"title": "Connect your own AI model.",
"tags": {
"title": "Tags",
"desc": "Independently configurable with any model."
},
"embeddings": {
"title": "Embeddings",
"desc": "Independently configurable with any model."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Independently configurable with any model."
}
},
"pricing": {
"label": "Plans & Pricing",
"title": "Choose your level of amplification.",
"desc": "Flexible options for creative minds, from individual use to large organizations.",
"monthly": "Monthly",
"annual": "Annual",
"perMonth": "/month",
"perMonthAnnual": "/month, billed annually",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Most popular",
"basic": {
"name": "Basic",
"desc": "Discover the magic of Momento.",
"cta": "Get started",
"feature0": "100 Notes max",
"feature1": "3 Notebooks",
"feature2": "50 AI credits (Lifetime)",
"feature3": "Semantic search",
"feature4": "7-day history"
},
"pro": {
"name": "Pro",
"desc": "For demanding consultants and creators.",
"cta": "Upgrade to Pro",
"feature0": "Unlimited notes",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 semantic searches",
"feature3": "Agents (12 runs/month)",
"feature4": "30-day history",
"feature5": "Email support"
},
"business": {
"name": "Business",
"desc": "For teams and product managers.",
"cta": "Choose Business",
"feature0": "10 Collaborators included",
"feature1": "BYOK (13 providers)",
"feature2": "1000 semantic searches",
"feature3": "Agents (60 runs/month)",
"feature4": "Unlimited brainstorming",
"feature5": "API access"
},
"enterprise": {
"name": "Enterprise",
"desc": "Secure organizational memory.",
"cta": "Contact Sales",
"feature0": "Everything in Business",
"feature1": "Unlimited agents",
"feature2": "SSO / SAML",
"feature3": "Audit Logs & SLA",
"feature4": "Dedicated support",
"feature5": "Live onboarding"
}
},
"byok": {
"label": "Open Cloud Technology",
"title": "The BYOK Strategy",
"desc": "Already have OpenAI, Anthropic or Google API keys? Connect them directly to Momento. Use AI without imposed credit limits, paying only what you actually consume from your favorite provider.",
"noLockin": "No lock-in",
"noLockinDesc": "Switch providers in 1 click.",
"cost": "Optimized costs",
"costDesc": "Pay the direct API price.",
"configLabel": "Multi-Provider Config"
},
"cta": {
"title1": "Ready to unlock your",
"title2": "full potential?",
"desc": "Join thousands of researchers, designers and thinkers already using Momento to build their future.",
"button": "Launch Momento"
},
"footer": {
"desc": "The AI-amplified second brain. Designed for creative minds.",
"product": {
"title": "Product",
"link0": "Changelog",
"link1": "Documentation",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Community",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Legal",
"link0": "Privacy Policy",
"link1": "Terms of Service",
"link2": "Cookie Policy",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Failed to update recent notes setting"
},
"aiSettings": {
"title": "Configuración IA",
"title": "AI",
"description": "Configura tus funciones y preferencias impulsadas por IA",
"features": "Funciones de IA",
"provider": "Proveedor de IA",
@@ -917,7 +917,8 @@
"autoLabeling": "Sugerencias de etiquetas",
"autoLabelingDesc": "Sugiere y aplica etiquetas automáticamente a tus notas",
"noteHistory": "Historial de notas",
"noteHistoryDesc": "Habilitar instantáneas de versiones y restauración desde el Historial"
"noteHistoryDesc": "Habilitar instantáneas de versiones y restauración desde el Historial",
"titleSuggestions": "Sugerencia de títulos"
},
"general": {
"loading": "Cargando...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Detección de idioma",
"languageDetectionDesc": "Detecta automáticamente el idioma de cada nota",
"autoLabeling": "Etiquetado automático",
"autoLabelingDesc": "Sugiere y aplica etiquetas automáticamente"
"autoLabelingDesc": "Sugiere y aplica etiquetas automáticamente",
"fallbackSectionTitle": "Proveedor de respaldo (opcional)",
"fallbackSectionDescription": "Se usa automáticamente ante errores del proveedor (429, 5xx). Un reintento en 1,5 s.",
"fallbackProvider": "Proveedor de respaldo",
"fallbackModel": "Modelo de respaldo",
"fallbackNone": "Ninguno (desactivado)",
"fallbackModelPlaceholder": "p. ej. gpt-4o-mini"
},
"resend": {
"title": "Resend (Recomendado)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Error al eliminar",
"roleUpdateSuccess": "Rol de usuario actualizado a {role}",
"roleUpdateFailed": "Error al actualizar rol",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Degradar",
"promote": "Promover",
"confirmDelete": "¿Estás seguro de que quieres eliminar este usuario?",
@@ -1180,6 +1189,7 @@
"name": "Nombre",
"email": "Correo electrónico",
"role": "Rol",
"subscription": "Subscription",
"createdAt": "Creado",
"actions": "Acciones"
},
@@ -1371,7 +1381,7 @@
"loading": "Cargando..."
},
"dataManagement": {
"title": "Data Management",
"title": "Data",
"toolsDescription": "Tools to maintain your database health",
"exporting": "Exportando",
"importing": "Importando",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Configuración general",
"title": "General",
"description": "Configuración general de la aplicación"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Colapsar"
},
"mcpSettings": {
"title": "Configuración MCP",
"title": "MCP",
"description": "Gestiona tus claves API y configura herramientas externas",
"whatIsMcp": {
"title": "¿Qué es MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "El anfitrión de la sesión ha alcanzado su límite de IA. Pídele que mejore su plan.",
"quotaHost": "Has alcanzado tu límite de IA para este brainstorm. Mejora tu plan para continuar."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Bienvenido a {tier}. Tus funciones están desbloqueadas.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Uso actual",
"currentPeriod": "Período actual",
"aiCredits": "Créditos IA",
"used": "usados",
"billing": "Facturación",
"renewal": "Renovación",
"paidPlanDesc": "Su suscripción se renueva automáticamente.",
"businessDescription": "Para equipos y líderes de producto."
},
"landing": {
"nav": {
"features": "Funciones",
"agents": "Agentes IA",
"brainstorm": "Brainstorm",
"pricing": "Precios",
"tech": "Arquitectura",
"login": "Iniciar sesión",
"cta": "Empezar"
},
"hero": {
"badge": "Impulsado por inteligencia artificial",
"title1": "Tu segundo cerebro,",
"title2": "por fin amplificado.",
"subtitle": "Momento es más que una app de notas. Es un ecosistema inteligente que conecta, analiza y desarrolla tus ideas en tiempo real con 6 tipos de agentes IA y búsqueda semántica de vanguardia.",
"cta": "Regístrate ahora",
"secondary": "Ver funciones",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Conexión detectada con tu proyecto de diseño sostenible de marzo de 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 ideas generadas"
},
"features": {
"label": "Capacidades IA",
"title": "Inteligencia fluida,",
"title2": "tejida en cada palabra.",
"desc": "Momento orquesta tus ideas mediante una arquitectura multi-proveedor.",
"f1Title": "Búsqueda semántica",
"f1Desc": "Deja de buscar por palabras clave. Encuentra por concepto. Nuestro motor híbrido Vector + FTS entiende la intención detrás de tus notas.",
"f2Title": "Chat RAG contextual",
"f2Desc": "Conversa con tu conocimiento. Nuestros agentes leen tus notas, exploran la web y analizan tus documentos para responder con precisión.",
"f3Title": "Escritura aumentada",
"f3Desc": "Reformulación, sugerencias de títulos, etiquetado automático y resúmenes. La IA trabaja en segundo plano para estructurar tu pensamiento."
},
"agents": {
"label": "Agentes especializados",
"title": "Delega el trabajo complejo.",
"desc": "6 tipos de agentes IA autónomos para automatizar tu investigación, resúmenes y presentaciones.",
"scraper": {
"title": "Scraper",
"desc": "Extrae URLs, analiza feeds RSS y sintetiza información con colocación inteligente de imágenes."
},
"researcher": {
"title": "Researcher",
"desc": "Genera consultas complejas, explora fuentes web y redacta notas de investigación estructuradas."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Transforma tus notas en presentaciones PowerPoint profesionales o diapositivas HTML interactivas."
},
"monitor": {
"title": "Monitor",
"desc": "Analiza continuamente tus cuadernos para detectar tendencias y nuevos insights."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Convierte tus ideas en diagramas Excalidraw fluidos (mapas mentales, diagramas de flujo) con auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Define tus propios agentes con roles y fuentes de datos específicos."
}
},
"brainstorm": {
"label": "Olas de pensamiento",
"title": "Lluvia de ideas radial en tiempo real.",
"waveGeneration": {
"title": "Generación por olas",
"desc": "Variaciones, analogías y luego disrupciones. La IA lleva tu concepto inicial hasta sus límites."
},
"collaboration": {
"title": "Colaboración nativa",
"desc": "Cursores fantasma IA, avatares sincronizados y movimiento de nodos en tiempo real."
},
"export": {
"title": "Exportación semántica",
"desc": "Convierte toda tu sesión de brainstorming en notas estructuradas con un clic."
},
"disruptionLabel": "DISRUPCIÓN",
"disruptionText": "Arquitectura modular 2.0",
"analogyLabel": "ANALOGÍA",
"analogyText": "El ciclo de las mareas"
},
"tech": {
"label": "Arquitectura y proveedores",
"title": "Conecta tu propio modelo de IA.",
"tags": {
"title": "Tags",
"desc": "Configurable de forma independiente con cualquier modelo."
},
"embeddings": {
"title": "Embeddings",
"desc": "Configurable de forma independiente con cualquier modelo."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Configurable de forma independiente con cualquier modelo."
}
},
"pricing": {
"label": "Planes y precios",
"title": "Elige tu nivel de amplificación.",
"desc": "Opciones flexibles para mentes creativas, del uso individual a grandes organizaciones.",
"monthly": "Mensual",
"annual": "Anual",
"perMonth": "/mes",
"perMonthAnnual": "/mes, facturado anualmente",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Más popular",
"basic": {
"name": "Basic",
"desc": "Descubre la magia de Momento.",
"cta": "Empezar",
"feature0": "100 notas máx.",
"feature1": "3 cuadernos",
"feature2": "50 créditos IA (de por vida)",
"feature3": "Búsqueda semántica",
"feature4": "Historial 7 días"
},
"pro": {
"name": "Pro",
"desc": "Para consultores y creadores exigentes.",
"cta": "Pasar a Pro",
"feature0": "Notas ilimitadas",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 búsquedas semánticas",
"feature3": "Agentes (12 ejecuciones/mes)",
"feature4": "Historial 30 días",
"feature5": "Soporte por email"
},
"business": {
"name": "Business",
"desc": "Para equipos y product managers.",
"cta": "Elegir Business",
"feature0": "10 colaboradores incluidos",
"feature1": "BYOK (13 proveedores)",
"feature2": "1000 búsquedas semánticas",
"feature3": "Agentes (60 ejecuciones/mes)",
"feature4": "Brainstorm ilimitado",
"feature5": "Acceso API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Memoria organizacional segura.",
"cta": "Contactar ventas",
"feature0": "Todo Business",
"feature1": "Agentes ilimitados",
"feature2": "SSO / SAML",
"feature3": "Audit Logs y SLA",
"feature4": "Soporte dedicado",
"feature5": "Onboarding en vivo"
}
},
"byok": {
"label": "Tecnología cloud abierta",
"title": "La estrategia BYOK",
"desc": "¿Ya tienes claves API de OpenAI, Anthropic o Google? Conéctalas directamente a Momento. Usa IA sin límites de crédito impuestos, pagando solo lo que consumes con tu proveedor favorito.",
"noLockin": "Sin lock-in",
"noLockinDesc": "Cambia de proveedor en 1 clic.",
"cost": "Costes optimizados",
"costDesc": "Paga el precio directo de la API.",
"configLabel": "Config multi-proveedor"
},
"cta": {
"title1": "¿Listo para liberar tu",
"title2": "máximo potencial?",
"desc": "Únete a miles de investigadores, diseñadores y pensadores que ya usan Momento para construir su futuro.",
"button": "Lanzar Momento"
},
"footer": {
"desc": "El segundo cerebro amplificado por IA. Diseñado para mentes creativas.",
"product": {
"title": "Producto",
"link0": "Changelog",
"link1": "Documentación",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Comunidad",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Legal",
"link0": "Política de privacidad",
"link1": "Términos de servicio",
"link2": "Política de cookies",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -463,8 +463,8 @@
"undoAI": "لغو تبدیل هوش مصنوعی",
"undoApplied": "متن اصلی بازگردانده شد",
"minWordsError": "یادداشت باید حداقل ۵ کلمه داشته باشد.",
"wordCountMin": "حداقل {min} کلمه برای بازنویسی انتخاب کنید (فعلاً {current} کلمه)",
"wordCountMax": "حداکثر {max} کلمه برای بازنویسی انتخاب کنید (فعلاً {current} کلمه)",
"wordCountMin": "حداقل {min} کلمه لازم است ({current} کلمه فعلی)",
"wordCountMax": "حداکثر {max} کلمه مجاز ({current} کلمه فعلی)",
"genericError": "خطای هوش مصنوعی",
"actionError": "خطا در حین عمل هوش مصنوعی",
"appliedToNote": "در یادداشت اعمال شد",
@@ -630,7 +630,9 @@
"creative": "Creative",
"academic": "Academic",
"casual": "Casual"
}
},
"featureLocked": "این قابلیت نیازمند طرح PRO یا بالاتر است.",
"quotaExceeded": "محدودیت ماهانه تکمیل شده. ماه آینده بازنشانی می‌شود."
},
"titleSuggestions": {
"available": "پیشنهادات عنوان",
@@ -887,10 +889,11 @@
"showRecentNotes": "نمایش بخش یادداشت‌های اخیر",
"showRecentNotesDescription": "نمایش یادداشت‌های اخیر (۷ روز گذشته) در صفحه اصلی",
"recentNotesUpdateSuccess": "تنظیم یادداشت‌های اخیر با موفقیت به‌روزرسانی شد",
"recentNotesUpdateFailed": "به‌روزرسانی تنظیم یادداشت‌های اخیر شکست خورد"
"recentNotesUpdateFailed": "به‌روزرسانی تنظیم یادداشت‌های اخیر شکست خورد",
"tab": "پروفایل"
},
"aiSettings": {
"title": "تنظیمات هوش مصنوعی",
"title": "هوش مصنوعی",
"description": "ویژگی‌ها و ترجیحات هوش مصنوعی خود را پیکربندی کنید",
"features": "ویژگی‌های هوش مصنوعی",
"provider": "فروشنده هوش مصنوعی",
@@ -917,7 +920,8 @@
"autoLabeling": "پیشنهاد برچسب",
"autoLabelingDesc": "به طور خودکار برچسب‌ها را به یادداشت‌های شما پیشنهاد و اعمال می‌کند",
"noteHistory": "تاریخچه یادداشت",
"noteHistoryDesc": "فعال‌سازی اسنپ‌شات نسخه‌ها و بازیابی از تاریخچه"
"noteHistoryDesc": "فعال‌سازی اسنپ‌شات نسخه‌ها و بازیابی از تاریخچه",
"titleSuggestions": "پیشنهاد عنوان"
},
"general": {
"loading": "در حال بارگذاری...",
@@ -1115,7 +1119,13 @@
"languageDetection": "تشخیص زبان",
"languageDetectionDesc": "تشخیص خودکار زبان هر یادداشت",
"autoLabeling": "برچسب‌گذاری خودکار",
"autoLabelingDesc": "پیشنهاد و اعمال خودکار برچسب‌ها"
"autoLabelingDesc": "پیشنهاد و اعمال خودکار برچسب‌ها",
"fallbackSectionTitle": "ارائه‌دهنده پشتیبان (اختیاری)",
"fallbackSectionDescription": "در صورت خطای ارائه‌دهنده (429، 5xx) به‌صورت خودکار استفاده می‌شود. یک تلاش مجدد در ۱,۵ ثانیه.",
"fallbackProvider": "ارائه‌دهنده پشتیبان",
"fallbackModel": "مدل پشتیبان",
"fallbackNone": "هیچ (غیرفعال)",
"fallbackModelPlaceholder": "مثلاً gpt-4o-mini"
},
"resend": {
"title": "Resend (پیشنهادی)",
@@ -1173,6 +1183,8 @@
"deleteFailed": "شکست در حذف",
"roleUpdateSuccess": "نقش کاربر به {role} به‌روزرسانی شد",
"roleUpdateFailed": "شکست در به‌روزرسانی نقش",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "تنزل",
"promote": "ارتقا",
"confirmDelete": "مطمئن هستید؟ این عمل قابل بازگشت نیست.",
@@ -1180,6 +1192,7 @@
"name": "نام",
"email": "ایمیل",
"role": "نقش",
"subscription": "Subscription",
"createdAt": "تاریخ ایجاد",
"actions": "عملیات"
},
@@ -1317,7 +1330,8 @@
"documentation": "مستندات",
"reportIssues": "گزارش مشکلات",
"feedback": "بازخورد"
}
},
"tab": "درباره"
},
"support": {
"title": "پشتیبانی از توسعه Memento",
@@ -1371,7 +1385,7 @@
"loading": "در حال بارگذاری..."
},
"dataManagement": {
"title": "مدیریت داده",
"title": "دادهها",
"toolsDescription": "ابزارهایی برای حفظ سلامت پایگاه داده",
"exporting": "در حال صادرات...",
"importing": "در حال وارد کردن...",
@@ -1433,10 +1447,11 @@
"fontSystem": "فونت پیش‌فرض سیستم",
"fontInterDefault": "Inter (default)",
"fontPlayfairDisplay": "Playfair Display",
"fontJetBrainsMono": "JetBrains Mono"
"fontJetBrainsMono": "JetBrains Mono",
"tab": "ظاهر"
},
"generalSettings": {
"title": "تنظیمات عمومی",
"title": "عمومی",
"description": "تنظیمات عمومی برنامه"
},
"toast": {
@@ -1622,7 +1637,7 @@
"collapse": "جمع کردن"
},
"mcpSettings": {
"title": "تنظیمات MCP",
"title": "MCP",
"description": "مدیریت کلیدهای API و پیکربندی ابزارهای خارجی",
"whatIsMcp": {
"title": "MCP چیست؟",
@@ -2211,7 +2226,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "میزبان جلسه به سقف هوش مصنوعی رسیده. از او بخواهید طرحش را ارتقا دهد.",
"quotaHost": "به سقف هوش مصنوعی این طوفان فکری رسیدید. برای ادامه، طرح خود را ارتقا دهید."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2230,7 +2247,12 @@
"proChat": "100 chat messages / month",
"later": "Later",
"upgradePricing": "Upgrade to Pro",
"addApiKey": "Use your own API key (BYOK)"
"addApiKey": "Use your own API key (BYOK)",
"featureReformulate": "بازنویسی",
"featureChat": "پیام‌های هوش مصنوعی",
"featureBrainstormCreate": "ایجاد طوفان فکری",
"featureBrainstormExpand": "گسترش طوفان فکری",
"featureBrainstormEnrich": "غنی‌سازی طوفان فکری"
},
"byokSettings": {
"title": "Your API keys (BYOK)",
@@ -2323,6 +2345,216 @@
"checkoutSuccessBody": "به {tier} خوش آمدید. ویژگی‌های شما اکنون باز شده‌اند.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"tab": "صورتحساب",
"currentUsage": "مصرف فعلی",
"currentPeriod": "دوره جاری",
"aiCredits": "اعتبار هوش مصنوعی",
"used": "استفاده شده",
"billing": "صورتحساب",
"renewal": "تمدید",
"paidPlanDesc": "اشتراک شما به‌طور خودکار تمدید می‌شود.",
"businessDescription": "برای تیم‌ها و مدیران محصول."
},
"landing": {
"nav": {
"features": "امکانات",
"agents": "دستیاران هوشمند",
"brainstorm": "طوفان فکری",
"pricing": "تعرفه‌ها",
"tech": "ساختار فنی",
"login": "ورود",
"cta": "شروع کنید"
},
"hero": {
"badge": "توانمند با هوش مصنوعی",
"title1": "مغز دوم شما،",
"title2": "حالا قوی‌تر از همیشه.",
"subtitle": "مومنتو یه noting app ساده نیست؛ یه سیستم هوشمندی‌ه که با ۶ نوع دستیار AI و جستجوی معنایی پیشرفته، لحظه‌به‌لحظه به ایده‌هاتون جان می‌بخشه و پخش‌شون می‌کنه.",
"cta": "همین الان ثبت‌نام کنید",
"secondary": "امکانات رو ببینید",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"ارتباط پیدا شد با پروژه طراحی پایدار شما از مارس ۲۰۲۴...\"",
"brainstormLive": "طوفان فکری زنده",
"ideasGenerated": "+۱۲ ایده تولید شد"
},
"features": {
"label": "توانایی‌های هوشمند",
"title": "هوشی روان و بی‌وقفه،",
"title2": "نفوذکرده در هر کلمه.",
"desc": "مومنتو با معماری چندمنبعی، ایده‌هاتون رو هماهنگ و مدیریت می‌کنه.",
"f1Title": "جستجوی معنایی",
"f1Desc": "کلمه‌کلیدی دور ریخته. اینجا معنا مهمه. موتور ترکیبی ما منظور پشت نوشته‌هاتون رو می‌فهمه، نه فقط کلماتش رو.",
"f2Title": "چت هوشمند و آگاه",
"f2Desc": "با دانش خودتون حرف بزنید. دستیارهای ما یادداشت‌هاتون رو می‌خونن، وب رو می‌گردن و اسناد رو تحلیل می‌کنن تا دقیق‌ترین جواب رو بهتون بدن.",
"f3Title": "نوشتار هوشمند",
"f3Desc": "از بازنویسی و پیشنهاد عنوان گرفته تا برچسب‌گذاری و خلاصه‌سازی خودکار — AI همیشه توی پس‌زمینه کار می‌کنه تا افکارتون منظم بشه."
},
"agents": {
"label": "دستیاران تخصصی",
"title": "کارای سخت رو بسپارید به ما.",
"desc": "۶ نوع دستیار هوشمند و خودکار برای راحت‌کردن تحقیق، خلاصه‌سازی و ارائه‌هاتون.",
"scraper": {
"title": "Scraper",
"desc": "آدرس‌ها رو اسکرپ می‌کنه، فیدهای RSS رو پردازش می‌کنه و اطلاعات رو با جایگذاری هوشمند تصاویر خلاصه‌سازی می‌کنه."
},
"researcher": {
"title": "Researcher",
"desc": "کوئری‌های پیچیده تولید می‌کنه، منابع وب رو کاوش می‌کنه و یادداشت‌های تحقیقاتی ساختاریافته می‌نویسه."
},
"slideGen": {
"title": "Slide Gen",
"desc": "یادداشت‌هاتون رو به ارائه‌های حرفه‌ای PowerPoint یا اسلایدهای HTML تعاملی تبدیل می‌کنه."
},
"monitor": {
"title": "Monitor",
"desc": "دائماً دفترچه‌هاتون رو تحلیل می‌کنه تا روند‌ها و بینش‌های جدید رو شناسایی کنه."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "ایده‌هاتون رو به نمودارهای Excalidraw روان (مپ ذهنی، فلوچارت) با چیدمان خودکار تبدیل می‌کنه."
},
"custom": {
"title": "Custom",
"desc": "دستیارهای اختصاصی خودتون رو با نقش‌ها و منابع داده خاص تعریف کنید."
}
},
"brainstorm": {
"label": "امواج ایده",
"title": "طوفان فکری شعاعی و زنده.",
"waveGeneration": {
"title": "تولید موجی",
"desc": "تنوع، تشبیه، بعدش اختلال. AI مفهوم اولیه شما رو تا مرزهاش می‌چرخونه."
},
"collaboration": {
"title": "همکاری ذاتی",
"desc": "کرسرهای شبح AI، آواتارهای همگام‌شده و جابجایی گره‌ها در زمان واقعی."
},
"export": {
"title": "خروجی معنایی",
"desc": "کل طوفان فکری‌تون رو با یه کلیک به یادداشت‌های ساختاریافته تبدیل کنید."
},
"disruptionLabel": "اختلال",
"disruptionText": "معماری ماژولار ۲.۰",
"analogyLabel": "تشبیه",
"analogyText": "چرخه جزر و مد"
},
"tech": {
"label": "ساختار و ارائه‌دهندگان",
"title": "مدل AI خودتون رو وصل کنید.",
"tags": {
"title": "برچسب‌ها",
"desc": "مستقل از هر مدلی قابل تنظیم."
},
"embeddings": {
"title": "Embeddings",
"desc": "مستقل از هر مدلی قابل تنظیم."
},
"chatRag": {
"title": "Chat RAG",
"desc": "مستقل از هر مدلی قابل تنظیم."
}
},
"pricing": {
"label": "پلن‌ها و تعرفه‌ها",
"title": "سطح توانمندی خودتون رو انتخاب کنید.",
"desc": "گزینه‌های منعطف برای ذهن‌های خلاق — از استفاده شخصی تا سازمان‌های بزرگ.",
"monthly": "ماهانه",
"annual": "سالانه",
"perMonth": "/ماه",
"perMonthAnnual": "/ماه، پرداخت سالانه",
"perUser": "+ ۳,۹۰€/کاربر",
"perUserAnnual": "+ ۲,۹۰€/کاربر، پرداخت سالانه",
"popular": "پرطرفدارترین",
"basic": {
"name": "Basic",
"desc": "ذوق مومنتو رو بچشید.",
"cta": "شروع کنید",
"feature0": "حداکثر ۱۰۰ یادداشت",
"feature1": "۳ دفترچه",
"feature2": "۵۰ اعتبار AI (مادام‌العمر)",
"feature3": "جستجوی معنایی",
"feature4": "تاریخچه ۷ روزه"
},
"pro": {
"name": "Pro",
"desc": "برای مشاوران و خالق‌های سخت‌گیر.",
"cta": "ارتقا به پرو",
"feature0": "یادداشت نامحدود",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "۲۰۰ جستجوی معنایی",
"feature3": "دستیارها (۱۲ اجرا/ماه)",
"feature4": "تاریخچه ۳۰ روزه",
"feature5": "پشتیبانی ایمیل"
},
"business": {
"name": "Business",
"desc": "برای تیم‌ها و مدیران محصول.",
"cta": "انتخاب بیزینس",
"feature0": "۱۰ همکار شامل",
"feature1": "BYOK (۱۳ ارائه‌دهنده)",
"feature2": "۱۰۰۰ جستجوی معنایی",
"feature3": "دستیارها (۶۰ اجرا/ماه)",
"feature4": "طوفان فکری نامحدود",
"feature5": "دسترسی API"
},
"enterprise": {
"name": "Enterprise",
"desc": "حافظه سازمانی امن.",
"cta": "تماس با فروش",
"feature0": "همه امکانات بیزینس",
"feature1": "دستیارهای نامحدود",
"feature2": "SSO / SAML",
"feature3": "لاگ حسابرسی و SLA",
"feature4": "پشتیبانی اختصاصی",
"feature5": "آنبوردینگ زنده"
}
},
"byok": {
"label": "زیرساخت ابری باز",
"title": "استراتژی BYOK",
"desc": "کلید API اپن‌ای، آنتروپیک یا گوگل دارید؟ مستقیم وصلش کنید به مومنتو. بدون محدودیت اعتبار، فقط همون‌قدر هزینه می‌کنید که واقعاً مصرف کردید.",
"noLockin": "بدون قفل‌شدگی",
"noLockinDesc": "با یه کلیک ارائه‌دهنده عوض کنید.",
"cost": "هزینه بهینه",
"costDesc": "همون قیمت مستقیم API رو می‌دید.",
"configLabel": "تنظیمات چندمنبعی"
},
"cta": {
"title1": "آماده‌اید",
"title2": "استعداد واقعیتون رو شکوفا کنید؟",
"desc": "به هزاران پژوهشگر، طراح و متفکر ملحق بشید که از مومنتو برای ساختن آینده‌شون استفاده می‌کنن.",
"button": "اجرا مومنتو"
},
"footer": {
"desc": "مغز دوم هوشمند با AI. طراحی‌شده برای ذهن‌های خلاق.",
"product": {
"title": "محصول",
"link0": "تغییرات",
"link1": "مستندات",
"link2": "نقشه راه",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "جامعه",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "حقوقی",
"link0": "حریم خصوصی",
"link1": "شرایط استفاده",
"link2": "سیاست کوکی",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -463,8 +463,8 @@
"undoAI": "Annuler la transformation IA",
"undoApplied": "Texte original restauré",
"minWordsError": "La note doit contenir au moins 5 mots pour utiliser les actions IA.",
"wordCountMin": "Veuillez sélectionner au moins {min} mots pour reformuler ({current} mots sélectionnés)",
"wordCountMax": "Veuillez sélectionner au maximum {max} mots pour reformuler ({current} mots sélectionnés)",
"wordCountMin": "Minimum {min} mots requis ({current} actuels)",
"wordCountMax": "Maximum {max} mots autorisés ({current} actuels)",
"genericError": "Erreur IA",
"actionError": "Erreur lors de l'action IA",
"appliedToNote": "Appliqué à la note",
@@ -630,7 +630,9 @@
"presentationReadyBadge": "Présentation prête",
"openInLabTitle": "Ouvrir dans le Lab",
"inlineSummaryMarkdown": "**Résumé :**",
"networkErrorShort": "Erreur réseau."
"networkErrorShort": "Erreur réseau.",
"featureLocked": "Cette fonctionnalité nécessite le plan PRO ou supérieur.",
"quotaExceeded": "Limite mensuelle atteinte. Se réinitialise le mois prochain."
},
"titleSuggestions": {
"available": "Suggestions de titre",
@@ -887,10 +889,11 @@
"showRecentNotes": "Afficher la section Récent",
"showRecentNotesDescription": "Afficher les notes récentes (7 derniers jours) sur la page principale",
"recentNotesUpdateSuccess": "Paramètre des notes récentes mis à jour avec succès",
"recentNotesUpdateFailed": "Échec de la mise à jour du paramètre des notes récentes"
"recentNotesUpdateFailed": "Échec de la mise à jour du paramètre des notes récentes",
"tab": "Profil"
},
"aiSettings": {
"title": "Paramètres IA",
"title": "IA",
"description": "Configurez vos fonctionnalités IA et préférences",
"features": "Fonctionnalités IA",
"provider": "Fournisseur IA",
@@ -917,7 +920,8 @@
"autoLabeling": "Suggestion des labels",
"autoLabelingDesc": "Suggère et applique des étiquettes automatiquement à vos notes",
"noteHistory": "Historique des notes",
"noteHistoryDesc": "Active les snapshots de versions et la restauration depuis History"
"noteHistoryDesc": "Active les snapshots de versions et la restauration depuis History",
"titleSuggestions": "Suggestion de titres"
},
"general": {
"loading": "Chargement...",
@@ -1179,6 +1183,8 @@
"deleteFailed": "Échec de la suppression",
"roleUpdateSuccess": "Rôle de l'utilisateur mis à jour à {role}",
"roleUpdateFailed": "Échec de la mise à jour du rôle",
"tierUpdateSuccess": "Abonnement mis à jour à {tier}",
"tierUpdateFailed": "Échec de la mise à jour de l'abonnement",
"demote": "Rétrograder en utilisateur",
"promote": "Promouvoir en admin",
"confirmDelete": "Êtes-vous sûr ? Cette action est irréversible.",
@@ -1186,6 +1192,7 @@
"name": "Nom",
"email": "Courriel",
"role": "Rôle",
"subscription": "Abonnement",
"createdAt": "Créé le",
"actions": "Actions"
},
@@ -1323,7 +1330,8 @@
"documentation": "Documentation",
"reportIssues": "Signaler des problèmes",
"feedback": "Commentaires"
}
},
"tab": "À propos"
},
"support": {
"title": "Supporter le développement de Memento",
@@ -1377,7 +1385,7 @@
"loading": "Chargement..."
},
"dataManagement": {
"title": "Gestion des données",
"title": "Données",
"toolsDescription": "Outils pour maintenir la santé de votre base de données",
"exporting": "Exportation...",
"importing": "Importation...",
@@ -1439,7 +1447,8 @@
"fontSystem": "Police système par défaut",
"fontInterDefault": "Inter (défaut)",
"fontPlayfairDisplay": "Playfair Display",
"fontJetBrainsMono": "JetBrains Mono"
"fontJetBrainsMono": "JetBrains Mono",
"tab": "Apparence"
},
"usageMeter": {
"packName": "Pack découverte IA",
@@ -1458,10 +1467,15 @@
"proChat": "100 messages de chat / mois",
"later": "Plus tard",
"upgradePricing": "Passer à Pro",
"addApiKey": "Utiliser votre propre clé API (BYOK)"
"addApiKey": "Utiliser votre propre clé API (BYOK)",
"featureReformulate": "Reformulations",
"featureChat": "Messages IA",
"featureBrainstormCreate": "Créations brainstorm",
"featureBrainstormExpand": "Extensions brainstorm",
"featureBrainstormEnrich": "Enrichissements brainstorm"
},
"generalSettings": {
"title": "Paramètres généraux",
"title": "Généraux",
"description": "Paramètres généraux de l'application"
},
"toast": {
@@ -1647,7 +1661,7 @@
"collapse": "Réduire"
},
"mcpSettings": {
"title": "Paramètres MCP",
"title": "MCP",
"description": "Gérez vos clés API et configurez les outils externes",
"whatIsMcp": {
"title": "Qu'est-ce que MCP ?",
@@ -2331,6 +2345,216 @@
"checkoutSuccessBody": "Bienvenue sur {tier}. Vos fonctionnalités sont maintenant débloquées.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"tab": "Facturation",
"currentUsage": "Utilisation actuelle",
"currentPeriod": "Période en cours",
"aiCredits": "Crédits IA",
"used": "utilisés",
"billing": "Facturation",
"renewal": "Renouvellement",
"paidPlanDesc": "Votre abonnement se renouvelle automatiquement.",
"businessDescription": "Pour les équipes et chefs de produit."
},
"landing": {
"nav": {
"features": "Fonctionnalités",
"agents": "Agents IA",
"brainstorm": "Brainstorm",
"pricing": "Tarifs",
"tech": "Architecture",
"login": "Se connecter",
"cta": "Commencez"
},
"hero": {
"badge": "Augmenté par l'Intelligence Artificielle",
"title1": "Votre second cerveau,",
"title2": "enfin amplifié.",
"subtitle": "Momento n'est pas qu'une simple application de notes. C'est un écosystème intelligent qui connecte, analyse et développe vos idées en temps réel grâce à 6 types d'agents IA et une recherche sémantique de pointe.",
"cta": "S'inscrire maintenant",
"secondary": "Voir les fonctionnalités",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Connexion détectée avec votre projet de design durable de Mars 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 idées générées"
},
"features": {
"label": "Capacités IA",
"title": "Une intelligence fluide,",
"title2": "intégrée à chaque mot.",
"desc": "Momento orchestre vos idées grâce à une architecture multi-fournisseurs.",
"f1Title": "Recherche Sémantique",
"f1Desc": "Ne cherchez plus par mots-clés. Trouvez par concept. Notre moteur hybride Vector + FTS comprend l'intention derrière vos notes.",
"f2Title": "Chat RAG Contextuel",
"f2Desc": "Discutez avec votre savoir. Nos agents lisent vos notes, explorent le web et analysent vos documents pour répondre avec précision.",
"f3Title": "Écriture Augmentée",
"f3Desc": "Reformulation, suggestions de titres, tagging automatique et résumés. L'IA travaille en arrière-plan pour structurer votre pensée."
},
"agents": {
"label": "Agents Spécialisés",
"title": "Déléguez le travail complexe.",
"desc": "6 types d'agents IA autonomes pour automatiser vos recherches, vos résumés et vos présentations.",
"scraper": {
"title": "Scraper",
"desc": "Scrape des URLs, parse les flux RSS et synthétise l'info avec placement d'images intelligent."
},
"researcher": {
"title": "Researcher",
"desc": "Génère des requêtes complexes, explore les sources web et rédige des notes de recherche structurées."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Transforme vos notes en présentations PowerPoint professionnelles ou Slides HTML Interactives."
},
"monitor": {
"title": "Monitor",
"desc": "Analyse continuellement vos carnets pour détecter les tendances et les nouveaux insights."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Convertit vos idées en diagrammes Excalidraw fluides (Mindmaps, Flowcharts) avec auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Définissez vos propres agents avec des rôles et des sources de données spécifiques."
}
},
"brainstorm": {
"label": "Vagues de Pensée",
"title": "Brainstorming radial en temps réel.",
"waveGeneration": {
"title": "Génération par Vagues",
"desc": "Variations, Analogies, puis Disruptions. L'IA pousse votre concept initial dans ses retranchements."
},
"collaboration": {
"title": "Collaboration Native",
"desc": "Curseurs fantômes IA, avatars synchronisés et déplacement de nœuds en temps réel."
},
"export": {
"title": "Export Sémantique",
"desc": "Convertissez tout votre brainstorm en notes structurées d'un seul clic."
},
"disruptionLabel": "DISRUPTION",
"disruptionText": "Architecture Modulaire 2.0",
"analogyLabel": "ANALOGIE",
"analogyText": "Le cycle des marées"
},
"tech": {
"label": "Architecture & Fournisseurs",
"title": "Connectez votre propre intelligence.",
"tags": {
"title": "Tags",
"desc": "Indépendamment configurable avec n'importe quel modèle."
},
"embeddings": {
"title": "Embeddings",
"desc": "Indépendamment configurable avec n'importe quel modèle."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Indépendamment configurable avec n'importe quel modèle."
}
},
"pricing": {
"label": "Plans & Tarification",
"title": "Choisissez votre niveau d'amplification.",
"desc": "Des options flexibles pour les esprits créatifs, de l'usage individuel aux grandes organisations.",
"monthly": "Mensuel",
"annual": "Annuel",
"perMonth": "/mois",
"perMonthAnnual": "/mois, facturé annuellement",
"perUser": "+ 3,90€/user",
"perUserAnnual": "+ 2,90€/user, facturé annuellement",
"popular": "Le plus populaire",
"basic": {
"name": "Basic",
"desc": "Pour découvrir la magie de Momento.",
"cta": "Commencer",
"feature0": "100 Notes max",
"feature1": "3 Carnets",
"feature2": "50 crédits IA (Lifetime)",
"feature3": "Recherche sémantique",
"feature4": "Historique 7 jours"
},
"pro": {
"name": "Pro",
"desc": "Pour les consultants et créateurs exigeants.",
"cta": "Passer Pro",
"feature0": "Notes illimitées",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 recherches sémantiques",
"feature3": "Agents (12 runs/mois)",
"feature4": "Historique 30 jours",
"feature5": "Support Email"
},
"business": {
"name": "Business",
"desc": "Pour les équipes et chefs de produit.",
"cta": "Choisir Business",
"feature0": "10 Collaborateurs inclus",
"feature1": "BYOK (13 fournisseurs)",
"feature2": "1000 recherches sémantiques",
"feature3": "Agents (60 runs/mois)",
"feature4": "Brainstorm illimité",
"feature5": "Accès API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Mémoire organisationnelle sécurisée.",
"cta": "Contacter Ventes",
"feature0": "Tout Business",
"feature1": "Agents illimités",
"feature2": "SSO / SAML",
"feature3": "Audit Logs & SLA",
"feature4": "Support Dédié",
"feature5": "Onboarding Live"
}
},
"byok": {
"label": "Technologie Cloud Ouverte",
"title": "La stratégie BYOK",
"desc": "Vous possédez déjà des clés API OpenAI, Anthropic ou Google ? Connectez-les directement à Momento. Utilisez l'IA sans limites de crédits imposées, en payant uniquement ce que vous consommez chez votre fournisseur favori.",
"noLockin": "Pas de lock-in",
"noLockinDesc": "Changez de fournisseur en 1 clic.",
"cost": "Coûts optimisés",
"costDesc": "Payez le prix direct API.",
"configLabel": "Config Multi-Fournisseurs"
},
"cta": {
"title1": "Prêt à libérer votre",
"title2": "plein potentiel ?",
"desc": "Rejoignez des milliers de chercheurs, designers et penseurs qui utilisent déjà Momento pour construire leur futur.",
"button": "Lancer Momento"
},
"footer": {
"desc": "Le second cerveau amplifié par l'IA. Pensé pour les esprits créatifs.",
"product": {
"title": "Produit",
"link0": "Changelog",
"link1": "Documentation",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Communauté",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Légal",
"link0": "Politique de confidentialité",
"link1": "Conditions d'utilisation",
"link2": "Cookies",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Failed to update recent notes setting"
},
"aiSettings": {
"title": "AI सेटिंग्स",
"title": "AI",
"description": "अपनी AI-संचालित सुविधाओं और प्राथमिकताओं को कॉन्फ़िगर करें",
"features": "AI सुविधाएं",
"provider": "AI प्रदाता",
@@ -917,7 +917,8 @@
"autoLabeling": "सुझावों को लेबल करें",
"autoLabelingDesc": "स्वचालित रूप से आपके नोट्स पर लेबल सुझाता है और लागू करता है",
"noteHistory": "इतिहास नोट करें",
"noteHistoryDesc": "इतिहास से संस्करण स्नैपशॉट और पुनर्स्थापना सक्षम करें"
"noteHistoryDesc": "इतिहास से संस्करण स्नैपशॉट और पुनर्स्थापना सक्षम करें",
"titleSuggestions": "शीर्षक सुझाव"
},
"general": {
"loading": "लोड हो रहा है...",
@@ -1115,7 +1116,13 @@
"languageDetection": "भाषा पहचान",
"languageDetectionDesc": "प्रत्येक नोट की भाषा का स्वचालित पता लगाएं",
"autoLabeling": "स्वतः लेबलिंग",
"autoLabelingDesc": "लेबल स्वचालित रूप से सुझाएँ और लागू करें"
"autoLabelingDesc": "लेबल स्वचालित रूप से सुझाएँ और लागू करें",
"fallbackSectionTitle": "फ़ॉलबैक प्रदाता (वैकल्पिक)",
"fallbackSectionDescription": "प्रदाता त्रुटियों (429, 5xx) पर स्वतः उपयोग। 1.5 सेकंड में एक पुनः प्रयास।",
"fallbackProvider": "फ़ॉलबैक प्रदाता",
"fallbackModel": "फ़ॉलबैक मॉडल",
"fallbackNone": "कोई नहीं (अक्षम)",
"fallbackModelPlaceholder": "उदा. gpt-4o-mini"
},
"resend": {
"title": "Resend (अनुशंसित)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "हटाने में विफल",
"roleUpdateSuccess": "उपयोगकर्ता भूमिका को {role} में अपडेट किया गया",
"roleUpdateFailed": "भूमिका अपडेट करने में विफल",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "अवमत करें",
"promote": "पदोन्नत करें",
"confirmDelete": "Are you sure? This action cannot be undone.",
@@ -1180,6 +1189,7 @@
"name": "नाम",
"email": "ईमेल",
"role": "भूमिका",
"subscription": "Subscription",
"createdAt": "बनाया गया",
"actions": "कार्रवाई"
},
@@ -1371,7 +1381,7 @@
"loading": "लोड हो रहा है..."
},
"dataManagement": {
"title": "डेटा प्रबंधन",
"title": "Data",
"toolsDescription": "अपने डेटाबेस स्वास्थ्य को बनाए रखने के लिए उपकरण",
"exporting": "निर्यात हो रहा है...",
"importing": "आयात हो रहा है...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "सामान्य सेटिंग्स",
"title": "General",
"description": "सामान्य एप्लिकेशन सेटिंग्स"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "संकुचित करें"
},
"mcpSettings": {
"title": "MCP सेटिंग्स",
"title": "MCP",
"description": "API कुंजियाँ प्रबंधित करें और बाहरी टूल कॉन्फ़िगर करें",
"whatIsMcp": {
"title": "MCP क्या है?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "सत्र के होस्ट की AI सीमा समाप्त हो गई है। उनसे अपना प्लान अपग्रेड करने को कहें।",
"quotaHost": "इस ब्रेनस्टॉर्म के लिए आपकी AI सीमा समाप्त हो गई है। जारी रखने के लिए प्लान अपग्रेड करें।"
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "{tier} में आपका स्वागत है।",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "वर्तमान उपयोग",
"currentPeriod": "वर्तमान अवधि",
"aiCredits": "AI क्रेडिट",
"used": "उपयोग किया",
"billing": "बिलिंग",
"renewal": "नवीनीकरण",
"paidPlanDesc": "आपकी सदस्यता स्वचालित रूप से नवीनीकरण होती है।",
"businessDescription": "टीमों और उत्पाद नेताओं के लिए।"
},
"landing": {
"nav": {
"features": "सुविधाएँ",
"agents": "AI एजेंट",
"brainstorm": "Brainstorm",
"pricing": "मूल्य",
"tech": "आर्किटेक्चर",
"login": "लॉग इन",
"cta": "शुरू करें"
},
"hero": {
"badge": "कृत्रिम बुद्धिमत्ता से संचालित",
"title1": "आपका दूसरा दिमाग,",
"title2": "आखिरकार प्रबलित।",
"subtitle": "Momento सिर्फ़ नोट ऐप नहीं है। यह एक बुद्धिमान इकोसिस्टम है जो 6 प्रकार के AI एजेंट और अत्याधुनिक सिमैंटिक खोज के साथ आपके विचारों को रियल टाइम में जोड़ता, विश्लेषण करता और विकसित करता है।",
"cta": "अभी साइन अप करें",
"secondary": "सुविधाएँ देखें",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"मार्च 2024 के आपके सतत डिज़ाइन प्रोजेक्ट से कनेक्शन मिला...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 विचार जनरेट"
},
"features": {
"label": "AI क्षमताएँ",
"title": "प्रवाही बुद्धि,",
"title2": "हर शब्द में बुनी हुई।",
"desc": "Momento मल्टी-प्रोवाइडर आर्किटेक्चर से आपके विचारों को व्यवस्थित करता है।",
"f1Title": "सिमैंटिक खोज",
"f1Desc": "कीवर्ड से खोजना छोड़ें। अवधारणा से खोजें। हमारा हाइब्रिड Vector + FTS इंजन आपके नोट्स के पीछे के इरादे को समझता है।",
"f2Title": "संदर्भित RAG चैट",
"f2Desc": "अपने ज्ञान से बात करें। हमारे एजेंट नोट्स पढ़ते हैं, वेब खोजते हैं और दस्तावेज़ विश्लेषण कर सटीक उत्तर देते हैं।",
"f3Title": "संवर्धित लेखन",
"f3Desc": "पुनर्लेखन, शीर्षक सुझाव, ऑटो-टैगिंग और सारांश। AI पृष्ठभूमि में आपकी सोच को संरचित करता है।"
},
"agents": {
"label": "विशेषज्ञ एजेंट",
"title": "जटिल काम सौंपें।",
"desc": "6 प्रकार के स्वायत्त AI एजेंट आपके शोध, सारांश और प्रस्तुतियों को स्वचालित करते हैं।",
"scraper": {
"title": "Scraper",
"desc": "URL स्क्रैप करता है, RSS पार्स करता है और स्मार्ट इमेज प्लेसमेंट के साथ जानकारी संश्लेषित करता है।"
},
"researcher": {
"title": "Researcher",
"desc": "जटिल क्वेरी बनाता है, वेब स्रोत खोजता है और संरचित शोध नोट्स लिखता है।"
},
"slideGen": {
"title": "Slide Gen",
"desc": "आपके नोट्स को प्रोफेशनल PowerPoint या इंटरैक्टिव HTML स्लाइड में बदलता है।"
},
"monitor": {
"title": "Monitor",
"desc": "ट्रेंड और नई अंतर्दृष्टि के लिए आपके नोटबुक का निरंतर विश्लेषण करता है।"
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "विचारों को Excalidraw के प्रवाही आरेख (माइंडमैप, फ्लोचार्ट) में ऑटो-लेआउट के साथ बदलता है।"
},
"custom": {
"title": "Custom",
"desc": "विशिष्ट भूमिकाओं और डेटा स्रोतों के साथ अपने एजेंट परिभाषित करें।"
}
},
"brainstorm": {
"label": "विचार की लहरें",
"title": "रियल टाइम रेडियल ब्रेनस्टॉर्मिंग।",
"waveGeneration": {
"title": "वेव जनरेशन",
"desc": "विविधता, सादृश्य, फिर व्यवधान। AI आपकी प्रारंभिक अवधारणा को उसकी सीमा तक धकेलता है।"
},
"collaboration": {
"title": "मूल सहयोग",
"desc": "AI घोस्ट कर्सर, सिंक अवतार और रियल टाइम नोड मूवमेंट।"
},
"export": {
"title": "सिमैंटिक निर्यात",
"desc": "पूरा ब्रेनस्टॉर्म एक क्लिक में संरचित नोट्स में बदलें।"
},
"disruptionLabel": "व्यवधान",
"disruptionText": "मॉड्यूलर आर्किटेक्चर 2.0",
"analogyLabel": "सादृश्य",
"analogyText": "ज्वार का चक्र"
},
"tech": {
"label": "आर्किटेक्चर और प्रोवाइडर",
"title": "अपना AI मॉडल कनेक्ट करें।",
"tags": {
"title": "Tags",
"desc": "किसी भी मॉडल के साथ स्वतंत्र रूप से कॉन्फ़िगर करने योग्य।"
},
"embeddings": {
"title": "Embeddings",
"desc": "किसी भी मॉडल के साथ स्वतंत्र रूप से कॉन्फ़िगर करने योग्य।"
},
"chatRag": {
"title": "Chat RAG",
"desc": "किसी भी मॉडल के साथ स्वतंत्र रूप से कॉन्फ़िगर करने योग्य।"
}
},
"pricing": {
"label": "प्लान और मूल्य",
"title": "अपना प्रबलन स्तर चुनें।",
"desc": "रचनात्मक दिमागों के लिए लचीले विकल्प — व्यक्तिगत उपयोग से बड़े संगठनों तक।",
"monthly": "मासिक",
"annual": "वार्षिक",
"perMonth": "/माह",
"perMonthAnnual": "/माह, वार्षिक बिलिंग",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "सबसे लोकप्रिय",
"basic": {
"name": "Basic",
"desc": "Momento का जादू खोजें।",
"cta": "शुरू करें",
"feature0": "अधिकतम 100 नोट",
"feature1": "3 नोटबुक",
"feature2": "50 AI क्रेडिट (आजीवन)",
"feature3": "सिमैंटिक खोज",
"feature4": "7 दिन का इतिहास"
},
"pro": {
"name": "Pro",
"desc": "मांग वाले सलाहकारों और रचनाकारों के लिए।",
"cta": "Pro में अपग्रेड",
"feature0": "असीमित नोट",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 सिमैंटिक खोज",
"feature3": "एजेंट (12 रन/माह)",
"feature4": "30 दिन का इतिहास",
"feature5": "ईमेल सहायता"
},
"business": {
"name": "Business",
"desc": "टीमों और प्रोडक्ट मैनेजरों के लिए।",
"cta": "Business चुनें",
"feature0": "10 सहयोगी शामिल",
"feature1": "BYOK (13 प्रोवाइडर)",
"feature2": "1000 सिमैंटिक खोज",
"feature3": "एजेंट (60 रन/माह)",
"feature4": "असीमित ब्रेनस्टॉर्म",
"feature5": "API एक्सेस"
},
"enterprise": {
"name": "Enterprise",
"desc": "सुरक्षित संगठनात्मक स्मृति।",
"cta": "बिक्री से संपर्क",
"feature0": "Business की सब कुछ",
"feature1": "असीमित एजेंट",
"feature2": "SSO / SAML",
"feature3": "Audit Logs और SLA",
"feature4": "समर्पित सहायता",
"feature5": "लाइव ऑनबोर्डिंग"
}
},
"byok": {
"label": "ओपन क्लाउड तकनीक",
"title": "BYOK रणनीति",
"desc": "पहले से OpenAI, Anthropic या Google API कुंजी हैं? सीधे Momento से जोड़ें। बिना लागू क्रेडिट सीमा के AI उपयोग करें, केवल पसंदीदा प्रोवाइडर पर वास्तविक खपत का भुगतान करें।",
"noLockin": "कोई लॉक-इन नहीं",
"noLockinDesc": "1 क्लिक में प्रोवाइडर बदलें।",
"cost": "अनुकूलित लागत",
"costDesc": "सीधी API कीमत चुकाएँ।",
"configLabel": "मल्टी-प्रोवाइडर कॉन्फ़िग"
},
"cta": {
"title1": "अपनी",
"title2": "पूरी क्षमता खोलने के लिए तैयार?",
"desc": "हज़ारों शोधकर्ताओं, डिज़ाइनरों और विचारकों से जुड़ें जो Momento से अपना भविष्य बना रहे हैं।",
"button": "Momento लॉन्च करें"
},
"footer": {
"desc": "AI-प्रबलित दूसरा दिमाग। रचनात्मक दिमागों के लिए।",
"product": {
"title": "उत्पाद",
"link0": "चेंजलॉग",
"link1": "दस्तावेज़",
"link2": "रोडमैप",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "समुदाय",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "कानूनी",
"link0": "गोपनीयता",
"link1": "सेवा की शर्तें",
"link2": "कुकी नीति",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -400,7 +400,7 @@
"transformError": "Error during transformation",
"convertToRichtext": "Converti in Rich Text",
"convertingToRichtext": "Conversione...",
"assistant": "AI Assistant",
"assistant": "AI Note",
"generating": "Generating...",
"generateTitles": "Generate titles",
"reformulateText": "Reformulate text",
@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Failed to update recent notes setting"
},
"aiSettings": {
"title": "Impostazioni AI",
"title": "AI",
"description": "Configura le funzionalità AI e le preferenze",
"features": "Funzionalità AI",
"provider": "Provider AI",
@@ -917,7 +917,8 @@
"autoLabeling": "Suggerimenti per le etichette",
"autoLabelingDesc": "Suggerisce e applica automaticamente le etichette alle tue note",
"noteHistory": "Nota la storia",
"noteHistoryDesc": "Abilita gli snapshot della versione e il ripristino dalla cronologia"
"noteHistoryDesc": "Abilita gli snapshot della versione e il ripristino dalla cronologia",
"titleSuggestions": "Suggerimento titoli"
},
"general": {
"loading": "Caricamento...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Rilevamento lingua",
"languageDetectionDesc": "Rileva automaticamente la lingua di ogni nota",
"autoLabeling": "Etichettatura automatica",
"autoLabelingDesc": "Suggerisce e applica etichette automaticamente"
"autoLabelingDesc": "Suggerisce e applica etichette automaticamente",
"fallbackSectionTitle": "Provider di riserva (opzionale)",
"fallbackSectionDescription": "Usato automaticamente in caso di errori del provider (429, 5xx). Un solo nuovo tentativo entro 1,5 s.",
"fallbackProvider": "Provider di riserva",
"fallbackModel": "Modello di riserva",
"fallbackNone": "Nessuno (disattivato)",
"fallbackModelPlaceholder": "es. gpt-4o-mini"
},
"resend": {
"title": "Resend (Consigliato)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Failed to delete",
"roleUpdateSuccess": "User role updated to {role}",
"roleUpdateFailed": "Failed to update role",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Declassa",
"promote": "Promuovi",
"confirmDelete": "Sei sicuro di voler eliminare questo utente?",
@@ -1180,6 +1189,7 @@
"name": "Name",
"email": "Email",
"role": "Role",
"subscription": "Subscription",
"createdAt": "Created At",
"actions": "Actions"
},
@@ -1371,7 +1381,7 @@
"loading": "Caricamento..."
},
"dataManagement": {
"title": "Data Management",
"title": "Data",
"toolsDescription": "Tools to maintain your database health",
"exporting": "Esportazione in corso...",
"importing": "Importazione in corso...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Impostazioni generali",
"title": "General",
"description": "Impostazioni generali dell'applicazione"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Comprimi"
},
"mcpSettings": {
"title": "Impostazioni MCP",
"title": "MCP",
"description": "Gestisci le chiavi API e configura gli strumenti esterni",
"whatIsMcp": {
"title": "Cos'è MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "L'host della sessione ha raggiunto il limite IA. Chiedigli di aggiornare il piano.",
"quotaHost": "Hai raggiunto il limite IA per questo brainstorm. Passa a un piano superiore per continuare."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Benvenuto su {tier}. Le tue funzionalità sono ora sbloccate.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Utilizzo attuale",
"currentPeriod": "Periodo corrente",
"aiCredits": "Crediti IA",
"used": "utilizzati",
"billing": "Fatturazione",
"renewal": "Rinnovo",
"paidPlanDesc": "Il tuo abbonamento si rinnova automaticamente.",
"businessDescription": "Per team e responsabili di prodotto."
},
"landing": {
"nav": {
"features": "Funzionalità",
"agents": "Agenti IA",
"brainstorm": "Brainstorm",
"pricing": "Prezzi",
"tech": "Architettura",
"login": "Accedi",
"cta": "Inizia"
},
"hero": {
"badge": "Potenziato dall'intelligenza artificiale",
"title1": "Il tuo secondo cervello,",
"title2": "finalmente amplificato.",
"subtitle": "Momento è più di un'app per appunti. È un ecosistema intelligente che collega, analizza e sviluppa le tue idee in tempo reale con 6 tipi di agenti IA e ricerca semantica all'avanguardia.",
"cta": "Registrati ora",
"secondary": "Scopri le funzionalità",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Connessione rilevata con il tuo progetto di design sostenibile di marzo 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 idee generate"
},
"features": {
"label": "Capacità IA",
"title": "Intelligenza fluida,",
"title2": "intrecciata in ogni parola.",
"desc": "Momento orchestra le tue idee attraverso un'architettura multi-provider.",
"f1Title": "Ricerca semantica",
"f1Desc": "Smetti di cercare per parole chiave. Trova per concetto. Il nostro motore ibrido Vector + FTS comprende l'intento dietro le tue note.",
"f2Title": "Chat RAG contestuale",
"f2Desc": "Parla con la tua conoscenza. I nostri agenti leggono le tue note, esplorano il web e analizzano i documenti per rispondere con precisione.",
"f3Title": "Scrittura aumentata",
"f3Desc": "Riformulazione, suggerimenti di titoli, tagging automatico e riassunti. L'IA lavora in background per strutturare il tuo pensiero."
},
"agents": {
"label": "Agenti specializzati",
"title": "Delega il lavoro complesso.",
"desc": "6 tipi di agenti IA autonomi per automatizzare ricerche, riassunti e presentazioni.",
"scraper": {
"title": "Scraper",
"desc": "Estrae URL, analizza feed RSS e sintetizza informazioni con posizionamento intelligente delle immagini."
},
"researcher": {
"title": "Researcher",
"desc": "Genera query complesse, esplora fonti web e scrive note di ricerca strutturate."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Trasforma le tue note in presentazioni PowerPoint professionali o slide HTML interattive."
},
"monitor": {
"title": "Monitor",
"desc": "Analizza continuamente i tuoi quaderni per rilevare tendenze e nuovi insight."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Converte le tue idee in diagrammi Excalidraw fluidi (mappe mentali, flowchart) con auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Definisci agenti personalizzati con ruoli e fonti dati specifici."
}
},
"brainstorm": {
"label": "Onde di pensiero",
"title": "Brainstorming radiale in tempo reale.",
"waveGeneration": {
"title": "Generazione a onde",
"desc": "Variazioni, analogie e poi disruption. L'IA spinge il tuo concetto iniziale ai suoi limiti."
},
"collaboration": {
"title": "Collaborazione nativa",
"desc": "Cursori fantasma IA, avatar sincronizzati e spostamento nodi in tempo reale."
},
"export": {
"title": "Export semantico",
"desc": "Converti l'intero brainstorming in note strutturate con un clic."
},
"disruptionLabel": "DISRUPTION",
"disruptionText": "Architettura modulare 2.0",
"analogyLabel": "ANALOGIA",
"analogyText": "Il ciclo delle maree"
},
"tech": {
"label": "Architettura e provider",
"title": "Collega il tuo modello IA.",
"tags": {
"title": "Tags",
"desc": "Configurabile indipendentemente con qualsiasi modello."
},
"embeddings": {
"title": "Embeddings",
"desc": "Configurabile indipendentemente con qualsiasi modello."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Configurabile indipendentemente con qualsiasi modello."
}
},
"pricing": {
"label": "Piani e prezzi",
"title": "Scegli il tuo livello di amplificazione.",
"desc": "Opzioni flessibili per menti creative, dall'uso individuale alle grandi organizzazioni.",
"monthly": "Mensile",
"annual": "Annuale",
"perMonth": "/mese",
"perMonthAnnual": "/mese, fatturato annualmente",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Più popolare",
"basic": {
"name": "Basic",
"desc": "Scopri la magia di Momento.",
"cta": "Inizia",
"feature0": "100 note max",
"feature1": "3 quaderni",
"feature2": "50 crediti IA (a vita)",
"feature3": "Ricerca semantica",
"feature4": "Cronologia 7 giorni"
},
"pro": {
"name": "Pro",
"desc": "Per consulenti e creatori esigenti.",
"cta": "Passa a Pro",
"feature0": "Note illimitate",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 ricerche semantiche",
"feature3": "Agenti (12 esecuzioni/mese)",
"feature4": "Cronologia 30 giorni",
"feature5": "Supporto email"
},
"business": {
"name": "Business",
"desc": "Per team e product manager.",
"cta": "Scegli Business",
"feature0": "10 collaboratori inclusi",
"feature1": "BYOK (13 provider)",
"feature2": "1000 ricerche semantiche",
"feature3": "Agenti (60 esecuzioni/mese)",
"feature4": "Brainstorm illimitato",
"feature5": "Accesso API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Memoria organizzativa sicura.",
"cta": "Contatta vendite",
"feature0": "Tutto Business",
"feature1": "Agenti illimitati",
"feature2": "SSO / SAML",
"feature3": "Audit Logs e SLA",
"feature4": "Supporto dedicato",
"feature5": "Onboarding live"
}
},
"byok": {
"label": "Tecnologia cloud aperta",
"title": "La strategia BYOK",
"desc": "Hai già chiavi API OpenAI, Anthropic o Google? Collegale direttamente a Momento. Usa l'IA senza limiti di credito imposti, pagando solo ciò che consumi dal tuo provider preferito.",
"noLockin": "Nessun lock-in",
"noLockinDesc": "Cambia provider in 1 clic.",
"cost": "Costi ottimizzati",
"costDesc": "Paga il prezzo API diretto.",
"configLabel": "Config multi-provider"
},
"cta": {
"title1": "Pronto a liberare il tuo",
"title2": "pieno potenziale?",
"desc": "Unisciti a migliaia di ricercatori, designer e pensatori che usano già Momento per costruire il loro futuro.",
"button": "Avvia Momento"
},
"footer": {
"desc": "Il secondo cervello amplificato dall'IA. Pensato per menti creative.",
"product": {
"title": "Prodotto",
"link0": "Changelog",
"link1": "Documentazione",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Community",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Legale",
"link0": "Privacy",
"link1": "Termini di servizio",
"link2": "Cookie",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "最近のノート設定の更新に失敗しました"
},
"aiSettings": {
"title": "AI設定",
"title": "AI",
"description": "AI機能と設定を構成",
"features": "AI機能",
"provider": "AIプロバイダー",
@@ -917,7 +917,8 @@
"autoLabeling": "ラベルの提案",
"autoLabelingDesc": "ラベルを自動的に提案してメモに適用します",
"noteHistory": "メモ履歴",
"noteHistoryDesc": "バージョンのスナップショットと履歴からの復元を有効にする"
"noteHistoryDesc": "バージョンのスナップショットと履歴からの復元を有効にする",
"titleSuggestions": "タイトル提案"
},
"general": {
"loading": "読み込み中...",
@@ -1115,7 +1116,13 @@
"languageDetection": "言語検出",
"languageDetectionDesc": "各ノートの言語を自動検出",
"autoLabeling": "自動ラベリング",
"autoLabelingDesc": "ラベルを自動で提案・適用"
"autoLabelingDesc": "ラベルを自動で提案・適用",
"fallbackSectionTitle": "フォールバックプロバイダー(任意)",
"fallbackSectionDescription": "プロバイダーエラー時429、5xxに自動使用。1.5秒以内に1回再試行。",
"fallbackProvider": "フォールバックプロバイダー",
"fallbackModel": "フォールバックモデル",
"fallbackNone": "なし(無効)",
"fallbackModelPlaceholder": "例: gpt-4o-mini"
},
"resend": {
"title": "Resend推奨",
@@ -1173,6 +1180,8 @@
"deleteFailed": "削除に失敗しました",
"roleUpdateSuccess": "ユーザーの役割が{role}に更新されました",
"roleUpdateFailed": "役割の更新に失敗しました",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "降格",
"promote": "昇格",
"confirmDelete": "よろしいですか?この操作は元に戻せません。",
@@ -1180,6 +1189,7 @@
"name": "名前",
"email": "メール",
"role": "役割",
"subscription": "Subscription",
"createdAt": "作成日",
"actions": "アクション"
},
@@ -1371,7 +1381,7 @@
"loading": "読み込み中..."
},
"dataManagement": {
"title": "データ管理",
"title": "Data",
"toolsDescription": "データベースの健全性を維持するツール",
"exporting": "エクスポート中...",
"importing": "インポート中...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "一般設定",
"title": "General",
"description": "一般的なアプリケーション設定"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "折りたたむ"
},
"mcpSettings": {
"title": "MCP設定",
"title": "MCP",
"description": "APIキーの管理と外部ツールの設定",
"whatIsMcp": {
"title": "MCPとは",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "セッションのホストがAI利用上限に達しました。プランのアップグレードを依頼してください。",
"quotaHost": "このブレインストームのAI上限に達しました。続けるにはプランをアップグレードしてください。"
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "{tier}へようこそ。機能が解放されました。",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "現在の使用量",
"currentPeriod": "現在の期間",
"aiCredits": "AIクレジット",
"used": "使用済み",
"billing": "請求",
"renewal": "更新",
"paidPlanDesc": "サブスクリプションは自動更新されます。",
"businessDescription": "チームとプロダクトリーダー向け。"
},
"landing": {
"nav": {
"features": "機能",
"agents": "AIエージェント",
"brainstorm": "Brainstorm",
"pricing": "料金",
"tech": "アーキテクチャ",
"login": "ログイン",
"cta": "はじめる"
},
"hero": {
"badge": "人工知能で強化",
"title1": "あなたの第二の脳、",
"title2": "ついに増幅される。",
"subtitle": "Momentoは単なるメモアプリではありません。6種類のAIエージェントと最先端のセマンティック検索で、アイデアをリアルタイムに接続・分析・発展させるインテリジェントなエコシステムです。",
"cta": "今すぐ登録",
"secondary": "機能を見る",
"memoryEcho": "Memory Echo",
"memoryEchoText": "「2024年3月のサステナブルデザインプロジェクトとの関連を検出しました...」",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12件のアイデアを生成"
},
"features": {
"label": "AI機能",
"title": "流れるような知性、",
"title2": "言葉のひとつひとつに織り込まれる。",
"desc": "Momentoはマルチプロバイダーアーキテクチャでアイデアを統合します。",
"f1Title": "セマンティック検索",
"f1Desc": "キーワード検索はもう不要。概念で探せます。ハイブリッドのVector + FTSエンジンがートの意図を理解します。",
"f2Title": "コンテキストRAGチャット",
"f2Desc": "知識と対話できます。エージェントがートを読み、Webを探索し、文書を分析して正確に回答します。",
"f3Title": "拡張ライティング",
"f3Desc": "言い換え、タイトル提案、自動タグ付け、要約。AIがバックグラウンドで思考を整理します。"
},
"agents": {
"label": "専門エージェント",
"title": "複雑な作業を任せましょう。",
"desc": "調査・要約・プレゼンを自動化する6種類の自律型AIエージェント。",
"scraper": {
"title": "Scraper",
"desc": "URLを取得しRSSを解析、画像配置も賢く情報を統合します。"
},
"researcher": {
"title": "Researcher",
"desc": "複雑なクエリを生成しWebソースを探索、構造化された調査ートを作成します。"
},
"slideGen": {
"title": "Slide Gen",
"desc": "ートをプロ品質のPowerPointやインタラクティブHTMLスライドに変換します。"
},
"monitor": {
"title": "Monitor",
"desc": "ノートブックを継続分析し、トレンドや新しいインサイトを検出します。"
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "アイデアをExcalidrawの滑らかな図マインドマップ、フローチャートに自動レイアウトで変換します。"
},
"custom": {
"title": "Custom",
"desc": "役割とデータソースを指定した独自エージェントを定義できます。"
}
},
"brainstorm": {
"label": "思考の波",
"title": "リアルタイムの放射状ブレインストーム。",
"waveGeneration": {
"title": "ウェーブ生成",
"desc": "バリエーション、アナロジー、そしてディスラプション。AIが初期コンセプトを限界まで押し広げます。"
},
"collaboration": {
"title": "ネイティブコラボレーション",
"desc": "AIゴーストカーソル、同期アバター、ードのリアルタイム移動。"
},
"export": {
"title": "セマンティックエクスポート",
"desc": "ブレインストーム全体をワンクリックで構造化ノートに変換。"
},
"disruptionLabel": "ディスラプション",
"disruptionText": "モジュラーアーキテクチャ 2.0",
"analogyLabel": "アナロジー",
"analogyText": "潮汐のサイクル"
},
"tech": {
"label": "アーキテクチャとプロバイダー",
"title": "独自のAIモデルを接続。",
"tags": {
"title": "Tags",
"desc": "任意のモデルで独立して設定可能。"
},
"embeddings": {
"title": "Embeddings",
"desc": "任意のモデルで独立して設定可能。"
},
"chatRag": {
"title": "Chat RAG",
"desc": "任意のモデルで独立して設定可能。"
}
},
"pricing": {
"label": "プランと料金",
"title": "増幅レベルを選びましょう。",
"desc": "個人利用から大規模組織まで、クリエイティブな方に柔軟な選択肢。",
"monthly": "月額",
"annual": "年額",
"perMonth": "/月",
"perMonthAnnual": "/月(年払い)",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "最も人気",
"basic": {
"name": "Basic",
"desc": "Momentoの魔法を体験。",
"cta": "はじめる",
"feature0": "最大100ート",
"feature1": "3ートブック",
"feature2": "AIクレジット50生涯",
"feature3": "セマンティック検索",
"feature4": "7日間の履歴"
},
"pro": {
"name": "Pro",
"desc": "高い要求を持つコンサル・クリエイター向け。",
"cta": "Proにアップグレード",
"feature0": "無制限ノート",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "セマンティック検索200回",
"feature3": "エージェント月12回",
"feature4": "30日間の履歴",
"feature5": "メールサポート"
},
"business": {
"name": "Business",
"desc": "チームとプロダクトマネージャー向け。",
"cta": "Businessを選択",
"feature0": "10名のコラボレーター込み",
"feature1": "BYOK13プロバイダー",
"feature2": "セマンティック検索1000回",
"feature3": "エージェント月60回",
"feature4": "無制限ブレインストーム",
"feature5": "APIアクセス"
},
"enterprise": {
"name": "Enterprise",
"desc": "安全な組織メモリ。",
"cta": "営業に問い合わせ",
"feature0": "Businessの全機能",
"feature1": "無制限エージェント",
"feature2": "SSO / SAML",
"feature3": "監査ログとSLA",
"feature4": "専任サポート",
"feature5": "ライブオンボーディング"
}
},
"byok": {
"label": "オープンクラウド技術",
"title": "BYOK戦略",
"desc": "OpenAI、Anthropic、GoogleのAPIキーをお持ちですかMomentoに直接接続。クレジット上限なしで、お好みのプロバイダーの実消費分だけお支払い。",
"noLockin": "ロックインなし",
"noLockinDesc": "ワンクリックでプロバイダー変更。",
"cost": "最適化されたコスト",
"costDesc": "APIの直接価格で支払い。",
"configLabel": "マルチプロバイダー設定"
},
"cta": {
"title1": "あなたの",
"title2": "可能性を解き放つ準備はできましたか?",
"desc": "Momentoで未来を築く研究者、デザイナー、思想家の仲間入りを。",
"button": "Momentoを起動"
},
"footer": {
"desc": "AIで増幅された第二の脳。クリエイティブな方のために。",
"product": {
"title": "製品",
"link0": "変更履歴",
"link1": "ドキュメント",
"link2": "ロードマップ",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "コミュニティ",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "法的情報",
"link0": "プライバシー",
"link1": "利用規約",
"link2": "Cookie",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "최근 노트 설정 업데이트 실패"
},
"aiSettings": {
"title": "AI 설정",
"title": "AI",
"description": "AI 기반 기능 및 환경설정 구성",
"features": "AI 기능",
"provider": "AI 공급자",
@@ -917,7 +917,8 @@
"autoLabeling": "라벨 제안",
"autoLabelingDesc": "노트에 라벨을 자동으로 제안하고 적용합니다.",
"noteHistory": "메모 기록",
"noteHistoryDesc": "버전 스냅샷 및 기록 복원 활성화"
"noteHistoryDesc": "버전 스냅샷 및 기록 복원 활성화",
"titleSuggestions": "제목 제안"
},
"general": {
"loading": "로딩 중...",
@@ -1115,7 +1116,13 @@
"languageDetection": "언어 감지",
"languageDetectionDesc": "각 노트의 언어 자동 감지",
"autoLabeling": "자동 라벨링",
"autoLabelingDesc": "라벨 자동 제안 및 적용"
"autoLabelingDesc": "라벨 자동 제안 및 적용",
"fallbackSectionTitle": "대체 제공업체(선택)",
"fallbackSectionDescription": "제공업체 오류(429, 5xx) 시 자동 사용. 1.5초 이내 1회 재시도.",
"fallbackProvider": "대체 제공업체",
"fallbackModel": "대체 모델",
"fallbackNone": "없음(비활성화)",
"fallbackModelPlaceholder": "예: gpt-4o-mini"
},
"resend": {
"title": "Resend (권장)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "삭제 실패",
"roleUpdateSuccess": "사용자 역할이 {role}(으)로 업데이트되었습니다",
"roleUpdateFailed": "역할 업데이트 실패",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "강등",
"promote": "승격",
"confirmDelete": "확실합니까? 이 작업은 되돌릴 수 없습니다.",
@@ -1180,6 +1189,7 @@
"name": "이름",
"email": "이메일",
"role": "역할",
"subscription": "Subscription",
"createdAt": "생성일",
"actions": "작업"
},
@@ -1371,7 +1381,7 @@
"loading": "로딩 중..."
},
"dataManagement": {
"title": "데이터 관리",
"title": "Data",
"toolsDescription": "데이터베이스 상태를 유지하는 도구",
"exporting": "내보내는 중...",
"importing": "가져오는 중...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "일반 설정",
"title": "General",
"description": "일반 애플리케이션 설정"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "접기"
},
"mcpSettings": {
"title": "MCP 설정",
"title": "MCP",
"description": "API 키 관리 및 외부 도구 구성",
"whatIsMcp": {
"title": "MCP란 무엇인가요?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "세션 호스트의 AI 한도에 도달했습니다. 플랜 업그레이드를 요청하세요.",
"quotaHost": "이 브레인스토밍의 AI 한도에 도달했습니다. 계속하려면 플랜을 업그레이드하세요."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "{tier}에 오신 것을 환영합니다.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "현재 사용량",
"currentPeriod": "현재 기간",
"aiCredits": "AI 크레딧",
"used": "사용됨",
"billing": "결제",
"renewal": "갱신",
"paidPlanDesc": "구독이 자동으로 갱신됩니다.",
"businessDescription": "팀 및 프로덕트 리더를 위한 요금제."
},
"landing": {
"nav": {
"features": "기능",
"agents": "AI 에이전트",
"brainstorm": "Brainstorm",
"pricing": "요금",
"tech": "아키텍처",
"login": "로그인",
"cta": "시작하기"
},
"hero": {
"badge": "인공지능으로 구동",
"title1": "당신의 제2의 뇌,",
"title2": "드디어 증폭됩니다.",
"subtitle": "Momento는 단순한 메모 앱이 아닙니다. 6종류의 AI 에이전트와 최첨단 시맨틱 검색으로 아이디어를 실시간으로 연결·분석·발전시키는 지능형 생태계입니다.",
"cta": "지금 가입하기",
"secondary": "기능 보기",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"2024년 3월 지속 가능 디자인 프로젝트와의 연결 감지...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12개 아이디어 생성"
},
"features": {
"label": "AI 역량",
"title": "유연한 지능,",
"title2": "모든 단어에 스며듭니다.",
"desc": "Momento는 멀티 프로바이더 아키텍처로 아이디어를 조율합니다.",
"f1Title": "시맨틱 검색",
"f1Desc": "키워드 검색은 그만. 개념으로 찾으세요. 하이브리드 Vector + FTS 엔진이 노트 뒤의 의도를 이해합니다.",
"f2Title": "컨텍스트 RAG 채팅",
"f2Desc": "지식과 대화하세요. 에이전트가 노트를 읽고, 웹을 탐색하며, 문서를 분석해 정확히 답합니다.",
"f3Title": "증강 글쓰기",
"f3Desc": "재작성, 제목 제안, 자동 태깅, 요약. AI가 백그라운드에서 사고를 구조화합니다."
},
"agents": {
"label": "전문 에이전트",
"title": "복잡한 작업을 위임하세요.",
"desc": "연구, 요약, 프레젠테이션을 자동화하는 6종류의 자율 AI 에이전트.",
"scraper": {
"title": "Scraper",
"desc": "URL을 수집하고 RSS를 파싱하며 이미지 배치까지 똑똑하게 정보를 종합합니다."
},
"researcher": {
"title": "Researcher",
"desc": "복잡한 쿼리를 생성하고 웹 소스를 탐색해 구조화된 연구 노트를 작성합니다."
},
"slideGen": {
"title": "Slide Gen",
"desc": "노트를 전문 PowerPoint 또는 인터랙티브 HTML 슬라이드로 변환합니다."
},
"monitor": {
"title": "Monitor",
"desc": "노트북을 지속 분석해 트렌드와 새로운 인사이트를 감지합니다."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "아이디어를 Excalidraw의 유려한 다이어그램(마인드맵, 플로차트)으로 자동 배치합니다."
},
"custom": {
"title": "Custom",
"desc": "역할과 데이터 소스를 지정한 맞춤 에이전트를 정의하세요."
}
},
"brainstorm": {
"label": "사고의 파동",
"title": "실시간 방사형 브레인스토밍.",
"waveGeneration": {
"title": "웨이브 생성",
"desc": "변형, 유추, 그다음 파괴적 혁신. AI가 초기 개념을 한계까지 밀어냅니다."
},
"collaboration": {
"title": "네이티브 협업",
"desc": "AI 고스트 커서, 동기화된 아바타, 실시간 노드 이동."
},
"export": {
"title": "시맨틱보내기",
"desc": "브레인스토밍 전체를 한 번의 클릭으로 구조화된 노트로 변환."
},
"disruptionLabel": "파괴적 혁신",
"disruptionText": "모듈형 아키텍처 2.0",
"analogyLabel": "유추",
"analogyText": "조석의 리듬"
},
"tech": {
"label": "아키텍처 및 프로바이더",
"title": "자신만의 AI 모델을 연결하세요.",
"tags": {
"title": "Tags",
"desc": "어떤 모델과도 독립적으로 설정 가능."
},
"embeddings": {
"title": "Embeddings",
"desc": "어떤 모델과도 독립적으로 설정 가능."
},
"chatRag": {
"title": "Chat RAG",
"desc": "어떤 모델과도 독립적으로 설정 가능."
}
},
"pricing": {
"label": "플랜 및 요금",
"title": "증폭 수준을 선택하세요.",
"desc": "개인 사용부터 대규모 조직까지, 창의적인 분들을 위한 유연한 옵션.",
"monthly": "월간",
"annual": "연간",
"perMonth": "/월",
"perMonthAnnual": "/월, 연간 결제",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "가장 인기",
"basic": {
"name": "Basic",
"desc": "Momento의 마법을 경험하세요.",
"cta": "시작하기",
"feature0": "최대 100개 노트",
"feature1": "3개 노트북",
"feature2": "AI 크레딧 50 (평생)",
"feature3": "시맨틱 검색",
"feature4": "7일 기록"
},
"pro": {
"name": "Pro",
"desc": "까다로운 컨설턴트와 크리에이터를 위해.",
"cta": "Pro로 업그레이드",
"feature0": "무제한 노트",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "시맨틱 검색 200회",
"feature3": "에이전트 (월 12회)",
"feature4": "30일 기록",
"feature5": "이메일 지원"
},
"business": {
"name": "Business",
"desc": "팀과 프로덕트 매니저를 위해.",
"cta": "Business 선택",
"feature0": "10명 협업자 포함",
"feature1": "BYOK (13개 프로바이더)",
"feature2": "시맨틱 검색 1000회",
"feature3": "에이전트 (월 60회)",
"feature4": "무제한 브레인스토밍",
"feature5": "API 액세스"
},
"enterprise": {
"name": "Enterprise",
"desc": "안전한 조직 메모리.",
"cta": "영업 문의",
"feature0": "Business 전체",
"feature1": "무제한 에이전트",
"feature2": "SSO / SAML",
"feature3": "감사 로그 및 SLA",
"feature4": "전담 지원",
"feature5": "라이브 온보딩"
}
},
"byok": {
"label": "오픈 클라우드 기술",
"title": "BYOK 전략",
"desc": "OpenAI, Anthropic, Google API 키가 있으신가요? Momento에 직접 연결하세요. 강제 크레딧 한도 없이, 선호하는 프로바이더의 실제 사용량만 지불합니다.",
"noLockin": "락인 없음",
"noLockinDesc": "클릭 한 번으로 프로바이더 변경.",
"cost": "최적화된 비용",
"costDesc": "API 직접 가격으로 지불.",
"configLabel": "멀티 프로바이더 설정"
},
"cta": {
"title1": "당신의",
"title2": "잠재력을 깨울 준비가 되셨나요?",
"desc": "Momento로 미래를 만들고 있는 수천 명의 연구자, 디자이너, 사상가와 함께하세요.",
"button": "Momento 실행"
},
"footer": {
"desc": "AI로 증폭된 제2의 뇌. 창의적인 분들을 위해.",
"product": {
"title": "제품",
"link0": "변경 이력",
"link1": "문서",
"link2": "로드맵",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "커뮤니티",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "법적 고지",
"link0": "개인정보",
"link1": "이용약관",
"link2": "쿠키 정책",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Recente notities-instelling bijwerken mislukt"
},
"aiSettings": {
"title": "AI-instellingen",
"title": "AI",
"description": "Configureer uw AI-aangedreven functies en voorkeuren",
"features": "AI-functies",
"provider": "AI-provider",
@@ -917,7 +917,8 @@
"autoLabeling": "Labelsuggesties",
"autoLabelingDesc": "Stelt automatisch labels voor en past deze toe op uw notities",
"noteHistory": "Let op de geschiedenis",
"noteHistoryDesc": "Schakel momentopnamen van versies en herstel vanuit de geschiedenis in"
"noteHistoryDesc": "Schakel momentopnamen van versies en herstel vanuit de geschiedenis in",
"titleSuggestions": "Titelsuggesties"
},
"general": {
"loading": "Laden...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Taaldetectie",
"languageDetectionDesc": "Detecteert automatisch de taal van elke notitie",
"autoLabeling": "Automatisch labelen",
"autoLabelingDesc": "Stelt labels voor en past ze automatisch toe"
"autoLabelingDesc": "Stelt labels voor en past ze automatisch toe",
"fallbackSectionTitle": "Fallback-provider (optioneel)",
"fallbackSectionDescription": "Wordt automatisch gebruikt bij providerfouten (429, 5xx). Eén nieuwe poging binnen 1,5 s.",
"fallbackProvider": "Fallback-provider",
"fallbackModel": "Fallback-model",
"fallbackNone": "Geen (uitgeschakeld)",
"fallbackModelPlaceholder": "bijv. gpt-4o-mini"
},
"resend": {
"title": "Resend (Aanbevolen)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Verwijderen mislukt",
"roleUpdateSuccess": "Gebruikersrol bijgewerkt naar {role}",
"roleUpdateFailed": "Rol bijwerken mislukt",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Degraderen",
"promote": "Bevorderen",
"confirmDelete": "Weet u zeker dat u deze gebruiker wilt verwijderen?",
@@ -1180,6 +1189,7 @@
"name": "Naam",
"email": "E-mail",
"role": "Rol",
"subscription": "Subscription",
"createdAt": "Aangemaakt op",
"actions": "Acties"
},
@@ -1371,7 +1381,7 @@
"loading": "Laden..."
},
"dataManagement": {
"title": "Gegevensbeheer",
"title": "Data",
"toolsDescription": "Hulpmiddelen om de gezondheid van uw database te behouden",
"exporting": "Exporteren...",
"importing": "Importeren...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Algemene instellingen",
"title": "General",
"description": "Algemene applicatie-instellingen"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Inklappen"
},
"mcpSettings": {
"title": "MCP-instellingen",
"title": "MCP",
"description": "Beheer uw API-sleutels en configureer externe tools",
"whatIsMcp": {
"title": "Wat is MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "De sessiehost heeft zijn AI-limiet bereikt. Vraag om een upgrade van het abonnement.",
"quotaHost": "Je hebt je AI-limiet voor deze brainstorm bereikt. Upgrade je abonnement om door te gaan."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Welkom bij {tier}. Je functies zijn nu ontgrendeld.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Huidig gebruik",
"currentPeriod": "Huidige periode",
"aiCredits": "AI-credits",
"used": "gebruikt",
"billing": "Facturatie",
"renewal": "Verlenging",
"paidPlanDesc": "Uw abonnement wordt automatisch verlengd.",
"businessDescription": "Voor teams en productmanagers."
},
"landing": {
"nav": {
"features": "Functies",
"agents": "AI-agents",
"brainstorm": "Brainstorm",
"pricing": "Prijzen",
"tech": "Architectuur",
"login": "Inloggen",
"cta": "Aan de slag"
},
"hero": {
"badge": "Aangedreven door kunstmatige intelligentie",
"title1": "Uw tweede brein,",
"title2": "eindelijk versterkt.",
"subtitle": "Momento is meer dan een notitie-app. Het is een intelligent ecosysteem dat uw ideeën in realtime verbindt, analyseert en ontwikkelt met 6 soorten AI-agents en geavanceerde semantische zoekfunctie.",
"cta": "Nu registreren",
"secondary": "Bekijk functies",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Verbinding gedetecteerd met uw duurzaam designproject van maart 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 ideeën gegenereerd"
},
"features": {
"label": "AI-mogelijkheden",
"title": "Vloeiende intelligentie,",
"title2": "verweven in elk woord.",
"desc": "Momento orkestreert uw ideeën via een multi-provider architectuur.",
"f1Title": "Semantisch zoeken",
"f1Desc": "Stop met zoeken op trefwoorden. Vind op concept. Onze hybride Vector + FTS-engine begrijpt de intentie achter uw notities.",
"f2Title": "Contextuele RAG-chat",
"f2Desc": "Praat met uw kennis. Onze agents lezen uw notities, verkennen het web en analyseren documenten voor nauwkeurige antwoorden.",
"f3Title": "Augmented schrijven",
"f3Desc": "Herschrijven, titelsuggesties, automatisch taggen en samenvattingen. AI structureert uw denken op de achtergrond."
},
"agents": {
"label": "Gespecialiseerde agents",
"title": "Delegeer complex werk.",
"desc": "6 soorten autonome AI-agents voor onderzoek, samenvattingen en presentaties.",
"scraper": {
"title": "Scraper",
"desc": "Scrapet URL's, parseert RSS-feeds en synthetiseert info met slimme beeldplaatsing."
},
"researcher": {
"title": "Researcher",
"desc": "Genereert complexe queries, verkent webbronnen en schrijft gestructureerde onderzoeksnotities."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Zet uw notities om in professionele PowerPoint-presentaties of interactieve HTML-slides."
},
"monitor": {
"title": "Monitor",
"desc": "Analyseert continu uw notitieboeken om trends en nieuwe inzichten te detecteren."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Zet ideeën om in vloeiende Excalidraw-diagrammen (mindmaps, flowcharts) met auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Definieer eigen agents met specifieke rollen en databronnen."
}
},
"brainstorm": {
"label": "Gedachtegolven",
"title": "Radiale brainstorming in realtime.",
"waveGeneration": {
"title": "Golfgeneratie",
"desc": "Variaties, analogieën, dan disrupties. AI duwt uw startconcept tot het uiterste."
},
"collaboration": {
"title": "Native samenwerking",
"desc": "AI-spookcursors, gesynchroniseerde avatars en realtime knooppuntbeweging."
},
"export": {
"title": "Semantische export",
"desc": "Zet uw hele brainstorm om in gestructureerde notities met één klik."
},
"disruptionLabel": "DISRUPTIE",
"disruptionText": "Modulaire architectuur 2.0",
"analogyLabel": "ANALOGIE",
"analogyText": "Het getijdenritme"
},
"tech": {
"label": "Architectuur en providers",
"title": "Koppel uw eigen AI-model.",
"tags": {
"title": "Tags",
"desc": "Onafhankelijk configureerbaar met elk model."
},
"embeddings": {
"title": "Embeddings",
"desc": "Onafhankelijk configureerbaar met elk model."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Onafhankelijk configureerbaar met elk model."
}
},
"pricing": {
"label": "Plannen en prijzen",
"title": "Kies uw versterkingsniveau.",
"desc": "Flexibele opties voor creatieve geesten, van individueel gebruik tot grote organisaties.",
"monthly": "Maandelijks",
"annual": "Jaarlijks",
"perMonth": "/maand",
"perMonthAnnual": "/maand, jaarlijks gefactureerd",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Meest populair",
"basic": {
"name": "Basic",
"desc": "Ontdek de magie van Momento.",
"cta": "Starten",
"feature0": "Max. 100 notities",
"feature1": "3 notitieboeken",
"feature2": "50 AI-credits (levenslang)",
"feature3": "Semantisch zoeken",
"feature4": "7 dagen geschiedenis"
},
"pro": {
"name": "Pro",
"desc": "Voor veeleisende consultants en makers.",
"cta": "Upgrade naar Pro",
"feature0": "Onbeperkte notities",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 semantische zoekopdrachten",
"feature3": "Agents (12 runs/maand)",
"feature4": "30 dagen geschiedenis",
"feature5": "E-mailondersteuning"
},
"business": {
"name": "Business",
"desc": "Voor teams en productmanagers.",
"cta": "Kies Business",
"feature0": "10 medewerkers inbegrepen",
"feature1": "BYOK (13 providers)",
"feature2": "1000 semantische zoekopdrachten",
"feature3": "Agents (60 runs/maand)",
"feature4": "Onbeperkt brainstormen",
"feature5": "API-toegang"
},
"enterprise": {
"name": "Enterprise",
"desc": "Veilig organisatiegeheugen.",
"cta": "Contact verkoop",
"feature0": "Alles uit Business",
"feature1": "Onbeperkte agents",
"feature2": "SSO / SAML",
"feature3": "Audit Logs en SLA",
"feature4": "Dedicated support",
"feature5": "Live onboarding"
}
},
"byok": {
"label": "Open cloudtechnologie",
"title": "De BYOK-strategie",
"desc": "Heeft u al API-sleutels van OpenAI, Anthropic of Google? Koppel ze direct aan Momento. Gebruik AI zonder opgelegde creditlimieten en betaal alleen wat u daadwerkelijk verbruikt bij uw favoriete provider.",
"noLockin": "Geen lock-in",
"noLockinDesc": "Wissel provider in 1 klik.",
"cost": "Geoptimaliseerde kosten",
"costDesc": "Betaal de directe API-prijs.",
"configLabel": "Multi-provider config"
},
"cta": {
"title1": "Klaar om uw",
"title2": "volledige potentieel te ontgrendelen?",
"desc": "Sluit u aan bij duizenden onderzoekers, ontwerpers en denkers die Momento al gebruiken om hun toekomst te bouwen.",
"button": "Momento starten"
},
"footer": {
"desc": "Het AI-versterkte tweede brein. Ontworpen voor creatieve geesten.",
"product": {
"title": "Product",
"link0": "Changelog",
"link1": "Documentatie",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Community",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Juridisch",
"link0": "Privacybeleid",
"link1": "Servicevoorwaarden",
"link2": "Cookiebeleid",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Nie udało się zaktualizować ustawienia ostatnich notatek"
},
"aiSettings": {
"title": "Ustawienia AI",
"title": "AI",
"description": "Skonfiguruj swoje funkcje AI i preferencje",
"features": "Funkcje AI",
"provider": "Dostawca AI",
@@ -917,7 +917,8 @@
"autoLabeling": "Sugestie dotyczące etykiet",
"autoLabelingDesc": "Automatycznie sugeruje i stosuje etykiety do notatek",
"noteHistory": "Uwaga na historię",
"noteHistoryDesc": "Włącz migawki wersji i przywracanie z Historii"
"noteHistoryDesc": "Włącz migawki wersji i przywracanie z Historii",
"titleSuggestions": "Sugestie tytułów"
},
"general": {
"loading": "Ładowanie...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Wykrywanie języka",
"languageDetectionDesc": "Automatycznie wykrywa język każdej notatki",
"autoLabeling": "Automatyczne etykietowanie",
"autoLabelingDesc": "Automatycznie sugeruje i stosuje etykiety"
"autoLabelingDesc": "Automatycznie sugeruje i stosuje etykiety",
"fallbackSectionTitle": "Zapasowy dostawca (opcjonalnie)",
"fallbackSectionDescription": "Używany automatycznie przy błędach dostawcy (429, 5xx). Jedna ponowna próba w 1,5 s.",
"fallbackProvider": "Zapasowy dostawca",
"fallbackModel": "Zapasowy model",
"fallbackNone": "Brak (wyłączone)",
"fallbackModelPlaceholder": "np. gpt-4o-mini"
},
"resend": {
"title": "Resend (Zalecane)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Nie udało się usunąć",
"roleUpdateSuccess": "Rola użytkownika zmieniona na {role}",
"roleUpdateFailed": "Nie udało się zaktualizować roli",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Zdegraduj",
"promote": "Awansuj",
"confirmDelete": "Czy na pewno chcesz usunąć tego użytkownika?",
@@ -1180,6 +1189,7 @@
"name": "Imię",
"email": "E-mail",
"role": "Rola",
"subscription": "Subscription",
"createdAt": "Utworzono",
"actions": "Akcje"
},
@@ -1371,7 +1381,7 @@
"loading": "Ładowanie..."
},
"dataManagement": {
"title": "Zarządzanie danymi",
"title": "Data",
"toolsDescription": "Narzędzia do utrzymania kondycji bazy danych",
"exporting": "Eksportowanie...",
"importing": "Importowanie...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Ustawienia ogólne",
"title": "General",
"description": "Ogólne ustawienia aplikacji"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Zwiń"
},
"mcpSettings": {
"title": "Ustawienia MCP",
"title": "MCP",
"description": "Zarządzaj kluczami API i konfiguruj narzędzia zewnętrzne",
"whatIsMcp": {
"title": "Czym jest MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "Gospodarz sesji wyczerpał limit AI. Poproś go o ulepszenie planu.",
"quotaHost": "Osiągnąłeś limit AI dla tego brainstormu. Ulepsz plan, aby kontynuować."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Witamy w {tier}. Twoje funkcje są teraz odblokowane.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Bieżące użycie",
"currentPeriod": "Bieżący okres",
"aiCredits": "Kredyty AI",
"used": "użyte",
"billing": "Rozliczenia",
"renewal": "Odnowienie",
"paidPlanDesc": "Twoja subskrypcja odnawia się automatycznie.",
"businessDescription": "Dla zespołów i kierowników produktu."
},
"landing": {
"nav": {
"features": "Funkcje",
"agents": "Agenci AI",
"brainstorm": "Brainstorm",
"pricing": "Cennik",
"tech": "Architektura",
"login": "Zaloguj się",
"cta": "Rozpocznij"
},
"hero": {
"badge": "Napędzane sztuczną inteligencją",
"title1": "Twój drugi mózg,",
"title2": "wreszcie wzmocniony.",
"subtitle": "Momento to coś więcej niż aplikacja do notatek. To inteligentny ekosystem, który łączy, analizuje i rozwija Twoje pomysły w czasie rzeczywistym dzięki 6 typom agentów AI i zaawansowanemu wyszukiwaniu semantycznemu.",
"cta": "Zarejestruj się teraz",
"secondary": "Zobacz funkcje",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Wykryto powiązanie z Twoim projektem zrównoważonego designu z marca 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 wygenerowanych pomysłów"
},
"features": {
"label": "Możliwości AI",
"title": "Płynna inteligencja,",
"title2": "wpleciona w każde słowo.",
"desc": "Momento orkiestruje Twoje pomysły dzięki architekturze multi-provider.",
"f1Title": "Wyszukiwanie semantyczne",
"f1Desc": "Koniec z wyszukiwaniem po słowach kluczowych. Szukaj po pojęciu. Nasz hybrydowy silnik Vector + FTS rozumie intencję za Twoimi notatkami.",
"f2Title": "Kontekstowy czat RAG",
"f2Desc": "Rozmawiaj ze swoją wiedzą. Nasi agenci czytają notatki, eksplorują sieć i analizują dokumenty, by odpowiadać precyzyjnie.",
"f3Title": "Wzmocnione pisanie",
"f3Desc": "Przeformułowania, sugestie tytułów, automatyczne tagowanie i podsumowania. AI porządkuje Twoje myślenie w tle."
},
"agents": {
"label": "Wyspecjalizowani agenci",
"title": "Deleguj złożoną pracę.",
"desc": "6 typów autonomicznych agentów AI do automatyzacji badań, streszczeń i prezentacji.",
"scraper": {
"title": "Scraper",
"desc": "Pobiera URL-e, parsuje kanały RSS i syntetyzuje informacje z inteligentnym układem obrazów."
},
"researcher": {
"title": "Researcher",
"desc": "Generuje złożone zapytania, eksploruje źródła web i pisze ustrukturyzowane notatki badawcze."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Zamienia notatki w profesjonalne prezentacje PowerPoint lub interaktywne slajdy HTML."
},
"monitor": {
"title": "Monitor",
"desc": "Ciągle analizuje zeszyty, by wykrywać trendy i nowe spostrzeżenia."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Zamienia pomysły w płynne diagramy Excalidraw (mapy myśli, schematy) z auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Zdefiniuj własnych agentów z określonymi rolami i źródłami danych."
}
},
"brainstorm": {
"label": "Fale myśli",
"title": "Radialny brainstorming w czasie rzeczywistym.",
"waveGeneration": {
"title": "Generacja falowa",
"desc": "Wariacje, analogie, potem dysrupcje. AI popycha początkowy koncept do granic możliwości."
},
"collaboration": {
"title": "Natywna współpraca",
"desc": "Duchowe kursory AI, zsynchronizowane awatary i ruch węzłów w czasie rzeczywistym."
},
"export": {
"title": "Eksport semantyczny",
"desc": "Zamień cały brainstorming w ustrukturyzowane notatki jednym kliknięciem."
},
"disruptionLabel": "DYSRUPCJA",
"disruptionText": "Architektura modułowa 2.0",
"analogyLabel": "ANALOGIA",
"analogyText": "Cykl pływów"
},
"tech": {
"label": "Architektura i dostawcy",
"title": "Podłącz własny model AI.",
"tags": {
"title": "Tagi",
"desc": "Niezależnie konfigurowalne z dowolnym modelem."
},
"embeddings": {
"title": "Embeddings",
"desc": "Niezależnie konfigurowalne z dowolnym modelem."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Niezależnie konfigurowalne z dowolnym modelem."
}
},
"pricing": {
"label": "Plany i ceny",
"title": "Wybierz poziom wzmocnienia.",
"desc": "Elastyczne opcje dla kreatywnych umysłów — od użytku indywidualnego po duże organizacje.",
"monthly": "Miesięcznie",
"annual": "Rocznie",
"perMonth": "/mies.",
"perMonthAnnual": "/mies., rozliczane rocznie",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Najpopularniejszy",
"basic": {
"name": "Basic",
"desc": "Odkryj magię Momento.",
"cta": "Zacznij",
"feature0": "Maks. 100 notatek",
"feature1": "3 zeszyty",
"feature2": "50 kredytów AI (dożywotnio)",
"feature3": "Wyszukiwanie semantyczne",
"feature4": "Historia 7 dni"
},
"pro": {
"name": "Pro",
"desc": "Dla wymagających konsultantów i twórców.",
"cta": "Przejdź na Pro",
"feature0": "Nieograniczone notatki",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 wyszukiwań semantycznych",
"feature3": "Agenci (12 uruchomień/mies.)",
"feature4": "Historia 30 dni",
"feature5": "Wsparcie e-mail"
},
"business": {
"name": "Business",
"desc": "Dla zespołów i product managerów.",
"cta": "Wybierz Business",
"feature0": "10 współpracowników w cenie",
"feature1": "BYOK (13 dostawców)",
"feature2": "1000 wyszukiwań semantycznych",
"feature3": "Agenci (60 uruchomień/mies.)",
"feature4": "Nieograniczony brainstorm",
"feature5": "Dostęp API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Bezpieczna pamięć organizacyjna.",
"cta": "Kontakt ze sprzedażą",
"feature0": "Wszystko z Business",
"feature1": "Nieograniczeni agenci",
"feature2": "SSO / SAML",
"feature3": "Audit Logs i SLA",
"feature4": "Dedykowane wsparcie",
"feature5": "Onboarding na żywo"
}
},
"byok": {
"label": "Otwarta technologia chmurowa",
"title": "Strategia BYOK",
"desc": "Masz już klucze API OpenAI, Anthropic lub Google? Podłącz je bezpośrednio do Momento. Korzystaj z AI bez narzuconych limitów kredytów, płacąc tylko za faktyczne zużycie u ulubionego dostawcy.",
"noLockin": "Bez lock-in",
"noLockinDesc": "Zmień dostawcę jednym kliknięciem.",
"cost": "Zoptymalizowane koszty",
"costDesc": "Płać bezpośrednią cenę API.",
"configLabel": "Konfiguracja multi-provider"
},
"cta": {
"title1": "Gotowy, by uwolnić swój",
"title2": "pełny potencjał?",
"desc": "Dołącz do tysięcy badaczy, designerów i myślicieli, którzy już budują przyszłość z Momento.",
"button": "Uruchom Momento"
},
"footer": {
"desc": "Drugie mózg wzmocnione przez AI. Stworzone dla kreatywnych umysłów.",
"product": {
"title": "Produkt",
"link0": "Changelog",
"link1": "Dokumentacja",
"link2": "Roadmapa",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Społeczność",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Prawne",
"link0": "Polityka prywatności",
"link1": "Regulamin",
"link2": "Polityka cookies",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Falha ao atualizar configuração de notas recentes"
},
"aiSettings": {
"title": "Configurações de IA",
"title": "AI",
"description": "Configure seus recursos e preferências com IA",
"features": "Recursos de IA",
"provider": "Provedor de IA",
@@ -917,7 +917,8 @@
"autoLabeling": "Sugestões de rótulos",
"autoLabelingDesc": "Sugere e aplica rótulos automaticamente às suas notas",
"noteHistory": "Histórico de notas",
"noteHistoryDesc": "Habilite snapshots de versão e restauração do histórico"
"noteHistoryDesc": "Habilite snapshots de versão e restauração do histórico",
"titleSuggestions": "Sugestão de títulos"
},
"general": {
"loading": "Carregando...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Detecção de idioma",
"languageDetectionDesc": "Detecta automaticamente o idioma de cada nota",
"autoLabeling": "Rotulagem automática",
"autoLabelingDesc": "Sugere e aplica rótulos automaticamente"
"autoLabelingDesc": "Sugere e aplica rótulos automaticamente",
"fallbackSectionTitle": "Provedor de contingência (opcional)",
"fallbackSectionDescription": "Usado automaticamente em erros do provedor (429, 5xx). Uma nova tentativa em 1,5 s.",
"fallbackProvider": "Provedor de contingência",
"fallbackModel": "Modelo de contingência",
"fallbackNone": "Nenhum (desativado)",
"fallbackModelPlaceholder": "ex.: gpt-4o-mini"
},
"resend": {
"title": "Resend (Recomendado)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Falha ao excluir",
"roleUpdateSuccess": "Função do usuário atualizada para {role}",
"roleUpdateFailed": "Falha ao atualizar função",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Rebaixar",
"promote": "Promover",
"confirmDelete": "Tem certeza de que deseja excluir este usuário?",
@@ -1180,6 +1189,7 @@
"name": "Nome",
"email": "E-mail",
"role": "Função",
"subscription": "Subscription",
"createdAt": "Criado em",
"actions": "Ações"
},
@@ -1371,7 +1381,7 @@
"loading": "Carregando..."
},
"dataManagement": {
"title": "Gerenciamento de Dados",
"title": "Data",
"toolsDescription": "Ferramentas para manter a saúde do seu banco de dados",
"exporting": "Exportando...",
"importing": "Importando...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Configurações gerais",
"title": "General",
"description": "Configurações gerais do aplicativo"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Recolher"
},
"mcpSettings": {
"title": "Configurações MCP",
"title": "MCP",
"description": "Gerencie suas chaves API e configure ferramentas externas",
"whatIsMcp": {
"title": "O que é MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "O anfitrião da sessão atingiu o limite de IA. Peça-lhe para atualizar o plano.",
"quotaHost": "Atingiu o limite de IA deste brainstorm. Atualize o plano para continuar."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Bem-vindo ao {tier}. As suas funcionalidades estão agora desbloqueadas.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Uso atual",
"currentPeriod": "Período atual",
"aiCredits": "Créditos IA",
"used": "usados",
"billing": "Faturação",
"renewal": "Renovação",
"paidPlanDesc": "Sua assinatura renova automaticamente.",
"businessDescription": "Para equipes e líderes de produto."
},
"landing": {
"nav": {
"features": "Funcionalidades",
"agents": "Agentes IA",
"brainstorm": "Brainstorm",
"pricing": "Preços",
"tech": "Arquitetura",
"login": "Entrar",
"cta": "Começar"
},
"hero": {
"badge": "Impulsionado por inteligência artificial",
"title1": "O seu segundo cérebro,",
"title2": "finalmente amplificado.",
"subtitle": "O Momento é mais do que uma app de notas. É um ecossistema inteligente que liga, analisa e desenvolve as suas ideias em tempo real com 6 tipos de agentes IA e pesquisa semântica de ponta.",
"cta": "Registe-se agora",
"secondary": "Ver funcionalidades",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Ligação detetada ao seu projeto de design sustentável de março de 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 ideias geradas"
},
"features": {
"label": "Capacidades IA",
"title": "Inteligência fluida,",
"title2": "entrelaçada em cada palavra.",
"desc": "O Momento orquestra as suas ideias através de uma arquitetura multi-fornecedor.",
"f1Title": "Pesquisa semântica",
"f1Desc": "Deixe de pesquisar por palavras-chave. Encontre por conceito. O nosso motor híbrido Vector + FTS compreende a intenção por trás das suas notas.",
"f2Title": "Chat RAG contextual",
"f2Desc": "Converse com o seu conhecimento. Os nossos agentes leem as suas notas, exploram a web e analisam documentos para responder com precisão.",
"f3Title": "Escrita aumentada",
"f3Desc": "Reformulação, sugestões de títulos, etiquetagem automática e resumos. A IA trabalha em segundo plano para estruturar o seu pensamento."
},
"agents": {
"label": "Agentes especializados",
"title": "Delegue o trabalho complexo.",
"desc": "6 tipos de agentes IA autónomos para automatizar pesquisa, resumos e apresentações.",
"scraper": {
"title": "Scraper",
"desc": "Extrai URLs, analisa feeds RSS e sintetiza informação com colocação inteligente de imagens."
},
"researcher": {
"title": "Researcher",
"desc": "Gera consultas complexas, explora fontes web e escreve notas de pesquisa estruturadas."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Transforma as suas notas em apresentações PowerPoint profissionais ou slides HTML interativos."
},
"monitor": {
"title": "Monitor",
"desc": "Analisa continuamente os seus cadernos para detetar tendências e novos insights."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Converte ideias em diagramas Excalidraw fluidos (mapas mentais, fluxogramas) com auto-layout."
},
"custom": {
"title": "Custom",
"desc": "Defina os seus próprios agentes com papéis e fontes de dados específicos."
}
},
"brainstorm": {
"label": "Ondas de pensamento",
"title": "Brainstorming radial em tempo real.",
"waveGeneration": {
"title": "Geração por ondas",
"desc": "Variações, analogias e depois disrupções. A IA leva o seu conceito inicial aos limites."
},
"collaboration": {
"title": "Colaboração nativa",
"desc": "Cursores fantasma IA, avatares sincronizados e movimento de nós em tempo real."
},
"export": {
"title": "Exportação semântica",
"desc": "Converta todo o brainstorming em notas estruturadas com um clique."
},
"disruptionLabel": "DISRUPÇÃO",
"disruptionText": "Arquitetura modular 2.0",
"analogyLabel": "ANALOGIA",
"analogyText": "O ciclo das marés"
},
"tech": {
"label": "Arquitetura e fornecedores",
"title": "Ligue o seu próprio modelo de IA.",
"tags": {
"title": "Tags",
"desc": "Configurável de forma independente com qualquer modelo."
},
"embeddings": {
"title": "Embeddings",
"desc": "Configurável de forma independente com qualquer modelo."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Configurável de forma independente com qualquer modelo."
}
},
"pricing": {
"label": "Planos e preços",
"title": "Escolha o seu nível de amplificação.",
"desc": "Opções flexíveis para mentes criativas, do uso individual a grandes organizações.",
"monthly": "Mensal",
"annual": "Anual",
"perMonth": "/mês",
"perMonthAnnual": "/mês, faturado anualmente",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Mais popular",
"basic": {
"name": "Basic",
"desc": "Descubra a magia do Momento.",
"cta": "Começar",
"feature0": "100 notas máx.",
"feature1": "3 cadernos",
"feature2": "50 créditos IA (vitalícios)",
"feature3": "Pesquisa semântica",
"feature4": "Histórico 7 dias"
},
"pro": {
"name": "Pro",
"desc": "Para consultores e criadores exigentes.",
"cta": "Upgrade para Pro",
"feature0": "Notas ilimitadas",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 pesquisas semânticas",
"feature3": "Agentes (12 execuções/mês)",
"feature4": "Histórico 30 dias",
"feature5": "Suporte por email"
},
"business": {
"name": "Business",
"desc": "Para equipas e product managers.",
"cta": "Escolher Business",
"feature0": "10 colaboradores incluídos",
"feature1": "BYOK (13 fornecedores)",
"feature2": "1000 pesquisas semânticas",
"feature3": "Agentes (60 execuções/mês)",
"feature4": "Brainstorm ilimitado",
"feature5": "Acesso API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Memória organizacional segura.",
"cta": "Contactar vendas",
"feature0": "Tudo do Business",
"feature1": "Agentes ilimitados",
"feature2": "SSO / SAML",
"feature3": "Audit Logs e SLA",
"feature4": "Suporte dedicado",
"feature5": "Onboarding ao vivo"
}
},
"byok": {
"label": "Tecnologia cloud aberta",
"title": "A estratégia BYOK",
"desc": "Já tem chaves API OpenAI, Anthropic ou Google? Ligue-as diretamente ao Momento. Use IA sem limites de crédito impostos, pagando apenas o que consome no seu fornecedor favorito.",
"noLockin": "Sem lock-in",
"noLockinDesc": "Mude de fornecedor em 1 clique.",
"cost": "Custos otimizados",
"costDesc": "Pague o preço direto da API.",
"configLabel": "Config multi-fornecedor"
},
"cta": {
"title1": "Pronto para libertar o seu",
"title2": "pleno potencial?",
"desc": "Junte-se a milhares de investigadores, designers e pensadores que já usam o Momento para construir o futuro.",
"button": "Lançar Momento"
},
"footer": {
"desc": "O segundo cérebro amplificado por IA. Feito para mentes criativas.",
"product": {
"title": "Produto",
"link0": "Changelog",
"link1": "Documentação",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Comunidade",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Legal",
"link0": "Privacidade",
"link1": "Termos de serviço",
"link2": "Cookies",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

View File

@@ -890,7 +890,7 @@
"recentNotesUpdateFailed": "Не удалось обновить настройку недавних заметок"
},
"aiSettings": {
"title": "Настройки ИИ",
"title": "AI",
"description": "Настройте функции и предпочтения на базе ИИ",
"features": "Функции ИИ",
"provider": "Провайдер ИИ",
@@ -917,7 +917,8 @@
"autoLabeling": "Предложения по ярлыкам",
"autoLabelingDesc": "Автоматически предлагает и применяет ярлыки к вашим заметкам.",
"noteHistory": "История заметок",
"noteHistoryDesc": "Включить снимки версий и восстановление из истории"
"noteHistoryDesc": "Включить снимки версий и восстановление из истории",
"titleSuggestions": "Предложения заголовков"
},
"general": {
"loading": "Загрузка...",
@@ -1115,7 +1116,13 @@
"languageDetection": "Определение языка",
"languageDetectionDesc": "Автоопределение языка каждой заметки",
"autoLabeling": "Автомаркировка",
"autoLabelingDesc": "Автопредложение и применение меток"
"autoLabelingDesc": "Автопредложение и применение меток",
"fallbackSectionTitle": "Резервный провайдер (необязательно)",
"fallbackSectionDescription": "Используется автоматически при ошибках провайдера (429, 5xx). Одна повторная попытка за 1,5 с.",
"fallbackProvider": "Резервный провайдер",
"fallbackModel": "Резервная модель",
"fallbackNone": "Нет (отключено)",
"fallbackModelPlaceholder": "напр. gpt-4o-mini"
},
"resend": {
"title": "Resend (Рекомендуется)",
@@ -1173,6 +1180,8 @@
"deleteFailed": "Не удалось удалить",
"roleUpdateSuccess": "Роль пользователя обновлена на {role}",
"roleUpdateFailed": "Не удалось обновить роль",
"tierUpdateSuccess": "Subscription updated to {tier}",
"tierUpdateFailed": "Failed to update subscription",
"demote": "Понизить",
"promote": "Повысить",
"confirmDelete": "Вы уверены, что хотите удалить этого пользователя?",
@@ -1180,6 +1189,7 @@
"name": "Имя",
"email": "Эл. почта",
"role": "Роль",
"subscription": "Subscription",
"createdAt": "Дата создания",
"actions": "Действия"
},
@@ -1371,7 +1381,7 @@
"loading": "Загрузка..."
},
"dataManagement": {
"title": "Управление данными",
"title": "Data",
"toolsDescription": "Инструменты для поддержания базы данных в рабочем состоянии",
"exporting": "Экспорт...",
"importing": "Импорт...",
@@ -1436,7 +1446,7 @@
"fontJetBrainsMono": "JetBrains Mono"
},
"generalSettings": {
"title": "Общие настройки",
"title": "General",
"description": "Общие настройки приложения"
},
"toast": {
@@ -1622,7 +1632,7 @@
"collapse": "Свернуть"
},
"mcpSettings": {
"title": "Настройки MCP",
"title": "MCP",
"description": "Управление ключами API и настройка внешних инструментов",
"whatIsMcp": {
"title": "Что такое MCP?",
@@ -2211,7 +2221,9 @@
"exportDefaultNoteTitle": "Synthesis",
"exportOpening": "Opening…",
"ownerBadge": "Owner",
"waveBadge": "Wave {wave}"
"waveBadge": "Wave {wave}",
"quotaGuest": "Организатор сессии исчерпал лимит ИИ. Попросите его обновить тариф.",
"quotaHost": "Вы исчерпали лимит ИИ для этого мозгового штурма. Обновите тариф, чтобы продолжить."
},
"usageMeter": {
"packName": "AI Discovery Pack",
@@ -2323,6 +2335,215 @@
"checkoutSuccessBody": "Добро пожаловать в {tier}. Ваши функции теперь доступны.",
"subscriptionType": "subscriptionType",
"renewalDate": "renewalDate",
"noRenewalDate": "—"
"noRenewalDate": "—",
"currentUsage": "Текущее использование",
"currentPeriod": "Текущий период",
"aiCredits": "ИИ-кредиты",
"used": "использовано",
"billing": "Оплата",
"renewal": "Продление",
"paidPlanDesc": "Ваша подписка продлевается автоматически.",
"businessDescription": "Для команд и руководителей продуктов."
},
"landing": {
"nav": {
"features": "Возможности",
"agents": "ИИ-агенты",
"brainstorm": "Brainstorm",
"pricing": "Тарифы",
"tech": "Архитектура",
"login": "Войти",
"cta": "Начать"
},
"hero": {
"badge": "На базе искусственного интеллекта",
"title1": "Ваш второй мозг,",
"title2": "наконец усиленный.",
"subtitle": "Momento — это больше, чем приложение для заметок. Это интеллектуальная экосистема, которая связывает, анализирует и развивает ваши идеи в реальном времени с 6 типами ИИ-агентов и передовым семантическим поиском.",
"cta": "Зарегистрироваться",
"secondary": "Смотреть возможности",
"memoryEcho": "Memory Echo",
"memoryEchoText": "\"Обнаружена связь с вашим проектом устойчивого дизайна от марта 2024...\"",
"brainstormLive": "Brainstorm Live",
"ideasGenerated": "+12 идей сгенерировано"
},
"features": {
"label": "Возможности ИИ",
"title": "Плавный интеллект,",
"title2": "в каждом слове.",
"desc": "Momento управляет вашими идеями через мультипровайдерную архитектуру.",
"f1Title": "Семантический поиск",
"f1Desc": "Хватит искать по ключевым словам. Ищите по смыслу. Наш гибридный движок Vector + FTS понимает намерение за вашими заметками.",
"f2Title": "Контекстный RAG-чат",
"f2Desc": "Общайтесь со своими знаниями. Агенты читают заметки, исследуют веб и анализируют документы для точных ответов.",
"f3Title": "Усиленное письмо",
"f3Desc": "Переформулировка, предложения заголовков, автотеги и резюме. ИИ структурирует ваше мышление в фоне."
},
"agents": {
"label": "Специализированные агенты",
"title": "Делегируйте сложную работу.",
"desc": "6 типов автономных ИИ-агентов для автоматизации исследований, резюме и презентаций.",
"scraper": {
"title": "Scraper",
"desc": "Собирает URL, парсит RSS-ленты и синтезирует информацию с умным размещением изображений."
},
"researcher": {
"title": "Researcher",
"desc": "Генерирует сложные запросы, исследует веб-источники и пишет структурированные исследовательские заметки."
},
"slideGen": {
"title": "Slide Gen",
"desc": "Превращает заметки в профессиональные презентации PowerPoint или интерактивные HTML-слайды."
},
"monitor": {
"title": "Monitor",
"desc": "Непрерывно анализирует блокноты для выявления трендов и новых инсайтов."
},
"diagramGen": {
"title": "Diagram Gen",
"desc": "Превращает идеи в плавные диаграммы Excalidraw (майндмэпы, блок-схемы) с авто-раскладкой."
},
"custom": {
"title": "Custom",
"desc": "Создавайте собственных агентов с нужными ролями и источниками данных."
}
},
"brainstorm": {
"label": "Волны мысли",
"title": "Радиальный мозговой штурм в реальном времени.",
"waveGeneration": {
"title": "Волновая генерация",
"desc": "Вариации, аналогии, затем дисрапции. ИИ доводит начальную концепцию до предела."
},
"collaboration": {
"title": "Нативная коллаборация",
"desc": "Призрачные курсоры ИИ, синхронизированные аватары и движение узлов в реальном времени."
},
"export": {
"title": "Семантический экспорт",
"desc": "Превратите весь мозговой штурм в структурированные заметки одним кликом."
},
"disruptionLabel": "ДИСРАПЦИЯ",
"disruptionText": "Модульная архитектура 2.0",
"analogyLabel": "АНАЛОГИЯ",
"analogyText": "Цикл приливов"
},
"tech": {
"label": "Архитектура и провайдеры",
"title": "Подключите свою модель ИИ.",
"tags": {
"title": "Теги",
"desc": "Независимо настраивается с любой моделью."
},
"embeddings": {
"title": "Embeddings",
"desc": "Независимо настраивается с любой моделью."
},
"chatRag": {
"title": "Chat RAG",
"desc": "Независимо настраивается с любой моделью."
}
},
"pricing": {
"label": "Тарифы и цены",
"title": "Выберите уровень усиления.",
"desc": "Гибкие варианты для творческих умов — от личного использования до крупных организаций.",
"monthly": "Ежемесячно",
"annual": "Ежегодно",
"perMonth": "/мес.",
"perMonthAnnual": "/мес., оплата раз в год",
"perUser": "+ 3.90€/user",
"perUserAnnual": "+ 2.90€/user, billed annually",
"popular": "Самый популярный",
"basic": {
"name": "Basic",
"desc": "Откройте магию Momento.",
"cta": "Начать",
"feature0": "До 100 заметок",
"feature1": "3 блокнота",
"feature2": "50 ИИ-кредитов (навсегда)",
"feature3": "Семантический поиск",
"feature4": "История 7 дней"
},
"pro": {
"name": "Pro",
"desc": "Для требовательных консультантов и авторов.",
"cta": "Перейти на Pro",
"feature0": "Безлимитные заметки",
"feature1": "BYOK (OpenAI/Anthropic)",
"feature2": "200 семантических поисков",
"feature3": "Агенты (12 запусков/мес.)",
"feature4": "История 30 дней",
"feature5": "Поддержка по email"
},
"business": {
"name": "Business",
"desc": "Для команд и продакт-менеджеров.",
"cta": "Выбрать Business",
"feature0": "10 участников включено",
"feature1": "BYOK (13 провайдеров)",
"feature2": "1000 семантических поисков",
"feature3": "Агенты (60 запусков/мес.)",
"feature4": "Безлимитный brainstorm",
"feature5": "Доступ к API"
},
"enterprise": {
"name": "Enterprise",
"desc": "Безопасная организационная память.",
"cta": "Связаться с продажами",
"feature0": "Всё из Business",
"feature1": "Безлимитные агенты",
"feature2": "SSO / SAML",
"feature3": "Audit Logs и SLA",
"feature4": "Выделенная поддержка",
"feature5": "Живой onboarding"
}
},
"byok": {
"label": "Открытая облачная технология",
"title": "Стратегия BYOK",
"desc": "Уже есть API-ключи OpenAI, Anthropic или Google? Подключите их напрямую к Momento. Используйте ИИ без навязанных лимитов кредитов, платя только за фактическое потребление у любимого провайдера.",
"noLockin": "Без lock-in",
"noLockinDesc": "Смените провайдера в 1 клик.",
"cost": "Оптимизированные расходы",
"costDesc": "Платите прямую цену API.",
"configLabel": "Мультипровайдерная настройка"
},
"cta": {
"title1": "Готовы раскрыть свой",
"title2": "полный потенциал?",
"desc": "Присоединяйтесь к тысячам исследователей, дизайнеров и мыслителей, которые уже строят будущее с Momento.",
"button": "Запустить Momento"
},
"footer": {
"desc": "Второй мозг, усиленный ИИ. Создан для творческих умов.",
"product": {
"title": "Продукт",
"link0": "Changelog",
"link1": "Документация",
"link2": "Roadmap",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"community": {
"title": "Сообщество",
"link0": "Discord",
"link1": "Twitter / X",
"link2": "LinkedIn",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
},
"legal": {
"title": "Правовая информация",
"link0": "Конфиденциальность",
"link1": "Условия использования",
"link2": "Cookies",
"link0Href": "#",
"link1Href": "#",
"link2Href": "#"
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More