diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 11cc3fb..508f0c3 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -50,6 +50,7 @@ import { deletePlatform, deleteTenant, deleteUserGroup, + GatewayApiError, getHealth, listFileStorageChannels, getFileStorageSettings, @@ -271,7 +272,8 @@ export function App() { loadedDataKeysRef.current.add('modelRateLimits'); loadedDataKeysRef.current.add('platforms'); }) - .catch(() => { + .catch((err) => { + if (handleAuthExpired(err, token)) return; loadedDataKeysRef.current.delete('modelRateLimits'); loadedDataKeysRef.current.delete('platforms'); }); @@ -370,6 +372,7 @@ export function App() { requestKeys.forEach((key) => loadedDataKeysRef.current.add(key)); setState('ready'); } catch (err) { + if (handleAuthExpired(err, nextToken)) return; setState('error'); setError(err instanceof Error ? err.message : '加载失败'); } finally { @@ -925,7 +928,7 @@ export function App() { } } - function signOut() { + function resetAuthenticatedSession() { persistAccessToken(''); setToken(''); loadedDataKeysRef.current = new Set(health ? ['health'] : []); @@ -962,6 +965,19 @@ export function App() { setWalletTransactionTotal(0); setWorkspaceTransactionQuery(defaultWorkspaceTransactionQuery()); setCoreMessage(''); + } + + function handleAuthExpired(err: unknown, failedToken: string) { + if (!failedToken || !(err instanceof GatewayApiError) || err.details.status !== 401) return false; + resetAuthenticatedSession(); + setError(''); + setAuthMode('login'); + navigatePath(pathForWorkspaceSection('overview')); + return true; + } + + function signOut() { + resetAuthenticatedSession(); navigatePath('/'); } diff --git a/apps/web/src/styles/playground.css b/apps/web/src/styles/playground.css index 42a28fc..7a2d1ce 100644 --- a/apps/web/src/styles/playground.css +++ b/apps/web/src/styles/playground.css @@ -753,6 +753,9 @@ } .contentShell[data-page="playground"] { + position: relative; + display: flex; + flex-direction: column; width: 100vw; height: 100%; min-height: 0; @@ -761,6 +764,31 @@ padding: 0; } +.contentShell[data-page="playground"] > .notice, +.contentShell[data-page="playground"] > .shBadge { + position: absolute; + z-index: 30; + flex: 0 0 auto; + box-shadow: 0 12px 34px rgba(154, 52, 18, 0.12); +} + +.contentShell[data-page="playground"] > .shBadge { + top: 10px; + left: 12px; +} + +.contentShell[data-page="playground"] > .notice { + top: 38px; + left: 12px; + width: min(680px, calc(100% - 24px)); + margin: 0; +} + +.contentShell[data-page="playground"] > .playgroundPage { + flex: 1 1 auto; + height: auto; +} + .playgroundPage { --playground-content-width: 960px; display: grid; @@ -1396,7 +1424,7 @@ position: sticky; bottom: 0; margin: auto auto 0; - padding-top: 18px; + padding: 18px 0 24px; background: linear-gradient(180deg, rgba(250, 250, 250, 0), var(--surface-subtle) 40%); } @@ -2223,7 +2251,7 @@ } .mediaComposerDock { - padding: 16px 40px 28px; + padding: 16px 40px 24px; background: linear-gradient(180deg, rgba(250, 250, 250, 0), var(--surface-subtle) 26%); } @@ -2388,7 +2416,11 @@ } .mediaComposerDock { - padding: 12px 18px 18px; + padding: 12px 18px 20px; + } + + .assistantComposerDock { + padding-bottom: 20px; } .composerQuickPrompts {