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:
1 parent
192144563b
commit
4a71965686
35 files changed
+3146
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,27 @@
|
||||
import { create } from 'zustand'
|
||||
import { api, setToken, getToken } from '../api/client'
|
||||
|
||||
interface AuthState {
|
||||
isLoggedIn: boolean
|
||||
username: string
|
||||
role: string
|
||||
login: (username: string, password: string) => Promise<void>
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
isLoggedIn: !!getToken(),
|
||||
username: '',
|
||||
role: 'user',
|
||||
|
||||
login: async (username: string, password: string) => {
|
||||
const { token } = await api.login(username, password)
|
||||
setToken(token)
|
||||
set({ isLoggedIn: true, username, role: 'admin' })
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
setToken(null)
|
||||
set({ isLoggedIn: false, username: '', role: 'user' })
|
||||
},
|
||||
}))
|
||||
@@ -0,0 +1,41 @@
|
||||
import { create } from 'zustand'
|
||||
import type { Message, IotDevice } from '../types/chat'
|
||||
|
||||
interface ChatState {
|
||||
messages: Message[]
|
||||
isStreaming: boolean
|
||||
currentSessionId: string | null
|
||||
iotDevices: IotDevice[]
|
||||
inputText: string
|
||||
|
||||
setInput: (text: string) => void
|
||||
setSessionId: (id: string) => void
|
||||
addMessage: (msg: Message) => void
|
||||
appendToLast: (content: string) => void
|
||||
setStreaming: (v: boolean) => void
|
||||
setIotDevices: (d: IotDevice[]) => void
|
||||
clearMessages: () => void
|
||||
}
|
||||
|
||||
export const useChatStore = create<ChatState>((set, get) => ({
|
||||
messages: [],
|
||||
isStreaming: false,
|
||||
currentSessionId: null,
|
||||
iotDevices: [],
|
||||
inputText: '',
|
||||
|
||||
setInput: (text) => set({ inputText: text }),
|
||||
setSessionId: (id) => set({ currentSessionId: id }),
|
||||
setStreaming: (v) => set({ isStreaming: v }),
|
||||
setIotDevices: (d) => set({ iotDevices: d }),
|
||||
|
||||
addMessage: (msg) => set((s) => ({ messages: [...s.messages, msg] })),
|
||||
appendToLast: (content) => {
|
||||
const msgs = [...get().messages]
|
||||
if (msgs.length > 0) {
|
||||
msgs[msgs.length - 1] = { ...msgs[msgs.length - 1], content: msgs[msgs.length - 1].content + content }
|
||||
}
|
||||
set({ messages: msgs })
|
||||
},
|
||||
clearMessages: () => set({ messages: [] }),
|
||||
}))
|
||||
Reference in new issue
Block a user