Files
new-api/web/default/src/features/dashboard/index.tsx
T

207 lines
6.4 KiB
TypeScript
Vendored

import { useState, useCallback, lazy, Suspense } from 'react'
import { getRouteApi } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { Skeleton } from '@/components/ui/skeleton'
import { SectionPageLayout } from '@/components/layout'
import {
CardStaggerContainer,
CardStaggerItem,
FadeIn,
} from '@/components/page-transition'
import { ModelsFilter } from './components/models/models-filter-dialog'
import { AnnouncementsPanel } from './components/overview/announcements-panel'
import { ApiInfoPanel } from './components/overview/api-info-panel'
import { FAQPanel } from './components/overview/faq-panel'
import { SummaryCards } from './components/overview/summary-cards'
import { UptimePanel } from './components/overview/uptime-panel'
import { DEFAULT_TIME_GRANULARITY } from './constants'
import {
type DashboardSectionId,
DASHBOARD_DEFAULT_SECTION,
} from './section-registry'
import { type DashboardFilters, type QuotaDataItem } from './types'
const route = getRouteApi('/_authenticated/dashboard/$section')
const LazyLogStatCards = lazy(() =>
import('./components/models/log-stat-cards').then((m) => ({
default: m.LogStatCards,
}))
)
const LazyModelCharts = lazy(() =>
import('./components/models/model-charts').then((m) => ({
default: m.ModelCharts,
}))
)
const LazyConsumptionDistributionChart = lazy(() =>
import('./components/models/consumption-distribution-chart').then((m) => ({
default: m.ConsumptionDistributionChart,
}))
)
const LazyUserCharts = lazy(() =>
import('./components/users/user-charts').then((m) => ({
default: m.UserCharts,
}))
)
function LogStatCardsFallback() {
return (
<div className='overflow-hidden rounded-lg border'>
<div className='divide-border/60 grid grid-cols-2 divide-x sm:grid-cols-3 lg:grid-cols-5'>
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className='px-4 py-3.5 sm:px-5 sm:py-4'>
<Skeleton className='h-3.5 w-16' />
<Skeleton className='mt-2 h-7 w-20' />
<Skeleton className='mt-1.5 h-3.5 w-28' />
</div>
))}
</div>
</div>
)
}
function ModelChartsFallback() {
return (
<div className='overflow-hidden rounded-lg border'>
<div className='flex items-center justify-between border-b px-4 py-3 sm:px-5'>
<Skeleton className='h-5 w-32' />
<Skeleton className='h-8 w-72' />
</div>
<div className='h-96 p-2'>
<Skeleton className='h-full w-full' />
</div>
</div>
)
}
const SECTION_META: Record<
DashboardSectionId,
{ titleKey: string; descriptionKey: string }
> = {
overview: {
titleKey: 'Overview',
descriptionKey: 'View dashboard overview and statistics',
},
models: {
titleKey: 'Models',
descriptionKey: 'View model statistics and charts',
},
users: {
titleKey: 'User Analytics',
descriptionKey: 'View user consumption statistics and charts',
},
}
export function Dashboard() {
const { t } = useTranslation()
const params = route.useParams()
const activeSection = (params.section ??
DASHBOARD_DEFAULT_SECTION) as DashboardSectionId
const [modelFilters, setModelFilters] = useState<DashboardFilters>({})
const [modelData, setModelData] = useState<QuotaDataItem[]>([])
const [dataLoading, setDataLoading] = useState(false)
const handleFilterChange = useCallback((filters: DashboardFilters) => {
setModelFilters(filters)
}, [])
const handleResetFilters = useCallback(() => {
setModelFilters({})
}, [])
const handleDataUpdate = useCallback(
(data: QuotaDataItem[], loading: boolean) => {
setModelData(data)
setDataLoading(loading)
},
[]
)
const meta = SECTION_META[activeSection] ?? SECTION_META.overview
return (
<SectionPageLayout>
<SectionPageLayout.Title>{t(meta.titleKey)}</SectionPageLayout.Title>
<SectionPageLayout.Description>
{t(meta.descriptionKey)}
</SectionPageLayout.Description>
{activeSection === 'models' && (
<SectionPageLayout.Actions>
<ModelsFilter
onFilterChange={handleFilterChange}
onReset={handleResetFilters}
/>
</SectionPageLayout.Actions>
)}
<SectionPageLayout.Content>
<div className='space-y-4'>
{activeSection === 'overview' && (
<>
<SummaryCards />
<CardStaggerContainer className='grid grid-cols-1 gap-4 lg:grid-cols-2'>
<CardStaggerItem>
<ApiInfoPanel />
</CardStaggerItem>
<CardStaggerItem>
<AnnouncementsPanel />
</CardStaggerItem>
<CardStaggerItem>
<FAQPanel />
</CardStaggerItem>
<CardStaggerItem>
<UptimePanel />
</CardStaggerItem>
</CardStaggerContainer>
</>
)}
{activeSection === 'models' && (
<>
<FadeIn>
<Suspense fallback={<LogStatCardsFallback />}>
<LazyLogStatCards
filters={modelFilters}
onDataUpdate={handleDataUpdate}
/>
</Suspense>
</FadeIn>
<FadeIn delay={0.1}>
<Suspense fallback={<ModelChartsFallback />}>
<LazyConsumptionDistributionChart
data={modelData}
loading={dataLoading}
timeGranularity={
modelFilters.time_granularity || DEFAULT_TIME_GRANULARITY
}
/>
</Suspense>
</FadeIn>
<FadeIn delay={0.15}>
<Suspense fallback={<ModelChartsFallback />}>
<LazyModelCharts
data={modelData}
loading={dataLoading}
timeGranularity={
modelFilters.time_granularity || DEFAULT_TIME_GRANULARITY
}
/>
</Suspense>
</FadeIn>
</>
)}
{activeSection === 'users' && (
<FadeIn>
<Suspense fallback={<ModelChartsFallback />}>
<LazyUserCharts />
</Suspense>
</FadeIn>
)}
</div>
</SectionPageLayout.Content>
</SectionPageLayout>
)
}