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:
CJACK
2026-02-22 17:25:48 +08:00
parent 5d3989a9a7
commit 6c48429b90
152 changed files with 13583 additions and 11817 deletions

View 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>
)
}

View 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,
}
}

View 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,
}
}