diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 62b4069fd..8e98180aa 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -501,11 +501,6 @@ export default function Page() { const displayLogo = mounted ? (resolvedTheme === "dark" && siteLogoDark ? siteLogoDark : siteLogo) : siteLogo; - const shouldInvertLogo = - mounted && - resolvedTheme === "dark" && - !siteLogoDark?.trim() && - Boolean(siteLogo?.trim()); const hasThirdPartyLogin = (isDingTalkEnabled && (Boolean(dingTalkAppKey) || dingTalkOrganizations.length > 0)) || (isGoogleEnabled && isGoogleConfigured); @@ -545,7 +540,7 @@ export default function Page() { {siteName} ) : null}

登录 {siteName}

diff --git a/app/api/favicon/route.ts b/app/api/favicon/route.ts index 0382b454f..092c6190d 100644 --- a/app/api/favicon/route.ts +++ b/app/api/favicon/route.ts @@ -8,7 +8,6 @@ export const dynamic = "force-dynamic"; export const revalidate = 0; const DEFAULT_CONTENT_TYPE = "image/x-icon"; -const DARK_FAVICON_CONTENT_TYPE = "image/png"; const FETCH_TIMEOUT_MS = 10_000; function noStoreHeaders(contentType: string) { @@ -71,86 +70,16 @@ function uniqueNonEmptyValues(values: string[]) { return result; } -async function readLocalFallbackFavicon(preferDark = false) { +async function readLocalFallbackFavicon() { const file = await readFile( join(process.cwd(), "public", "default-favicon.ico") ); - if (preferDark) { - const transformed = await transformFaviconForDark(file).catch(() => null); - const fallback = - transformed || (await renderDarkFallbackFavicon().catch(() => null)); - if (fallback) { - return new NextResponse(fallback, { - status: 200, - headers: noStoreHeaders(DARK_FAVICON_CONTENT_TYPE), - }); - } - } return new NextResponse(file, { status: 200, headers: noStoreHeaders(DEFAULT_CONTENT_TYPE), }); } -async function renderDarkFallbackFavicon() { - const sharp = (await import("sharp")).default; - const svg = `\ - - - - -`; - return await sharp(Buffer.from(svg)).png().toBuffer(); -} - -async function transformFaviconForDark(buffer: Buffer | ArrayBuffer) { - const sharp = (await import("sharp")).default; - const inputBuffer = Buffer.isBuffer(buffer) - ? buffer - : Buffer.from(new Uint8Array(buffer)); - const createBaseImage = () => - sharp(inputBuffer, { - animated: false, - failOn: "none", - }).resize({ - width: 64, - height: 64, - fit: "contain", - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }); - - const { data, info } = await createBaseImage() - .ensureAlpha() - .raw() - .toBuffer({ resolveWithObject: true }); - let visiblePixelCount = 0; - let luminanceTotal = 0; - - for (let index = 0; index < data.length; index += info.channels) { - const alpha = data[index + 3] ?? 255; - if (alpha < 24) continue; - - const red = data[index] ?? 0; - const green = data[index + 1] ?? 0; - const blue = data[index + 2] ?? 0; - luminanceTotal += 0.2126 * red + 0.7152 * green + 0.0722 * blue; - visiblePixelCount += 1; - } - - const averageLuminance = - visiblePixelCount > 0 ? luminanceTotal / visiblePixelCount : 0; - const needsInvertForDark = averageLuminance < 150; - const output = createBaseImage(); - - if (needsInvertForDark) { - output.negate({ alpha: false }); - } - - return await output - .png() - .toBuffer(); -} - export async function GET(request: Request) { try { const requestUrl = new URL(request.url); @@ -159,20 +88,14 @@ export async function GET(request: Request) { const configuredFavicon = String(settings?.favicon || "").trim(); const configuredLightLogo = String(settings?.siteLogo || "").trim(); const configuredDarkLogo = String(settings?.siteLogoDark || "").trim(); - const rawFaviconCandidates = preferDark - ? uniqueNonEmptyValues([ - configuredFavicon, - configuredDarkLogo, - configuredLightLogo, - ]) - : uniqueNonEmptyValues([ - configuredFavicon, - configuredLightLogo, - configuredDarkLogo, - ]); + const rawFaviconCandidates = configuredFavicon + ? [configuredFavicon] + : preferDark + ? uniqueNonEmptyValues([configuredDarkLogo, configuredLightLogo]) + : uniqueNonEmptyValues([configuredLightLogo, configuredDarkLogo]); if (rawFaviconCandidates.length === 0) { - return await readLocalFallbackFavicon(preferDark); + return await readLocalFallbackFavicon(); } for (const rawFaviconUrl of rawFaviconCandidates) { @@ -220,42 +143,15 @@ export async function GET(request: Request) { const contentType = upstream.headers.get("content-type") || inferContentType(faviconUrl); - if (preferDark) { - const transformed = await transformFaviconForDark(buffer).catch((error) => { - console.error("[favicon] Failed to transform dark favicon candidate:", { - targetUrl, - error, - }); - return null; - }); - if (!transformed) continue; - - return new NextResponse(transformed, { - status: 200, - headers: noStoreHeaders(DARK_FAVICON_CONTENT_TYPE), - }); - } - return new NextResponse(buffer, { status: 200, headers: noStoreHeaders(contentType), }); } - if (preferDark) { - const fallback = await renderDarkFallbackFavicon().catch(() => null); - if (fallback) { - return new NextResponse(fallback, { - status: 200, - headers: noStoreHeaders(DARK_FAVICON_CONTENT_TYPE), - }); - } - } - - return await readLocalFallbackFavicon(preferDark); + return await readLocalFallbackFavicon(); } catch (error) { console.error("[favicon] Failed to proxy favicon:", error); - const preferDark = new URL(request.url).searchParams.get("theme") === "dark"; - return await readLocalFallbackFavicon(preferDark); + return await readLocalFallbackFavicon(); } } diff --git a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx index 8e501117e..3fe6797ae 100644 --- a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx +++ b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx @@ -9636,10 +9636,6 @@ export default function CanvasEditorLegacyPage({ : lightCanvasBrandLogo || darkCanvasBrandLogo : lightCanvasBrandLogo || darkCanvasBrandLogo ).trim(); - const shouldInvertCanvasBrandLogo = - isDarkCanvasTheme && - Boolean(canvasBrandLogo) && - !hasSeparateDarkCanvasBrandLogo; const brandEntry = (
diff --git a/app/layout.tsx b/app/layout.tsx index f26b5bee7..fcee64a6a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,19 +24,10 @@ export async function generateMetadata(): Promise { "AI design workspace for Canvas, Agent workflows, image editing, 3D/CAD, and local AI operations.", icons: { icon: [ - { - url: "/api/favicon?theme=dark", - media: "(prefers-color-scheme: dark)", - sizes: "any", - type: "image/png", - }, { url: "/api/favicon", - media: "(prefers-color-scheme: light)", sizes: "any", }, - { url: "/api/favicon?theme=dark", sizes: "any", type: "image/png" }, - { url: "/api/favicon", type: "image/x-icon" }, ], apple: "/api/favicon", shortcut: "/api/favicon", diff --git a/components/agents/AgentCanvasTopNavDark.tsx b/components/agents/AgentCanvasTopNavDark.tsx index ebb5a0613..6e97e2794 100644 --- a/components/agents/AgentCanvasTopNavDark.tsx +++ b/components/agents/AgentCanvasTopNavDark.tsx @@ -53,7 +53,6 @@ export function AgentCanvasTopNavDark({ }: AgentCanvasTopNavDarkProps) { const resolvedSiteName = String(siteName || "AI Chatbot").trim() || "AI Chatbot"; const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || ""; - const shouldInvertLogo = !siteLogoDark?.trim() && Boolean(siteLogo?.trim()); return (
@@ -80,10 +79,7 @@ export function AgentCanvasTopNavDark({ {resolvedSiteName} ) : (
diff --git a/components/canvas/LiblibVideoCanvasReplica.tsx b/components/canvas/LiblibVideoCanvasReplica.tsx index 227473f3e..6e48ad0e1 100644 --- a/components/canvas/LiblibVideoCanvasReplica.tsx +++ b/components/canvas/LiblibVideoCanvasReplica.tsx @@ -6695,17 +6695,13 @@ function StylizedBrandLogo({ }) { const resolvedSiteName = resolveSiteName(siteName); const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || ""; - const shouldInvertLogo = !siteLogoDark?.trim() && Boolean(siteLogo?.trim()); return ( {displayLogo ? ( {resolvedSiteName} ) : ( diff --git a/components/canvas/canvas-logo-loading.tsx b/components/canvas/canvas-logo-loading.tsx index 531fd401f..73f273231 100644 --- a/components/canvas/canvas-logo-loading.tsx +++ b/components/canvas/canvas-logo-loading.tsx @@ -33,9 +33,7 @@ export function CanvasLogoLoadingView({ alt={resolvedSiteName} className={cn( "h-[34px] w-[34px] object-contain", - hasSeparateDarkLogo - ? "dark:hidden" - : "dark:brightness-0 dark:invert" + hasSeparateDarkLogo && "dark:hidden" )} src={lightLogo} /> diff --git a/components/favicon-updater.tsx b/components/favicon-updater.tsx index 9c41c507b..6c9c5a0d4 100644 --- a/components/favicon-updater.tsx +++ b/components/favicon-updater.tsx @@ -5,24 +5,12 @@ import { useEffect } from "react"; export function FaviconUpdater() { useEffect(() => { let isDisposed = false; - const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)"); - - const getFaviconUrl = () => { - const html = document.documentElement; - const hasDarkClass = html.classList.contains("dark"); - const hasLightClass = html.classList.contains("light"); - const prefersDark = Boolean(mediaQuery?.matches); - const isDark = hasDarkClass || (!hasLightClass && prefersDark); - return isDark ? "/api/favicon?theme=dark" : "/api/favicon"; - }; + const getFaviconUrl = () => "/api/favicon"; const updateFavicon = () => { if (isDisposed) return; const urlWithTheme = getFaviconUrl(); - const iconType = urlWithTheme.includes("theme=dark") - ? "image/png" - : "image/x-icon"; const href = `${urlWithTheme}${urlWithTheme.includes("?") ? "&" : "?"}v=${Date.now()}`; const managedSelector = @@ -35,12 +23,12 @@ export function FaviconUpdater() { link.href = href; link.setAttribute("data-managed-favicon", "true"); if (link.rel === "icon" || link.rel === "shortcut icon") { - link.type = iconType; + link.removeAttribute("type"); link.setAttribute("sizes", "any"); } }); - const ensureLink = (rel: string, type?: string) => { + const ensureLink = (rel: string) => { let link = document.querySelector( `link[rel="${rel}"]` ) as HTMLLinkElement | null; @@ -51,17 +39,15 @@ export function FaviconUpdater() { } link.href = href; link.setAttribute("data-managed-favicon", "true"); - if (type) { - link.type = type; - } + link.removeAttribute("type"); if (rel === "icon") { link.setAttribute("sizes", "any"); } }; - ensureLink("icon", iconType); + ensureLink("icon"); ensureLink("apple-touch-icon"); - ensureLink("shortcut icon", iconType); + ensureLink("shortcut icon"); }; const observer = new MutationObserver(updateFavicon); @@ -69,8 +55,6 @@ export function FaviconUpdater() { attributes: true, attributeFilter: ["class"], }); - mediaQuery?.addEventListener?.("change", updateFavicon); - Promise.resolve().then(() => { updateFavicon(); }); @@ -78,7 +62,6 @@ export function FaviconUpdater() { return () => { isDisposed = true; observer.disconnect(); - mediaQuery?.removeEventListener?.("change", updateFavicon); }; }, []); diff --git a/components/home/lovart-home.tsx b/components/home/lovart-home.tsx index 5c9565254..e4a0912d7 100644 --- a/components/home/lovart-home.tsx +++ b/components/home/lovart-home.tsx @@ -653,8 +653,6 @@ export function LovartHome({ resolvedTheme === "dark" ? siteLogoDark || siteLogo || "" : siteLogo || siteLogoDark || ""; - const shouldInvertLogo = - resolvedTheme === "dark" && !siteLogoDark && Boolean(siteLogo); const { data: tutorialVideosData } = useSWR<{ videos: TutorialVideo[] }>( isSecondaryDataReady && siteSettingsData?.tutorialEnabled @@ -1528,7 +1526,6 @@ export function LovartHome({ contentClassName="pl-[98px]" logo={displayLogo} onLogoClick={() => window.scrollTo({ top: 0, behavior: "smooth" })} - shouldInvertLogo={shouldInvertLogo} showBrand={false} siteName={siteName || "Lovart"} > @@ -1778,7 +1775,6 @@ export function LovartHome({ ? "/settings/agent-workflow" : null } - shouldInvertLogo={shouldInvertLogo} siteLogo={displayLogo} siteName={siteName || "Lovart"} toolbox1Enabled={siteSettingsData?.toolbox1Enabled !== false} @@ -1814,7 +1810,6 @@ export function LovartHome({ prompt={prompt} removeFile={removeFile} setPrompt={setPrompt} - shouldInvertLogo={shouldInvertLogo} siteName={siteName} selectedModelId={selectedComposerModel.id} /> @@ -1952,7 +1947,7 @@ export function LovartHome({ {siteName} ) : (
diff --git a/components/home/lovart/lovart-hero-composer.tsx b/components/home/lovart/lovart-hero-composer.tsx index c5f491c59..59d6e334b 100644 --- a/components/home/lovart/lovart-hero-composer.tsx +++ b/components/home/lovart/lovart-hero-composer.tsx @@ -34,7 +34,6 @@ type LovartHeroComposerProps = { prompt: string; removeFile: (id: string) => void; setPrompt: (value: string) => void; - shouldInvertLogo: boolean; siteName: string; modelOptions: ModelOption[]; onModelChange: (modelId: string) => void; @@ -60,7 +59,6 @@ export function LovartHeroComposer({ prompt, removeFile, setPrompt, - shouldInvertLogo, siteName, modelOptions, onModelChange, @@ -91,7 +89,7 @@ export function LovartHeroComposer({ {siteName} ) : (
diff --git a/components/home/lovart/lovart-sidebar.tsx b/components/home/lovart/lovart-sidebar.tsx index e00a52458..cc9fdc394 100644 --- a/components/home/lovart/lovart-sidebar.tsx +++ b/components/home/lovart/lovart-sidebar.tsx @@ -42,7 +42,6 @@ type LovartSidebarProps = { } ) => ReactNode; settingsHref: string | null; - shouldInvertLogo?: boolean; siteLogo?: string | null; siteName: string; toolbox1Enabled: boolean; @@ -71,7 +70,6 @@ export function LovartSidebar({ productPlanningEnabled, renderIcon, settingsHref, - shouldInvertLogo = false, siteLogo, siteName, toolbox1Enabled, @@ -184,12 +182,7 @@ export function LovartSidebar({ {siteName} ) : ( diff --git a/components/layout/brand-top-bar.tsx b/components/layout/brand-top-bar.tsx index c8d79afa5..32af236b0 100644 --- a/components/layout/brand-top-bar.tsx +++ b/components/layout/brand-top-bar.tsx @@ -12,7 +12,6 @@ type BrandTopBarProps = { logo?: string | null; onLogoClick?: () => void; position?: "fixed" | "sticky" | "static"; - shouldInvertLogo?: boolean; showBrand?: boolean; siteName: string; }; @@ -25,7 +24,6 @@ export function BrandTopBar({ logo, onLogoClick, position = "fixed", - shouldInvertLogo = false, showBrand = true, siteName, }: BrandTopBarProps) { @@ -61,10 +59,7 @@ export function BrandTopBar({ {logo ? ( {resolvedSiteName} ) : ( diff --git a/components/pascal-editor/pascal-editor-frame.tsx b/components/pascal-editor/pascal-editor-frame.tsx index d2479a71e..cf9f93f47 100644 --- a/components/pascal-editor/pascal-editor-frame.tsx +++ b/components/pascal-editor/pascal-editor-frame.tsx @@ -59,8 +59,6 @@ export function PascalEditorFrame({ const lightLogo = String(siteLogo || "").trim(); return darkLogo || lightLogo; }, [siteLogo, siteLogoDark]); - const shouldInvertLogo = - !String(siteLogoDark || "").trim() && Boolean(String(siteLogo || "").trim()); const userEmail = String(user?.email || "").trim(); const userLabel = String(user?.name || userEmail || "已登录用户").trim(); const isGuest = guestRegex.test(userEmail); @@ -91,10 +89,7 @@ export function PascalEditorFrame({ {displayLogo ? ( {resolvedSiteName} ) : ( diff --git a/components/top-navbar.tsx b/components/top-navbar.tsx index 9e9ede5a1..0b944d4ab 100644 --- a/components/top-navbar.tsx +++ b/components/top-navbar.tsx @@ -150,7 +150,7 @@ export function TopNavbar({ } }; - // 决定显示哪个 LOGO(浅色用普通,深色用暗色) + // 彩色 logo 不能用滤镜自动反白;暗色主题只在配置了独立 dark logo 时切换。 const displayLogo = resolvedTheme === "dark" ? siteLogoDark && siteLogoDark.trim() !== "" @@ -159,10 +159,6 @@ export function TopNavbar({ : siteLogo && siteLogo.trim() !== "" ? siteLogo : siteLogoDark; - const shouldInvertLogo = - resolvedTheme === "dark" && - (!siteLogoDark || siteLogoDark.trim() === "") && - Boolean(siteLogo && siteLogo.trim() !== ""); const getShortLabel = (label: string) => { const trimmed = label.trim(); @@ -287,7 +283,7 @@ export function TopNavbar({ {displayLogo && ( {siteName} )}