import { useStore } from '@nanostores/react'; import type { LinksFunction, LoaderFunctionArgs } from '@remix-run/node'; import { data } from '@remix-run/node'; import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration, useRouteError, useRouteLoaderData, } from '@remix-run/react'; import tailwindReset from '@unocss/reset/tailwind-compat.css?url'; import { useEffect } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { ClientOnly } from 'remix-utils/client-only'; import { Toaster } from 'sonner'; import { getUser } from './lib/.server/auth'; import { getUserUsageStats } from './lib/.server/chatUsage'; import { get1PanelConnectionSettings, getNetlifyConnectionSettings, getVercelConnectionSettings, } from './lib/.server/connectionSettings'; import { logStore } from './lib/stores/logs'; import { themeStore } from './lib/stores/theme'; import globalStyles from './styles/index.scss?url'; import { stripIndents } from './utils/strip-indent'; import 'virtual:uno.css'; import type { ComponentType } from 'react'; import { useState } from 'react'; import { isProduction } from './lib/stores/settings'; // 定义连接设置类型 export interface ConnectionSettings { _1PanelConnection: boolean; netlifyConnection: boolean; vercelConnection: boolean; } export async function loader({ request }: LoaderFunctionArgs) { const userContext = await getUser(request); const userChatUsage = await getUserUsageStats(userContext.userInfo?.sub as string); const userId = userContext?.userInfo?.sub as string; let connectionSettings: ConnectionSettings = { _1PanelConnection: false, netlifyConnection: false, vercelConnection: false, }; if (userId) { // 获取用户连接设置 const [_1PanelSettings, netlifySettings, vercelSettings] = await Promise.all([ get1PanelConnectionSettings(userId), getNetlifyConnectionSettings(userId), getVercelConnectionSettings(userId), ]); connectionSettings = { _1PanelConnection: !!_1PanelSettings, netlifyConnection: !!netlifySettings, vercelConnection: !!vercelSettings, }; } return data({ auth: { isAuthenticated: userContext.isAuthenticated, userInfo: userContext.isAuthenticated ? userContext.userInfo : null, }, chatUsage: userChatUsage, ENV: { OPERATING_ENV: process.env.OPERATING_ENV || '', MAX_UPLOAD_SIZE_MB: parseInt(process.env.MAX_UPLOAD_SIZE_MB || '5'), }, connectionSettings, }); } export const links: LinksFunction = () => [ { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml', }, { rel: 'stylesheet', href: tailwindReset }, { rel: 'stylesheet', href: globalStyles }, { rel: 'preconnect', href: 'https://fonts.googleapis.com', }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', }, ]; const inlineThemeCode = stripIndents` setTutorialKitTheme(); function setTutorialKitTheme() { let theme = localStorage.getItem('upage_theme'); if (!theme) { theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } document.querySelector('html')?.setAttribute('data-theme', theme); } `; export function Layout({ children }: { children: React.ReactNode }) { const production = useStore(isProduction); const data = useRouteLoaderData<{ ENV: { OPERATING_ENV: string; MAX_UPLOAD_SIZE_MB: number }; }>('root'); const theme = useStore(themeStore); useEffect(() => { document.querySelector('html')?.setAttribute('data-theme', theme); }, [theme]); return (
状态: {error.status}
{error.data}
> ) : error instanceof Error ? ({error.message}
) : (发生了未知错误
)} 返回首页