fix: add missing error handling for sendMessage promise rejections
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -83,19 +83,24 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
|
||||
}
|
||||
}
|
||||
|
||||
await sendMessage(
|
||||
{ text },
|
||||
{
|
||||
body: {
|
||||
tone: selectedTone,
|
||||
chatScope,
|
||||
notebookId: chatScope !== 'all' ? chatScope : undefined,
|
||||
webSearch: webSearch && webSearchAvailable,
|
||||
conversationId: convId,
|
||||
language,
|
||||
try {
|
||||
await sendMessage(
|
||||
{ text },
|
||||
{
|
||||
body: {
|
||||
tone: selectedTone,
|
||||
chatScope,
|
||||
notebookId: chatScope !== 'all' ? chatScope : undefined,
|
||||
webSearch: webSearch && webSearchAvailable,
|
||||
conversationId: convId,
|
||||
language,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Chat send error:', error)
|
||||
toast.error(t('chat.assistantError') || 'Failed to send message')
|
||||
}
|
||||
}
|
||||
|
||||
const fetchHistory = async () => {
|
||||
|
||||
@@ -137,17 +137,22 @@ export function ChatContainer({ initialConversations, notebooks, webSearchAvaila
|
||||
}
|
||||
}
|
||||
|
||||
await sendMessage(
|
||||
{ text: content },
|
||||
{
|
||||
body: {
|
||||
conversationId: convId,
|
||||
notebookId: notebookId || selectedNotebook || undefined,
|
||||
language,
|
||||
webSearch: webSearchEnabled,
|
||||
},
|
||||
}
|
||||
)
|
||||
try {
|
||||
await sendMessage(
|
||||
{ text: content },
|
||||
{
|
||||
body: {
|
||||
conversationId: convId,
|
||||
notebookId: notebookId || selectedNotebook || undefined,
|
||||
language,
|
||||
webSearch: webSearchEnabled,
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Chat send error:', error)
|
||||
toast.error(t('chat.assistantError') || 'Failed to send message')
|
||||
}
|
||||
}
|
||||
|
||||
const handleNewChat = () => {
|
||||
|
||||
@@ -220,7 +220,12 @@ export function ContextualAIChat({
|
||||
const text = input.trim()
|
||||
if (!text || isLoading) return
|
||||
setInput('')
|
||||
await sendMessage({ text }, { body: buildChatBody() })
|
||||
try {
|
||||
await sendMessage({ text }, { body: buildChatBody() })
|
||||
} catch (error) {
|
||||
console.error('Chat send error:', error)
|
||||
toast.error(t('chat.assistantError') || 'Failed to send message')
|
||||
}
|
||||
}
|
||||
|
||||
// ── Action execution ────────────────────────────────────────────────────────
|
||||
@@ -493,7 +498,7 @@ export function ContextualAIChat({
|
||||
<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">
|
||||
<Sparkles className="h-[18px] w-[18px] shrink-0 text-[#D4A373]" />
|
||||
<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">
|
||||
@@ -510,9 +515,12 @@ export function ContextualAIChat({
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 hover:bg-white/40 rounded-full transition-colors text-[#1C1C1C]/40"
|
||||
className="p-1.5 hover:bg-white/40 rounded-full transition-colors text-[#1C1C1C]/40 group"
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
<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" />
|
||||
<X size={18} className="absolute inset-0 m-auto opacity-0 scale-0 transition-all duration-200 group-hover:opacity-100 group-hover:scale-100" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -534,7 +542,7 @@ export function ContextualAIChat({
|
||||
>
|
||||
{tab.label}
|
||||
{activeTab === tab.id && (
|
||||
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-[#75B2D6]" />
|
||||
<motion.div layoutId="activeTab" className="absolute bottom-0 left-0 right-0 h-[2px] bg-memento-accent" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
@@ -562,7 +570,7 @@ export function ContextualAIChat({
|
||||
{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="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-memento-blue">
|
||||
{resourcePreview.source === 'chat' ? 'Injecter depuis Discussion' : 'Aperçu IA'}
|
||||
</p>
|
||||
<button onClick={() => setResourcePreview(null)} className="text-[#1C1C1C]/40 hover:text-[#1C1C1C]">
|
||||
@@ -576,7 +584,7 @@ export function ContextualAIChat({
|
||||
</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={handleApplyResourcePreview} className="flex-1 py-3.5 bg-[#75B2D6] text-white rounded-xl text-[10px] font-bold uppercase tracking-widest shadow-lg shadow-[#75B2D6]/20 transition-all hover:opacity-90">APPLIQUER À LA NOTE</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>
|
||||
)}
|
||||
@@ -594,7 +602,7 @@ export function ContextualAIChat({
|
||||
{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">
|
||||
<MessageSquare size={32} className="text-[#75B2D6]/60" />
|
||||
<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>
|
||||
</div>
|
||||
@@ -623,7 +631,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="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 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>
|
||||
)}
|
||||
@@ -664,11 +672,11 @@ export function ContextualAIChat({
|
||||
className={cn(
|
||||
'h-[52px] rounded-xl border transition-all flex flex-col items-center justify-center gap-1.5 shadow-sm',
|
||||
isActive
|
||||
? 'bg-[#75B2D6]/10 border-[#75B2D6] text-[#75B2D6]'
|
||||
? 'bg-memento-blue/10 border-memento-blue text-memento-blue'
|
||||
: 'bg-white/60 border-border text-[#1C1C1C]/40 hover:border-[#1C1C1C]/20'
|
||||
)}
|
||||
>
|
||||
<Icon size={14} className={isActive ? 'text-[#75B2D6]' : 'text-[#1C1C1C]/40'} />
|
||||
<Icon size={14} className={isActive ? 'text-memento-blue' : 'text-[#1C1C1C]/40'} />
|
||||
<span className="text-[9px] font-bold uppercase tracking-tight">{tone.label}</span>
|
||||
</button>
|
||||
)
|
||||
@@ -680,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-[#75B2D6] 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-memento-blue transition-all resize-none leading-relaxed font-light custom-scrollbar shadow-sm text-[#1C1C1C]"
|
||||
placeholder="Posez votre question sur cette note..."
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
@@ -690,12 +698,12 @@ 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-[#75B2D6] bg-[#75B2D6]/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-[#1C1C1C]/20 hover:text-[#1C1C1C]")}
|
||||
title="Web Search"
|
||||
>
|
||||
<Globe size={18} />
|
||||
</button>
|
||||
<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">
|
||||
<button onClick={handleSend} disabled={!input.trim() || isLoading} className="p-2.5 bg-memento-blue text-white rounded-xl transition-all hover:scale-105 active:scale-95 shadow-lg shadow-memento-blue/20 disabled:opacity-30">
|
||||
<Send size={18} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -725,7 +733,7 @@ export function ContextualAIChat({
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
onClick={onUndoLastAction}
|
||||
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"
|
||||
className="w-full py-3.5 bg-memento-blue/20 border border-memento-blue/50 rounded-xl flex items-center justify-center gap-2 text-[11px] font-bold text-memento-blue uppercase tracking-[0.2em] hover:bg-memento-blue/30 transition-all shadow-md"
|
||||
>
|
||||
<RotateCcw size={12} /> {t('ai.undoLastAction')}
|
||||
</motion.button>
|
||||
@@ -736,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-[#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-[#FDFDFE] shadow-sm", loading && "animate-pulse", isActive && "bg-[#75B2D6] 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-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")}>
|
||||
{loading ? <Loader2 size={14} className="animate-spin" /> : <Icon size={14} />}
|
||||
</div>
|
||||
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-[#75B2D6]" : "text-[#1C1C1C]/80")}>{t(action.i18nKey)}</span>
|
||||
<span className={cn("text-[10px] font-bold uppercase tracking-widest", isActive ? "text-memento-blue" : "text-[#1C1C1C]/80")}>{t(action.i18nKey)}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@@ -763,7 +771,7 @@ export function ContextualAIChat({
|
||||
className={cn(
|
||||
"py-2 px-1 rounded-lg border text-[10px] font-bold uppercase tracking-tighter transition-all",
|
||||
translateTarget === lang
|
||||
? "bg-[#75B2D6] border-[#75B2D6] text-white shadow-md shadow-[#75B2D6]/20"
|
||||
? "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"
|
||||
)}
|
||||
>
|
||||
@@ -789,7 +797,7 @@ export function ContextualAIChat({
|
||||
<button
|
||||
onClick={() => handleAction(ACTION_IDS.find(a => a.id === 'translate')!, translateTarget)}
|
||||
disabled={!translateTarget || !!actionLoading}
|
||||
className="w-full py-3 bg-[#75B2D6] text-white rounded-xl text-[10px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-2 hover:opacity-90 disabled:opacity-50 shadow-lg shadow-[#75B2D6]/20"
|
||||
className="w-full py-3 bg-memento-blue text-white rounded-xl text-[10px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-2 hover:opacity-90 disabled:opacity-50 shadow-lg shadow-memento-blue/20"
|
||||
>
|
||||
<Languages size={14} /> {t('ai.translateNow')}
|
||||
</button>
|
||||
@@ -827,13 +835,13 @@ export function ContextualAIChat({
|
||||
<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-[#75B2D6]/30 transition-all duration-500 overflow-hidden shadow-sm">
|
||||
<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="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<Layout size={80} className="text-[#75B2D6]" />
|
||||
<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-[#75B2D6]"><Layout size={18} /></div>
|
||||
<div className="p-2 bg-white/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>
|
||||
@@ -850,14 +858,14 @@ export function ContextualAIChat({
|
||||
</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-[#75B2D6]/10 transition-all cursor-pointer text-[#1C1C1C]">
|
||||
<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]">
|
||||
<option value="professional">{t('ai.generate.styleProfessional')}</option>
|
||||
<option value="creative">Creative</option>
|
||||
<option value="brutalist">Brutalist</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => handleGenerate('slides')} disabled={!!generateLoading} className="w-full py-3.5 bg-[#75B2D6] text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-[#75B2D6]/20 uppercase tracking-[0.2em] disabled:opacity-50">
|
||||
<button onClick={() => handleGenerate('slides')} disabled={!!generateLoading} className="w-full py-3.5 bg-memento-blue text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-memento-blue/20 uppercase tracking-[0.2em] disabled:opacity-50">
|
||||
{generateLoading === 'slides' ? <Loader2 size={14} className="animate-spin" /> : <><Presentation size={14} className="opacity-80" /> {t('ai.generating')}</>}
|
||||
</button>
|
||||
|
||||
@@ -865,10 +873,10 @@ export function ContextualAIChat({
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
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-memento-blue/10 border border-memento-blue/20 rounded-xl space-y-3"
|
||||
>
|
||||
<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-memento-blue uppercase tracking-widest flex items-center gap-1.5">
|
||||
<Check size={12} /> Présentation prête
|
||||
</span>
|
||||
<a
|
||||
@@ -1034,7 +1042,7 @@ export function ContextualAIChat({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={handleResourcePreview} disabled={resourceEnriching || resourceScraping || !resourceText.trim()} className="w-full py-4 bg-[#75B2D6] text-white rounded-xl text-[11px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-3 hover:opacity-90 transition-opacity shadow-lg shadow-[#75B2D6]/20 disabled:opacity-50">
|
||||
<button onClick={handleResourcePreview} disabled={resourceEnriching || resourceScraping || !resourceText.trim()} className="w-full py-4 bg-memento-blue text-white rounded-xl text-[11px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-3 hover:opacity-90 transition-opacity shadow-lg shadow-memento-blue/20 disabled:opacity-50">
|
||||
{resourceEnriching || resourceScraping ? <Loader2 size={18} className="animate-spin" /> : <Sparkles size={18} />}
|
||||
{t('ai.resource.generatePreview')}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user