style: restore blue accents for AI dialog components and standardize gold header
This commit is contained in:
@@ -103,9 +103,9 @@ export default function AITestPage() {
|
|||||||
|
|
||||||
{/* 3. Chat Test - Horizontal Layout */}
|
{/* 3. Chat Test - Horizontal Layout */}
|
||||||
<div className="bg-card rounded-[4rem] border border-border/60 shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-700 group flex flex-col xl:flex-row">
|
<div className="bg-card rounded-[4rem] border border-border/60 shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-700 group flex flex-col xl:flex-row">
|
||||||
<div className="xl:w-1/3 p-12 md:p-16 border-b xl:border-b-0 xl:border-r border-border/40 bg-gradient-to-br from-violet-500/[0.05] to-transparent relative overflow-hidden">
|
<div className="xl:w-1/3 p-12 md:p-16 border-b xl:border-b-0 xl:border-r border-border/40 bg-gradient-to-br from-zinc-500/[0.05] to-transparent relative overflow-hidden">
|
||||||
<div className="absolute -right-10 -bottom-10 opacity-[0.03] group-hover:opacity-[0.08] transition-all duration-700 group-hover:scale-125 group-hover:-rotate-6">
|
<div className="absolute -right-10 -bottom-10 opacity-[0.03] group-hover:opacity-[0.08] transition-all duration-700 group-hover:scale-125 group-hover:-rotate-6">
|
||||||
<MessageSquare className="h-80 w-80 text-violet-500" />
|
<MessageSquare className="h-80 w-80 text-zinc-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative space-y-8">
|
<div className="relative space-y-8">
|
||||||
<div className="w-20 h-20 rounded-[1.5rem] bg-background flex items-center justify-center text-4xl shadow-2xl border border-border/50 group-hover:scale-110 transition-transform duration-500">
|
<div className="w-20 h-20 rounded-[1.5rem] bg-background flex items-center justify-center text-4xl shadow-2xl border border-border/50 group-hover:scale-110 transition-transform duration-500">
|
||||||
@@ -116,12 +116,12 @@ export default function AITestPage() {
|
|||||||
<p className="text-lg text-muted-foreground font-bold opacity-80 leading-relaxed">{t('admin.aiTest.chatTestDescription')}</p>
|
<p className="text-lg text-muted-foreground font-bold opacity-80 leading-relaxed">{t('admin.aiTest.chatTestDescription')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<span className="px-4 py-2 bg-violet-500/10 rounded-xl text-violet-600 text-[10px] font-black uppercase tracking-widest">Conversational</span>
|
<span className="px-4 py-2 bg-zinc-500/10 rounded-xl text-zinc-600 text-[10px] font-black uppercase tracking-widest">Conversational</span>
|
||||||
<span className="px-4 py-2 bg-violet-500/10 rounded-xl text-violet-600 text-[10px] font-black uppercase tracking-widest">Streaming</span>
|
<span className="px-4 py-2 bg-zinc-500/10 rounded-xl text-zinc-600 text-[10px] font-black uppercase tracking-widest">Streaming</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="xl:w-2/3 p-12 md:p-16 bg-gradient-to-l from-transparent to-violet-500/[0.01]">
|
<div className="xl:w-2/3 p-12 md:p-16 bg-gradient-to-l from-transparent to-zinc-500/[0.01]">
|
||||||
<div className="max-w-4xl">
|
<div className="max-w-4xl">
|
||||||
<AI_TESTER type="chat" />
|
<AI_TESTER type="chat" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
|||||||
{/* Chat Provider */}
|
{/* Chat Provider */}
|
||||||
<div className={`space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 ${activeAiTab === 'chat' ? 'block' : 'hidden'}`}>
|
<div className={`space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 ${activeAiTab === 'chat' ? 'block' : 'hidden'}`}>
|
||||||
<h3 className="text-base font-semibold flex items-center gap-2">
|
<h3 className="text-base font-semibold flex items-center gap-2">
|
||||||
<span className="text-blue-600">💬</span> {t('admin.ai.chatProvider')}
|
<span className="text-zinc-600">💬</span> {t('admin.ai.chatProvider')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-muted-foreground">{t('admin.ai.chatDescription')}</p>
|
<p className="text-xs text-muted-foreground">{t('admin.ai.chatDescription')}</p>
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default async function MainLayout({
|
|||||||
<Sidebar user={session?.user} />
|
<Sidebar user={session?.user} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<main className="memento-paper-texture flex min-h-0 flex-1 flex-col overflow-y-auto scroll-smooth">
|
<main className="flex min-h-0 flex-1 flex-col overflow-y-auto scroll-smooth bg-background">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export default function DataSettingsPage() {
|
|||||||
{/* Export card */}
|
{/* Export card */}
|
||||||
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="w-12 h-12 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-600 shrink-0">
|
<div className="w-12 h-12 rounded-full bg-zinc-500/10 flex items-center justify-center text-zinc-600 shrink-0">
|
||||||
<Download className="h-6 w-6" />
|
<Download className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -117,12 +117,10 @@
|
|||||||
/* Architectural Grid — texture & navigation (réf. architectural-grid1) */
|
/* Architectural Grid — texture & navigation (réf. architectural-grid1) */
|
||||||
.memento-paper-texture {
|
.memento-paper-texture {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
background-image: url("https://www.transparenttextures.com/patterns/natural-paper.png");
|
|
||||||
background-size: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .memento-paper-texture {
|
html.dark .memento-paper-texture {
|
||||||
background-image: none;
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.memento-sidebar-depth {
|
.memento-sidebar-depth {
|
||||||
@@ -353,15 +351,15 @@ html.dark .memento-active-nav {
|
|||||||
--popover-foreground: #212529;
|
--popover-foreground: #212529;
|
||||||
--primary: #ACB995;
|
--primary: #ACB995;
|
||||||
--primary-foreground: #F9F8F6;
|
--primary-foreground: #F9F8F6;
|
||||||
--secondary: #75B2D6;
|
--secondary: #E9ECEF;
|
||||||
--secondary-foreground: #212529;
|
--secondary-foreground: #212529;
|
||||||
--muted: #75B2D6;
|
--muted: #F1F3F5;
|
||||||
--muted-foreground: rgba(33, 37, 41, 0.6);
|
--muted-foreground: rgba(33, 37, 41, 0.6);
|
||||||
--accent: #75B2D6;
|
--accent: #F8F9FA;
|
||||||
--accent-foreground: #212529;
|
--accent-foreground: #212529;
|
||||||
--destructive: #E11D48;
|
--destructive: #E11D48;
|
||||||
--border: #75B2D6;
|
--border: rgba(0, 0, 0, 0.08);
|
||||||
--input: #75B2D6;
|
--input: rgba(0, 0, 0, 0.08);
|
||||||
--ring: rgba(33, 37, 41, 0.3);
|
--ring: rgba(33, 37, 41, 0.3);
|
||||||
--ai-accent: #ACB995;
|
--ai-accent: #ACB995;
|
||||||
--pinned-gold: #F59E0B;
|
--pinned-gold: #F59E0B;
|
||||||
@@ -373,7 +371,7 @@ html.dark .memento-active-nav {
|
|||||||
--sidebar-primary-foreground: #F9F8F6;
|
--sidebar-primary-foreground: #F9F8F6;
|
||||||
--sidebar-accent: #F9F8F6;
|
--sidebar-accent: #F9F8F6;
|
||||||
--sidebar-accent-foreground: #212529;
|
--sidebar-accent-foreground: #212529;
|
||||||
--sidebar-border: #75B2D6;
|
--sidebar-border: rgba(0, 0, 0, 0.05);
|
||||||
--sidebar-ring: rgba(33, 37, 41, 0.2);
|
--sidebar-ring: rgba(33, 37, 41, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,139 +477,177 @@ html.dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='midnight'] {
|
[data-theme='midnight'] {
|
||||||
--background: oklch(0.94 0.005 250);
|
--background: #F8F9FA;
|
||||||
/* Gris-bleu très pâle */
|
--foreground: #212529;
|
||||||
--foreground: oklch(0.18 0.03 250);
|
--card: #ffffff;
|
||||||
/* Gris-bleu très foncé */
|
--card-foreground: #212529;
|
||||||
--card: oklch(0.97 0.006 250);
|
--primary: #ACB995;
|
||||||
/* Gris-bleu pâle */
|
--primary-foreground: #F8F9FA;
|
||||||
--card-foreground: oklch(0.18 0.03 250);
|
--secondary: #E9ECEF;
|
||||||
--primary: oklch(0.5 0.12 250);
|
--secondary-foreground: #212529;
|
||||||
/* Gris-bleu saturé */
|
--muted: #F1F3F5;
|
||||||
--primary-foreground: oklch(0.99 0 0);
|
--muted-foreground: rgba(33, 37, 41, 0.6);
|
||||||
/* Blanc */
|
--accent: #F8F9FA;
|
||||||
--secondary: oklch(0.2 0.01 250);
|
--accent-foreground: #212529;
|
||||||
--secondary-foreground: oklch(0.18 0.03 250);
|
--destructive: #E11D48;
|
||||||
--muted: oklch(0.22 0.01 250);
|
--border: rgba(0, 0, 0, 0.08);
|
||||||
--muted-foreground: oklch(0.55 0.02 250);
|
--input: rgba(0, 0, 0, 0.08);
|
||||||
--accent: oklch(0.25 0.015 250);
|
--ring: rgba(33, 37, 41, 0.3);
|
||||||
--accent-foreground: oklch(0.18 0.03 250);
|
--popover: #ffffff;
|
||||||
--destructive: oklch(0.6 0.22 25);
|
--popover-foreground: #212529;
|
||||||
--border: oklch(0.82 0.015 250);
|
--sidebar: #ffffff;
|
||||||
--input: oklch(0.82 0.015 250);
|
--sidebar-foreground: #212529;
|
||||||
--ring: oklch(0.65 0.015 250);
|
--sidebar-primary: #212529;
|
||||||
--popover: oklch(0.97 0.006 250);
|
--sidebar-primary-foreground: #F8F9FA;
|
||||||
--popover-foreground: oklch(0.18 0.03 250);
|
--sidebar-accent: #F8F9FA;
|
||||||
--sidebar: oklch(0.9 0.01 250);
|
--sidebar-accent-foreground: #212529;
|
||||||
--sidebar-foreground: oklch(0.18 0.03 250);
|
--sidebar-border: rgba(0, 0, 0, 0.05);
|
||||||
--sidebar-primary: oklch(0.5 0.12 250);
|
--sidebar-ring: rgba(33, 37, 41, 0.2);
|
||||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
|
||||||
--sidebar-accent: oklch(0.25 0.015 250);
|
|
||||||
--sidebar-accent-foreground: oklch(0.18 0.03 250);
|
|
||||||
--sidebar-border: oklch(0.85 0.015 250);
|
|
||||||
--sidebar-ring: oklch(0.65 0.015 250);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='midnight'].dark {
|
[data-theme='midnight'].dark {
|
||||||
--background: oklch(0.1 0.01 250);
|
--background: #1C1C1C;
|
||||||
/* Noir profond */
|
--foreground: #F8F9FA;
|
||||||
--foreground: oklch(0.96 0.005 250);
|
--card: #252525;
|
||||||
/* Blanc grisâtre */
|
--card-foreground: #F8F9FA;
|
||||||
--card: oklch(0.15 0.015 250);
|
--primary: #ACB995;
|
||||||
/* Gris-bleu très foncé */
|
--primary-foreground: #1C1C1C;
|
||||||
--card-foreground: oklch(0.96 0.005 250);
|
--secondary: #2A2A2A;
|
||||||
--primary: oklch(0.6 0.12 250);
|
--secondary-foreground: #F8F9FA;
|
||||||
/* Gris-bleu vibrant */
|
--muted: #222222;
|
||||||
--primary-foreground: oklch(0.1 0 0);
|
--muted-foreground: rgba(248, 249, 250, 0.5);
|
||||||
/* Noir */
|
--accent: #2A2A2A;
|
||||||
--secondary: oklch(0.18 0.015 250);
|
--accent-foreground: #F8F9FA;
|
||||||
--secondary-foreground: oklch(0.96 0.005 250);
|
--destructive: #E11D48;
|
||||||
--muted: oklch(0.2 0.015 250);
|
--border: rgba(255, 255, 255, 0.1);
|
||||||
--muted-foreground: oklch(0.5 0.02 250);
|
--input: rgba(255, 255, 255, 0.1);
|
||||||
--accent: oklch(0.26 0.02 250);
|
--ring: rgba(255, 255, 255, 0.2);
|
||||||
--accent-foreground: oklch(0.96 0.005 250);
|
--popover: #252525;
|
||||||
--destructive: oklch(0.65 0.2 25);
|
--popover-foreground: #F8F9FA;
|
||||||
--border: oklch(0.33 0.02 250);
|
--sidebar: #181818;
|
||||||
--input: oklch(0.33 0.02 250);
|
--sidebar-foreground: #F8F9FA;
|
||||||
--ring: oklch(0.55 0.02 250);
|
--sidebar-primary: #F8F9FA;
|
||||||
--popover: oklch(0.15 0.015 250);
|
--sidebar-primary-foreground: #1C1C1C;
|
||||||
--popover-foreground: oklch(0.96 0.005 250);
|
--sidebar-accent: #2A2A2A;
|
||||||
--sidebar: oklch(0.08 0.01 250);
|
--sidebar-accent-foreground: #F8F9FA;
|
||||||
--sidebar-foreground: oklch(0.96 0.005 250);
|
--sidebar-border: rgba(255, 255, 255, 0.08);
|
||||||
--sidebar-primary: oklch(0.6 0.12 250);
|
--sidebar-ring: rgba(255, 255, 255, 0.15);
|
||||||
--sidebar-primary-foreground: oklch(0.1 0 0);
|
|
||||||
--sidebar-accent: oklch(0.22 0.02 250);
|
|
||||||
--sidebar-accent-foreground: oklch(0.96 0.005 250);
|
|
||||||
--sidebar-border: oklch(0.3 0.02 250);
|
|
||||||
--sidebar-ring: oklch(0.55 0.02 250);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='blue'] {
|
[data-theme='blue'] {
|
||||||
--background: oklch(0.985 0.005 225);
|
--background: #F8F9FA;
|
||||||
/* Blanc légèrement bleuté */
|
--foreground: #212529;
|
||||||
--foreground: oklch(0.18 0.035 225);
|
--card: #ffffff;
|
||||||
/* Gris-bleu foncé saturé */
|
--card-foreground: #212529;
|
||||||
--card: oklch(1 0 0);
|
--primary: #ACB995;
|
||||||
/* Blanc pur */
|
--primary-foreground: #F8F9FA;
|
||||||
--card-foreground: oklch(0.18 0.035 225);
|
--secondary: #E9ECEF;
|
||||||
--primary: oklch(0.5 0.15 225);
|
--secondary-foreground: #212529;
|
||||||
/* Bleu vibrant */
|
--muted: #F1F3F5;
|
||||||
--primary-foreground: oklch(0.99 0 0);
|
--muted-foreground: rgba(33, 37, 41, 0.6);
|
||||||
/* Blanc */
|
--accent: #F8F9FA;
|
||||||
--secondary: oklch(0.93 0.008 225);
|
--accent-foreground: #212529;
|
||||||
--secondary-foreground: oklch(0.18 0.035 225);
|
--destructive: #E11D48;
|
||||||
--muted: oklch(0.9 0.01 225);
|
--border: rgba(0, 0, 0, 0.08);
|
||||||
--muted-foreground: oklch(0.58 0.015 225);
|
--input: rgba(0, 0, 0, 0.08);
|
||||||
--accent: oklch(0.93 0.01 225);
|
--ring: rgba(33, 37, 41, 0.3);
|
||||||
--accent-foreground: oklch(0.18 0.035 225);
|
--popover: #ffffff;
|
||||||
--destructive: oklch(0.6 0.2 25);
|
--popover-foreground: #212529;
|
||||||
--border: oklch(0.83 0.012 225);
|
--sidebar: #ffffff;
|
||||||
--input: oklch(0.83 0.012 225);
|
--sidebar-foreground: #212529;
|
||||||
--ring: oklch(0.65 0.015 225);
|
--sidebar-primary: #212529;
|
||||||
--popover: oklch(1 0 0);
|
--sidebar-primary-foreground: #F8F9FA;
|
||||||
--popover-foreground: oklch(0.18 0.035 225);
|
--sidebar-accent: #F8F9FA;
|
||||||
--sidebar: oklch(0.965 0.008 225);
|
--sidebar-accent-foreground: #212529;
|
||||||
--sidebar-foreground: oklch(0.18 0.035 225);
|
--sidebar-border: rgba(0, 0, 0, 0.05);
|
||||||
--sidebar-primary: oklch(0.5 0.15 225);
|
--sidebar-ring: rgba(33, 37, 41, 0.2);
|
||||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
|
||||||
--sidebar-accent: oklch(0.93 0.01 225);
|
|
||||||
--sidebar-accent-foreground: oklch(0.18 0.035 225);
|
|
||||||
--sidebar-border: oklch(0.87 0.012 225);
|
|
||||||
--sidebar-ring: oklch(0.65 0.015 225);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='blue'].dark {
|
[data-theme='blue'].dark {
|
||||||
--background: oklch(0.13 0.008 225);
|
--background: #1C1C1C;
|
||||||
/* Noir légèrement bleuté */
|
--foreground: #F8F9FA;
|
||||||
--foreground: oklch(0.97 0.006 225);
|
--card: #252525;
|
||||||
/* Blanc légèrement bleuté */
|
--card-foreground: #F8F9FA;
|
||||||
--card: oklch(0.17 0.01 225);
|
--primary: #ACB995;
|
||||||
/* Gris-bleu foncé */
|
--primary-foreground: #1C1C1C;
|
||||||
--card-foreground: oklch(0.97 0.006 225);
|
--secondary: #2A2A2A;
|
||||||
--primary: oklch(0.6 0.15 225);
|
--secondary-foreground: #F8F9FA;
|
||||||
/* Bleu vibrant plus clair */
|
--muted: #222222;
|
||||||
--primary-foreground: oklch(0.1 0 0);
|
--muted-foreground: rgba(248, 249, 250, 0.5);
|
||||||
/* Noir */
|
--accent: #2A2A2A;
|
||||||
--secondary: oklch(0.22 0.015 225);
|
--accent-foreground: #F8F9FA;
|
||||||
--secondary-foreground: oklch(0.97 0.006 225);
|
--destructive: #E11D48;
|
||||||
--muted: oklch(0.25 0.02 225);
|
--border: rgba(255, 255, 255, 0.1);
|
||||||
--muted-foreground: oklch(0.52 0.018 225);
|
--input: rgba(255, 255, 255, 0.1);
|
||||||
--accent: oklch(0.28 0.025 225);
|
--ring: rgba(255, 255, 255, 0.2);
|
||||||
--accent-foreground: oklch(0.97 0.006 225);
|
--popover: #252525;
|
||||||
--destructive: oklch(0.65 0.22 25);
|
--popover-foreground: #F8F9FA;
|
||||||
--border: oklch(0.35 0.018 225);
|
--sidebar: #181818;
|
||||||
--input: oklch(0.35 0.018 225);
|
--sidebar-foreground: #F8F9FA;
|
||||||
--ring: oklch(0.55 0.02 225);
|
--sidebar-primary: #F8F9FA;
|
||||||
--popover: oklch(0.17 0.01 225);
|
--sidebar-primary-foreground: #1C1C1C;
|
||||||
--popover-foreground: oklch(0.97 0.006 225);
|
--sidebar-accent: #2A2A2A;
|
||||||
--sidebar: oklch(0.1 0.01 225);
|
--sidebar-accent-foreground: #F8F9FA;
|
||||||
--sidebar-foreground: oklch(0.97 0.006 225);
|
--sidebar-border: rgba(255, 255, 255, 0.08);
|
||||||
--sidebar-primary: oklch(0.6 0.15 225);
|
--sidebar-ring: rgba(255, 255, 255, 0.15);
|
||||||
--sidebar-primary-foreground: oklch(0.1 0 0);
|
}
|
||||||
--sidebar-accent: oklch(0.25 0.025 225);
|
|
||||||
--sidebar-accent-foreground: oklch(0.97 0.006 225);
|
[data-theme='ocean'] {
|
||||||
--sidebar-border: oklch(0.32 0.018 225);
|
--background: #F8F9FA;
|
||||||
--sidebar-ring: oklch(0.55 0.02 225);
|
--foreground: #212529;
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-foreground: #212529;
|
||||||
|
--primary: #ACB995;
|
||||||
|
--primary-foreground: #F8F9FA;
|
||||||
|
--secondary: #E9ECEF;
|
||||||
|
--secondary-foreground: #212529;
|
||||||
|
--muted: #F1F3F5;
|
||||||
|
--muted-foreground: rgba(33, 37, 41, 0.6);
|
||||||
|
--accent: #F8F9FA;
|
||||||
|
--accent-foreground: #212529;
|
||||||
|
--destructive: #E11D48;
|
||||||
|
--border: rgba(0, 0, 0, 0.08);
|
||||||
|
--input: rgba(0, 0, 0, 0.08);
|
||||||
|
--ring: rgba(33, 37, 41, 0.3);
|
||||||
|
--popover: #ffffff;
|
||||||
|
--popover-foreground: #212529;
|
||||||
|
--sidebar: #ffffff;
|
||||||
|
--sidebar-foreground: #212529;
|
||||||
|
--sidebar-primary: #212529;
|
||||||
|
--sidebar-primary-foreground: #F8F9FA;
|
||||||
|
--sidebar-accent: #F8F9FA;
|
||||||
|
--sidebar-accent-foreground: #212529;
|
||||||
|
--sidebar-border: rgba(0, 0, 0, 0.05);
|
||||||
|
--sidebar-ring: rgba(33, 37, 41, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='ocean'].dark {
|
||||||
|
--background: #1C1C1C;
|
||||||
|
--foreground: #F8F9FA;
|
||||||
|
--card: #252525;
|
||||||
|
--card-foreground: #F8F9FA;
|
||||||
|
--primary: #ACB995;
|
||||||
|
--primary-foreground: #1C1C1C;
|
||||||
|
--secondary: #2A2A2A;
|
||||||
|
--secondary-foreground: #F8F9FA;
|
||||||
|
--muted: #222222;
|
||||||
|
--muted-foreground: rgba(248, 249, 250, 0.5);
|
||||||
|
--accent: #2A2A2A;
|
||||||
|
--accent-foreground: #F8F9FA;
|
||||||
|
--destructive: #E11D48;
|
||||||
|
--border: rgba(255, 255, 255, 0.1);
|
||||||
|
--input: rgba(255, 255, 255, 0.1);
|
||||||
|
--ring: rgba(255, 255, 255, 0.2);
|
||||||
|
--popover: #252525;
|
||||||
|
--popover-foreground: #F8F9FA;
|
||||||
|
--sidebar: #181818;
|
||||||
|
--sidebar-foreground: #F8F9FA;
|
||||||
|
--sidebar-primary: #F8F9FA;
|
||||||
|
--sidebar-primary-foreground: #1C1C1C;
|
||||||
|
--sidebar-accent: #2A2A2A;
|
||||||
|
--sidebar-accent-foreground: #F8F9FA;
|
||||||
|
--sidebar-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--sidebar-ring: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='sepia'] {
|
[data-theme='sepia'] {
|
||||||
@@ -922,64 +958,7 @@ html.dark {
|
|||||||
--sidebar-ring: oklch(0.6 0.02 55);
|
--sidebar-ring: oklch(0.6 0.02 55);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ocean — soft teal/cyan tones */
|
|
||||||
[data-theme='ocean'] {
|
|
||||||
--background: oklch(0.97 0.01 195);
|
|
||||||
--foreground: oklch(0.22 0.02 195);
|
|
||||||
--card: oklch(0.99 0.005 195);
|
|
||||||
--card-foreground: oklch(0.22 0.02 195);
|
|
||||||
--popover: oklch(0.99 0.005 195);
|
|
||||||
--popover-foreground: oklch(0.22 0.02 195);
|
|
||||||
--primary: oklch(0.5 0.12 195);
|
|
||||||
--primary-foreground: oklch(0.99 0 0);
|
|
||||||
--secondary: oklch(0.93 0.015 195);
|
|
||||||
--secondary-foreground: oklch(0.22 0.02 195);
|
|
||||||
--muted: oklch(0.92 0.012 195);
|
|
||||||
--muted-foreground: oklch(0.55 0.02 195);
|
|
||||||
--accent: oklch(0.92 0.015 195);
|
|
||||||
--accent-foreground: oklch(0.22 0.02 195);
|
|
||||||
--destructive: oklch(0.6 0.18 25);
|
|
||||||
--border: oklch(0.85 0.015 195);
|
|
||||||
--input: oklch(0.85 0.015 195);
|
|
||||||
--ring: oklch(0.5 0.12 195);
|
|
||||||
--sidebar: oklch(0.95 0.012 195);
|
|
||||||
--sidebar-foreground: oklch(0.22 0.02 195);
|
|
||||||
--sidebar-primary: oklch(0.5 0.12 195);
|
|
||||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
|
||||||
--sidebar-accent: oklch(0.92 0.015 195);
|
|
||||||
--sidebar-accent-foreground: oklch(0.22 0.02 195);
|
|
||||||
--sidebar-border: oklch(0.88 0.015 195);
|
|
||||||
--sidebar-ring: oklch(0.6 0.015 195);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme='ocean'].dark {
|
|
||||||
--background: oklch(0.14 0.01 195);
|
|
||||||
--foreground: oklch(0.95 0.01 195);
|
|
||||||
--card: oklch(0.18 0.015 195);
|
|
||||||
--card-foreground: oklch(0.95 0.01 195);
|
|
||||||
--popover: oklch(0.18 0.015 195);
|
|
||||||
--popover-foreground: oklch(0.95 0.01 195);
|
|
||||||
--primary: oklch(0.6 0.1 195);
|
|
||||||
--primary-foreground: oklch(0.1 0 0);
|
|
||||||
--secondary: oklch(0.24 0.02 195);
|
|
||||||
--secondary-foreground: oklch(0.95 0.01 195);
|
|
||||||
--muted: oklch(0.22 0.015 195);
|
|
||||||
--muted-foreground: oklch(0.55 0.02 195);
|
|
||||||
--accent: oklch(0.26 0.02 195);
|
|
||||||
--accent-foreground: oklch(0.95 0.01 195);
|
|
||||||
--destructive: oklch(0.65 0.18 25);
|
|
||||||
--border: oklch(0.33 0.02 195);
|
|
||||||
--input: oklch(0.33 0.02 195);
|
|
||||||
--ring: oklch(0.6 0.02 195);
|
|
||||||
--sidebar: oklch(0.12 0.01 195);
|
|
||||||
--sidebar-foreground: oklch(0.95 0.01 195);
|
|
||||||
--sidebar-primary: oklch(0.6 0.1 195);
|
|
||||||
--sidebar-primary-foreground: oklch(0.1 0 0);
|
|
||||||
--sidebar-accent: oklch(0.26 0.02 195);
|
|
||||||
--sidebar-accent-foreground: oklch(0.95 0.01 195);
|
|
||||||
--sidebar-border: oklch(0.33 0.02 195);
|
|
||||||
--sidebar-ring: oklch(0.6 0.02 195);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sunset — warm coral/peach tones */
|
/* Sunset — warm coral/peach tones */
|
||||||
[data-theme='sunset'] {
|
[data-theme='sunset'] {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn(
|
<aside className={cn(
|
||||||
"fixed bottom-20 right-6 border border-border/40 bg-card flex flex-col z-40 shadow-2xl rounded-2xl overflow-hidden transition-all duration-300",
|
"fixed bottom-20 right-6 border border-border/40 bg-[#FDFDFE] flex flex-col z-40 shadow-2xl rounded-2xl overflow-hidden transition-all duration-300",
|
||||||
isExpanded ? "w-[80vw] h-[85vh] max-w-[1200px]" : "h-[700px] max-h-[85vh] w-[360px]"
|
isExpanded ? "w-[80vw] h-[85vh] max-w-[1200px]" : "h-[700px] max-h-[85vh] w-[360px]"
|
||||||
)}>
|
)}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -255,7 +255,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
|||||||
<div className={cn(
|
<div className={cn(
|
||||||
'w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 border text-[10px] font-bold',
|
'w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 border text-[10px] font-bold',
|
||||||
msg.role === 'user'
|
msg.role === 'user'
|
||||||
? 'bg-slate-100 dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300'
|
? 'bg-zinc-100 border-zinc-200 text-zinc-600'
|
||||||
: 'bg-[#75B2D6]/10 text-[#75B2D6] border-[#75B2D6]/20',
|
: 'bg-[#75B2D6]/10 text-[#75B2D6] border-[#75B2D6]/20',
|
||||||
)}>
|
)}>
|
||||||
{msg.role === 'user' ? 'U' : <Bot className="h-4 w-4" />}
|
{msg.role === 'user' ? 'U' : <Bot className="h-4 w-4" />}
|
||||||
@@ -350,7 +350,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
|||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<span className="text-[9px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5 ml-1">{t('ai.discussionContextLabel')}</span>
|
<span className="text-[9px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5 ml-1">{t('ai.discussionContextLabel')}</span>
|
||||||
<Select value={chatScope} onValueChange={setChatScope}>
|
<Select value={chatScope} onValueChange={setChatScope}>
|
||||||
<SelectTrigger className="h-8 text-xs bg-card border-border/60">
|
<SelectTrigger className="h-8 text-xs bg-[#FDFDFE] border-border/60">
|
||||||
<SelectValue placeholder={t('ai.selectNotebook')} />
|
<SelectValue placeholder={t('ai.selectNotebook')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -388,7 +388,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
|||||||
"py-1 rounded-md border text-[10px] font-medium transition-all flex flex-col items-center justify-center gap-0.5",
|
"py-1 rounded-md border text-[10px] font-medium transition-all flex flex-col items-center justify-center gap-0.5",
|
||||||
isSelected
|
isSelected
|
||||||
? "border-[#75B2D6] bg-[#75B2D6]/10 text-[#75B2D6] shadow-sm"
|
? "border-[#75B2D6] bg-[#75B2D6]/10 text-[#75B2D6] shadow-sm"
|
||||||
: "border-border/60 bg-card text-muted-foreground hover:bg-muted hover:border-border"
|
: "border-border/60 bg-[#FDFDFE] text-muted-foreground hover:bg-muted hover:border-border"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="h-3 w-3" />
|
<Icon className="h-3 w-3" />
|
||||||
@@ -400,7 +400,7 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text Input */}
|
{/* Text Input */}
|
||||||
<div className="relative bg-card border border-border/60 rounded-xl p-1 focus-within:border-[#75B2D6] focus-within:ring-1 focus-within:ring-[#75B2D6]/20 transition-all shadow-sm">
|
<div className="relative bg-[#FDFDFE] border border-border/60 rounded-xl p-1 focus-within:border-[#75B2D6] focus-within:ring-1 focus-within:ring-[#75B2D6]/20 transition-all shadow-sm">
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full bg-transparent border-none focus:ring-0 resize-none text-sm text-foreground placeholder:text-muted-foreground/70 p-2 min-h-[60px] max-h-[120px]"
|
className="w-full bg-transparent border-none focus:ring-0 resize-none text-sm text-foreground placeholder:text-muted-foreground/70 p-2 min-h-[60px] max-h-[120px]"
|
||||||
placeholder={t('ai.chatPlaceholder')}
|
placeholder={t('ai.chatPlaceholder')}
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ export function ContextualAIChat({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<aside className={cn(
|
<aside className={cn(
|
||||||
'border-l border-border bg-[#F9F8F6] flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
|
'border-l border-border bg-[#FDFDFE] flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
|
||||||
expanded
|
expanded
|
||||||
? 'fixed right-0 top-0 h-screen w-[640px] z-[200]'
|
? 'fixed right-0 top-0 h-screen w-[640px] z-[200]'
|
||||||
: 'h-full w-[360px]',
|
: 'h-full w-[360px]',
|
||||||
@@ -492,7 +492,7 @@ export function ContextualAIChat({
|
|||||||
<div className="p-6 border-b border-border shrink-0">
|
<div className="p-6 border-b border-border shrink-0">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="min-w-0 space-y-2">
|
<div className="min-w-0 space-y-2">
|
||||||
<h2 className="font-serif text-xl font-medium text-[#D4A373] flex items-center gap-2 leading-tight">
|
<h2 className="font-serif text-xl font-medium text-[#1C1C1C] flex items-center gap-2 leading-tight">
|
||||||
<Sparkles className="h-[18px] w-[18px] shrink-0 text-[#D4A373]" />
|
<Sparkles className="h-[18px] w-[18px] shrink-0 text-[#D4A373]" />
|
||||||
IA Assistant
|
IA Assistant
|
||||||
</h2>
|
</h2>
|
||||||
@@ -534,7 +534,7 @@ export function ContextualAIChat({
|
|||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
{activeTab === tab.id && (
|
{activeTab === tab.id && (
|
||||||
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-[#D4A373]" />
|
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-[#75B2D6]" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -542,9 +542,9 @@ export function ContextualAIChat({
|
|||||||
|
|
||||||
<div className="flex-1 flex flex-col min-h-0 relative">
|
<div className="flex-1 flex flex-col min-h-0 relative">
|
||||||
{actionPreview && (
|
{actionPreview && (
|
||||||
<div className="absolute inset-0 z-20 flex flex-col bg-[#F2F0E9]/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
|
<div className="absolute inset-0 z-20 flex flex-col bg-[#FDFDFE]/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
|
||||||
<div className="px-6 py-4 border-b border-border flex items-center justify-between shrink-0">
|
<div className="px-6 py-4 border-b border-border flex items-center justify-between shrink-0">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-widest text-[#D4A373]">{actionPreview.label}</p>
|
<p className="text-[10px] font-bold uppercase tracking-widest text-[#75B2D6]">{actionPreview.label}</p>
|
||||||
<button onClick={handleDiscardPreview} className="text-[#1C1C1C]/40 hover:text-[#1C1C1C]"><X size={18} /></button>
|
<button onClick={handleDiscardPreview} className="text-[#1C1C1C]/40 hover:text-[#1C1C1C]"><X size={18} /></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
|
||||||
@@ -554,13 +554,13 @@ export function ContextualAIChat({
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-6 border-t border-border flex gap-3 shrink-0">
|
<div className="p-6 border-t border-border flex gap-3 shrink-0">
|
||||||
<button onClick={handleDiscardPreview} className="flex-1 py-3.5 text-[10px] font-bold uppercase tracking-widest text-[#1C1C1C]/40 hover:text-[#1C1C1C] transition-all">ANNULER</button>
|
<button onClick={handleDiscardPreview} className="flex-1 py-3.5 text-[10px] font-bold uppercase tracking-widest text-[#1C1C1C]/40 hover:text-[#1C1C1C] transition-all">ANNULER</button>
|
||||||
<button onClick={handleApplyPreview} className="flex-1 py-3.5 bg-[#1C1C1C] text-[#F2F0E9] rounded-xl text-[10px] font-bold uppercase tracking-widest shadow-lg transition-all hover:opacity-90">APPLIQUER À LA NOTE</button>
|
<button onClick={handleApplyPreview} className="flex-1 py-3.5 bg-[#1C1C1C] text-[#FDFDFE] rounded-xl text-[10px] font-bold uppercase tracking-widest shadow-lg transition-all hover:opacity-90">APPLIQUER À LA NOTE</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{resourcePreview && (
|
{resourcePreview && (
|
||||||
<div className="absolute inset-0 z-20 flex flex-col bg-[#F2F0E9]/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
|
<div className="absolute inset-0 z-20 flex flex-col bg-[#FDFDFE]/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
|
||||||
<div className="px-6 py-4 border-b border-border/40 flex items-center justify-between shrink-0">
|
<div className="px-6 py-4 border-b border-border/40 flex items-center justify-between shrink-0">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-widest text-[#75B2D6]">
|
<p className="text-[10px] font-bold uppercase tracking-widest text-[#75B2D6]">
|
||||||
{resourcePreview.source === 'chat' ? 'Injecter depuis Discussion' : 'Aperçu IA'}
|
{resourcePreview.source === 'chat' ? 'Injecter depuis Discussion' : 'Aperçu IA'}
|
||||||
@@ -594,7 +594,7 @@ export function ContextualAIChat({
|
|||||||
{messages.length === 0 && (
|
{messages.length === 0 && (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-center space-y-6 py-12">
|
<div className="h-full flex flex-col items-center justify-center text-center space-y-6 py-12">
|
||||||
<div className="w-20 h-20 rounded-full bg-white/40 backdrop-blur-sm border border-dashed border-border flex items-center justify-center shadow-sm">
|
<div className="w-20 h-20 rounded-full bg-white/40 backdrop-blur-sm border border-dashed border-border flex items-center justify-center shadow-sm">
|
||||||
<MessageSquare size={32} className="text-[#D4A373]/30" />
|
<MessageSquare size={32} className="text-[#75B2D6]/60" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-serif italic text-[#1C1C1C]/40 leading-relaxed max-w-[200px]">Posez une question à l'Assistant pour commencer.</p>
|
<p className="text-xs font-serif italic text-[#1C1C1C]/40 leading-relaxed max-w-[200px]">Posez une question à l'Assistant pour commencer.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -606,12 +606,12 @@ export function ContextualAIChat({
|
|||||||
return (
|
return (
|
||||||
<div key={msg.id} className={cn('flex flex-col gap-3', !isAssistant && 'items-end')} onMouseEnter={() => isAssistant && setHoveredMsgId(msg.id)} onMouseLeave={() => setHoveredMsgId(null)}>
|
<div key={msg.id} className={cn('flex flex-col gap-3', !isAssistant && 'items-end')} onMouseEnter={() => isAssistant && setHoveredMsgId(msg.id)} onMouseLeave={() => setHoveredMsgId(null)}>
|
||||||
<div className="relative group max-w-[95%]">
|
<div className="relative group max-w-[95%]">
|
||||||
<div className={cn('p-5 rounded-2xl text-sm leading-relaxed transition-all shadow-sm', !isAssistant ? 'bg-[#1C1C1C] text-[#F2F0E9]' : 'bg-white/60 backdrop-blur-sm border border-border text-[#1C1C1C]')}>
|
<div className={cn('p-5 rounded-2xl text-sm leading-relaxed transition-all shadow-sm', !isAssistant ? 'bg-[#1C1C1C] text-[#FDFDFE]' : 'bg-white/60 backdrop-blur-sm border border-border text-[#1C1C1C]')}>
|
||||||
{isAssistant ? <MarkdownContent content={content} /> : <p className="font-medium">{content}</p>}
|
{isAssistant ? <MarkdownContent content={content} /> : <p className="font-medium">{content}</p>}
|
||||||
</div>
|
</div>
|
||||||
{isAssistant && onApplyToNote && (hoveredMsgId === msg.id || messages.at(-1)?.id === msg.id) && (
|
{isAssistant && onApplyToNote && (hoveredMsgId === msg.id || messages.at(-1)?.id === msg.id) && (
|
||||||
<div className="flex gap-2 mt-3 opacity-0 group-hover:opacity-100 transition-all">
|
<div className="flex gap-2 mt-3 opacity-0 group-hover:opacity-100 transition-all">
|
||||||
<button onClick={() => handleInjectFromChat(content, 'replace')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-[#1C1C1C] text-[#F2F0E9] hover:opacity-90">REPLACER</button>
|
<button onClick={() => handleInjectFromChat(content, 'replace')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-[#1C1C1C] text-[#FDFDFE] hover:opacity-90">REPLACER</button>
|
||||||
<button onClick={() => handleInjectFromChat(content, 'complete')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-white/40 backdrop-blur-sm border border-border text-[#1C1C1C] hover:bg-white/60">COMPLÉTER</button>
|
<button onClick={() => handleInjectFromChat(content, 'complete')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-white/40 backdrop-blur-sm border border-border text-[#1C1C1C] hover:bg-white/60">COMPLÉTER</button>
|
||||||
<button onClick={() => handleInjectFromChat(content, 'merge')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-white/40 backdrop-blur-sm border border-border text-[#1C1C1C] hover:bg-white/60">FUSIONNER</button>
|
<button onClick={() => handleInjectFromChat(content, 'merge')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-white/40 backdrop-blur-sm border border-border text-[#1C1C1C] hover:bg-white/60">FUSIONNER</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -623,7 +623,7 @@ export function ContextualAIChat({
|
|||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="bg-white/60 backdrop-blur-sm border border-border p-5 rounded-2xl shadow-sm w-fit">
|
<div className="bg-white/60 backdrop-blur-sm border border-border p-5 rounded-2xl shadow-sm w-fit">
|
||||||
<div className="flex gap-1.5"><span className="w-1.5 h-1.5 bg-[#D4A373] rounded-full animate-pulse" /><span className="w-1.5 h-1.5 bg-[#D4A373] rounded-full animate-pulse delay-75" /><span className="w-1.5 h-1.5 bg-[#D4A373] rounded-full animate-pulse delay-150" /></div>
|
<div className="flex gap-1.5"><span className="w-1.5 h-1.5 bg-[#75B2D6] rounded-full animate-pulse" /><span className="w-1.5 h-1.5 bg-[#75B2D6] rounded-full animate-pulse delay-75" /><span className="w-1.5 h-1.5 bg-[#75B2D6] rounded-full animate-pulse delay-150" /></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -642,7 +642,7 @@ export function ContextualAIChat({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</div>
|
</div>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="rounded-xl border-border shadow-xl bg-[#F2F0E9]">
|
<SelectContent className="rounded-xl border-border shadow-xl bg-[#FDFDFE]">
|
||||||
<SelectItem value="note" className="text-[11px] py-2.5 uppercase tracking-wider font-bold">Cette note</SelectItem>
|
<SelectItem value="note" className="text-[11px] py-2.5 uppercase tracking-wider font-bold">Cette note</SelectItem>
|
||||||
<SelectItem value="all" className="text-[11px] py-2.5 uppercase tracking-wider font-bold">Tout Momento</SelectItem>
|
<SelectItem value="all" className="text-[11px] py-2.5 uppercase tracking-wider font-bold">Tout Momento</SelectItem>
|
||||||
{notebooks.map(nb => (
|
{notebooks.map(nb => (
|
||||||
@@ -664,11 +664,11 @@ export function ContextualAIChat({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'h-[52px] rounded-xl border transition-all flex flex-col items-center justify-center gap-1.5 shadow-sm',
|
'h-[52px] rounded-xl border transition-all flex flex-col items-center justify-center gap-1.5 shadow-sm',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[#ACB995]/10 border-[#ACB995] text-[#ACB995]'
|
? 'bg-[#75B2D6]/10 border-[#75B2D6] text-[#75B2D6]'
|
||||||
: 'bg-white/60 border-border text-[#1C1C1C]/40 hover:border-[#1C1C1C]/20'
|
: 'bg-white/60 border-border text-[#1C1C1C]/40 hover:border-[#1C1C1C]/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon size={14} className={isActive ? 'text-[#ACB995]' : 'text-[#1C1C1C]/40'} />
|
<Icon size={14} className={isActive ? 'text-[#75B2D6]' : 'text-[#1C1C1C]/40'} />
|
||||||
<span className="text-[9px] font-bold uppercase tracking-tight">{tone.label}</span>
|
<span className="text-[9px] font-bold uppercase tracking-tight">{tone.label}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -680,7 +680,7 @@ export function ContextualAIChat({
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<textarea
|
<textarea
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full bg-white/60 border border-border rounded-2xl p-5 pr-14 text-sm outline-none focus:border-[#ACB995] transition-all resize-none leading-relaxed font-light custom-scrollbar shadow-sm text-[#1C1C1C]"
|
className="w-full bg-white/60 border border-border rounded-2xl p-5 pr-14 text-sm outline-none focus:border-[#75B2D6] transition-all resize-none leading-relaxed font-light custom-scrollbar shadow-sm text-[#1C1C1C]"
|
||||||
placeholder="Posez votre question sur cette note..."
|
placeholder="Posez votre question sur cette note..."
|
||||||
value={input}
|
value={input}
|
||||||
onChange={e => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
@@ -690,12 +690,12 @@ export function ContextualAIChat({
|
|||||||
<div className="absolute right-4 bottom-4 flex gap-2">
|
<div className="absolute right-4 bottom-4 flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setWebSearch(!webSearch)}
|
onClick={() => setWebSearch(!webSearch)}
|
||||||
className={cn("p-2.5 rounded-xl transition-colors", webSearch ? "text-[#ACB995] bg-[#ACB995]/10" : "text-[#1C1C1C]/20 hover:text-[#1C1C1C]")}
|
className={cn("p-2.5 rounded-xl transition-colors", webSearch ? "text-[#75B2D6] bg-[#75B2D6]/10" : "text-[#1C1C1C]/20 hover:text-[#1C1C1C]")}
|
||||||
title="Web Search"
|
title="Web Search"
|
||||||
>
|
>
|
||||||
<Globe size={18} />
|
<Globe size={18} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={handleSend} disabled={!input.trim() || isLoading} className="p-2.5 bg-[#ACB995] text-white rounded-xl transition-all hover:scale-105 active:scale-95 shadow-lg shadow-[#ACB995]/20 disabled:opacity-30">
|
<button onClick={handleSend} disabled={!input.trim() || isLoading} className="p-2.5 bg-[#75B2D6] text-white rounded-xl transition-all hover:scale-105 active:scale-95 shadow-lg shadow-[#75B2D6]/20 disabled:opacity-30">
|
||||||
<Send size={18} />
|
<Send size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -725,7 +725,7 @@ export function ContextualAIChat({
|
|||||||
initial={{ opacity: 0, y: -10 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
onClick={onUndoLastAction}
|
onClick={onUndoLastAction}
|
||||||
className="w-full py-3.5 bg-[#D4A373]/20 border border-[#D4A373]/50 rounded-xl flex items-center justify-center gap-2 text-[11px] font-bold text-[#D4A373] uppercase tracking-[0.2em] hover:bg-[#D4A373]/30 transition-all shadow-md"
|
className="w-full py-3.5 bg-[#75B2D6]/20 border border-[#75B2D6]/50 rounded-xl flex items-center justify-center gap-2 text-[11px] font-bold text-[#75B2D6] uppercase tracking-[0.2em] hover:bg-[#75B2D6]/30 transition-all shadow-md"
|
||||||
>
|
>
|
||||||
<RotateCcw size={12} /> {t('ai.undoLastAction')}
|
<RotateCcw size={12} /> {t('ai.undoLastAction')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
@@ -736,11 +736,11 @@ export function ContextualAIChat({
|
|||||||
const isActive = action.id === 'translate' && showLangPicker
|
const isActive = action.id === 'translate' && showLangPicker
|
||||||
const Icon = action.icon
|
const Icon = action.icon
|
||||||
return (
|
return (
|
||||||
<button key={i} onClick={() => action.id === 'translate' ? setShowLangPicker(v => !v) : handleAction(action)} disabled={!!actionLoading} className={cn("flex flex-col items-center gap-3 p-4 bg-white/40 backdrop-blur-sm border rounded-xl transition-all group shadow-sm", isActive ? "border-[#ACB995] bg-[#ACB995]/5" : "border-border hover:border-[#1C1C1C]/20")}>
|
<button key={i} onClick={() => action.id === 'translate' ? setShowLangPicker(v => !v) : handleAction(action)} disabled={!!actionLoading} className={cn("flex flex-col items-center gap-3 p-4 bg-white/40 backdrop-blur-sm border rounded-xl transition-all group shadow-sm", isActive ? "border-[#75B2D6] bg-[#75B2D6]/5" : "border-border hover:border-[#1C1C1C]/20")}>
|
||||||
<div className={cn("p-2 rounded-lg bg-white/60 transition-colors group-hover:bg-[#1C1C1C] group-hover:text-[#F2F0E9] shadow-sm", loading && "animate-pulse", isActive && "bg-[#ACB995] text-white")}>
|
<div className={cn("p-2 rounded-lg bg-white/60 transition-colors group-hover:bg-[#1C1C1C] group-hover:text-[#FDFDFE] shadow-sm", loading && "animate-pulse", isActive && "bg-[#75B2D6] text-white")}>
|
||||||
{loading ? <Loader2 size={14} className="animate-spin" /> : <Icon size={14} />}
|
{loading ? <Loader2 size={14} className="animate-spin" /> : <Icon size={14} />}
|
||||||
</div>
|
</div>
|
||||||
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-[#ACB995]" : "text-[#1C1C1C]/80")}>{t(action.i18nKey)}</span>
|
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-[#75B2D6]" : "text-[#1C1C1C]/80")}>{t(action.i18nKey)}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -763,7 +763,7 @@ export function ContextualAIChat({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"py-2 px-1 rounded-lg border text-[10px] font-bold uppercase tracking-tighter transition-all",
|
"py-2 px-1 rounded-lg border text-[10px] font-bold uppercase tracking-tighter transition-all",
|
||||||
translateTarget === lang
|
translateTarget === lang
|
||||||
? "bg-[#ACB995] border-[#ACB995] text-white shadow-md shadow-[#ACB995]/20"
|
? "bg-[#75B2D6] border-[#75B2D6] text-white shadow-md shadow-[#75B2D6]/20"
|
||||||
: "bg-white/60 border-border text-[#1C1C1C]/60 hover:border-[#1C1C1C]/20"
|
: "bg-white/60 border-border text-[#1C1C1C]/60 hover:border-[#1C1C1C]/20"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -862,13 +862,13 @@ export function ContextualAIChat({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{generateResult?.type === 'slides' && generateResult.canvasId && (
|
{generateResult?.type === 'slides' && generateResult.canvasId && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mt-4 p-4 bg-[#75B2D6]/10 border border-[#75B2D6]/20 rounded-xl space-y-3"
|
className="mt-4 p-4 bg-[#75B2D6]/10 border border-[#75B2D6]/20 rounded-xl space-y-3"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-[9px] font-bold text-[#75B2D6] uppercase tracking-widest flex items-center gap-1.5">
|
<span className="text-[9px] font-bold text-[#75B2D6] uppercase tracking-widest flex items-center gap-1.5">
|
||||||
<Check size={12} /> Présentation prête
|
<Check size={12} /> Présentation prête
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
@@ -905,7 +905,7 @@ export function ContextualAIChat({
|
|||||||
mToast.error('Échec du téléchargement')
|
mToast.error('Échec du téléchargement')
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="flex items-center justify-center gap-2 w-full py-2.5 bg-[#ACB995] text-white rounded-lg text-[10px] font-bold uppercase tracking-[0.15em] hover:opacity-90 transition-opacity shadow-sm"
|
className="flex items-center justify-center gap-2 w-full py-2.5 bg-[#75B2D6] text-white rounded-lg text-[10px] font-bold uppercase tracking-[0.15em] hover:opacity-90 transition-opacity shadow-sm"
|
||||||
>
|
>
|
||||||
<Download size={13} />
|
<Download size={13} />
|
||||||
Télécharger .pptx
|
Télécharger .pptx
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
|
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
|
||||||
>
|
>
|
||||||
<Sparkles size={16} />
|
<Sparkles size={16} />
|
||||||
<span>Réorganiser les notes</span>
|
<span>{t('notes.reorganize') || 'Réorganiser les notes'}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export function LabelBadge({
|
|||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{isAI && <Sparkles className="h-3 w-3 text-sky-500 dark:text-sky-400" />}
|
{isAI && <Sparkles className="h-3 w-3 text-[#75B2D6]" />}
|
||||||
{label}
|
<span className="truncate">{label}</span>
|
||||||
{onRemove && (
|
{onRemove && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -60,9 +60,9 @@ export function LabelBadge({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isAI && (
|
{isAI && (
|
||||||
<span className="relative flex h-1.5 w-1.5 ml-0.5">
|
<span className="relative flex h-1.5 w-1.5 ml-1">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#75B2D6] opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-sky-500"></span>
|
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-[#75B2D6]"></span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export function McpSettingsPanel({ initialKeys, serverStatus }: McpSettingsPanel
|
|||||||
{/* Section 1: What is MCP */}
|
{/* Section 1: What is MCP */}
|
||||||
<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden break-inside-avoid">
|
<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden break-inside-avoid">
|
||||||
<div className="flex items-center gap-3 p-6 border-b border-border">
|
<div className="flex items-center gap-3 p-6 border-b border-border">
|
||||||
<div className="w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 shrink-0">
|
<div className="w-10 h-10 rounded-full bg-zinc-500/10 flex items-center justify-center text-zinc-500 shrink-0">
|
||||||
<Info className="h-5 w-5" />
|
<Info className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -135,7 +135,7 @@ export function McpSettingsPanel({ initialKeys, serverStatus }: McpSettingsPanel
|
|||||||
href="https://modelcontextprotocol.io"
|
href="https://modelcontextprotocol.io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-1 text-sm text-blue-600 hover:underline mt-4"
|
className="inline-flex items-center gap-1 text-sm text-zinc-600 hover:underline mt-4"
|
||||||
>
|
>
|
||||||
{t('mcpSettings.whatIsMcp.learnMore')}
|
{t('mcpSettings.whatIsMcp.learnMore')}
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
@@ -156,7 +156,7 @@ export function McpSettingsPanel({ initialKeys, serverStatus }: McpSettingsPanel
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="space-y-4 text-sm">
|
<div className="space-y-4 text-sm">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">{t('mcpSettings.serverStatus.mode')}</span>
|
<span className="text-zinc-600">{t('mcpSettings.serverStatus.mode')}</span>
|
||||||
<Badge variant="secondary">{serverStatus.mode.toUpperCase()}</Badge>
|
<Badge variant="secondary">{serverStatus.mode.toUpperCase()}</Badge>
|
||||||
</div>
|
</div>
|
||||||
{serverStatus.mode === 'sse' && serverStatus.url && (
|
{serverStatus.mode === 'sse' && serverStatus.url && (
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ interface ReminderNote {
|
|||||||
|
|
||||||
// ── Memento brand tokens ──────────────────────────────────────────────────────
|
// ── Memento brand tokens ──────────────────────────────────────────────────────
|
||||||
const C = {
|
const C = {
|
||||||
blue: '#E9ECEF',
|
blue: '#FDFDFE',
|
||||||
gold: '#D4A373',
|
gold: '#D4A373',
|
||||||
green: '#A3B18A',
|
green: '#A3B18A',
|
||||||
dark: '#1C1C1C',
|
dark: '#1C1C1C',
|
||||||
beige: '#F2F0E9',
|
beige: '#FDFDFE',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationPanel() {
|
export function NotificationPanel() {
|
||||||
@@ -148,19 +148,19 @@ export function NotificationPanel() {
|
|||||||
|
|
||||||
// ── icon bg/color per notification type ──────────────────────────────────
|
// ── icon bg/color per notification type ──────────────────────────────────
|
||||||
const notifIconStyle = (type: string) => {
|
const notifIconStyle = (type: string) => {
|
||||||
if (type === 'agent_success') return { bg: `${C.green}20`, color: C.green }
|
if (type === 'agent_success') return { bg: `${C.gold}20`, color: C.gold }
|
||||||
if (type === 'agent_slides_ready') return { bg: `${C.blue}20`, color: C.blue }
|
if (type === 'agent_slides_ready') return { bg: `${C.gold}20`, color: C.gold }
|
||||||
if (type === 'agent_canvas_ready') return { bg: `${C.blue}20`, color: C.blue }
|
if (type === 'agent_canvas_ready') return { bg: `${C.gold}20`, color: C.gold }
|
||||||
if (type === 'agent_failure') return { bg: '#EF444420', color: '#EF4444' }
|
if (type === 'agent_failure') return { bg: '#EF444420', color: '#EF4444' }
|
||||||
return { bg: `${C.gold}20`, color: C.gold }
|
return { bg: `${C.green}20`, color: C.green }
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifLabelColor = (type: string) => {
|
const notifLabelColor = (type: string) => {
|
||||||
if (type === 'agent_success') return C.green
|
if (type.startsWith('agent')) {
|
||||||
if (type === 'agent_slides_ready') return C.blue
|
if (type === 'agent_failure') return '#EF4444'
|
||||||
if (type === 'agent_canvas_ready') return C.blue
|
return C.gold
|
||||||
if (type === 'agent_failure') return '#EF4444'
|
}
|
||||||
return C.gold
|
return C.green
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -173,7 +173,7 @@ export function NotificationPanel() {
|
|||||||
{pendingCount > 0 && (
|
{pendingCount > 0 && (
|
||||||
<span
|
<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"
|
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.gold }}
|
style={{ background: C.green }}
|
||||||
>
|
>
|
||||||
{pendingCount > 9 ? '9+' : pendingCount}
|
{pendingCount > 9 ? '9+' : pendingCount}
|
||||||
</span>
|
</span>
|
||||||
@@ -181,9 +181,9 @@ export function NotificationPanel() {
|
|||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<PopoverContent align="end" className="w-80 p-0 rounded-2xl overflow-hidden shadow-xl border border-black/10">
|
<PopoverContent align="end" className="w-80 p-0 rounded-2xl overflow-hidden shadow-2xl border border-black/20">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-4 py-3 border-b flex items-center justify-between" style={{ background: `${C.beige}` }}>
|
<div className="px-4 py-3 border-b flex items-center justify-between" style={{ background: '#FDFDFE' }}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Bell className="h-4 w-4" style={{ color: C.dark }} />
|
<Bell className="h-4 w-4" style={{ color: C.dark }} />
|
||||||
<span className="font-bold text-sm tracking-tight" style={{ color: C.dark }}>
|
<span className="font-bold text-sm tracking-tight" style={{ color: C.dark }}>
|
||||||
@@ -203,7 +203,7 @@ export function NotificationPanel() {
|
|||||||
{pendingCount > 0 && (
|
{pendingCount > 0 && (
|
||||||
<span
|
<span
|
||||||
className="h-5 px-1.5 flex items-center justify-center rounded-full text-white text-[9px] font-bold"
|
className="h-5 px-1.5 flex items-center justify-center rounded-full text-white text-[9px] font-bold"
|
||||||
style={{ background: C.gold }}
|
style={{ background: C.green }}
|
||||||
>
|
>
|
||||||
{pendingCount}
|
{pendingCount}
|
||||||
</span>
|
</span>
|
||||||
@@ -323,15 +323,15 @@ export function NotificationPanel() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleToggleReminder(note.id, true)}
|
onClick={() => handleToggleReminder(note.id, true)}
|
||||||
className="mt-0.5 flex-none transition-colors hover:opacity-70"
|
className="mt-0.5 flex-none transition-colors hover:opacity-70"
|
||||||
style={{ color: C.gold }}
|
style={{ color: C.green }}
|
||||||
title={t('reminders.markDone')}
|
title={t('reminders.markDone')}
|
||||||
>
|
>
|
||||||
<Circle className="w-4 h-4" />
|
<Circle className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-1.5 mb-0.5">
|
<div className="flex items-center gap-1.5 mb-0.5">
|
||||||
<AlertCircle className="w-3 h-3" style={{ color: C.gold }} />
|
<AlertCircle className="w-3 h-3" style={{ color: C.green }} />
|
||||||
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: C.gold }}>
|
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: C.green }}>
|
||||||
{t('reminders.overdue')}
|
{t('reminders.overdue')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function Toaster() {
|
|||||||
success: 'border-l-4 border-l-emerald-400',
|
success: 'border-l-4 border-l-emerald-400',
|
||||||
error: 'border-l-4 border-l-red-400',
|
error: 'border-l-4 border-l-red-400',
|
||||||
warning: 'border-l-4 border-l-amber-400',
|
warning: 'border-l-4 border-l-amber-400',
|
||||||
info: 'border-l-4 border-l-sky-400',
|
info: 'border-l-4 border-l-zinc-400',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -24,37 +24,32 @@ function updateDocumentDirection(lang: SupportedLanguage) {
|
|||||||
document.documentElement.dir = RTL_LANGUAGES.includes(lang) ? 'rtl' : 'ltr'
|
document.documentElement.dir = RTL_LANGUAGES.includes(lang) ? 'rtl' : 'ltr'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the actual language to use:
|
|
||||||
* 1. If localStorage has a saved preference, use that (client only)
|
|
||||||
* 2. Otherwise fall back to the server-detected initialLanguage
|
|
||||||
*/
|
|
||||||
function resolveLanguage(fallback: SupportedLanguage): SupportedLanguage {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem('user-language') as SupportedLanguage
|
|
||||||
if (saved && SUPPORTED_LANGS.includes(saved)) return saved
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LanguageProvider({ children, initialLanguage = 'en', initialTranslations }: {
|
export function LanguageProvider({ children, initialLanguage = 'en', initialTranslations }: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
initialLanguage?: SupportedLanguage
|
initialLanguage?: SupportedLanguage
|
||||||
initialTranslations?: Translations
|
initialTranslations?: Translations
|
||||||
}) {
|
}) {
|
||||||
// Resolve language synchronously from localStorage BEFORE any effect runs.
|
// Use server-provided initialLanguage for the first render to prevent hydration mismatch.
|
||||||
// This prevents the flash where initialLanguage ('en') overrides RTL.
|
const [language, setLanguageState] = useState<SupportedLanguage>(initialLanguage)
|
||||||
const [language, setLanguageState] = useState<SupportedLanguage>(() => resolveLanguage(initialLanguage))
|
|
||||||
|
|
||||||
// Start with server-provided translations or English fallback
|
// Start with server-provided translations or English fallback
|
||||||
const [translations, setTranslations] = useState<Translations>(
|
const [translations, setTranslations] = useState<Translations>(
|
||||||
(initialTranslations || enTranslations) as unknown as Translations
|
(initialTranslations || enTranslations) as unknown as Translations
|
||||||
)
|
)
|
||||||
|
|
||||||
const cacheRef = useRef<Map<SupportedLanguage, Translations>>(new Map())
|
const cacheRef = useRef<Map<SupportedLanguage, Translations>>(new Map())
|
||||||
const isFirstRender = useRef(true)
|
const isFirstRender = useRef(true)
|
||||||
|
|
||||||
|
// Load saved preference from localStorage AFTER hydration
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = localStorage.getItem('user-language') as SupportedLanguage
|
||||||
|
if (saved && SUPPORTED_LANGS.includes(saved) && saved !== initialLanguage) {
|
||||||
|
setLanguageState(saved)
|
||||||
|
}
|
||||||
|
}, [initialLanguage])
|
||||||
|
|
||||||
// Load translations when language changes (with caching)
|
// Load translations when language changes (with caching)
|
||||||
// On first render, skip updateDocumentDirection since the inline script already set it.
|
// On first render, skip updateDocumentDirection since the inline script already set it.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -42,11 +42,21 @@
|
|||||||
"noLabelsInNotebook": "No labels in this notebook yet",
|
"noLabelsInNotebook": "No labels in this notebook yet",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
"trash": "Trash",
|
"trash": "Trash",
|
||||||
"clearFilter": "Remove filter"
|
"clearFilter": "Remove filter",
|
||||||
|
"inbox": "Inbox",
|
||||||
|
"sharedWithMe": "Shared with me",
|
||||||
|
"sortNewest": "Newest first",
|
||||||
|
"sortOldest": "Oldest first",
|
||||||
|
"sortAlpha": "A → Z",
|
||||||
|
"accountMenu": "Account menu",
|
||||||
|
"profile": "Profile",
|
||||||
|
"signOut": "Sign out",
|
||||||
|
"sortOrder": "Sort order"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"title": "Notes",
|
"title": "Notes",
|
||||||
"newNote": "New note",
|
"newNote": "New note",
|
||||||
|
"reorganize": "Reorganize notes",
|
||||||
"untitled": "Untitled",
|
"untitled": "Untitled",
|
||||||
"placeholder": "Take a note...",
|
"placeholder": "Take a note...",
|
||||||
"markdownPlaceholder": "Take a note... (Markdown supported)",
|
"markdownPlaceholder": "Take a note... (Markdown supported)",
|
||||||
|
|||||||
@@ -42,11 +42,21 @@
|
|||||||
"noLabelsInNotebook": "Aucune étiquette dans ce carnet",
|
"noLabelsInNotebook": "Aucune étiquette dans ce carnet",
|
||||||
"archive": "Archives",
|
"archive": "Archives",
|
||||||
"trash": "Corbeille",
|
"trash": "Corbeille",
|
||||||
"clearFilter": "Retirer le filtre"
|
"clearFilter": "Retirer le filtre",
|
||||||
|
"inbox": "Boîte de réception",
|
||||||
|
"sharedWithMe": "Partagées avec moi",
|
||||||
|
"sortNewest": "Plus récentes",
|
||||||
|
"sortOldest": "Plus anciennes",
|
||||||
|
"sortAlpha": "A → Z",
|
||||||
|
"accountMenu": "Menu du compte",
|
||||||
|
"profile": "Profil",
|
||||||
|
"signOut": "Se déconnecter",
|
||||||
|
"sortOrder": "Ordre de tri"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"title": "Notes",
|
"title": "Notes",
|
||||||
"newNote": "Nouvelle note",
|
"newNote": "Nouvelle note",
|
||||||
|
"reorganize": "Réorganiser les notes",
|
||||||
"untitled": "Sans titre",
|
"untitled": "Sans titre",
|
||||||
"placeholder": "Prenez une note...",
|
"placeholder": "Prenez une note...",
|
||||||
"markdownPlaceholder": "Prenez une note... (Markdown supporté)",
|
"markdownPlaceholder": "Prenez une note... (Markdown supporté)",
|
||||||
|
|||||||
Reference in New Issue
Block a user