perf: make lovart home shell load first

This commit is contained in:
Your Name 2026-05-26 19:49:45 +08:00
parent 1db4444c7b
commit bac7d6f196
5 changed files with 84 additions and 109 deletions

View File

@ -1,51 +0,0 @@
import { Suspense } from "react";
import { LovartHome } from "@/components/home/lovart-home";
import { auth } from "../(auth)/auth";
import { getSiteSettings, getSystemSetting } from "@/lib/db/queries";
export default function Page() {
return (
<Suspense fallback={<div className="flex h-dvh" />}>
<NewChatPage />
</Suspense>
);
}
async function NewChatPage() {
const [session, siteSettings, surpriseCategoriesSetting] =
await Promise.all([
auth(),
getSiteSettings(),
getSystemSetting({ key: "home_surprise_categories" }).catch(() => undefined),
]);
const surpriseCategories = Array.isArray(surpriseCategoriesSetting?.value)
? surpriseCategoriesSetting?.value
.map((item: unknown) => String(item || "").trim())
.filter(Boolean)
: ["全部", "精选"];
return (
<LovartHome
user={
session?.user
? {
id: session.user.id,
email: session.user.email,
name: session.user.name,
type: session.user.type,
}
: null
}
isAuthenticated={Boolean(session?.user && session.user.type !== "guest")}
siteName={siteSettings.siteName || "AI Chatbot"}
siteLogo={siteSettings.siteLogo || ""}
siteLogoDark={siteSettings.siteLogoDark || ""}
surpriseCategories={surpriseCategories}
heroPromoText={siteSettings.lovartPromoText}
heroPromoActionText={siteSettings.lovartPromoActionText}
heroTitleText={siteSettings.lovartHeroTitle}
heroSubtitleText={siteSettings.lovartHeroSubtitle}
/>
);
}

View File

