fix(chat): move toolbar to Header and add New Session button
- Move ChatToolbar (session selector, refresh, thinking toggle) from the Chat page body into the Header component, so controls appear at the same level as the "Chat" title - Add New Session button (+) to create a fresh conversation - Add newSession action to chat store - Header conditionally renders ChatToolbar only on /chat route - Chat page fills full content area without duplicate toolbar
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Header Component
|
* Header Component
|
||||||
* Top navigation bar with page title
|
* Top navigation bar with page title and page-specific controls.
|
||||||
|
* On the Chat page, shows session selector, refresh, thinking toggle, and new session.
|
||||||
*/
|
*/
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { ChatToolbar } from '@/pages/Chat/ChatToolbar';
|
||||||
|
|
||||||
// Page titles mapping
|
// Page titles mapping
|
||||||
const pageTitles: Record<string, string> = {
|
const pageTitles: Record<string, string> = {
|
||||||
@@ -16,13 +18,15 @@ const pageTitles: Record<string, string> = {
|
|||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Get current page title
|
|
||||||
const currentTitle = pageTitles[location.pathname] || 'ClawX';
|
const currentTitle = pageTitles[location.pathname] || 'ClawX';
|
||||||
|
const isChatPage = location.pathname === '/chat';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-14 items-center border-b bg-background px-6">
|
<header className="flex h-14 items-center justify-between border-b bg-background px-6">
|
||||||
<h2 className="text-lg font-semibold">{currentTitle}</h2>
|
<h2 className="text-lg font-semibold">{currentTitle}</h2>
|
||||||
|
|
||||||
|
{/* Chat-specific controls */}
|
||||||
|
{isChatPage && <ChatToolbar />}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Chat Toolbar
|
* Chat Toolbar
|
||||||
* Session selector, refresh, and thinking toggle controls.
|
* Session selector, new session, refresh, and thinking toggle.
|
||||||
|
* Rendered in the Header when on the Chat page.
|
||||||
*/
|
*/
|
||||||
import { RefreshCw, Brain, ChevronDown } from 'lucide-react';
|
import { RefreshCw, Brain, ChevronDown, Plus } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useChatStore } from '@/stores/chat';
|
import { useChatStore } from '@/stores/chat';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -11,6 +12,7 @@ export function ChatToolbar() {
|
|||||||
const sessions = useChatStore((s) => s.sessions);
|
const sessions = useChatStore((s) => s.sessions);
|
||||||
const currentSessionKey = useChatStore((s) => s.currentSessionKey);
|
const currentSessionKey = useChatStore((s) => s.currentSessionKey);
|
||||||
const switchSession = useChatStore((s) => s.switchSession);
|
const switchSession = useChatStore((s) => s.switchSession);
|
||||||
|
const newSession = useChatStore((s) => s.newSession);
|
||||||
const refresh = useChatStore((s) => s.refresh);
|
const refresh = useChatStore((s) => s.refresh);
|
||||||
const loading = useChatStore((s) => s.loading);
|
const loading = useChatStore((s) => s.loading);
|
||||||
const showThinking = useChatStore((s) => s.showThinking);
|
const showThinking = useChatStore((s) => s.showThinking);
|
||||||
@@ -51,6 +53,17 @@ export function ChatToolbar() {
|
|||||||
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* New Session */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={newSession}
|
||||||
|
title="New session"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
{/* Refresh */}
|
{/* Refresh */}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { useChatStore } from '@/stores/chat';
|
import { useChatStore } from '@/stores/chat';
|
||||||
import { useGatewayStore } from '@/stores/gateway';
|
import { useGatewayStore } from '@/stores/gateway';
|
||||||
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
|
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
|
||||||
import { ChatToolbar } from './ChatToolbar';
|
|
||||||
import { ChatMessage } from './ChatMessage';
|
import { ChatMessage } from './ChatMessage';
|
||||||
import { ChatInput } from './ChatInput';
|
import { ChatInput } from './ChatInput';
|
||||||
import { extractText } from './message-utils';
|
import { extractText } from './message-utils';
|
||||||
@@ -63,13 +62,7 @@ export function Chat() {
|
|||||||
const streamText = streamingMessage ? extractText(streamingMessage) : '';
|
const streamText = streamingMessage ? extractText(streamingMessage) : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[calc(100vh-4rem)] flex-col">
|
<div className="flex flex-col -m-6" style={{ height: 'calc(100vh - 3.5rem)' }}>
|
||||||
{/* Toolbar: session selector, refresh, thinking toggle */}
|
|
||||||
<div className="flex items-center justify-between px-4 py-2 border-b">
|
|
||||||
<div /> {/* spacer */}
|
|
||||||
<ChatToolbar />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Messages Area */}
|
{/* Messages Area */}
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-4">
|
<div className="flex-1 overflow-y-auto px-4 py-4">
|
||||||
<div className="max-w-4xl mx-auto space-y-4">
|
<div className="max-w-4xl mx-auto space-y-4">
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ interface ChatState {
|
|||||||
// Actions
|
// Actions
|
||||||
loadSessions: () => Promise<void>;
|
loadSessions: () => Promise<void>;
|
||||||
switchSession: (key: string) => void;
|
switchSession: (key: string) => void;
|
||||||
|
newSession: () => void;
|
||||||
loadHistory: () => Promise<void>;
|
loadHistory: () => Promise<void>;
|
||||||
sendMessage: (text: string) => Promise<void>;
|
sendMessage: (text: string) => Promise<void>;
|
||||||
handleChatEvent: (event: Record<string, unknown>) => void;
|
handleChatEvent: (event: Record<string, unknown>) => void;
|
||||||
@@ -129,6 +130,23 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|||||||
get().loadHistory();
|
get().loadHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── New session ──
|
||||||
|
|
||||||
|
newSession: () => {
|
||||||
|
// Generate a new unique session key and switch to it
|
||||||
|
const newKey = `session-${Date.now()}`;
|
||||||
|
set({
|
||||||
|
currentSessionKey: newKey,
|
||||||
|
messages: [],
|
||||||
|
streamingText: '',
|
||||||
|
streamingMessage: null,
|
||||||
|
activeRunId: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
// Reload sessions list to include the new one after first message
|
||||||
|
get().loadSessions();
|
||||||
|
},
|
||||||
|
|
||||||
// ── Load chat history ──
|
// ── Load chat history ──
|
||||||
|
|
||||||
loadHistory: async () => {
|
loadHistory: async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user