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:
2026-09-01 13:13:40 +08:00
co-authored by Claude
parent 1783c60afa
commit b61d09f09f
3201 changed files with 160436 additions and 171 deletions
+37
View File
@@ -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>
)
}
+30
View File
@@ -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)