fix: Add error toasts and optimistic messages to chat session hook

Errors from sendChatMessage were silently swallowed. Now shows toast
on failure and immediately renders the user's message optimistically.
This commit is contained in:
Lukas May
2026-03-04 10:31:19 +01:00
parent f06ac953eb
commit 1b5fdfde55

View File

@@ -1,4 +1,5 @@
import { useCallback, useMemo, useRef } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import { trpc } from '@/lib/trpc';
import { useLiveUpdates } from './useLiveUpdates';
@@ -46,10 +47,13 @@ export function useChatSession(
): UseChatSessionResult {
const utils = trpc.useUtils();
// Optimistic messages shown before server confirms
const [optimisticMessages, setOptimisticMessages] = useState<ChatMessage[]>([]);
// Live updates for chat + agent events
useLiveUpdates([
{ prefix: 'chat:', invalidate: ['getChatSession'] },
{ prefix: 'agent:', invalidate: ['getChatSession'] },
{ prefix: 'agent:', invalidate: ['getChatSession', 'getAgent'] },
{ prefix: 'changeset:', invalidate: ['getChatSession'] },
]);
@@ -59,7 +63,14 @@ export function useChatSession(
{ enabled: !!targetId },
);
const session = (sessionQuery.data as ChatSession | null) ?? null;
const messages = session?.messages ?? [];
const serverMessages = session?.messages ?? [];
// Merge: show server messages, plus any optimistic ones not yet confirmed
const serverMsgIds = new Set(serverMessages.map(m => m.id));
const messages = [
...serverMessages,
...optimisticMessages.filter(m => !serverMsgIds.has(m.id)),
];
// Query agent status if session has an agent
const agentQuery = trpc.getAgent.useQuery(
@@ -86,6 +97,13 @@ export function useChatSession(
const sendMutation = trpc.sendChatMessage.useMutation({
onSuccess: () => {
void utils.getChatSession.invalidate({ targetType, targetId });
// Clear optimistic messages once server confirms
setOptimisticMessages([]);
},
onError: (err) => {
toast.error(`Chat failed: ${err.message}`);
// Remove optimistic messages on error
setOptimisticMessages([]);
},
});
@@ -94,6 +112,9 @@ export function useChatSession(
onSuccess: () => {
void utils.getChatSession.invalidate({ targetType, targetId });
},
onError: (err) => {
toast.error(`Failed to close chat: ${err.message}`);
},
});
const sendMutateRef = useRef(sendMutation.mutate);
@@ -105,6 +126,17 @@ export function useChatSession(
const sendMessage = useCallback(
(message: string) => {
// Add optimistic user message immediately
const optimistic: ChatMessage = {
id: `opt-${Date.now()}`,
chatSessionId: '',
role: 'user',
content: message,
changeSetId: null,
createdAt: new Date().toISOString(),
};
setOptimisticMessages(prev => [...prev, optimistic]);
sendMutateRef.current({
targetType,
targetId,