preserve colorful brand logos
This commit is contained in:
parent
d48e26fe9a
commit
778fc1c0b8
@ -501,11 +501,6 @@ export default function Page() {
|
|||||||
const displayLogo = mounted
|
const displayLogo = mounted
|
||||||
? (resolvedTheme === "dark" && siteLogoDark ? siteLogoDark : siteLogo)
|
? (resolvedTheme === "dark" && siteLogoDark ? siteLogoDark : siteLogo)
|
||||||
: siteLogo;
|
: siteLogo;
|
||||||
const shouldInvertLogo =
|
|
||||||
mounted &&
|
|
||||||
resolvedTheme === "dark" &&
|
|
||||||
!siteLogoDark?.trim() &&
|
|
||||||
Boolean(siteLogo?.trim());
|
|
||||||
const hasThirdPartyLogin =
|
const hasThirdPartyLogin =
|
||||||
(isDingTalkEnabled && (Boolean(dingTalkAppKey) || dingTalkOrganizations.length > 0)) ||
|
(isDingTalkEnabled && (Boolean(dingTalkAppKey) || dingTalkOrganizations.length > 0)) ||
|
||||||
(isGoogleEnabled && isGoogleConfigured);
|
(isGoogleEnabled && isGoogleConfigured);
|
||||||
@ -545,7 +540,7 @@ export default function Page() {
|
|||||||
<img
|
<img
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className={`mb-2 h-12 w-auto object-contain ${shouldInvertLogo ? "brightness-0 invert" : ""}`}
|
className="mb-2 h-12 w-auto object-contain"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<h3 className="font-semibold text-xl dark:text-zinc-50">登录 {siteName}</h3>
|
<h3 className="font-semibold text-xl dark:text-zinc-50">登录 {siteName}</h3>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ export const dynamic = "force-dynamic";
|
|||||||
export const revalidate = 0;
|
export const revalidate = 0;
|
||||||
|
|
||||||
const DEFAULT_CONTENT_TYPE = "image/x-icon";
|
const DEFAULT_CONTENT_TYPE = "image/x-icon";
|
||||||
const DARK_FAVICON_CONTENT_TYPE = "image/png";
|
|
||||||
const FETCH_TIMEOUT_MS = 10_000;
|
const FETCH_TIMEOUT_MS = 10_000;
|
||||||
|
|
||||||
function noStoreHeaders(contentType: string) {
|
function noStoreHeaders(contentType: string) {
|
||||||
@ -71,86 +70,16 @@ function uniqueNonEmptyValues(values: string[]) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readLocalFallbackFavicon(preferDark = false) {
|
async function readLocalFallbackFavicon() {
|
||||||
const file = await readFile(
|
const file = await readFile(
|
||||||
join(process.cwd(), "public", "default-favicon.ico")
|
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, {
|
return new NextResponse(file, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: noStoreHeaders(DEFAULT_CONTENT_TYPE),
|
headers: noStoreHeaders(DEFAULT_CONTENT_TYPE),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderDarkFallbackFavicon() {
|
|
||||||
const sharp = (await import("sharp")).default;
|
|
||||||
const svg = `\
|
|
||||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="64" height="64" fill="none"/>
|
|
||||||
<path d="M15 9h34v12H29v13h20v12H29v9l-14-11V9z" fill="#fff"/>
|
|
||||||
<path d="M37 25h12v30L37 45V25z" fill="#fff" opacity=".92"/>
|
|
||||||
</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) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
const requestUrl = new URL(request.url);
|
const requestUrl = new URL(request.url);
|
||||||
@ -159,20 +88,14 @@ export async function GET(request: Request) {
|
|||||||
const configuredFavicon = String(settings?.favicon || "").trim();
|
const configuredFavicon = String(settings?.favicon || "").trim();
|
||||||
const configuredLightLogo = String(settings?.siteLogo || "").trim();
|
const configuredLightLogo = String(settings?.siteLogo || "").trim();
|
||||||
const configuredDarkLogo = String(settings?.siteLogoDark || "").trim();
|
const configuredDarkLogo = String(settings?.siteLogoDark || "").trim();
|
||||||
const rawFaviconCandidates = preferDark
|
const rawFaviconCandidates = configuredFavicon
|
||||||
? uniqueNonEmptyValues([
|
? [configuredFavicon]
|
||||||
configuredFavicon,
|
: preferDark
|
||||||
configuredDarkLogo,
|
? uniqueNonEmptyValues([configuredDarkLogo, configuredLightLogo])
|
||||||
configuredLightLogo,
|
: uniqueNonEmptyValues([configuredLightLogo, configuredDarkLogo]);
|
||||||
])
|
|
||||||
: uniqueNonEmptyValues([
|
|
||||||
configuredFavicon,
|
|
||||||
configuredLightLogo,
|
|
||||||
configuredDarkLogo,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (rawFaviconCandidates.length === 0) {
|
if (rawFaviconCandidates.length === 0) {
|
||||||
return await readLocalFallbackFavicon(preferDark);
|
return await readLocalFallbackFavicon();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const rawFaviconUrl of rawFaviconCandidates) {
|
for (const rawFaviconUrl of rawFaviconCandidates) {
|
||||||
@ -220,42 +143,15 @@ export async function GET(request: Request) {
|
|||||||
const contentType =
|
const contentType =
|
||||||
upstream.headers.get("content-type") || inferContentType(faviconUrl);
|
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, {
|
return new NextResponse(buffer, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: noStoreHeaders(contentType),
|
headers: noStoreHeaders(contentType),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferDark) {
|
return await readLocalFallbackFavicon();
|
||||||
const fallback = await renderDarkFallbackFavicon().catch(() => null);
|
|
||||||
if (fallback) {
|
|
||||||
return new NextResponse(fallback, {
|
|
||||||
status: 200,
|
|
||||||
headers: noStoreHeaders(DARK_FAVICON_CONTENT_TYPE),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await readLocalFallbackFavicon(preferDark);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[favicon] Failed to proxy favicon:", error);
|
console.error("[favicon] Failed to proxy favicon:", error);
|
||||||
const preferDark = new URL(request.url).searchParams.get("theme") === "dark";
|
return await readLocalFallbackFavicon();
|
||||||
return await readLocalFallbackFavicon(preferDark);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9636,10 +9636,6 @@ export default function CanvasEditorLegacyPage({
|
|||||||
: lightCanvasBrandLogo || darkCanvasBrandLogo
|
: lightCanvasBrandLogo || darkCanvasBrandLogo
|
||||||
: lightCanvasBrandLogo || darkCanvasBrandLogo
|
: lightCanvasBrandLogo || darkCanvasBrandLogo
|
||||||
).trim();
|
).trim();
|
||||||
const shouldInvertCanvasBrandLogo =
|
|
||||||
isDarkCanvasTheme &&
|
|
||||||
Boolean(canvasBrandLogo) &&
|
|
||||||
!hasSeparateDarkCanvasBrandLogo;
|
|
||||||
|
|
||||||
const brandEntry = (
|
const brandEntry = (
|
||||||
<div
|
<div
|
||||||
@ -9654,11 +9650,7 @@ export default function CanvasEditorLegacyPage({
|
|||||||
{canvasBrandLogo ? (
|
{canvasBrandLogo ? (
|
||||||
<img
|
<img
|
||||||
alt={siteSettingsData?.siteName || "返回首页"}
|
alt={siteSettingsData?.siteName || "返回首页"}
|
||||||
className={cn(
|
className="h-7 w-7 object-contain"
|
||||||
"h-7 w-7 object-contain",
|
|
||||||
shouldInvertCanvasBrandLogo && "brightness-0 invert",
|
|
||||||
!hasSeparateDarkCanvasBrandLogo && "dark:brightness-0 dark:invert"
|
|
||||||
)}
|
|
||||||
draggable={false}
|
draggable={false}
|
||||||
src={canvasBrandLogo}
|
src={canvasBrandLogo}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -24,19 +24,10 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
"AI design workspace for Canvas, Agent workflows, image editing, 3D/CAD, and local AI operations.",
|
"AI design workspace for Canvas, Agent workflows, image editing, 3D/CAD, and local AI operations.",
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{
|
|
||||||
url: "/api/favicon?theme=dark",
|
|
||||||
media: "(prefers-color-scheme: dark)",
|
|
||||||
sizes: "any",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
url: "/api/favicon",
|
url: "/api/favicon",
|
||||||
media: "(prefers-color-scheme: light)",
|
|
||||||
sizes: "any",
|
sizes: "any",
|
||||||
},
|
},
|
||||||
{ url: "/api/favicon?theme=dark", sizes: "any", type: "image/png" },
|
|
||||||
{ url: "/api/favicon", type: "image/x-icon" },
|
|
||||||
],
|
],
|
||||||
apple: "/api/favicon",
|
apple: "/api/favicon",
|
||||||
shortcut: "/api/favicon",
|
shortcut: "/api/favicon",
|
||||||
|
|||||||
@ -53,7 +53,6 @@ export function AgentCanvasTopNavDark({
|
|||||||
}: AgentCanvasTopNavDarkProps) {
|
}: AgentCanvasTopNavDarkProps) {
|
||||||
const resolvedSiteName = String(siteName || "AI Chatbot").trim() || "AI Chatbot";
|
const resolvedSiteName = String(siteName || "AI Chatbot").trim() || "AI Chatbot";
|
||||||
const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || "";
|
const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || "";
|
||||||
const shouldInvertLogo = !siteLogoDark?.trim() && Boolean(siteLogo?.trim());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed top-0 left-0 right-0 z-50 border-b border-white/5 bg-black/80 backdrop-blur-xl">
|
<header className="fixed top-0 left-0 right-0 z-50 border-b border-white/5 bg-black/80 backdrop-blur-xl">
|
||||||
@ -80,10 +79,7 @@ export function AgentCanvasTopNavDark({
|
|||||||
<img
|
<img
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
alt={resolvedSiteName}
|
alt={resolvedSiteName}
|
||||||
className={cn(
|
className="h-8 w-8 shrink-0 object-contain"
|
||||||
"h-8 w-8 shrink-0 object-contain",
|
|
||||||
shouldInvertLogo && "brightness-0 invert"
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-white/10">
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-white/10">
|
||||||
|
|||||||
@ -6695,17 +6695,13 @@ function StylizedBrandLogo({
|
|||||||
}) {
|
}) {
|
||||||
const resolvedSiteName = resolveSiteName(siteName);
|
const resolvedSiteName = resolveSiteName(siteName);
|
||||||
const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || "";
|
const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || "";
|
||||||
const shouldInvertLogo = !siteLogoDark?.trim() && Boolean(siteLogo?.trim());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="flex min-w-0 items-center gap-2.5">
|
<span className="flex min-w-0 items-center gap-2.5">
|
||||||
{displayLogo ? (
|
{displayLogo ? (
|
||||||
<img
|
<img
|
||||||
alt={resolvedSiteName}
|
alt={resolvedSiteName}
|
||||||
className={cn(
|
className="h-8 w-8 shrink-0 object-contain"
|
||||||
"h-8 w-8 shrink-0 object-contain",
|
|
||||||
shouldInvertLogo && "brightness-0 invert"
|
|
||||||
)}
|
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -33,9 +33,7 @@ export function CanvasLogoLoadingView({
|
|||||||
alt={resolvedSiteName}
|
alt={resolvedSiteName}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-[34px] w-[34px] object-contain",
|
"h-[34px] w-[34px] object-contain",
|
||||||
hasSeparateDarkLogo
|
hasSeparateDarkLogo && "dark:hidden"
|
||||||
? "dark:hidden"
|
|
||||||
: "dark:brightness-0 dark:invert"
|
|
||||||
)}
|
)}
|
||||||
src={lightLogo}
|
src={lightLogo}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,24 +5,12 @@ import { useEffect } from "react";
|
|||||||
export function FaviconUpdater() {
|
export function FaviconUpdater() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isDisposed = false;
|
let isDisposed = false;
|
||||||
const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
|
const getFaviconUrl = () => "/api/favicon";
|
||||||
|
|
||||||
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 updateFavicon = () => {
|
const updateFavicon = () => {
|
||||||
if (isDisposed) return;
|
if (isDisposed) return;
|
||||||
|
|
||||||
const urlWithTheme = getFaviconUrl();
|
const urlWithTheme = getFaviconUrl();
|
||||||
const iconType = urlWithTheme.includes("theme=dark")
|
|
||||||
? "image/png"
|
|
||||||
: "image/x-icon";
|
|
||||||
const href =
|
const href =
|
||||||
`${urlWithTheme}${urlWithTheme.includes("?") ? "&" : "?"}v=${Date.now()}`;
|
`${urlWithTheme}${urlWithTheme.includes("?") ? "&" : "?"}v=${Date.now()}`;
|
||||||
const managedSelector =
|
const managedSelector =
|
||||||
@ -35,12 +23,12 @@ export function FaviconUpdater() {
|
|||||||
link.href = href;
|
link.href = href;
|
||||||
link.setAttribute("data-managed-favicon", "true");
|
link.setAttribute("data-managed-favicon", "true");
|
||||||
if (link.rel === "icon" || link.rel === "shortcut icon") {
|
if (link.rel === "icon" || link.rel === "shortcut icon") {
|
||||||
link.type = iconType;
|
link.removeAttribute("type");
|
||||||
link.setAttribute("sizes", "any");
|
link.setAttribute("sizes", "any");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const ensureLink = (rel: string, type?: string) => {
|
const ensureLink = (rel: string) => {
|
||||||
let link = document.querySelector(
|
let link = document.querySelector(
|
||||||
`link[rel="${rel}"]`
|
`link[rel="${rel}"]`
|
||||||
) as HTMLLinkElement | null;
|
) as HTMLLinkElement | null;
|
||||||
@ -51,17 +39,15 @@ export function FaviconUpdater() {
|
|||||||
}
|
}
|
||||||
link.href = href;
|
link.href = href;
|
||||||
link.setAttribute("data-managed-favicon", "true");
|
link.setAttribute("data-managed-favicon", "true");
|
||||||
if (type) {
|
link.removeAttribute("type");
|
||||||
link.type = type;
|
|
||||||
}
|
|
||||||
if (rel === "icon") {
|
if (rel === "icon") {
|
||||||
link.setAttribute("sizes", "any");
|
link.setAttribute("sizes", "any");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ensureLink("icon", iconType);
|
ensureLink("icon");
|
||||||
ensureLink("apple-touch-icon");
|
ensureLink("apple-touch-icon");
|
||||||
ensureLink("shortcut icon", iconType);
|
ensureLink("shortcut icon");
|
||||||
};
|
};
|
||||||
|
|
||||||
const observer = new MutationObserver(updateFavicon);
|
const observer = new MutationObserver(updateFavicon);
|
||||||
@ -69,8 +55,6 @@ export function FaviconUpdater() {
|
|||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ["class"],
|
attributeFilter: ["class"],
|
||||||
});
|
});
|
||||||
mediaQuery?.addEventListener?.("change", updateFavicon);
|
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
updateFavicon();
|
updateFavicon();
|
||||||
});
|
});
|
||||||
@ -78,7 +62,6 @@ export function FaviconUpdater() {
|
|||||||
return () => {
|
return () => {
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
mediaQuery?.removeEventListener?.("change", updateFavicon);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -653,8 +653,6 @@ export function LovartHome({
|
|||||||
resolvedTheme === "dark"
|
resolvedTheme === "dark"
|
||||||
? siteLogoDark || siteLogo || ""
|
? siteLogoDark || siteLogo || ""
|
||||||
: siteLogo || siteLogoDark || "";
|
: siteLogo || siteLogoDark || "";
|
||||||
const shouldInvertLogo =
|
|
||||||
resolvedTheme === "dark" && !siteLogoDark && Boolean(siteLogo);
|
|
||||||
|
|
||||||
const { data: tutorialVideosData } = useSWR<{ videos: TutorialVideo[] }>(
|
const { data: tutorialVideosData } = useSWR<{ videos: TutorialVideo[] }>(
|
||||||
isSecondaryDataReady && siteSettingsData?.tutorialEnabled
|
isSecondaryDataReady && siteSettingsData?.tutorialEnabled
|
||||||
@ -1528,7 +1526,6 @@ export function LovartHome({
|
|||||||
contentClassName="pl-[98px]"
|
contentClassName="pl-[98px]"
|
||||||
logo={displayLogo}
|
logo={displayLogo}
|
||||||
onLogoClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
onLogoClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
||||||
shouldInvertLogo={shouldInvertLogo}
|
|
||||||
showBrand={false}
|
showBrand={false}
|
||||||
siteName={siteName || "Lovart"}
|
siteName={siteName || "Lovart"}
|
||||||
>
|
>
|
||||||
@ -1778,7 +1775,6 @@ export function LovartHome({
|
|||||||
? "/settings/agent-workflow"
|
? "/settings/agent-workflow"
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
shouldInvertLogo={shouldInvertLogo}
|
|
||||||
siteLogo={displayLogo}
|
siteLogo={displayLogo}
|
||||||
siteName={siteName || "Lovart"}
|
siteName={siteName || "Lovart"}
|
||||||
toolbox1Enabled={siteSettingsData?.toolbox1Enabled !== false}
|
toolbox1Enabled={siteSettingsData?.toolbox1Enabled !== false}
|
||||||
@ -1814,7 +1810,6 @@ export function LovartHome({
|
|||||||
prompt={prompt}
|
prompt={prompt}
|
||||||
removeFile={removeFile}
|
removeFile={removeFile}
|
||||||
setPrompt={setPrompt}
|
setPrompt={setPrompt}
|
||||||
shouldInvertLogo={shouldInvertLogo}
|
|
||||||
siteName={siteName}
|
siteName={siteName}
|
||||||
selectedModelId={selectedComposerModel.id}
|
selectedModelId={selectedComposerModel.id}
|
||||||
/>
|
/>
|
||||||
@ -1952,7 +1947,7 @@ export function LovartHome({
|
|||||||
<img
|
<img
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className={`h-7 w-7 rounded-full object-cover ${shouldInvertLogo ? "brightness-0 invert" : ""}`}
|
className="h-7 w-7 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-black text-white dark:bg-white dark:text-black">
|
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-black text-white dark:bg-white dark:text-black">
|
||||||
|
|||||||
@ -34,7 +34,6 @@ type LovartHeroComposerProps = {
|
|||||||
prompt: string;
|
prompt: string;
|
||||||
removeFile: (id: string) => void;
|
removeFile: (id: string) => void;
|
||||||
setPrompt: (value: string) => void;
|
setPrompt: (value: string) => void;
|
||||||
shouldInvertLogo: boolean;
|
|
||||||
siteName: string;
|
siteName: string;
|
||||||
modelOptions: ModelOption[];
|
modelOptions: ModelOption[];
|
||||||
onModelChange: (modelId: string) => void;
|
onModelChange: (modelId: string) => void;
|
||||||
@ -60,7 +59,6 @@ export function LovartHeroComposer({
|
|||||||
prompt,
|
prompt,
|
||||||
removeFile,
|
removeFile,
|
||||||
setPrompt,
|
setPrompt,
|
||||||
shouldInvertLogo,
|
|
||||||
siteName,
|
siteName,
|
||||||
modelOptions,
|
modelOptions,
|
||||||
onModelChange,
|
onModelChange,
|
||||||
@ -91,7 +89,7 @@ export function LovartHeroComposer({
|
|||||||
<img
|
<img
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className={`h-10 w-10 rounded-full object-cover ${shouldInvertLogo ? "brightness-0 invert" : ""}`}
|
className="h-10 w-10 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-black text-white dark:bg-white dark:text-black">
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-black text-white dark:bg-white dark:text-black">
|
||||||
|
|||||||
@ -42,7 +42,6 @@ type LovartSidebarProps = {
|
|||||||
}
|
}
|
||||||
) => ReactNode;
|
) => ReactNode;
|
||||||
settingsHref: string | null;
|
settingsHref: string | null;
|
||||||
shouldInvertLogo?: boolean;
|
|
||||||
siteLogo?: string | null;
|
siteLogo?: string | null;
|
||||||
siteName: string;
|
siteName: string;
|
||||||
toolbox1Enabled: boolean;
|
toolbox1Enabled: boolean;
|
||||||
@ -71,7 +70,6 @@ export function LovartSidebar({
|
|||||||
productPlanningEnabled,
|
productPlanningEnabled,
|
||||||
renderIcon,
|
renderIcon,
|
||||||
settingsHref,
|
settingsHref,
|
||||||
shouldInvertLogo = false,
|
|
||||||
siteLogo,
|
siteLogo,
|
||||||
siteName,
|
siteName,
|
||||||
toolbox1Enabled,
|
toolbox1Enabled,
|
||||||
@ -184,12 +182,7 @@ export function LovartSidebar({
|
|||||||
<img
|
<img
|
||||||
src={siteLogo}
|
src={siteLogo}
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className={[
|
className="h-8 w-8 rounded-full object-cover"
|
||||||
"h-8 w-8 rounded-full object-cover",
|
|
||||||
shouldInvertLogo ? "brightness-0 invert" : "",
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ")}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-black text-[13px] font-semibold text-white dark:bg-white dark:text-black">
|
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-black text-[13px] font-semibold text-white dark:bg-white dark:text-black">
|
||||||
|
|||||||
@ -12,7 +12,6 @@ type BrandTopBarProps = {
|
|||||||
logo?: string | null;
|
logo?: string | null;
|
||||||
onLogoClick?: () => void;
|
onLogoClick?: () => void;
|
||||||
position?: "fixed" | "sticky" | "static";
|
position?: "fixed" | "sticky" | "static";
|
||||||
shouldInvertLogo?: boolean;
|
|
||||||
showBrand?: boolean;
|
showBrand?: boolean;
|
||||||
siteName: string;
|
siteName: string;
|
||||||
};
|
};
|
||||||
@ -25,7 +24,6 @@ export function BrandTopBar({
|
|||||||
logo,
|
logo,
|
||||||
onLogoClick,
|
onLogoClick,
|
||||||
position = "fixed",
|
position = "fixed",
|
||||||
shouldInvertLogo = false,
|
|
||||||
showBrand = true,
|
showBrand = true,
|
||||||
siteName,
|
siteName,
|
||||||
}: BrandTopBarProps) {
|
}: BrandTopBarProps) {
|
||||||
@ -61,10 +59,7 @@ export function BrandTopBar({
|
|||||||
{logo ? (
|
{logo ? (
|
||||||
<img
|
<img
|
||||||
alt={resolvedSiteName}
|
alt={resolvedSiteName}
|
||||||
className={cn(
|
className="h-8 w-8 rounded-full object-cover"
|
||||||
"h-8 w-8 rounded-full object-cover",
|
|
||||||
shouldInvertLogo && "brightness-0 invert"
|
|
||||||
)}
|
|
||||||
src={logo}
|
src={logo}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -59,8 +59,6 @@ export function PascalEditorFrame({
|
|||||||
const lightLogo = String(siteLogo || "").trim();
|
const lightLogo = String(siteLogo || "").trim();
|
||||||
return darkLogo || lightLogo;
|
return darkLogo || lightLogo;
|
||||||
}, [siteLogo, siteLogoDark]);
|
}, [siteLogo, siteLogoDark]);
|
||||||
const shouldInvertLogo =
|
|
||||||
!String(siteLogoDark || "").trim() && Boolean(String(siteLogo || "").trim());
|
|
||||||
const userEmail = String(user?.email || "").trim();
|
const userEmail = String(user?.email || "").trim();
|
||||||
const userLabel = String(user?.name || userEmail || "已登录用户").trim();
|
const userLabel = String(user?.name || userEmail || "已登录用户").trim();
|
||||||
const isGuest = guestRegex.test(userEmail);
|
const isGuest = guestRegex.test(userEmail);
|
||||||
@ -91,10 +89,7 @@ export function PascalEditorFrame({
|
|||||||
{displayLogo ? (
|
{displayLogo ? (
|
||||||
<img
|
<img
|
||||||
alt={resolvedSiteName}
|
alt={resolvedSiteName}
|
||||||
className={cn(
|
className="h-7 w-7 shrink-0 object-contain"
|
||||||
"h-7 w-7 shrink-0 object-contain",
|
|
||||||
shouldInvertLogo && "brightness-0 invert"
|
|
||||||
)}
|
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -150,7 +150,7 @@ export function TopNavbar({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 决定显示哪个 LOGO(浅色用普通,深色用暗色)
|
// 彩色 logo 不能用滤镜自动反白;暗色主题只在配置了独立 dark logo 时切换。
|
||||||
const displayLogo =
|
const displayLogo =
|
||||||
resolvedTheme === "dark"
|
resolvedTheme === "dark"
|
||||||
? siteLogoDark && siteLogoDark.trim() !== ""
|
? siteLogoDark && siteLogoDark.trim() !== ""
|
||||||
@ -159,10 +159,6 @@ export function TopNavbar({
|
|||||||
: siteLogo && siteLogo.trim() !== ""
|
: siteLogo && siteLogo.trim() !== ""
|
||||||
? siteLogo
|
? siteLogo
|
||||||
: siteLogoDark;
|
: siteLogoDark;
|
||||||
const shouldInvertLogo =
|
|
||||||
resolvedTheme === "dark" &&
|
|
||||||
(!siteLogoDark || siteLogoDark.trim() === "") &&
|
|
||||||
Boolean(siteLogo && siteLogo.trim() !== "");
|
|
||||||
|
|
||||||
const getShortLabel = (label: string) => {
|
const getShortLabel = (label: string) => {
|
||||||
const trimmed = label.trim();
|
const trimmed = label.trim();
|
||||||
@ -287,7 +283,7 @@ export function TopNavbar({
|
|||||||
{displayLogo && (
|
{displayLogo && (
|
||||||
<img
|
<img
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className={shouldInvertLogo ? "h-7 w-7 object-contain brightness-0 invert" : "h-7 w-7 object-contain"}
|
className="h-7 w-7 object-contain"
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user