feat: add ETag cache optimization, code-split WebUI, and refactor XML tool scanner

- Chat history: early 304 via Revision()/DetailRevision() to avoid full snapshot reads
- WebUI: lazy-load tab containers with Suspense fallback
- Toolstream: split tool_sieve_xml.go into tags.go and scan.go
- CI: trigger on main branch, guard cross-build to dev/main pushes only
- Docs: add DEVELOPER.md developer quick reference

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
CJACK
2026-04-27 14:37:23 +08:00
parent 1602c3a43c
commit 6959aa2982
10 changed files with 403 additions and 192 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { Suspense, lazy, useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import {
LayoutDashboard,
@@ -11,20 +11,33 @@ import {
Server,
Users,
Globe,
History
History,
Loader2
} from 'lucide-react'
import clsx from 'clsx'
import AccountManagerContainer from '../features/account/AccountManagerContainer'
import ApiTesterContainer from '../features/apiTester/ApiTesterContainer'
import ChatHistoryContainer from '../features/chatHistory/ChatHistoryContainer'
import BatchImport from '../components/BatchImport'
import VercelSyncContainer from '../features/vercel/VercelSyncContainer'
import SettingsContainer from '../features/settings/SettingsContainer'
import ProxyManagerContainer from '../features/proxy/ProxyManagerContainer'
import LanguageToggle from '../components/LanguageToggle'
import { useI18n } from '../i18n'
const AccountManagerContainer = lazy(() => import('../features/account/AccountManagerContainer'))
const ApiTesterContainer = lazy(() => import('../features/apiTester/ApiTesterContainer'))
const ChatHistoryContainer = lazy(() => import('../features/chatHistory/ChatHistoryContainer'))
const BatchImport = lazy(() => import('../components/BatchImport'))
const VercelSyncContainer = lazy(() => import('../features/vercel/VercelSyncContainer'))
const SettingsContainer = lazy(() => import('../features/settings/SettingsContainer'))
const ProxyManagerContainer = lazy(() => import('../features/proxy/ProxyManagerContainer'))
function TabLoadingFallback({ label }) {
return (
<div className="min-h-[320px] rounded-lg border border-border bg-card flex items-center justify-center">
<div className="flex items-center gap-3 text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin" />
<span>{label}</span>
</div>
</div>
)
}
export default function DashboardShell({ token, onLogout, config, fetchConfig, showMessage, message, onForceLogout, isVercel }) {
const { t } = useI18n()
const location = useLocation()
@@ -47,6 +60,7 @@ export default function DashboardShell({ token, onLogout, config, fetchConfig, s
const pathTab = routeSegments[0] || ''
const activeTab = tabIds.has(pathTab) ? pathTab : 'accounts'
const adminBasePath = pathSegments[0] === 'admin' ? '/admin' : ''
const activeNavItem = navItems.find(n => n.id === activeTab)
const navigateToTab = useCallback((tabID) => {
const nextPath = tabID === 'accounts'
@@ -232,10 +246,10 @@ export default function DashboardShell({ token, onLogout, config, fetchConfig, s
<div className="max-w-6xl mx-auto space-y-4 lg:space-y-6">
<div className="hidden lg:block mb-8">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{navItems.find(n => n.id === activeTab)?.label}
{activeNavItem?.label}
</h1>
<p className="text-muted-foreground">
{navItems.find(n => n.id === activeTab)?.description}
{activeNavItem?.description}
</p>
</div>
@@ -251,7 +265,9 @@ export default function DashboardShell({ token, onLogout, config, fetchConfig, s
)}
<div className="animate-in fade-in duration-500">
{renderTab()}
<Suspense fallback={<TabLoadingFallback label={activeNavItem?.label || 'DS2API'} />}>
{renderTab()}
</Suspense>
</div>
</div>
</div>