feat: Add "New Chat" button to reset chat session in-place

Adds startNewSession() to useChatSession hook that closes the current
session without closing the panel. New Plus button in chat header
appears when a conversation exists, with shift+click to skip confirm.
This commit is contained in:
Lukas May
2026-03-04 11:39:58 +01:00
parent 9c09683029
commit b6ac797644
2 changed files with 24 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'motion/react'; import { motion, AnimatePresence } from 'motion/react';
import { X, Loader2, AlertTriangle, RotateCcw } from 'lucide-react'; import { X, Plus, Loader2, AlertTriangle, RotateCcw } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useChatSession } from '@/hooks/useChatSession'; import { useChatSession } from '@/hooks/useChatSession';
import { ChatBubble } from './ChatBubble'; import { ChatBubble } from './ChatBubble';
@@ -42,7 +42,7 @@ function ChatSlideOverInner({
initiativeId: string; initiativeId: string;
onClose: () => void; onClose: () => void;
}) { }) {
const { messages, agentStatus, agentError, sendMessage, retryLastMessage, closeSession, isSending } = const { session, messages, agentStatus, agentError, sendMessage, retryLastMessage, closeSession, startNewSession, isSending } =
useChatSession(target.type, target.id, initiativeId); useChatSession(target.type, target.id, initiativeId);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
@@ -105,6 +105,19 @@ function ChatSlideOverInner({
Processing... Processing...
</div> </div>
)} )}
{session && messages.length > 0 && (
<button
className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
title="New Chat"
disabled={isAgentWorking}
onClick={(e) => {
if (!e.shiftKey && !window.confirm('Start a new chat? The current conversation will be closed.')) return;
startNewSession();
}}
>
<Plus className="h-4 w-4" />
</button>
)}
<button <button
className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
onClick={handleClose} onClick={handleClose}

View File

@@ -32,6 +32,7 @@ export interface UseChatSessionResult {
sendMessage: (message: string) => void; sendMessage: (message: string) => void;
retryLastMessage: () => void; retryLastMessage: () => void;
closeSession: () => void; closeSession: () => void;
startNewSession: () => void;
isSending: boolean; isSending: boolean;
isLoading: boolean; isLoading: boolean;
} }
@@ -186,6 +187,13 @@ export function useChatSession(
} }
}, []); }, []);
const startNewSession = useCallback(() => {
const s = sessionRef.current;
if (!s) return;
closeMutateRef.current({ sessionId: s.id });
setOptimisticMessages([]);
}, []);
const isLoading = sessionQuery.isLoading; const isLoading = sessionQuery.isLoading;
const isSending = sendMutation.isPending; const isSending = sendMutation.isPending;
@@ -197,6 +205,7 @@ export function useChatSession(
sendMessage, sendMessage,
retryLastMessage, retryLastMessage,
closeSession, closeSession,
startNewSession,
isSending, isSending,
isLoading, isLoading,
}; };