feat: Show agent errors in chat UI with retry button

When the chat agent crashes (e.g., expired OAuth token), display the
error message inline with a Retry button that re-sends the last user
message. Input stays enabled so users can also send a new message.
This commit is contained in:
Lukas May
2026-03-04 11:23:41 +01:00
parent 413a501570
commit 84dcb0193d
2 changed files with 47 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { X, Loader2 } from 'lucide-react';
import { X, Loader2, AlertTriangle, RotateCcw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useChatSession } from '@/hooks/useChatSession';
import { ChatBubble } from './ChatBubble';
import { ChatInput } from './ChatInput';
@@ -41,7 +42,7 @@ function ChatSlideOverInner({
initiativeId: string;
onClose: () => void;
}) {
const { messages, agentStatus, sendMessage, closeSession, isSending } =
const { messages, agentStatus, agentError, sendMessage, closeSession, isSending } =
useChatSession(target.type, target.id, initiativeId);
const scrollRef = useRef<HTMLDivElement>(null);
@@ -130,6 +131,32 @@ function ChatSlideOverInner({
</div>
</div>
)}
{agentStatus === 'crashed' && agentError && (
<div className="mx-4 my-2 rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2.5">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-status-error-fg" />
<div className="min-w-0 flex-1">
<p className="text-xs font-medium text-status-error-fg">Agent error</p>
<p className="mt-0.5 text-xs text-status-error-fg/80 break-words">{agentError}</p>
</div>
</div>
<div className="mt-2 flex justify-end">
<Button
variant="outline"
size="sm"
className="h-6 gap-1.5 text-xs"
onClick={() => {
// Re-send the last user message to retry
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
if (lastUserMsg) sendMessage(lastUserMsg.content);
}}
>
<RotateCcw className="h-3 w-3" />
Retry
</Button>
</div>
</div>
)}
</div>
{/* Input */}

View File

@@ -3,7 +3,7 @@ import { toast } from 'sonner';
import { trpc } from '@/lib/trpc';
import { useLiveUpdates } from './useLiveUpdates';
export type ChatAgentStatus = 'idle' | 'running' | 'waiting' | 'none';
export type ChatAgentStatus = 'idle' | 'running' | 'waiting' | 'crashed' | 'none';
export interface ChatMessage {
id: string;
@@ -28,6 +28,7 @@ export interface UseChatSessionResult {
session: ChatSession | null;
messages: ChatMessage[];
agentStatus: ChatAgentStatus;
agentError: string | null;
sendMessage: (message: string) => void;
closeSession: () => void;
isSending: boolean;
@@ -88,11 +89,26 @@ export function useChatSession(
return 'waiting';
case 'idle':
return 'idle';
case 'crashed':
return 'crashed';
default:
return 'none';
}
}, [session?.agentId, agentQuery.data]);
// Extract error message when agent crashed
const agentError = useMemo(() => {
if (agentStatus !== 'crashed' || !agentQuery.data) return null;
const result = (agentQuery.data as { result?: string }).result;
if (!result) return 'Agent crashed unexpectedly';
try {
const parsed = JSON.parse(result);
return parsed.message ?? result;
} catch {
return result;
}
}, [agentStatus, agentQuery.data]);
// Send message mutation
const sendMutation = trpc.sendChatMessage.useMutation({
onSuccess: () => {
@@ -161,6 +177,7 @@ export function useChatSession(
session,
messages,
agentStatus,
agentError,
sendMessage,
closeSession,
isSending,