mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-15 13:45:10 +08:00
feat: Implement DeepSeek integration, refactor model adapters for streaming and tool calls, enhance admin and account management, and introduce new UI features for settings, API testing, and Vercel sync.
This commit is contained in:
84
webui/src/app/AppRoutes.jsx
Normal file
84
webui/src/app/AppRoutes.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import LandingPage from '../components/LandingPage'
|
||||
import Login from '../components/Login'
|
||||
import DashboardShell from '../layout/DashboardShell'
|
||||
import { useI18n } from '../i18n'
|
||||
import { useAdminAuth } from './useAdminAuth'
|
||||
import { useAdminConfig } from './useAdminConfig'
|
||||
|
||||
export default function AppRoutes() {
|
||||
const { t } = useI18n()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
const isProduction = import.meta.env.MODE === 'production'
|
||||
const {
|
||||
token,
|
||||
authChecking,
|
||||
message,
|
||||
isAdminRoute,
|
||||
isVercel,
|
||||
showMessage,
|
||||
handleLogin,
|
||||
handleLogout,
|
||||
} = useAdminAuth({ isProduction, location, t })
|
||||
|
||||
const {
|
||||
config,
|
||||
fetchConfig,
|
||||
} = useAdminConfig({ token, showMessage, t })
|
||||
|
||||
if (isAdminRoute && authChecking) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
|
||||
<p className="text-muted-foreground animate-pulse">{t('auth.checking')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
{!isProduction && (
|
||||
<Route path="/" element={<LandingPage onEnter={() => navigate('/admin')} />} />
|
||||
)}
|
||||
<Route path={isProduction ? "/" : "/admin"} element={
|
||||
token ? (
|
||||
<DashboardShell
|
||||
token={token}
|
||||
onLogout={handleLogout}
|
||||
config={config}
|
||||
fetchConfig={fetchConfig}
|
||||
showMessage={showMessage}
|
||||
message={message}
|
||||
onForceLogout={handleLogout}
|
||||
isVercel={isVercel}
|
||||
/>
|
||||
) : (
|
||||
<div className="min-h-screen flex flex-col bg-background relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none z-0">
|
||||
<div className="absolute top-[-10%] right-[-10%] w-[50%] h-[50%] bg-primary/5 rounded-full blur-[120px]"></div>
|
||||
<div className="absolute bottom-[-10%] left-[-10%] w-[50%] h-[50%] bg-accent/5 rounded-full blur-[120px]"></div>
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<div className={clsx(
|
||||
"fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-lg border animate-in slide-in-from-top-2 fade-in",
|
||||
message.type === 'error' ? "bg-destructive/10 border-destructive/20 text-destructive" :
|
||||
"bg-primary/10 border-primary/20 text-primary"
|
||||
)}>
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
<Login onLogin={handleLogin} onMessage={showMessage} />
|
||||
</div>
|
||||
)
|
||||
} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
70
webui/src/app/useAdminAuth.js
Normal file
70
webui/src/app/useAdminAuth.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { detectRuntimeEnv } from '../utils/runtimeEnv'
|
||||
|
||||
export function useAdminAuth({ isProduction, location, t }) {
|
||||
const [message, setMessage] = useState(null)
|
||||
const [token, setToken] = useState(null)
|
||||
const [authChecking, setAuthChecking] = useState(true)
|
||||
|
||||
const isAdminRoute = location.pathname.startsWith('/admin') || isProduction
|
||||
const runtimeEnv = useMemo(() => detectRuntimeEnv(), [])
|
||||
const isVercel = runtimeEnv.isVercel
|
||||
|
||||
const showMessage = useCallback((type, text) => {
|
||||
setMessage({ type, text })
|
||||
setTimeout(() => setMessage(null), 5000)
|
||||
}, [])
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
setToken(null)
|
||||
localStorage.removeItem('ds2api_token')
|
||||
localStorage.removeItem('ds2api_token_expires')
|
||||
sessionStorage.removeItem('ds2api_token')
|
||||
sessionStorage.removeItem('ds2api_token_expires')
|
||||
}, [])
|
||||
|
||||
const handleLogin = useCallback((newToken) => {
|
||||
setToken(newToken)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdminRoute) {
|
||||
setAuthChecking(false)
|
||||
return
|
||||
}
|
||||
|
||||
const checkAuth = async () => {
|
||||
const storedToken = localStorage.getItem('ds2api_token') || sessionStorage.getItem('ds2api_token')
|
||||
const expiresAt = parseInt(localStorage.getItem('ds2api_token_expires') || sessionStorage.getItem('ds2api_token_expires') || '0')
|
||||
|
||||
if (storedToken && expiresAt > Date.now()) {
|
||||
try {
|
||||
const res = await fetch('/admin/verify', {
|
||||
headers: { 'Authorization': `Bearer ${storedToken}` }
|
||||
})
|
||||
if (res.ok) {
|
||||
setToken(storedToken)
|
||||
} else {
|
||||
handleLogout()
|
||||
}
|
||||
} catch {
|
||||
setToken(storedToken)
|
||||
}
|
||||
}
|
||||
setAuthChecking(false)
|
||||
}
|
||||
|
||||
checkAuth()
|
||||
}, [handleLogout, isAdminRoute, t])
|
||||
|
||||
return {
|
||||
token,
|
||||
authChecking,
|
||||
message,
|
||||
isAdminRoute,
|
||||
isVercel,
|
||||
showMessage,
|
||||
handleLogin,
|
||||
handleLogout,
|
||||
}
|
||||
}
|
||||
32
webui/src/app/useAdminConfig.js
Normal file
32
webui/src/app/useAdminConfig.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export function useAdminConfig({ token, showMessage, t }) {
|
||||
const [config, setConfig] = useState({ keys: [], accounts: [] })
|
||||
|
||||
const fetchConfig = useCallback(async () => {
|
||||
if (!token) return
|
||||
try {
|
||||
const res = await fetch('/admin/config', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setConfig(data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch config:', e)
|
||||
showMessage('error', t('errors.fetchConfig', { error: e.message }))
|
||||
}
|
||||
}, [showMessage, t, token])
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
fetchConfig()
|
||||
}
|
||||
}, [fetchConfig, token])
|
||||
|
||||
return {
|
||||
config,
|
||||
fetchConfig,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user