Add HeXi bot codebase: custom plugins, web frontends, tests
- hexi core: message handling, rate limiting, cooldown, plugin manager - Custom plugins: BF stats, daily check-in, quotes, persona cards, etc. - Community plugins vendored under hexi/plugins with local fixes - Web admin frontends (learning-chat, persona-admin), unified hexi/web - Tests for rate_limit/cooldown/memes/persona; poetry.lock Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Button } from '@heroui/react'
|
||||
import { useAuth } from './auth'
|
||||
|
||||
export function LoginPage() {
|
||||
const { login } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [err, setErr] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const submit = async () => {
|
||||
setLoading(true); setErr('')
|
||||
try {
|
||||
await login(username, password)
|
||||
navigate('/hub')
|
||||
} catch (e: any) {
|
||||
setErr(e.message || '登录失败')
|
||||
} finally { setLoading(false) }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-800 flex items-center justify-center">
|
||||
<div className="w-full max-w-sm bg-white border border-slate-200 rounded-2xl shadow-sm p-8">
|
||||
<h1 className="text-xl font-bold mb-6">HeXi 管理系统</h1>
|
||||
<div className="space-y-4">
|
||||
<input className="w-full border border-slate-200 rounded-lg px-3 py-2 focus:border-indigo-500 focus:outline-none" placeholder="用户名" value={username} onChange={e => setUsername(e.target.value)} />
|
||||
<input className="w-full border border-slate-200 rounded-lg px-3 py-2 focus:border-indigo-500 focus:outline-none" type="password" placeholder="密码" value={password} onChange={e => setPassword(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') submit() }} />
|
||||
{err && <div className="text-red-500 text-sm">{err}</div>}
|
||||
<Button className="w-full" isDisabled={loading} onPress={submit}>{loading ? '登录中…' : '登录'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createContext, useContext, useState, type ReactNode } from 'react'
|
||||
import { authLogin, clearSession, getUserName, setSession } from '../api/client'
|
||||
|
||||
interface AuthState {
|
||||
token: string
|
||||
username: string
|
||||
login: (u: string, p: string) => Promise<void>
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
const Ctx = createContext<AuthState>({ token: '', username: '', login: async () => {}, logout: () => {} })
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [token, setToken] = useState(() => localStorage.getItem('hexi_web_token') || '')
|
||||
const [username, setUsername] = useState(() => localStorage.getItem('hexi_web_username') || '')
|
||||
const login = async (u: string, p: string) => {
|
||||
const data = await authLogin(u, p)
|
||||
setSession(data.token, data.username)
|
||||
setToken(data.token)
|
||||
setUsername(data.username)
|
||||
}
|
||||
const logout = () => {
|
||||
clearSession()
|
||||
setToken('')
|
||||
setUsername('')
|
||||
}
|
||||
return <Ctx.Provider value={{ token, username, login, logout }}>{children}</Ctx.Provider>
|
||||
}
|
||||
|
||||
export const useAuth = () => useContext(Ctx)
|
||||
Reference in New Issue
Block a user