fix: align canvas loading and composer controls
This commit is contained in:
parent
f02d18702a
commit
29033b5757
@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2 } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { createContext, type ReactNode, useContext, useMemo } from "react";
|
||||
import { CanvasLogoLoadingView } from "@/components/canvas/canvas-logo-loading";
|
||||
|
||||
const CanvasEditorLegacyPage = dynamic(() => import("./CanvasEditorLegacyPageClient"), {
|
||||
loading: () => <CanvasRouteLoader label="正在加载画布..." />,
|
||||
@ -21,10 +21,16 @@ const LiblibVideoCanvasReplica = dynamic(
|
||||
export function CanvasPageClientRouter({
|
||||
initialId,
|
||||
initialMode = "",
|
||||
initialSiteLogo = null,
|
||||
initialSiteLogoDark = null,
|
||||
initialSiteName = "AI Chatbot",
|
||||
initialWorkspaceType = "",
|
||||
}: {
|
||||
initialId: string;
|
||||
initialMode?: string;
|
||||
initialSiteLogo?: string | null;
|
||||
initialSiteLogoDark?: string | null;
|
||||
initialSiteName?: string | null;
|
||||
initialWorkspaceType?: string;
|
||||
}) {
|
||||
const routeParams = useParams();
|
||||
@ -37,23 +43,69 @@ export function CanvasPageClientRouter({
|
||||
const requestedAgentMode = mode === "agent";
|
||||
const isVideoMode = !requestedAgentMode && (mode === "video" || requestedWorkspaceType === "video");
|
||||
const legacyParams = useMemo(() => Promise.resolve({ id }), [id]);
|
||||
const loadingBrand = useMemo(
|
||||
() => ({
|
||||
siteLogo: initialSiteLogo,
|
||||
siteLogoDark: initialSiteLogoDark,
|
||||
siteName: initialSiteName,
|
||||
}),
|
||||
[initialSiteLogo, initialSiteLogoDark, initialSiteName]
|
||||
);
|
||||
|
||||
if (!id) {
|
||||
return <CanvasRouteLoader label="正在加载画布..." />;
|
||||
return (
|
||||
<CanvasLoadingBrandProvider value={loadingBrand}>
|
||||
<CanvasRouteLoader label="正在加载画布..." />
|
||||
</CanvasLoadingBrandProvider>
|
||||
);
|
||||
}
|
||||
|
||||
if (isVideoMode) {
|
||||
return <LiblibVideoCanvasReplica canvasId={id} />;
|
||||
return (
|
||||
<CanvasLoadingBrandProvider value={loadingBrand}>
|
||||
<LiblibVideoCanvasReplica canvasId={id} />
|
||||
</CanvasLoadingBrandProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return <CanvasEditorLegacyPage params={legacyParams} />;
|
||||
return (
|
||||
<CanvasLoadingBrandProvider value={loadingBrand}>
|
||||
<CanvasEditorLegacyPage params={legacyParams} />
|
||||
</CanvasLoadingBrandProvider>
|
||||
);
|
||||
}
|
||||
|
||||
type CanvasLoadingBrand = {
|
||||
siteLogo?: string | null;
|
||||
siteLogoDark?: string | null;
|
||||
siteName?: string | null;
|
||||
};
|
||||
|
||||
const CanvasLoadingBrandContext = createContext<CanvasLoadingBrand>({});
|
||||
|
||||
function CanvasLoadingBrandProvider({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: CanvasLoadingBrand;
|
||||
}) {
|
||||
return (
|
||||
<CanvasLoadingBrandContext.Provider value={value}>
|
||||
{children}
|
||||
</CanvasLoadingBrandContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function CanvasRouteLoader({ label }: { label: string }) {
|
||||
const brand = useContext(CanvasLoadingBrandContext);
|
||||
|
||||
return (
|
||||
<div className="flex h-dvh w-full items-center justify-center bg-[#151515] text-white/70">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
{label}
|
||||
</div>
|
||||
<CanvasLogoLoadingView
|
||||
label={label}
|
||||
siteLogo={brand.siteLogo}
|
||||
siteLogoDark={brand.siteLogoDark}
|
||||
siteName={brand.siteName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Suspense } from "react";
|
||||
import { CanvasLogoLoadingView } from "@/components/canvas/canvas-logo-loading";
|
||||
import { getSiteSettings } from "@/lib/db/queries";
|
||||
import { CanvasPageClientRouter } from "./CanvasPageClientRouter";
|
||||
|
||||
type CanvasEditorPageProps = {
|
||||
@ -20,12 +21,21 @@ export default async function CanvasEditorPage({
|
||||
}: CanvasEditorPageProps) {
|
||||
const { id } = await params;
|
||||
const resolvedSearchParams = searchParams ? await searchParams : {};
|
||||
const siteSettings = await getSiteSettings().catch(() => null);
|
||||
const loadingBrand = {
|
||||
siteLogo: siteSettings?.siteLogo,
|
||||
siteLogoDark: siteSettings?.siteLogoDark,
|
||||
siteName: siteSettings?.siteName,
|
||||
};
|
||||
|
||||
return (
|
||||
<Suspense fallback={<CanvasPageLoader />}>
|
||||
<Suspense fallback={<CanvasPageLoader {...loadingBrand} />}>
|
||||
<CanvasPageClientRouter
|
||||
initialId={id}
|
||||
initialMode={getFirstParamValue(resolvedSearchParams.mode)}
|
||||
initialSiteLogo={loadingBrand.siteLogo}
|
||||
initialSiteLogoDark={loadingBrand.siteLogoDark}
|
||||
initialSiteName={loadingBrand.siteName}
|
||||
initialWorkspaceType={getFirstParamValue(
|
||||
resolvedSearchParams.workspaceType
|
||||
)}
|
||||
@ -34,11 +44,21 @@ export default async function CanvasEditorPage({
|
||||
);
|
||||
}
|
||||
|
||||
function CanvasPageLoader() {
|
||||
function CanvasPageLoader({
|
||||
siteLogo,
|
||||
siteLogoDark,
|
||||
siteName,
|
||||
}: {
|
||||
siteLogo?: string | null;
|
||||
siteLogoDark?: string | null;
|
||||
siteName?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex h-dvh w-full items-center justify-center bg-[#151515] text-white/70">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
正在加载画布...
|
||||
</div>
|
||||
<CanvasLogoLoadingView
|
||||
label="正在加载画布..."
|
||||
siteLogo={siteLogo}
|
||||
siteLogoDark={siteLogoDark}
|
||||
siteName={siteName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { CanvasLogoLoadingView } from "@/components/canvas/canvas-logo-loading";
|
||||
import { getSiteSettings } from "@/lib/db/queries";
|
||||
|
||||
export default async function CanvasLoading() {
|
||||
const settings = await getSiteSettings().catch(() => null);
|
||||
|
||||
export default function CanvasLoading() {
|
||||
return (
|
||||
<div className="flex h-dvh w-screen items-center justify-center bg-[#050607] text-white/70">
|
||||
<div className="flex flex-col items-center gap-4 rounded-[32px] border border-white/10 bg-white/[0.04] px-8 py-7 shadow-[0_24px_90px_rgba(0,0,0,0.42)]">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-emerald-300" />
|
||||
<div className="text-sm tracking-[0.08em]">正在进入工作台...</div>
|
||||
</div>
|
||||
</div>
|
||||
<CanvasLogoLoadingView
|
||||
label="正在进入工作台"
|
||||
siteLogo={settings?.siteLogo}
|
||||
siteLogoDark={settings?.siteLogoDark}
|
||||
siteName={settings?.siteName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10875,7 +10875,7 @@ export function CanvasChatPanel({
|
||||
? "bg-gradient-to-r from-violet-600 to-fuchsia-600 text-white shadow-md hover:shadow-lg hover:opacity-90 dark:from-violet-500 dark:to-fuchsia-500"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted/80"
|
||||
: canSend
|
||||
? "bg-[#1f2328] text-white shadow-sm hover:bg-[#111418]"
|
||||
? "bg-[#eef0f3] text-[#20242a] shadow-sm ring-1 ring-inset ring-[#d5dae2] hover:bg-[#e4e7ec] dark:bg-white dark:text-zinc-950 dark:ring-white/15 dark:hover:bg-zinc-100"
|
||||
: "bg-[#f1f1f2] text-[#b6bac2] hover:bg-[#ececee] dark:bg-[#242529] dark:text-[#777d88] dark:hover:bg-[#2c2d33]"
|
||||
)}
|
||||
onClick={handleSend}
|
||||
@ -10899,7 +10899,7 @@ export function CanvasChatPanel({
|
||||
canSend &&
|
||||
(isVideoWorkspace || isDetailGenerationMode
|
||||
? "opacity-95"
|
||||
: "opacity-95 invert dark:invert-0")
|
||||
: "opacity-95")
|
||||
)}
|
||||
fallback={Sparkles}
|
||||
item={getToolbarItem("chatStartGeneration")}
|
||||
@ -10919,7 +10919,7 @@ export function CanvasChatPanel({
|
||||
canSend &&
|
||||
(isVideoWorkspace || isDetailGenerationMode
|
||||
? "opacity-95"
|
||||
: "opacity-95 invert dark:invert-0")
|
||||
: "opacity-95")
|
||||
)}
|
||||
fallback={ArrowUp}
|
||||
item={getToolbarItem(
|
||||
|
||||
59
components/canvas/canvas-logo-loading.tsx
Normal file
59
components/canvas/canvas-logo-loading.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CanvasLogoLoadingViewProps = {
|
||||
label?: string;
|
||||
siteName?: string | null;
|
||||
siteLogo?: string | null;
|
||||
siteLogoDark?: string | null;
|
||||
};
|
||||
|
||||
export function CanvasLogoLoadingView({
|
||||
label = "正在加载画布...",
|
||||
siteName = "AI Chatbot",
|
||||
siteLogo,
|
||||
siteLogoDark,
|
||||
}: CanvasLogoLoadingViewProps) {
|
||||
const resolvedSiteName = String(siteName || "AI Chatbot").trim();
|
||||
const lightLogo = String(siteLogo || siteLogoDark || "").trim();
|
||||
const darkLogo = String(siteLogoDark || siteLogo || "").trim();
|
||||
const hasSeparateDarkLogo = Boolean(darkLogo && darkLogo !== lightLogo);
|
||||
|
||||
return (
|
||||
<div className="flex h-dvh w-screen items-center justify-center bg-[#f4f4f5] text-[#20242a] dark:bg-[#08090b] dark:text-zinc-100">
|
||||
<div className="flex flex-col items-center gap-5">
|
||||
<div className="relative flex h-28 w-28 items-center justify-center">
|
||||
<div className="absolute inset-0 animate-spin rounded-full bg-[conic-gradient(from_0deg,#ff7a00,#ffe04b,#48e6a5,#35a7ff,#b15cff,#ff4aa2,#ff7a00)] opacity-90 blur-[1px]" />
|
||||
<div className="absolute inset-2 animate-ping rounded-full bg-[conic-gradient(from_180deg,#ff7a00,#35a7ff,#b15cff,#48e6a5,#ff7a00)] opacity-25" />
|
||||
<div className="absolute inset-[6px] rounded-full bg-[#f4f4f5] dark:bg-[#08090b]" />
|
||||
<div className="relative flex h-20 w-20 items-center justify-center overflow-hidden rounded-full border border-black/8 bg-white shadow-[0_18px_54px_rgba(15,23,42,0.16)] dark:border-white/10 dark:bg-zinc-950 dark:shadow-[0_24px_70px_rgba(0,0,0,0.45)]">
|
||||
{lightLogo ? (
|
||||
<>
|
||||
<img
|
||||
alt={resolvedSiteName}
|
||||
className={cn(
|
||||
"h-12 w-12 object-contain",
|
||||
hasSeparateDarkLogo && "dark:hidden"
|
||||
)}
|
||||
src={lightLogo}
|
||||
/>
|
||||
{hasSeparateDarkLogo ? (
|
||||
<img
|
||||
alt={resolvedSiteName}
|
||||
className="hidden h-12 w-12 object-contain dark:block"
|
||||
src={darkLogo}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<Sparkles className="h-8 w-8 text-[#20242a] dark:text-white" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[13px] font-medium tracking-[0.18em] text-[#6b7280] dark:text-zinc-400">
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -150,24 +150,24 @@ export function LovartHeroComposer({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6 pl-2">
|
||||
<div className="flex h-11 items-center gap-5 pl-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-full text-[#20242a] hover:bg-black/5 dark:text-white/72 dark:hover:bg-white/8"
|
||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-full p-0 text-[#20242a] leading-none hover:bg-black/5 focus:outline-none focus:ring-0 dark:text-white/72 dark:hover:bg-white/8"
|
||||
aria-label="开启 Agent 模式"
|
||||
onClick={onStartAgentMode}
|
||||
title="开启 Agent 模式"
|
||||
>
|
||||
<Lightbulb className="h-5 w-5" />
|
||||
<Lightbulb className="block h-5 w-5" />
|
||||
</button>
|
||||
<Select value={selectedModelId} onValueChange={onModelChange}>
|
||||
<SelectTrigger
|
||||
aria-label="选择模型"
|
||||
className="h-9 w-9 rounded-full border-0 bg-transparent p-0 text-[#20242a] shadow-none hover:bg-black/5 focus:ring-0 focus:ring-offset-0 dark:text-white/72 dark:hover:bg-white/8 [&>svg]:hidden"
|
||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-full border-0 bg-transparent p-0 text-[#20242a] leading-none shadow-none hover:bg-black/5 focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white/72 dark:hover:bg-white/8 [&>svg]:hidden"
|
||||
title="选择模型"
|
||||
>
|
||||
<span className="inline-flex h-full w-full items-center justify-center">
|
||||
<Box className="h-5 w-5" />
|
||||
<span className="grid h-full w-full place-items-center">
|
||||
<Box className="block h-5 w-5" />
|
||||
</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user