- TypeScript types for chat session, messages, and responses - API client module with all 6 endpoints - Zustand store with session management, message sending, tree generation, import, resume - 7 chat components: ChatMessage, ChatInput, ChatPanel, PhaseIndicator, ChatToolbar, EmptyPreview, StaticTreePreview - AIChatBuilderPage with split-panel layout (60% chat / 40% preview) - Route at /ai/chat with lazy loading - "Build with AI" button on TreeLibraryPage - Session resume via URL search params Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
import { useEffect, useRef } from 'react'
|
|
import { ChatMessage } from './ChatMessage'
|
|
import { ChatInput } from './ChatInput'
|
|
import { Spinner } from '@/components/common/Spinner'
|
|
import type { ChatMessage as ChatMessageType } from '@/types'
|
|
|
|
interface ChatPanelProps {
|
|
messages: ChatMessageType[]
|
|
isResponding: boolean
|
|
onSendMessage: (content: string) => void
|
|
disabled?: boolean
|
|
}
|
|
|
|
export function ChatPanel({ messages, isResponding, onSendMessage, disabled }: ChatPanelProps) {
|
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
|
|
// Auto-scroll to bottom on new messages
|
|
useEffect(() => {
|
|
if (scrollRef.current) {
|
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight
|
|
}
|
|
}, [messages, isResponding])
|
|
|
|
return (
|
|
<div className="flex h-full flex-col">
|
|
{/* Messages */}
|
|
<div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
|
{messages.map((msg, i) => (
|
|
<ChatMessage key={i} message={msg} />
|
|
))}
|
|
{isResponding && (
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
<Spinner size="sm" />
|
|
<span>Thinking...</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Input */}
|
|
<ChatInput
|
|
onSend={onSendMessage}
|
|
disabled={disabled || isResponding}
|
|
placeholder={isResponding ? 'Waiting for response...' : 'Type a message...'}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|