feat: improve AI Chat UX, add notebook summary, and fix shared/reminders routing
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m50s

This commit is contained in:
Antigravity
2026-05-09 14:40:36 +00:00
parent 66e957fd59
commit 368b43cb8e
20 changed files with 292 additions and 196 deletions

View File

@@ -139,7 +139,7 @@ export function ContextualAIChat({
const { t, language } = useLanguage()
const webSearchAvailable = useWebSearchAvailable()
const [activeTab, setActiveTab] = useState<'chat' | 'actions' | 'resource'>('chat')
const [activeTab, setActiveTab] = useState<'chat' | 'actions' | 'resource'>('actions')
const [selectedTone, setSelectedTone] = useState('professional')
const [input, setInput] = useState('')
const [chatScope, setChatScope] = useState<'note' | 'all' | string>('note')
@@ -482,12 +482,12 @@ export function ContextualAIChat({
<>
{expanded && (
<div
className="fixed inset-0 z-[199] bg-[#1C1C1C]/40 backdrop-blur-sm"
className="fixed inset-0 z-[199] bg-foreground/40 backdrop-blur-sm"
onClick={() => setExpanded(false)}
/>
)}
<aside className={cn(
'border-l border-border bg-[#FDFDFE] flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
'border-l border-border bg-background flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
expanded
? 'fixed right-0 top-0 h-screen w-[640px] z-[200]'
: 'h-full w-[360px]',
@@ -497,25 +497,25 @@ export function ContextualAIChat({
<div className="p-6 border-b border-border shrink-0">
<div className="flex items-start justify-between">
<div className="min-w-0 space-y-2">
<h2 className="font-serif text-xl font-medium text-[#1C1C1C] flex items-center gap-2 leading-tight">
<h2 className="font-serif text-xl font-medium text-foreground flex items-center gap-2 leading-tight">
<Sparkles className="h-[18px] w-[18px] shrink-0 text-memento-accent" />
IA Assistant
</h2>
<p className="text-[11px] text-[#1C1C1C]/60 uppercase tracking-wider font-medium opacity-60 truncate">
<p className="text-[11px] text-foreground/60 uppercase tracking-wider font-medium opacity-60 truncate">
"{noteTitle || t('ai.currentNote')}"
</p>
</div>
<div className="flex items-center gap-1 shrink-0 -mt-1">
<button
onClick={() => setExpanded(e => !e)}
className="p-1.5 hover:bg-white/40 rounded-full transition-colors text-[#1C1C1C]/40"
className="p-1.5 hover:bg-muted/60 rounded-full transition-colors text-foreground/40"
title={expanded ? t('ai.shrinkPanel') : t('ai.expandPanel')}
>
{expanded ? <Minimize2 size={18} /> : <Maximize2 size={18} />}
</button>
<button
onClick={onClose}
className="p-1.5 hover:bg-white/40 rounded-full transition-colors text-[#1C1C1C]/40 group"
className="p-1.5 hover:bg-muted/60 rounded-full transition-colors text-foreground/40 group"
>
<div className="relative w-5 h-5 flex items-center justify-center">
<ChevronRight size={20} className="transition-all duration-200 group-hover:opacity-0 group-hover:scale-0" />
@@ -528,8 +528,8 @@ export function ContextualAIChat({
<div className="flex border-b border-border shrink-0 px-2">
{[
{ id: 'chat', label: 'Discussion', icon: <MessageSquare size={16} /> },
{ id: 'actions', label: 'Actions', icon: <Sparkles size={16} /> },
{ id: 'chat', label: 'Discussion', icon: <MessageSquare size={16} /> },
{ id: 'resource', label: 'Ressource', icon: <Link2 size={16} /> },
].map(tab => (
<button
@@ -537,12 +537,12 @@ export function ContextualAIChat({
onClick={() => setActiveTab(tab.id as any)}
className={cn(
"flex-1 flex items-center justify-center gap-2 py-3.5 text-[10px] font-bold uppercase tracking-[0.2em] transition-all relative",
activeTab === tab.id ? 'text-[#1C1C1C]' : 'text-[#1C1C1C]/60 hover:text-[#1C1C1C]'
activeTab === tab.id ? 'text-foreground' : 'text-foreground/60 hover:text-foreground'
)}
>
{tab.label}
{activeTab === tab.id && (
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-memento-accent" />
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-memento-blue" />
)}
</button>
))}
@@ -550,40 +550,40 @@ export function ContextualAIChat({
<div className="flex-1 flex flex-col min-h-0 relative">
{actionPreview && (
<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="absolute inset-0 z-20 flex flex-col bg-background/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">
<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>
<p className="text-[10px] font-bold uppercase tracking-widest text-memento-blue">{actionPreview.label}</p>
<button onClick={handleDiscardPreview} className="text-foreground/40 hover:text-foreground"><X size={18} /></button>
</div>
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
<div className="bg-white/60 backdrop-blur-sm border border-border p-6 rounded-2xl shadow-sm leading-relaxed text-sm">
<div className="bg-card/60 backdrop-blur-sm border border-border p-6 rounded-2xl shadow-sm leading-relaxed text-sm">
<MarkdownContent content={actionPreview.text} />
</div>
</div>
<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={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>
<button onClick={handleDiscardPreview} className="flex-1 py-3.5 text-[10px] font-bold uppercase tracking-widest text-foreground/40 hover:text-foreground transition-all">ANNULER</button>
<button onClick={handleApplyPreview} className="flex-1 py-3.5 bg-foreground text-background rounded-xl text-[10px] font-bold uppercase tracking-widest shadow-lg transition-all hover:opacity-90">APPLIQUER À LA NOTE</button>
</div>
</div>
)}
{resourcePreview && (
<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="absolute inset-0 z-20 flex flex-col bg-background/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">
<p className="text-[10px] font-bold uppercase tracking-widest text-memento-blue">
{resourcePreview.source === 'chat' ? 'Injecter depuis Discussion' : 'Aperçu IA'}
</p>
<button onClick={() => setResourcePreview(null)} className="text-[#1C1C1C]/40 hover:text-[#1C1C1C]">
<button onClick={() => setResourcePreview(null)} className="text-foreground/40 hover:text-foreground">
<X size={18} />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
<div className="bg-white/60 backdrop-blur-sm border border-border p-6 rounded-2xl shadow-sm leading-relaxed text-sm">
<div className="bg-card/60 backdrop-blur-sm border border-border p-6 rounded-2xl shadow-sm leading-relaxed text-sm">
<MarkdownContent content={resourcePreview.text} />
</div>
</div>
<div className="p-6 border-t border-border flex gap-3 shrink-0">
<button onClick={() => setResourcePreview(null)} 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={() => setResourcePreview(null)} className="flex-1 py-3.5 text-[10px] font-bold uppercase tracking-widest text-foreground/40 hover:text-foreground transition-all">ANNULER</button>
<button onClick={handleApplyResourcePreview} className="flex-1 py-3.5 bg-memento-blue text-white rounded-xl text-[10px] font-bold uppercase tracking-widest shadow-lg shadow-memento-blue/20 transition-all hover:opacity-90">APPLIQUER À LA NOTE</button>
</div>
</div>
@@ -601,10 +601,10 @@ export function ContextualAIChat({
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar space-y-8">
{messages.length === 0 && (
<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-card/40 backdrop-blur-sm border border-dashed border-border flex items-center justify-center shadow-sm">
<MessageSquare size={32} className="text-memento-blue/60" />
</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-foreground/40 leading-relaxed max-w-[200px]">Posez une question à l'Assistant pour commencer.</p>
</div>
)}
{messages.map((msg: UIMessage) => {
@@ -614,14 +614,14 @@ export function ContextualAIChat({
return (
<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={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]')}>
<div className={cn('p-5 rounded-2xl text-sm leading-relaxed transition-all shadow-sm', !isAssistant ? 'bg-foreground text-background' : 'bg-card/60 backdrop-blur-sm border border-border text-foreground')}>
{isAssistant ? <MarkdownContent content={content} /> : <p className="font-medium">{content}</p>}
</div>
{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">
<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, '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, 'replace')} className="px-3 py-1.5 rounded-lg text-[9px] font-bold uppercase tracking-widest bg-foreground text-background 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-card/40 backdrop-blur-sm border border-border text-foreground hover:bg-card/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-card/40 backdrop-blur-sm border border-border text-foreground hover:bg-card/60">FUSIONNER</button>
</div>
)}
</div>
@@ -630,7 +630,7 @@ export function ContextualAIChat({
})}
{isLoading && (
<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-card/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-memento-blue rounded-full animate-pulse" /><span className="w-1.5 h-1.5 bg-memento-blue rounded-full animate-pulse delay-75" /><span className="w-1.5 h-1.5 bg-memento-blue rounded-full animate-pulse delay-150" /></div>
</div>
</div>
@@ -642,15 +642,15 @@ export function ContextualAIChat({
<div className="px-6 py-8 border-t border-border shrink-0 space-y-6">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-[#1C1C1C]/40 px-1">CONTEXTE</label>
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-foreground/40 px-1">CONTEXTE</label>
<Select value={chatScope} onValueChange={setChatScope}>
<SelectTrigger className="w-full h-10 px-3 bg-white/60 backdrop-blur-sm border border-border rounded-xl text-[11px] flex items-center justify-between cursor-pointer hover:border-[#1C1C1C]/20 transition-all outline-none ring-0 shadow-sm text-[#1C1C1C]">
<SelectTrigger className="w-full h-10 px-3 bg-card/60 backdrop-blur-sm border border-border rounded-xl text-[11px] flex items-center justify-between cursor-pointer hover:border-foreground/20 transition-all outline-none ring-0 shadow-sm text-foreground">
<div className="flex items-center gap-2">
<BookOpen size={14} className="text-[#1C1C1C]/40" />
<BookOpen size={14} className="text-foreground/40" />
<SelectValue />
</div>
</SelectTrigger>
<SelectContent className="rounded-xl border-border shadow-xl bg-[#FDFDFE]">
<SelectContent className="rounded-xl border-border shadow-xl bg-background">
<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>
{notebooks.map(nb => (
@@ -660,7 +660,7 @@ export function ContextualAIChat({
</Select>
</div>
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-[#1C1C1C]/40 px-1">TON D'ÉCRITURE</label>
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-foreground/40 px-1">TON D'ÉCRITURE</label>
<div className="grid grid-cols-4 gap-1.5">
{TONES.map((tone) => {
const Icon = tone.icon
@@ -673,10 +673,10 @@ export function ContextualAIChat({
'h-[52px] rounded-xl border transition-all flex flex-col items-center justify-center gap-1.5 shadow-sm',
isActive
? 'bg-memento-blue/10 border-memento-blue text-memento-blue'
: 'bg-white/60 border-border text-[#1C1C1C]/40 hover:border-[#1C1C1C]/20'
: 'bg-card/60 border-border text-foreground/40 hover:border-foreground/20'
)}
>
<Icon size={14} className={isActive ? 'text-memento-blue' : 'text-[#1C1C1C]/40'} />
<Icon size={14} className={isActive ? 'text-memento-blue' : 'text-foreground/40'} />
<span className="text-[9px] font-bold uppercase tracking-tight">{tone.label}</span>
</button>
)
@@ -688,7 +688,7 @@ export function ContextualAIChat({
<div className="relative">
<textarea
rows={4}
className="w-full bg-white/60 border border-border rounded-2xl p-5 pr-14 text-sm outline-none focus:border-memento-blue transition-all resize-none leading-relaxed font-light custom-scrollbar shadow-sm text-[#1C1C1C]"
className="w-full bg-card/60 border border-border rounded-2xl p-5 pr-14 text-sm outline-none focus:border-memento-blue transition-all resize-none leading-relaxed font-light custom-scrollbar shadow-sm text-foreground"
placeholder="Posez votre question sur cette note..."
value={input}
onChange={e => setInput(e.target.value)}
@@ -698,7 +698,7 @@ export function ContextualAIChat({
<div className="absolute right-4 bottom-4 flex gap-2">
<button
onClick={() => setWebSearch(!webSearch)}
className={cn("p-2.5 rounded-xl transition-colors", webSearch ? "text-memento-blue bg-memento-blue/10" : "text-[#1C1C1C]/20 hover:text-[#1C1C1C]")}
className={cn("p-2.5 rounded-xl transition-colors", webSearch ? "text-memento-blue bg-memento-blue/10" : "text-foreground/20 hover:text-foreground")}
title="Web Search"
>
<Globe size={18} />
@@ -708,7 +708,7 @@ export function ContextualAIChat({
</button>
</div>
</div>
<p className="text-[9px] text-[#1C1C1C]/30 text-center mt-2 uppercase tracking-[0.2em] font-bold italic">Maj+Entrée = nouvelle ligne</p>
<p className="text-[9px] text-foreground/30 text-center mt-2 uppercase tracking-[0.2em] font-bold italic">Maj+Entrée = nouvelle ligne</p>
</div>
</motion.div>
)}
@@ -724,7 +724,7 @@ export function ContextualAIChat({
<div className="space-y-6">
<div className="flex items-center gap-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[9px] uppercase tracking-[0.3em] font-bold text-[#1C1C1C]/40 whitespace-nowrap">{t('ai.transformations')}</h4>
<h4 className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40 whitespace-nowrap">{t('ai.transformations')}</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
@@ -744,11 +744,11 @@ export function ContextualAIChat({
const isActive = action.id === 'translate' && showLangPicker
const Icon = action.icon
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-memento-blue bg-memento-blue/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-[#FDFDFE] shadow-sm", loading && "animate-pulse", isActive && "bg-memento-blue text-white")}>
<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-card/40 backdrop-blur-sm border rounded-xl transition-all group shadow-sm", isActive ? "border-memento-blue bg-memento-blue/5" : "border-border hover:border-foreground/20")}>
<div className={cn("p-2 rounded-lg bg-card/60 transition-colors group-hover:bg-foreground group-hover:text-background shadow-sm", loading && "animate-pulse", isActive && "bg-memento-blue text-white")}>
{loading ? <Loader2 size={14} className="animate-spin" /> : <Icon size={14} />}
</div>
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-memento-blue" : "text-[#1C1C1C]/80")}>{t(action.i18nKey)}</span>
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-memento-blue" : "text-foreground/80")}>{t(action.i18nKey)}</span>
</button>
)
})}
@@ -762,7 +762,7 @@ export function ContextualAIChat({
exit={{ height: 0, opacity: 0 }}
className="col-span-2 overflow-hidden"
>
<div className="mt-2 p-5 bg-white/40 backdrop-blur-sm border border-[#75B2D6]/30 rounded-2xl space-y-5 shadow-sm">
<div className="mt-2 p-5 bg-card/40 backdrop-blur-sm border border-memento-blue/30 rounded-2xl space-y-5 shadow-sm">
<div className="grid grid-cols-3 gap-2">
{['Français', 'English', 'Español', 'Deutsch', 'Persan', 'Portugais', 'Italiano', 'Chinois', 'Japonais'].map((lang) => (
<button
@@ -772,7 +772,7 @@ export function ContextualAIChat({
"py-2 px-1 rounded-lg border text-[10px] font-bold uppercase tracking-tighter transition-all",
translateTarget === lang
? "bg-memento-blue border-memento-blue text-white shadow-md shadow-memento-blue/20"
: "bg-white/60 border-border text-[#1C1C1C]/60 hover:border-[#1C1C1C]/20"
: "bg-card/60 border-border text-foreground/60 hover:border-foreground/20"
)}
>
{lang}
@@ -781,7 +781,7 @@ export function ContextualAIChat({
</div>
<div className="space-y-2">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-[#1C1C1C]/40 px-1">{t('ai.otherLanguage')}</span>
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-foreground/40 px-1">{t('ai.otherLanguage')}</span>
<input
type="text"
value={customLangInput}
@@ -790,7 +790,7 @@ export function ContextualAIChat({
setTranslateTarget(e.target.value)
}}
placeholder="ex: Arabe, Russe..."
className="w-full bg-white/60 border border-border rounded-xl px-4 py-2.5 text-[11px] outline-none focus:border-[#75B2D6] transition-all text-[#1C1C1C]"
className="w-full bg-card/60 border border-border rounded-xl px-4 py-2.5 text-[11px] outline-none focus:border-memento-blue transition-all text-foreground"
/>
</div>
@@ -810,18 +810,18 @@ export function ContextualAIChat({
<button
onClick={() => handleAction(ACTION_IDS.find(a => a.id === 'markdown')!)}
disabled={!!actionLoading}
className="col-span-2 flex items-center justify-center gap-3 py-3.5 bg-white/40 backdrop-blur-sm border border-border rounded-xl text-[10px] font-bold text-[#1C1C1C]/80 hover:bg-white/60 transition-all uppercase tracking-[0.2em] shadow-sm disabled:opacity-50"
className="col-span-2 flex items-center justify-center gap-3 py-3.5 bg-card/40 backdrop-blur-sm border border-border rounded-xl text-[10px] font-bold text-foreground/80 hover:bg-card/60 transition-all uppercase tracking-[0.2em] shadow-sm disabled:opacity-50"
>
<Code size={14} className="text-[#1C1C1C]/40" />
<Code size={14} className="text-foreground/40" />
{actionLoading === 'markdown' ? <Loader2 size={14} className="animate-spin" /> : t('ai.action.toMarkdown')}
</button>
) : (
<button
onClick={() => handleAction(ACTION_IDS.find(a => a.id === 'toRichText')!)}
disabled={!!actionLoading}
className="col-span-2 flex items-center justify-center gap-3 py-3.5 bg-white/40 backdrop-blur-sm border border-border rounded-xl text-[10px] font-bold text-[#1C1C1C]/80 hover:bg-white/60 transition-all uppercase tracking-[0.2em] shadow-sm disabled:opacity-50"
className="col-span-2 flex items-center justify-center gap-3 py-3.5 bg-card/40 backdrop-blur-sm border border-border rounded-xl text-[10px] font-bold text-foreground/80 hover:bg-card/60 transition-all uppercase tracking-[0.2em] shadow-sm disabled:opacity-50"
>
<Wand2 size={14} className="text-[#1C1C1C]/40" />
<Wand2 size={14} className="text-foreground/40" />
{actionLoading === 'toRichText' ? <Loader2 size={14} className="animate-spin" /> : t('ai.action.toRichText')}
</button>
)}
@@ -831,34 +831,34 @@ export function ContextualAIChat({
<div className="space-y-6">
<div className="flex items-center gap-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[9px] uppercase tracking-[0.3em] font-bold text-[#1C1C1C]/40 whitespace-nowrap">{t('ai.generationTools')}</h4>
<h4 className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40 whitespace-nowrap">{t('ai.generationTools')}</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<div className="group relative p-6 rounded-2xl bg-white/40 backdrop-blur-sm border border-border hover:border-memento-blue/30 transition-all duration-500 overflow-hidden shadow-sm">
<div className="group relative p-6 rounded-2xl bg-card/40 backdrop-blur-sm border border-border hover:border-memento-blue/30 transition-all duration-500 overflow-hidden shadow-sm">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Layout size={80} className="text-memento-blue" />
</div>
<div className="relative space-y-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-white/60 rounded-lg text-memento-blue"><Layout size={18} /></div>
<div className="p-2 bg-card/60 rounded-lg text-memento-blue"><Layout size={18} /></div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-[#1C1C1C] leading-none">{t('ai.generate.slides')}</h5>
<p className="text-[9px] text-[#1C1C1C]/40 uppercase tracking-tight">{t('ai.generate.sectionLabel')}</p>
<h5 className="text-sm font-bold text-foreground leading-none">{t('ai.generate.slides')}</h5>
<p className="text-[9px] text-foreground/40 uppercase tracking-tight">{t('ai.generate.sectionLabel')}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-[#1C1C1C]/40 px-1">{t('ai.generate.theme')}</span>
<select value={slideTheme} onChange={e => setSlideTheme(e.target.value)} className="w-full bg-white/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-[#75B2D6]/10 transition-all cursor-pointer text-[#1C1C1C]">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-foreground/40 px-1">{t('ai.generate.theme')}</span>
<select value={slideTheme} onChange={e => setSlideTheme(e.target.value)} className="w-full bg-card/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-memento-blue/10 transition-all cursor-pointer text-foreground">
<option value="architectural_mono">{t('ai.generate.themeArchitecturalMono')}</option>
<option value="vibrant_tech">{t('ai.generate.themeVibrantTech')}</option>
<option value="minimal_silk">{t('ai.generate.themeMinimalSilk')}</option>
</select>
</div>
<div className="space-y-1.5">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-[#1C1C1C]/40 px-1">{t('ai.generate.style')}</span>
<select value={slideStyle} onChange={e => setSlideStyle(e.target.value)} className="w-full bg-white/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-memento-blue/10 transition-all cursor-pointer text-[#1C1C1C]">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-foreground/40 px-1">{t('ai.generate.style')}</span>
<select value={slideStyle} onChange={e => setSlideStyle(e.target.value)} className="w-full bg-card/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-memento-blue/10 transition-all cursor-pointer text-foreground">
<option value="professional">{t('ai.generate.styleProfessional')}</option>
<option value="creative">Creative</option>
<option value="brutalist">Brutalist</option>
@@ -883,7 +883,7 @@ export function ContextualAIChat({
href={`/lab?id=${generateResult.canvasId}`}
target="_blank"
rel="noopener noreferrer"
className="p-1.5 bg-white/60 rounded-lg text-[#1C1C1C]/50 hover:text-[#1C1C1C] hover:bg-white transition-colors"
className="p-1.5 bg-card/60 rounded-lg text-foreground/50 hover:text-foreground hover:bg-card transition-colors"
title="Voir dans L'Atelier"
>
<ExternalLink size={12} />
@@ -913,7 +913,7 @@ export function ContextualAIChat({
mToast.error('Échec du téléchargement')
}
}}
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"
className="flex items-center justify-center gap-2 w-full py-2.5 bg-memento-blue text-white rounded-lg text-[10px] font-bold uppercase tracking-[0.15em] hover:opacity-90 transition-opacity shadow-sm"
>
<Download size={13} />
Télécharger .pptx
@@ -924,22 +924,22 @@ export function ContextualAIChat({
</div>
</div>
<div className="group relative p-6 rounded-2xl bg-white/40 backdrop-blur-sm border border-border hover:border-[#A3B18A]/30 transition-all duration-500 overflow-hidden shadow-sm">
<div className="group relative p-6 rounded-2xl bg-card/40 backdrop-blur-sm border border-border hover:border-primary/30 transition-all duration-500 overflow-hidden shadow-sm">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<BookOpen size={80} className="text-[#A3B18A]" />
<BookOpen size={80} className="text-primary" />
</div>
<div className="relative space-y-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-white/60 rounded-lg text-[#A3B18A]"><BookOpen size={18} /></div>
<div className="p-2 bg-card/60 rounded-lg text-primary"><BookOpen size={18} /></div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-[#1C1C1C] leading-none">{t('ai.generate.diagram')}</h5>
<p className="text-[9px] text-[#1C1C1C]/40 uppercase tracking-tight">{t('ai.generate.diagramReadyHint')}</p>
<h5 className="text-sm font-bold text-foreground leading-none">{t('ai.generate.diagram')}</h5>
<p className="text-[9px] text-foreground/40 uppercase tracking-tight">{t('ai.generate.diagramReadyHint')}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-[#1C1C1C]/40 px-1">{t('ai.generate.diagramType')}</span>
<select value={diagramType} onChange={e => setDiagramType(e.target.value)} className="w-full bg-white/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-[#A3B18A]/10 transition-all cursor-pointer text-[#1C1C1C]">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-foreground/40 px-1">{t('ai.generate.diagramType')}</span>
<select value={diagramType} onChange={e => setDiagramType(e.target.value)} className="w-full bg-card/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-primary/10 transition-all cursor-pointer text-foreground">
<option value="auto">{t('ai.generate.typeAuto')}</option>
<option value="flowchart">{t('ai.generate.typeFlowchart')}</option>
<option value="mind_map">{t('ai.generate.typeMindMap')}</option>
@@ -950,8 +950,8 @@ export function ContextualAIChat({
</select>
</div>
<div className="space-y-1.5">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-[#1C1C1C]/40 px-1">{t('ai.generate.style')}</span>
<select value={diagramStyle} onChange={e => setDiagramStyle(e.target.value)} className="w-full bg-white/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-[#A3B18A]/10 transition-all cursor-pointer text-[#1C1C1C]">
<span className="text-[8px] uppercase tracking-[0.2em] font-bold text-foreground/40 px-1">{t('ai.generate.style')}</span>
<select value={diagramStyle} onChange={e => setDiagramStyle(e.target.value)} className="w-full bg-card/60 border border-border rounded-lg px-2 py-2 text-[10px] outline-none focus:ring-1 ring-primary/10 transition-all cursor-pointer text-foreground">
<option value="sketchy">{t('ai.generate.styleSketchy')}</option>
<option value="soft">{t('ai.generate.styleSoft')}</option>
<option value="minimal">{t('ai.generate.styleMinimal')}</option>
@@ -962,7 +962,7 @@ export function ContextualAIChat({
</select>
</div>
</div>
<button onClick={() => handleGenerate('diagram')} disabled={!!generateLoading} className="w-full py-3.5 bg-[#A3B18A] text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-[#A3B18A]/20 uppercase tracking-[0.2em] disabled:opacity-50">
<button onClick={() => handleGenerate('diagram')} disabled={!!generateLoading} className="w-full py-3.5 bg-primary text-primary-foreground rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-primary/20 uppercase tracking-[0.2em] disabled:opacity-50">
{generateLoading === 'diagram' ? <Loader2 size={14} className="animate-spin" /> : <>{t('ai.generating')} <ArrowRightLeft size={14} className="opacity-60" /></>}
</button>
@@ -970,16 +970,16 @@ export function ContextualAIChat({
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 p-4 bg-[#A3B18A]/10 border border-[#A3B18A]/20 rounded-xl space-y-3"
className="mt-4 p-4 bg-primary/10 border border-primary/20 rounded-xl space-y-3"
>
<div className="flex items-center justify-between">
<span className="text-[9px] font-bold text-[#A3B18A] uppercase tracking-widest">{t('ai.generate.diagramReady')}</span>
<span className="text-[9px] font-bold text-primary uppercase tracking-widest">{t('ai.generate.diagramReady')}</span>
<div className="flex gap-2">
<a
href={`/lab?id=${generateResult.canvasId}`}
target="_blank"
rel="noopener noreferrer"
className="p-2 bg-white/60 rounded-lg text-[#1C1C1C] hover:bg-white transition-colors"
className="p-2 bg-card/60 rounded-lg text-foreground hover:bg-card transition-colors"
title={t('ai.generate.openInExcalidraw')}
>
<ExternalLink size={14} />
@@ -987,7 +987,7 @@ export function ContextualAIChat({
<button
onClick={() => handleEmbedDiagramInNote(generateResult.canvasId!)}
disabled={diagramEmbedLoading}
className="p-2 bg-[#A3B18A] text-white rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50"
className="p-2 bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50"
title={t('ai.generate.insertDiagramInNote')}
>
{diagramEmbedLoading ? <Loader2 size={14} className="animate-spin" /> : <ImagePlus size={14} />}
@@ -1017,27 +1017,27 @@ export function ContextualAIChat({
>
<div className="space-y-6">
<div className="space-y-3">
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-[#1C1C1C]/40">{t('ai.resource.urlLabel')}</label>
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40">{t('ai.resource.urlLabel')}</label>
<div className="relative">
<input type="text" placeholder="https://..." className="w-full bg-white/40 backdrop-blur-sm border border-border rounded-xl pl-4 pr-12 py-3.5 text-xs outline-none focus:border-[#75B2D6] transition-colors shadow-sm text-[#1C1C1C]" value={resourceUrl} onChange={e => setResourceUrl(e.target.value)} />
<Globe size={16} className="absolute right-4 top-1/2 -translate-y-1/2 text-[#1C1C1C]/20" />
<input type="text" placeholder="https://..." className="w-full bg-card/40 backdrop-blur-sm border border-border rounded-xl pl-4 pr-12 py-3.5 text-xs outline-none focus:border-memento-blue transition-colors shadow-sm text-foreground" value={resourceUrl} onChange={e => setResourceUrl(e.target.value)} />
<Globe size={16} className="absolute right-4 top-1/2 -translate-y-1/2 text-foreground/20" />
</div>
</div>
<div className="space-y-3">
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-[#1C1C1C]/40">{t('ai.resource.resourceText')}</label>
<textarea rows={10} placeholder={t('ai.resource.resourcePlaceholder')} className="w-full bg-white/40 backdrop-blur-sm border border-border rounded-xl p-5 text-sm outline-none focus:border-[#75B2D6] transition-colors resize-none leading-relaxed font-light shadow-sm text-[#1C1C1C]" value={resourceText} onChange={e => setResourceText(e.target.value)} />
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40">{t('ai.resource.resourceText')}</label>
<textarea rows={10} placeholder={t('ai.resource.resourcePlaceholder')} className="w-full bg-card/40 backdrop-blur-sm border border-border rounded-xl p-5 text-sm outline-none focus:border-memento-blue transition-colors resize-none leading-relaxed font-light shadow-sm text-foreground" value={resourceText} onChange={e => setResourceText(e.target.value)} />
</div>
<div className="space-y-4">
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-[#1C1C1C]/40">{t('ai.resource.integrationMode')}</label>
<label className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40">{t('ai.resource.integrationMode')}</label>
<div className="grid grid-cols-3 gap-2">
{[
{[
{ id: 'replace', label: t('ai.resource.modeReplace'), sub: t('ai.resource.modeReplaceDesc') },
{ id: 'complete', label: t('ai.resource.modeComplete'), sub: t('ai.resource.modeCompleteDesc') },
{ id: 'merge', label: t('ai.resource.modeMerge'), sub: t('ai.resource.modeMergeDesc') },
].map((mode) => (
<button key={mode.id} onClick={() => setResourceMode(mode.id as any)} className={cn("flex flex-col items-center justify-center p-3 rounded-xl border transition-all text-center", resourceMode === mode.id ? 'bg-[#75B2D6]/10 border-[#75B2D6] ring-1 ring-[#75B2D6]/20' : 'bg-white/40 backdrop-blur-sm border-border hover:bg-white/60 shadow-sm')}>
<span className={cn("text-[10px] font-bold uppercase tracking-wider", resourceMode === mode.id ? 'text-[#75B2D6]' : 'text-[#1C1C1C]')}>{mode.label}</span>
<span className="text-[8px] text-[#1C1C1C]/40 leading-tight mt-1 font-medium">{mode.sub}</span>
<button key={mode.id} onClick={() => setResourceMode(mode.id as any)} className={cn("flex flex-col items-center justify-center p-3 rounded-xl border transition-all text-center", resourceMode === mode.id ? 'bg-memento-blue/10 border-memento-blue ring-1 ring-memento-blue/20' : 'bg-card/40 backdrop-blur-sm border-border hover:bg-card/60 shadow-sm')}>
<span className={cn("text-[10px] font-bold uppercase tracking-wider", resourceMode === mode.id ? 'text-memento-blue' : 'text-foreground')}>{mode.label}</span>
<span className="text-[8px] text-foreground/40 leading-tight mt-1 font-medium">{mode.sub}</span>
</button>
))}
</div>