feat(frontend): add React SPA with chat, auth, and admin pages

- Vite 6 + React 19 + TypeScript + Tailwind 4 + Zustand 5
- Login page with JWT auth
- Chat interface: MessageList, ChatInput, ChatContainer
- WebSocket hook with auto-reconnect and exponential backoff
- Sidebar with new chat button + session navigation
- Admin dashboard placeholder
- 57 modules, 242KB JS, TypeScript zero errors
This commit is contained in:
Aska committed 2026-08-12 11:47:22 +08:00
1 parent 192144563b
commit 4a71965686
35 files changed
+3146

No files matched your search

@@ -0,0 +1,47 @@
import { useEffect, useRef } from 'react'
import { useChatStore } from '../../store/chatStore'
import { useWebSocket } from '../../hooks/useWebSocket'
import MessageList from './MessageList'
import ChatInput from './ChatInput'
export default function ChatContainer() {
const { send } = useWebSocket()
const { messages, isStreaming, currentSessionId, inputText, setInput, addMessage, setStreaming } = useChatStore()
const bottomRef = useRef<HTMLDivElement>(null)
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])
const handleSend = () => {
if (!inputText.trim() || !currentSessionId) return
const userMsg = {
id: crypto.randomUUID(), session_id: currentSessionId,
role: 'user' as const, content: inputText, created_at: new Date().toISOString(),
}
addMessage(userMsg)
setStreaming(true)
// Add placeholder for assistant response
addMessage({ id: crypto.randomUUID(), session_id: currentSessionId, role: 'assistant', content: '', created_at: new Date().toISOString() })
send({ type: 'message', content: inputText, session_id: currentSessionId, timestamp: Date.now() })
setInput('')
}
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-auto px-4 py-4">
<MessageList messages={messages} />
<div ref={bottomRef} />
</div>
<ChatInput
value={inputText}
onChange={setInput}
onSend={handleSend}
disabled={isStreaming}
/>
</div>
)
}