perf: improve home and canvas initial load
This commit is contained in:
parent
67b526408a
commit
7c1d985c09
@ -1976,7 +1976,7 @@ export default function CanvasEditorLegacyPage({
|
|||||||
);
|
);
|
||||||
const [activeWorkflowExecutionTaskKey, setActiveWorkflowExecutionTaskKey] =
|
const [activeWorkflowExecutionTaskKey, setActiveWorkflowExecutionTaskKey] =
|
||||||
useState<string | null>(null);
|
useState<string | null>(null);
|
||||||
const [isPanelOpen, setIsPanelOpen] = useState(() => !isVideoMode);
|
const [isPanelOpen, setIsPanelOpen] = useState(() => requestedAgentMode);
|
||||||
const [floatingPanelWidth, setFloatingPanelWidth] =
|
const [floatingPanelWidth, setFloatingPanelWidth] =
|
||||||
useState(FLOATING_PANEL_WIDTH);
|
useState(FLOATING_PANEL_WIDTH);
|
||||||
const [
|
const [
|
||||||
@ -3280,11 +3280,15 @@ export default function CanvasEditorLegacyPage({
|
|||||||
}, [checkCanvasAuthSession]);
|
}, [checkCanvasAuthSession]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isPanelOpen && !isVideoCanvasFileListOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const timeoutId = window.setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
setAreSecondaryPanelsReady(true);
|
setAreSecondaryPanelsReady(true);
|
||||||
}, 800);
|
}, 120);
|
||||||
return () => window.clearTimeout(timeoutId);
|
return () => window.clearTimeout(timeoutId);
|
||||||
}, []);
|
}, [isPanelOpen, isVideoCanvasFileListOpen]);
|
||||||
|
|
||||||
const hydrateCanvasProject = useCallback(
|
const hydrateCanvasProject = useCallback(
|
||||||
(project: CanvasInitialProject) => {
|
(project: CanvasInitialProject) => {
|
||||||
@ -10844,7 +10848,7 @@ export default function CanvasEditorLegacyPage({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden border-l bg-white dark:bg-zinc-950">
|
<div className="flex h-full w-full flex-col overflow-hidden border-l bg-white dark:bg-zinc-950">
|
||||||
{!areSecondaryPanelsReady ? (
|
{!isPanelOpen ? null : !areSecondaryPanelsReady ? (
|
||||||
<CanvasLazyPanelLoader label="正在准备侧边面板..." />
|
<CanvasLazyPanelLoader label="正在准备侧边面板..." />
|
||||||
) : panelMode === "chat" ? (
|
) : panelMode === "chat" ? (
|
||||||
// AI 对话内容 - 使用 key 强制在 id 变化时重新挂载
|
// AI 对话内容 - 使用 key 强制在 id 变化时重新挂载
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { signIn, signOut } from "next-auth/react";
|
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import useSWRInfinite from "swr/infinite";
|
import useSWRInfinite from "swr/infinite";
|
||||||
@ -60,7 +59,6 @@ import {
|
|||||||
getCanvasLaunchStorageKey,
|
getCanvasLaunchStorageKey,
|
||||||
} from "@/lib/canvas-launch";
|
} from "@/lib/canvas-launch";
|
||||||
import type { MembershipConfig } from "@/lib/membership/config";
|
import type { MembershipConfig } from "@/lib/membership/config";
|
||||||
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
|
|
||||||
import {
|
import {
|
||||||
normalizeLovartHomeSidebarConfig,
|
normalizeLovartHomeSidebarConfig,
|
||||||
type LovartHomeSidebarItemKey,
|
type LovartHomeSidebarItemKey,
|
||||||
@ -465,6 +463,7 @@ export function LovartHome({
|
|||||||
const [previewCursorVisible, setPreviewCursorVisible] = useState(true);
|
const [previewCursorVisible, setPreviewCursorVisible] = useState(true);
|
||||||
const [isSecondaryDataReady, setIsSecondaryDataReady] = useState(false);
|
const [isSecondaryDataReady, setIsSecondaryDataReady] = useState(false);
|
||||||
const [isFeaturedDataReady, setIsFeaturedDataReady] = useState(false);
|
const [isFeaturedDataReady, setIsFeaturedDataReady] = useState(false);
|
||||||
|
const [isHomeAnimationReady, setIsHomeAnimationReady] = useState(false);
|
||||||
const projectSectionRef = useRef<HTMLDivElement | null>(null);
|
const projectSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
const featuredSectionRef = useRef<HTMLElement | null>(null);
|
const featuredSectionRef = useRef<HTMLElement | null>(null);
|
||||||
const featuredLoadMoreRef = useRef<HTMLDivElement | null>(null);
|
const featuredLoadMoreRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -535,6 +534,31 @@ export function LovartHome({
|
|||||||
};
|
};
|
||||||
}, [isFeaturedDataReady]);
|
}, [isFeaturedDataReady]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined" || !isSecondaryDataReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadHomeAnimation = () => setIsHomeAnimationReady(true);
|
||||||
|
const idleWindow = window as Window & {
|
||||||
|
cancelIdleCallback?: (handle: number) => void;
|
||||||
|
requestIdleCallback?: (
|
||||||
|
callback: IdleRequestCallback,
|
||||||
|
options?: IdleRequestOptions
|
||||||
|
) => number;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof idleWindow.requestIdleCallback === "function") {
|
||||||
|
const idleId = idleWindow.requestIdleCallback(loadHomeAnimation, {
|
||||||
|
timeout: 3200,
|
||||||
|
});
|
||||||
|
return () => idleWindow.cancelIdleCallback?.(idleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = window.setTimeout(loadHomeAnimation, 1800);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [isSecondaryDataReady]);
|
||||||
|
|
||||||
const { data: canvasData, mutate: mutateCanvas } = useSWR<{
|
const { data: canvasData, mutate: mutateCanvas } = useSWR<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: CanvasProject[];
|
data: CanvasProject[];
|
||||||
@ -671,7 +695,10 @@ export function LovartHome({
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
htmlContent?: string;
|
htmlContent?: string;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
}>(isSecondaryDataReady ? "/api/home-animation" : null, fetcher);
|
}>(
|
||||||
|
isSecondaryDataReady && isHomeAnimationReady ? "/api/home-animation" : null,
|
||||||
|
fetcher
|
||||||
|
);
|
||||||
|
|
||||||
const membershipConfig = membershipData?.config;
|
const membershipConfig = membershipData?.config;
|
||||||
const membershipPlans = membershipConfig?.plans || [];
|
const membershipPlans = membershipConfig?.plans || [];
|
||||||
@ -896,6 +923,7 @@ export function LovartHome({
|
|||||||
} catch (_error) {}
|
} catch (_error) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const { signOut } = await import("next-auth/react");
|
||||||
await signOut({
|
await signOut({
|
||||||
callbackUrl: "/",
|
callbackUrl: "/",
|
||||||
redirect: false,
|
redirect: false,
|
||||||
@ -1109,6 +1137,9 @@ export function LovartHome({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const uploadSelectedFiles = async (): Promise<CanvasLaunchAttachment[]> => {
|
const uploadSelectedFiles = async (): Promise<CanvasLaunchAttachment[]> => {
|
||||||
|
const { fetchUploadWithRetry } = await import(
|
||||||
|
"@/lib/client/upload-with-retry"
|
||||||
|
);
|
||||||
const result: CanvasLaunchAttachment[] = [];
|
const result: CanvasLaunchAttachment[] = [];
|
||||||
|
|
||||||
for (const item of files) {
|
for (const item of files) {
|
||||||
@ -1146,6 +1177,7 @@ export function LovartHome({
|
|||||||
|
|
||||||
setIsLoggingIn(true);
|
setIsLoggingIn(true);
|
||||||
try {
|
try {
|
||||||
|
const { signIn } = await import("next-auth/react");
|
||||||
const result = await signIn("credentials", {
|
const result = await signIn("credentials", {
|
||||||
email,
|
email,
|
||||||
password: loginPassword,
|
password: loginPassword,
|
||||||
@ -1171,6 +1203,7 @@ export function LovartHome({
|
|||||||
toast.error("Google 登录未启用");
|
toast.error("Google 登录未启用");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { signIn } = await import("next-auth/react");
|
||||||
await signIn("google", { callbackUrl: "/" });
|
await signIn("google", { callbackUrl: "/" });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1226,6 +1259,9 @@ export function LovartHome({
|
|||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", blob, `edited-${Date.now()}.png`);
|
formData.append("file", blob, `edited-${Date.now()}.png`);
|
||||||
|
const { fetchUploadWithRetry } = await import(
|
||||||
|
"@/lib/client/upload-with-retry"
|
||||||
|
);
|
||||||
const uploadResponse = await fetchUploadWithRetry(formData);
|
const uploadResponse = await fetchUploadWithRetry(formData);
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
if (!uploadResponse.ok) {
|
||||||
@ -1802,7 +1838,9 @@ export function LovartHome({
|
|||||||
heroPromoText={heroPromoText}
|
heroPromoText={heroPromoText}
|
||||||
heroSubtitleText={heroSubtitleText}
|
heroSubtitleText={heroSubtitleText}
|
||||||
heroTitleText={heroTitleText}
|
heroTitleText={heroTitleText}
|
||||||
homeAnimationData={homeAnimationData}
|
homeAnimationData={
|
||||||
|
isHomeAnimationReady ? homeAnimationData : undefined
|
||||||
|
}
|
||||||
isAgentModeActive={isComposerAgentModeActive}
|
isAgentModeActive={isComposerAgentModeActive}
|
||||||
isMembershipEnabled={isMembershipEnabled}
|
isMembershipEnabled={isMembershipEnabled}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
|
|||||||
@ -62,6 +62,8 @@ export function LovartFeaturedGallery({
|
|||||||
<img
|
<img
|
||||||
src={item.thumbnailUrl || item.originalUrl}
|
src={item.thumbnailUrl || item.originalUrl}
|
||||||
alt={item.prompt || "灵感图"}
|
alt={item.prompt || "灵感图"}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
className="block h-auto w-full rounded-[6px] bg-[#e8e8e8] object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.01] dark:bg-[#1b1c20]"
|
className="block h-auto w-full rounded-[6px] bg-[#e8e8e8] object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.01] dark:bg-[#1b1c20]"
|
||||||
/>
|
/>
|
||||||
<div className="mt-[8px] flex h-[22px] items-center justify-between text-[11px] font-normal leading-none text-[#b8b8b8] dark:text-white/45">
|
<div className="mt-[8px] flex h-[22px] items-center justify-between text-[11px] font-normal leading-none text-[#b8b8b8] dark:text-white/45">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import dynamic from "next/dynamic";
|
||||||
import { ArrowUp, Box, Lightbulb, Loader2, Plus, Sparkles } from "lucide-react";
|
import { ArrowUp, Box, Lightbulb, Loader2, Plus, Sparkles } from "lucide-react";
|
||||||
import type { ChangeEvent } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
import { HomeAnimationEmbed } from "@/components/home/home-animation-embed";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -12,6 +12,19 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { ModelOption, UploadFileItem } from "./types";
|
import type { ModelOption, UploadFileItem } from "./types";
|
||||||
|
|
||||||
|
const HomeAnimationEmbed = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/home/home-animation-embed").then(
|
||||||
|
(module) => module.HomeAnimationEmbed
|
||||||
|
),
|
||||||
|
{
|
||||||
|
loading: () => (
|
||||||
|
<div className="h-[360px] w-full rounded-[24px] border border-black/10 bg-black/[0.03] md:h-[420px] dark:border-white/10 dark:bg-white/[0.04]" />
|
||||||
|
),
|
||||||
|
ssr: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
type LovartHeroComposerProps = {
|
type LovartHeroComposerProps = {
|
||||||
composerPlaceholder: string;
|
composerPlaceholder: string;
|
||||||
displayLogo: string;
|
displayLogo: string;
|
||||||
|
|||||||
@ -76,6 +76,8 @@ export function LovartProjectsSection({
|
|||||||
<img
|
<img
|
||||||
src={item.thumbnail}
|
src={item.thumbnail}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
className="h-full w-full object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.025]"
|
className="h-full w-full object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.025]"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user