@ -798,6 +798,7 @@ async function getScopedPublicSiteSettings(
}
const { settings, valueByKey } = await readScopedSiteSettings([
HOME_SURPRISE_CATEGORIES_KEY,
LOVART_COMPOSER_PREVIEW_TEXTS_KEY,
LOVART_COMPOSER_PREFIX_KEY,
LOVART_PROMPT_TAG_VISIBILITY_KEY,
@ -813,6 +814,16 @@ async function getScopedPublicSiteSettings(
return createScopedSiteSettingsResponse(scope, {
...createBrandingSettings(typedSettings),
lovartPromoText:
typedSettings?.lovartPromoText ||
"现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠",
lovartPromoActionText: typedSettings?.lovartPromoActionText || "立即升级",
lovartHeroTitle: typedSettings?.lovartHeroTitle || "由壹点AI 让设计更简单",
lovartHeroSubtitle:
typedSettings?.lovartHeroSubtitle || "懂你的设计代理,帮你搞定一切",
surpriseCategories: normalizeSurpriseCategories(
valueByKey.get(HOME_SURPRISE_CATEGORIES_KEY)
),
canvasDefaultModelId: typedSettings?.canvasDefaultModelId || null,
lovartComposerPrefix: normalizeLovartComposerPrefix(
valueByKey.get(LOVART_COMPOSER_PREFIX_KEY)

View File

@ -7,36 +7,26 @@ import { ImageDeliveryOptimizer } from "@/components/image-delivery-optimizer";
import "./globals.css";
import { SessionProvider } from "next-auth/react";
import { getSiteSettings } from "@/lib/db/queries";
const metadataBase = process.env.AUTH_URL
? new URL(process.env.AUTH_URL)
: undefined;
export async function generateMetadata(): Promise<Metadata> {
const settings = await getSiteSettings();
const updatedAt =
"updatedAt" in settings && settings.updatedAt
? new Date(settings.updatedAt).getTime()
: "default";
const faviconUrl = `/api/favicon?v=${updatedAt}`;
const metadataBase = process.env.AUTH_URL
? new URL(process.env.AUTH_URL)
: undefined;
return {
metadataBase,
title: {
template: `%s | ${settings.siteName}`,
default: settings.siteName,
},
description: settings.siteName,
icons: {
icon: [
{ url: faviconUrl, sizes: "any" },
{ url: faviconUrl, type: "image/x-icon" },
],
apple: faviconUrl,
shortcut: faviconUrl,
},
};
}
export const metadata: Metadata = {
metadataBase,
title: {
template: "%s | AI Chatbot",
default: "AI Chatbot",
},
description: "AI Chatbot",
icons: {
icon: [
{ url: "/api/favicon", sizes: "any" },
{ url: "/api/favicon", type: "image/x-icon" },
],
apple: "/api/favicon",
shortcut: "/api/favicon",
},
};
export const viewport = {
maximumScale: 1, // Disable auto-zoom on mobile Safari
@ -153,15 +143,11 @@ const CHUNK_ERROR_RECOVERY_SCRIPT = `\
});
})();`;
export default async function RootLayout({
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// 获取网站设置中的默认主题
const settings = await getSiteSettings();
const defaultTheme = settings.defaultTheme || "system";
return (
<html
// `next-themes` injects an extra classname to the body element to avoid
@ -188,7 +174,7 @@ export default async function RootLayout({
<ImageDeliveryOptimizer />
<ThemeProvider
attribute="class"
defaultTheme={defaultTheme}
defaultTheme="system"
disableTransitionOnChange
enableSystem
>

5
app/page.tsx Normal file
View File

@ -0,0 +1,5 @@
import { LovartHome } from "@/components/home/lovart-home";
export default function Page() {
return <LovartHome />;
}

View File

@ -228,12 +228,12 @@ type AccountCenterResponse = {
};
type LovartHomeProps = {
user: HomeUser | null;
isAuthenticated: boolean;
siteName: string;
user?: HomeUser | null;
isAuthenticated?: boolean;
siteName?: string;
siteLogo?: string | null;
siteLogoDark?: string | null;
surpriseCategories: string[];
surpriseCategories?: string[];
heroPromoText?: string | null;
heroPromoActionText?: string | null;
heroTitleText?: string | null;
@ -422,27 +422,19 @@ function AccountMenuItem({
}
export function LovartHome({
user,
isAuthenticated,
siteName,
siteLogo,
siteLogoDark,
surpriseCategories,
heroPromoText,
heroPromoActionText,
heroTitleText,
heroSubtitleText,
}: LovartHomeProps) {
user = null,
isAuthenticated = false,
siteName: initialSiteName = "AI Chatbot",
siteLogo: initialSiteLogo = null,
siteLogoDark: initialSiteLogoDark = null,
surpriseCategories: initialSurpriseCategories = ["全部", "精选"],
heroPromoText: initialHeroPromoText = null,
heroPromoActionText: initialHeroPromoActionText = null,
heroTitleText: initialHeroTitleText = null,
heroSubtitleText: initialHeroSubtitleText = null,
}: LovartHomeProps = {}) {
const router = useRouter();
const { theme, resolvedTheme, setTheme } = useTheme();
const displayLogo =
resolvedTheme === "dark"
? siteLogoDark?.trim() || siteLogo?.trim() || ""
: siteLogo?.trim() || siteLogoDark?.trim() || "";
const shouldInvertLogo =
resolvedTheme === "dark" &&
!siteLogoDark?.trim() &&
Boolean(siteLogo?.trim());
const sidebarPopupBaseClass =
"rounded-[24px] border border-black/8 bg-white/96 p-2.5 text-[#111827] shadow-[0_24px_64px_rgba(15,23,42,0.12)] backdrop-blur-xl";
const lovartSidebarButtonClass = (active = false) =>
@ -578,6 +570,14 @@ export function LovartHome({
);
const { data: siteSettingsData } = useSWR<{
siteName?: string | null;
siteLogo?: string | null;
siteLogoDark?: string | null;
lovartPromoText?: string | null;
lovartPromoActionText?: string | null;
lovartHeroTitle?: string | null;
lovartHeroSubtitle?: string | null;
surpriseCategories?: string[];
canvasDefaultModelId?: string | null;
lovartComposerPrefix?: string | null;
lovartComposerPreviewTexts?: string[];
@ -603,6 +603,30 @@ export function LovartHome({
lovartHomeSidebarConfig?: unknown;
}>("/api/site-settings?scope=home", fetcher);
const siteName = String(
siteSettingsData?.siteName || initialSiteName || "AI Chatbot"
).trim();
const siteLogo = String(siteSettingsData?.siteLogo || initialSiteLogo || "").trim();
const siteLogoDark = String(
siteSettingsData?.siteLogoDark || initialSiteLogoDark || ""
).trim();
const heroPromoText =
siteSettingsData?.lovartPromoText ?? initialHeroPromoText;
const heroPromoActionText =
siteSettingsData?.lovartPromoActionText ?? initialHeroPromoActionText;
const heroTitleText =
siteSettingsData?.lovartHeroTitle ?? initialHeroTitleText;
const heroSubtitleText =
siteSettingsData?.lovartHeroSubtitle ?? initialHeroSubtitleText;
const surpriseCategories =
siteSettingsData?.surpriseCategories ?? initialSurpriseCategories;
const displayLogo =
resolvedTheme === "dark"
? siteLogoDark || siteLogo || ""
: siteLogo || siteLogoDark || "";
const shouldInvertLogo =
resolvedTheme === "dark" && !siteLogoDark && Boolean(siteLogo);
const { data: tutorialVideosData } = useSWR<{ videos: TutorialVideo[] }>(
isSecondaryDataReady && siteSettingsData?.tutorialEnabled
? "/api/tutorial-videos"