Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog/unreleased/no-skeleton-fade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### English

- **Console** · Skeleton screens now swap to content instantly with no fade or entrance animation on any page, and route changes no longer play an entrance animation; usage logs no longer dim, slide or re-animate on paging, filtering, tab switches or auto-refresh.

### 中文

- **界面** · 所有页面的骨架屏结束后直接显示内容,不再有淡入或入场动画,切换页面也不再播放入场动画;使用日志在翻页、筛选、切换标签和自动刷新时不再变暗、滑动或重播动画。
7 changes: 2 additions & 5 deletions frontend/src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useRef, useState } from 'react'
import { useState } from 'react'
import { Outlet, useLocation } from 'react-router-dom'
import { AppHeader } from './AppHeader'
import { AppSidebar } from './AppSidebar'
import { useGsapReveal } from '@/hooks/useGsapReveal'
import { useI18n } from '@/hooks/useI18n'
import { Toaster } from '@/components/motion/Toaster'

Expand All @@ -23,8 +22,6 @@ export function AppLayout() {
const { page } = useI18n()
const key = routeKey[location.pathname] || 'home'
const copy = page(key)
const pageRef = useRef<HTMLDivElement>(null)
useGsapReveal(pageRef, location.pathname)

return (
<div className="relative flex h-dvh overflow-hidden bg-background text-foreground">
Expand All @@ -38,7 +35,7 @@ export function AppLayout() {
/>
<main className="min-h-0 flex-1 overflow-y-auto">
<div className="mx-auto w-full max-w-[1480px] px-4 pb-12 pt-5 sm:px-6 lg:px-8 lg:pb-16 lg:pt-6">
<div ref={pageRef} key={location.pathname}>
<div key={location.pathname}>
<Outlet />
</div>
</div>
Expand Down
78 changes: 0 additions & 78 deletions frontend/src/hooks/useSkeletonSwap.ts

This file was deleted.

25 changes: 0 additions & 25 deletions frontend/src/hooks/useTabPanelSlide.ts

This file was deleted.

4 changes: 1 addition & 3 deletions frontend/src/pages/AccessPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSkeletonExit } from '@/hooks/useSkeletonSwap'
import { CopyIcon } from '@/components/motion/CopyIcon'
import { copyText } from '@/lib/clipboard'
import { useEffect, useMemo, useState, type ReactNode } from 'react'
Expand Down Expand Up @@ -168,7 +167,6 @@ export function AccessPage() {
[chatEndpoint, payload, selectedAccount],
)

const skeletonExitRef = useSkeletonExit(loading && !overview)
if (loading && !overview) return <AccessPageSkeleton />

async function copy(value: string, kind: 'base' | 'curl') {
Expand Down Expand Up @@ -203,7 +201,7 @@ export function AccessPage() {
: t('requestIdle')

return (
<div ref={skeletonExitRef} className="space-y-5">
<div className="space-y-5">
<section className="grid gap-5 pb-6 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
<div data-gsap-reveal>
<h2 className="text-title font-semibold">{t('apiPlayground')}</h2>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/AccountsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSkeletonExit } from '@/hooks/useSkeletonSwap'
import { toast } from '@/lib/toast'
import { copyText } from '@/lib/clipboard'
import { useCallback, useEffect, useMemo, useState } from 'react'
Expand Down Expand Up @@ -191,7 +190,6 @@ export function AccountsPage() {
const shownFrom = filteredRows.length === 0 ? 0 : (currentPage - 1) * pageSize + 1
const shownTo = Math.min(filteredRows.length, currentPage * pageSize)

const skeletonExitRef = useSkeletonExit(accountsLoading && !hasAccounts)
if (accountsLoading && !hasAccounts) return <AccountsPageSkeleton />

async function run(id: string, kind: AccountBusyKind, action: () => Promise<void>) {
Expand Down Expand Up @@ -365,7 +363,7 @@ export function AccountsPage() {
}

return (
<div ref={skeletonExitRef} className="space-y-4">
<div className="space-y-4">
{checkinError ? <PageAlert title={checkinError} /> : null}
<section data-gsap-reveal className="grid gap-3 pb-3 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 text-body">
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/KeysPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSkeletonExit } from '@/hooks/useSkeletonSwap'
import { toast } from '@/lib/toast'
import { HoldToConfirm } from '@/components/motion/HoldToConfirm'
import { copyText } from '@/lib/clipboard'
Expand Down Expand Up @@ -171,11 +170,10 @@ export function KeysPage() {
}
}

const skeletonExitRef = useSkeletonExit(loading && !keys.length)
if (loading && !keys.length) return <KeysPageSkeleton />

return (
<div ref={skeletonExitRef} className="space-y-6">
<div className="space-y-6">
<section className="flex flex-wrap items-end justify-between gap-4 pb-4">
<div>
<h2 data-gsap-reveal className="text-title font-semibold">{t('navKeys')}</h2>
Expand Down
28 changes: 10 additions & 18 deletions frontend/src/pages/LogsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { toast } from '@/lib/toast'
import { useTabPanelSlide } from '@/hooks/useTabPanelSlide'
import { useSkeletonExit, useSkeletonSwap } from '@/hooks/useSkeletonSwap'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { getLocalTimeZone, type DateValue } from '@internationalized/date'
import {
Expand Down Expand Up @@ -49,7 +47,6 @@ import { useI18n } from '@/hooks/useI18n'
import { accountProviderLabel } from '@/lib/provider'

type PageTab = 'requests' | 'runtime'
const PAGE_TABS: readonly PageTab[] = ['requests', 'runtime']
type RequestFilter = 'all' | 'ok' | 'incomplete' | 'error' | 'canceled'
type RuntimeFilter = 'all' | 'info' | 'warn' | 'error'
type StreamFilter = 'all' | 'stream' | 'sync'
Expand Down Expand Up @@ -193,7 +190,6 @@ export function LogsPage() {
const [accounts, setAccounts] = useState<NonNullable<Overview['accounts']>>([])
const [models, setModels] = useState<NonNullable<Overview['models']>>([])
const [tab, setTab] = useState<PageTab>('requests')
useTabPanelSlide(tab, PAGE_TABS)
const [loading, setLoading] = useState(true)
const [booted, setBooted] = useState(false)
const [error, setError] = useState('')
Expand Down Expand Up @@ -221,7 +217,6 @@ export function LogsPage() {
const [selected, setSelected] = useState<RequestLog | null>(null)
const [detailOpen, setDetailOpen] = useState(false)
const [detailLoading, setDetailLoading] = useState(false)
useSkeletonSwap('detail', detailLoading)
const [detailError, setDetailError] = useState('')
const detailRequestRef = useRef(0)
const [clearOpen, setClearOpen] = useState(false)
Expand Down Expand Up @@ -475,20 +470,17 @@ export function LogsPage() {
}
}

// Refetches (page, filter, refresh) keep the current rows on screen, dimmed,
// instead of swapping back to the skeleton — the skeleton crossfade is for
// an empty list only, so paging no longer replays the entrance animation.
// Refetches (page, filter, refresh) keep the current rows on screen instead
// of swapping back to the skeleton; the skeleton is only for an empty list.
// No entrance/fade animation anywhere: skeleton -> content is an instant swap.
const listSkeleton = loading && (tab === 'requests' ? requests.length === 0 : runtime.length === 0)
const listStale = loading && !listSkeleton
useSkeletonSwap('logs', listSkeleton)
const skeletonExitRef = useSkeletonExit(loading && !booted)
if (loading && !booted) return <LogsPageSkeleton />

return (
<div ref={skeletonExitRef} className="space-y-6">
<div className="space-y-6">
<section className="flex flex-wrap items-end justify-between gap-4 pb-4">
<div>
<h2 data-gsap-reveal className="text-title font-semibold">{t('navLogs')}</h2>
<h2 className="text-title font-semibold">{t('navLogs')}</h2>
<p className="mt-1 max-w-2xl text-body leading-6 text-muted">{t('logsLead')}</p>
</div>
<div className="flex items-center gap-2">
Expand All @@ -509,11 +501,11 @@ export function LogsPage() {
<Tabs.ListContainer className="w-fit">
<Tabs.List>
<Tab id="requests" className="gap-1.5">
<Tabs.Indicator />
<Tabs.Indicator className="transition-none" />
<Scroll size={13} />{t('logsRequests')}
</Tab>
<Tab id="runtime" className="gap-1.5">
<Tabs.Indicator />
<Tabs.Indicator className="transition-none" />
<TerminalWindow size={13} />{t('logsRuntime')}
</Tab>
</Tabs.List>
Expand Down Expand Up @@ -717,7 +709,7 @@ export function LogsPage() {
</div>
</div>

<Card data-gsap-reveal data-skeleton-swap="logs" className={`overflow-hidden p-0 transition-opacity duration-200 ${listStale ? 'opacity-60' : ''}`} aria-busy={loading}>
<Card className="overflow-hidden p-0" aria-busy={loading}>
{listSkeleton ? (
<LogsRequestListSkeleton />
) : requests.length === 0 ? (
Expand Down Expand Up @@ -850,7 +842,7 @@ export function LogsPage() {
</div>
</div>

<Card data-gsap-reveal data-skeleton-swap="logs" className={`overflow-hidden p-0 transition-opacity duration-200 ${listStale ? 'opacity-60' : ''}`} aria-busy={loading}>
<Card className="overflow-hidden p-0" aria-busy={loading}>
{listSkeleton ? (
<LogsRuntimeListSkeleton />
) : runtime.length === 0 ? (
Expand Down Expand Up @@ -971,7 +963,7 @@ export function LogsPage() {
<X size={16} />
</Modal.CloseTrigger>
</Modal.Header>
<Modal.Body data-skeleton-swap="detail" className="space-y-4 px-5 pb-5">
<Modal.Body className="space-y-4 px-5 pb-5">
{detailLoading ? (
<RequestDetailSkeleton />
) : detailError ? (
Expand Down
16 changes: 6 additions & 10 deletions frontend/src/pages/OverviewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSkeletonExit, useSkeletonSwap } from '@/hooks/useSkeletonSwap'
import { lazy, Suspense, useEffect, useLayoutEffect, useRef, useState } from 'react'
import gsap from 'gsap'
import { Link } from 'react-router-dom'
Expand Down Expand Up @@ -86,8 +85,6 @@ export function OverviewPage() {
const [statsLoading, setStatsLoading] = useState(true)
const [accounts, setAccounts] = useState<NonNullable<Overview['accounts']>>([])
const [accountsLoading, setAccountsLoading] = useState(true)
useSkeletonSwap('stats', statsLoading)
useSkeletonSwap('accounts', accountsLoading)
const [modelCount, setModelCount] = useState(0)
const [resources, setResources] = useState<SystemResources | null>(null)

Expand Down Expand Up @@ -165,11 +162,10 @@ export function OverviewPage() {
{ label: t('metricTokens'), value: traffic.tokens.total, kind: 'compact' as const, detail: `${formatCompact(traffic.tokens.prompt)} / ${formatCompact(traffic.tokens.completion)}`, ok: true, icon: <Coins size={15} />, trend: null as number | null },
]

const skeletonExitRef = useSkeletonExit(loading && !overview)
if (loading && !overview) return <OverviewPageSkeleton />

return (
<div ref={skeletonExitRef} className="space-y-6">
<div className="space-y-6">
<section className="grid gap-4 pb-4 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
<div>
<h2 data-gsap-reveal className="text-title font-semibold">{t('homeDisplay')}</h2>
Expand Down Expand Up @@ -223,7 +219,7 @@ export function OverviewPage() {
</section>

<section className="grid gap-5">
<Card data-gsap-reveal data-skeleton-swap="stats" className="overflow-hidden p-0">
<Card data-gsap-reveal className="overflow-hidden p-0">
<div className="flex items-center justify-between gap-3 border-b border-separator px-5 py-4">
<div>
<h3 className="font-semibold">{t('statsTraffic')}</h3>
Expand Down Expand Up @@ -295,7 +291,7 @@ export function OverviewPage() {
</div>
))}
</div>
<div data-skeleton-swap="accounts" className="divide-y divide-separator">
<div className="divide-y divide-separator">
{accountsLoading ? (
<RankListSkeleton />
) : accounts.length === 0 ? (
Expand Down Expand Up @@ -356,7 +352,7 @@ export function OverviewPage() {
</section>

<section className="grid gap-5 xl:grid-cols-2 2xl:grid-cols-3">
<Card data-gsap-reveal data-skeleton-swap="stats" className="overflow-hidden p-0">
<Card data-gsap-reveal className="overflow-hidden p-0">
<div className="flex items-center justify-between gap-3 border-b border-separator px-5 py-4">
<div>
<h3 className="font-semibold">{t('statsTopProviders')}</h3>
Expand All @@ -379,7 +375,7 @@ export function OverviewPage() {
/>
)}
</Card>
<Card data-gsap-reveal data-skeleton-swap="stats" className="overflow-hidden p-0">
<Card data-gsap-reveal className="overflow-hidden p-0">
<div className="flex items-center justify-between gap-3 border-b border-separator px-5 py-4">
<div>
<h3 className="font-semibold">{t('statsTopModels')}</h3>
Expand All @@ -402,7 +398,7 @@ export function OverviewPage() {
)}
</Card>

<Card data-gsap-reveal data-skeleton-swap="stats" className="overflow-hidden p-0">
<Card data-gsap-reveal className="overflow-hidden p-0">
<div className="flex items-center justify-between gap-3 border-b border-separator px-5 py-4">
<div>
<h3 className="font-semibold">{traffic.errors.length ? t('statsErrorMix') : t('statsTopAccounts')}</h3>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/ProvidersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useSkeletonExit } from '@/hooks/useSkeletonSwap'
import { useEffect, useMemo, useState } from 'react'
import { Button, Card, Chip, Table, Tooltip } from '@heroui/react'
import { Cube, ArrowClockwise, MagnifyingGlass, Info } from '@phosphor-icons/react'
Expand Down Expand Up @@ -226,7 +225,6 @@ export function ProvidersPage() {
? t('logsShownTotal', { shown: `${shownFrom}–${shownTo}`, total: filtered.length })
: t('shownTotal', { shown: 0, total: models.length })

const skeletonExitRef = useSkeletonExit((loading && !overview) || modelsLoading)
if ((loading && !overview) || modelsLoading) return <ProvidersPageSkeleton />


Expand Down Expand Up @@ -326,7 +324,7 @@ export function ProvidersPage() {


return (
<div ref={skeletonExitRef} className="space-y-6">
<div className="space-y-6">
<section className="grid gap-5 pb-6 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
<div data-gsap-reveal>
<h2 className="text-title font-semibold">{t('availableModels')}</h2>
Expand Down
Loading
Loading