From f02d18702ab3a40cb07c8a506d63770bafe886a0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 10 Jun 2026 22:38:58 +0800 Subject: [PATCH] fix: refine Lovart-style canvas and home UI --- .../settings/admin-settings-client.tsx | 10 +- app/api/site-settings/route.ts | 10 +- .../[id]/CanvasEditorLegacyPageClient.tsx | 19 +- components/canvas/CanvasChatPanel.tsx | 540 ++++++++++-------- components/canvas/CanvasWorkspace.tsx | 5 + components/home/lovart-home.tsx | 126 ++-- .../home/lovart/lovart-hero-composer.tsx | 116 ++-- components/home/lovart/lovart-sidebar.tsx | 54 +- components/home/lovart/utils.ts | 2 +- components/image-params-bar.tsx | 256 ++++++++- ...32_add_site_banner_and_canvas_settings.sql | 3 +- lib/db/queries.ts | 2 +- lib/db/schema.ts | 2 +- 13 files changed, 717 insertions(+), 428 deletions(-) diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 16832f437..f897f6c03 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -2071,7 +2071,7 @@ export function AdminSettingsClient() { usageTimeMessage: "由壹点累了也是需要休息的", defaultTheme: "system", bannerEnabled: true, - bannerText: "升级会员, Nano Banana Pro 免费365天!", + bannerText: "限时特惠:升级会员最高立享 45% OFF!", bannerLink: null, toolbox1Name: "工具箱", toolbox1Enabled: true, @@ -2108,7 +2108,7 @@ export function AdminSettingsClient() { agentWorkflowDefaultModelId: null, canvasDetailDefaultModelId: null, canvasDetailAgentModelId: null, - lovartComposerPrefix: "让Buyi-ai", + lovartComposerPrefix: "让「由壹点AI」", lovartComposerPreviewTexts: [ "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", @@ -2181,7 +2181,7 @@ export function AdminSettingsClient() { defaultTheme: siteSettingsData.defaultTheme || "system", bannerEnabled: siteSettingsData.bannerEnabled ?? true, bannerText: - siteSettingsData.bannerText || "升级会员, Nano Banana Pro 免费365天!", + siteSettingsData.bannerText || "限时特惠:升级会员最高立享 45% OFF!", bannerLink: siteSettingsData.bannerLink || null, toolbox1Name: siteSettingsData.toolbox1Name || "工具箱", toolbox1Enabled: siteSettingsData.toolbox1Enabled ?? true, @@ -2269,7 +2269,7 @@ export function AdminSettingsClient() { (siteSettingsData as any).canvasDetailAgentModelId || null, lovartComposerPrefix: String((siteSettingsData as any).lovartComposerPrefix || "").trim() || - "让Buyi-ai", + "让「由壹点AI」", lovartComposerPreviewTexts: Array.isArray( siteSettingsData.lovartComposerPreviewTexts ) @@ -9905,7 +9905,7 @@ export function AdminSettingsClient() { lovartComposerPrefix: e.target.value.slice(0, 40), }) } - placeholder="让Buyi-ai" + placeholder="让「由壹点AI」" value={siteSettings.lovartComposerPrefix} />
diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index 9e50c0c46..40d3de440 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -382,7 +382,7 @@ function normalizeLovartComposerPreviewTexts(value: unknown): string[] { function normalizeLovartComposerPrefix(value: unknown): string { const normalized = String(value || "").trim(); if (!normalized) { - return "让Buyi-ai"; + return "让「由壹点AI」"; } return normalized.slice(0, 40); } @@ -538,7 +538,7 @@ const HOME_SITE_SETTINGS_FALLBACK: Record = { lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切", surpriseCategories: ["全部", "精选"], canvasDefaultModelId: null, - lovartComposerPrefix: "让Buyi-ai", + lovartComposerPrefix: "让「由壹点AI」", lovartComposerPreviewTexts: [], lovartPromptTagVisibility: {}, contactConfig: normalizeContactConfig(undefined), @@ -844,7 +844,7 @@ async function getScopedPublicSiteSettings( ...createBrandingSettings(typedSettings), bannerEnabled: typedSettings?.bannerEnabled ?? true, bannerText: - typedSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!", + typedSettings?.bannerText || "限时特惠:升级会员最高立享 45% OFF!", bannerLink: typedSettings?.bannerLink || null, lovartComposerPrefix: normalizeLovartComposerPrefix( valueByKey.get(LOVART_COMPOSER_PREFIX_KEY) @@ -1376,7 +1376,7 @@ export async function GET(request: NextRequest) { usageTimeEnd: "20:00", usageTimeMessage: "由壹点累了也是需要休息的", bannerEnabled: true, - bannerText: "升级会员, Nano Banana Pro 免费365天!", + bannerText: "限时特惠:升级会员最高立享 45% OFF!", bannerLink: null, toolbox1Name: "工具箱", toolbox1Enabled: true, @@ -2959,7 +2959,7 @@ export async function PUT(request: NextRequest) { usageTimeEnd: siteSettingsPayload.usageTimeEnd ?? "20:00", usageTimeMessage: siteSettingsPayload.usageTimeMessage ?? "由壹点累了也是需要休息的", bannerEnabled: siteSettingsPayload.bannerEnabled ?? true, - bannerText: siteSettingsPayload.bannerText ?? "升级会员, Nano Banana Pro 免费365天!", + bannerText: siteSettingsPayload.bannerText ?? "限时特惠:升级会员最高立享 45% OFF!", bannerLink: siteSettingsPayload.bannerLink, toolbox1Name: siteSettingsPayload.toolbox1Name ?? "工具箱", toolbox1Enabled: siteSettingsPayload.toolbox1Enabled ?? true, diff --git a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx index 6873c28db..596a030f2 100644 --- a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx +++ b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx @@ -57,6 +57,7 @@ import { ZoomOut, } from "lucide-react"; import dynamic from "next/dynamic"; +import { useTheme } from "next-themes"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { type ChangeEvent, @@ -244,6 +245,7 @@ import type { const CANVAS_SITE_SETTINGS_REFRESH_INTERVAL_MS = 15000; const CANVAS_SITE_SETTINGS_TIMEOUT_MS = 10000; +const CANVAS_SURFACE_BACKGROUND = "#f4f4f5"; const fetchCanvasSiteSettings = async (url: string) => { const separator = url.includes("?") ? "&" : "?"; @@ -1122,7 +1124,7 @@ function normalizeImportedCanvasState(payload: unknown): { }; } -const FLOATING_PANEL_WIDTH = 460; +const FLOATING_PANEL_WIDTH = 400; const FLOATING_PANEL_INSET = 16; const DOCKED_PANEL_INSET = 0; const FLOATING_UI_GAP = 16; @@ -1710,6 +1712,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { ? routeId : ""; const router = useRouter(); + const { resolvedTheme } = useTheme(); const mode = searchParams.get("mode"); const requestedWorkspaceType = searchParams.get("workspaceType"); const requestedAgentMode = mode === "agent"; @@ -1719,6 +1722,8 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { const activeCanvasWorkspaceType: CanvasWorkspaceType = isVideoMode ? "video" : "canvas"; + const canvasSurfaceBackground = + resolvedTheme === "dark" ? "#0a0a0c" : CANVAS_SURFACE_BACKGROUND; const defaultCanvasTitle = isVideoMode ? "未命名视频画布" : "未命名画布"; const workspaceLabel = isVideoMode ? "视频画布" : "画布"; const createNewCanvasUrl = isVideoMode @@ -8952,7 +8957,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { const brandEntry = (
@@ -8535,8 +8547,8 @@ export function CanvasChatPanel({ })}
- )} - + ) : null} + )} ) @@ -9093,11 +9105,11 @@ export function CanvasChatPanel({ )} {!isWorkspacePresentation && shouldShowCanvasBanner ? ( -
+
{ if (siteSettings?.bannerLink) { @@ -9105,13 +9117,23 @@ export function CanvasChatPanel({ } }} > -
-
- +
+
+
- {siteSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!"} + {siteSettings?.bannerText || "限时特惠:升级会员最高立享 45% OFF!"}
- e.stopPropagation()} /> +
) : null} @@ -9119,14 +9141,16 @@ export function CanvasChatPanel({ {/* 底部输入区域 - 混合输入(图片标签+文字可自由混合) */}
{isWorkspacePresentation && !isDetailGenerationMode ? ( @@ -9883,8 +9907,12 @@ export function CanvasChatPanel({ {/* 混合输入区域:使用 contenteditable 实现真正的混合编辑 */}
{!isWorkspacePresentation && !isDetailGenerationMode && selectedSkillTag && ( @@ -10207,14 +10235,56 @@ export function CanvasChatPanel({ ) : null}
) : null} -
+ {!isWorkspacePresentation && !isDetailGenerationMode && !isVideoWorkspace && activeComposerMode === "image" ? ( + + ) : null} +
{ @@ -10281,7 +10351,7 @@ export function CanvasChatPanel({
{hasAgentUploadSlots && ( -
+
{agentUploadSlots.map((slot, index) => { const slotAttachment = attachments[index]; @@ -10349,7 +10419,7 @@ export function CanvasChatPanel({ )} {isVideoModel && videoFrameSlots.length > 0 && ( -
+
{videoFrameSlots.map((label, index) => { const slotAttachment = attachments[index]; @@ -10419,10 +10489,10 @@ export function CanvasChatPanel({ {/* Bottom Toolbar */}
-
+
))} + {!isWorkspacePresentation && !isDetailGenerationMode && activeComposerMode !== "image" && (!isVideoModel || showVideoAttachmentButton) && ( + + )} {!isWorkspacePresentation ? ( - + - @@ -10517,61 +10606,61 @@ export function CanvasChatPanel({ side="top" sideOffset={10} className={cn( - "w-[156px] rounded-xl p-1.5", + "w-[200px] rounded-[18px] border border-[#d8dce3] bg-white p-2 text-[#20242a] shadow-[0_22px_54px_rgba(15,23,42,0.16)]", isVideoWorkspace - ? "border border-border/60 bg-background/95 shadow-[0_18px_36px_-24px_rgba(15,23,42,0.35)] backdrop-blur-xl" - : "border border-[#d6d9df] bg-[#f4f5f7]/90 shadow-[0_8px_18px_rgba(15,23,42,0.14)] dark:border-white/10 dark:bg-zinc-900/95" + ? "border border-border/60 bg-background/95 shadow-[0_14px_30px_-20px_rgba(15,23,42,0.35)] backdrop-blur-xl" + : "dark:border-white/10 dark:bg-zinc-900/95 dark:text-zinc-100" )} > {effectiveAgentEnabled ? ( handleSelectGenerationMode("agent")} className={cn( - "h-8 cursor-pointer rounded-lg px-2 text-[13px] font-medium", + "h-11 cursor-pointer rounded-[12px] px-3 text-[16px] font-normal [&_svg]:!size-5", isVideoWorkspace ? "text-foreground" : "text-[#2e3238] dark:text-zinc-100", activeComposerMode === "agent" ? isVideoWorkspace ? "bg-background" - : "bg-white/80 dark:bg-white/10" + : "bg-transparent dark:bg-white/10" : isVideoWorkspace ? "hover:bg-background/80" - : "hover:bg-white/70 dark:hover:bg-white/8" + : "hover:bg-[#f5f6f7] dark:hover:bg-white/8" )} - > - - - {getToolbarItem("chatModeAgentMenu").label} - - {activeComposerMode === "agent" ? ( - - ) : null} - + > + + + Agent + + {activeComposerMode === "agent" ? ( + + ) : null} + ) : null} {canUseDetailGeneration ? ( handleSelectGenerationMode("detail")} className={cn( - "h-8 cursor-pointer rounded-lg px-2 text-[13px] font-medium", + "h-11 cursor-pointer rounded-[12px] px-3 text-[16px] font-normal [&_svg]:!size-5", isVideoWorkspace ? "text-foreground" : "text-[#2e3238] dark:text-zinc-100", activeComposerMode === "detail" ? isVideoWorkspace ? "bg-background" - : "bg-white/80 dark:bg-white/10" + : "bg-transparent dark:bg-white/10" : isVideoWorkspace ? "hover:bg-background/80" - : "hover:bg-white/70 dark:hover:bg-white/8" + : "hover:bg-[#f5f6f7] dark:hover:bg-white/8" )} > @@ -10579,92 +10668,70 @@ export function CanvasChatPanel({ {getToolbarItem("chatModeDetailMenu").label} {activeComposerMode === "detail" ? ( - + ) : null} ) : null} handleSelectGenerationMode("image")} className={cn( - "h-8 cursor-pointer rounded-lg px-2 text-[13px] font-medium", + "h-11 cursor-pointer rounded-[12px] px-3 text-[16px] font-normal [&_svg]:!size-5", isVideoWorkspace ? "text-foreground" : "text-[#2e3238] dark:text-zinc-100", activeComposerMode === "image" ? isVideoWorkspace ? "bg-background" - : "bg-white/80 dark:bg-white/10" + : "bg-transparent dark:bg-white/10" : isVideoWorkspace ? "hover:bg-background/80" - : "hover:bg-white/70 dark:hover:bg-white/8" + : "hover:bg-[#f5f6f7] dark:hover:bg-white/8" )} > - {getToolbarItem("chatModeImageMenu").label} + 图像 {activeComposerMode === "image" ? ( - + ) : null} handleSelectGenerationMode("video")} className={cn( - "h-8 cursor-pointer rounded-lg px-2 text-[13px] font-medium", + "h-11 cursor-pointer rounded-[12px] px-3 text-[16px] font-normal [&_svg]:!size-5", isVideoWorkspace ? "text-foreground" : "text-[#2e3238] dark:text-zinc-100", activeComposerMode === "video" ? isVideoWorkspace ? "bg-background" - : "bg-white/80 dark:bg-white/10" + : "bg-transparent dark:bg-white/10" : isVideoWorkspace ? "hover:bg-background/80" - : "hover:bg-white/70 dark:hover:bg-white/8" + : "hover:bg-[#f5f6f7] dark:hover:bg-white/8" )} > - {getToolbarItem("chatModeVideoMenu").label} + 视频 {activeComposerMode === "video" ? ( - + ) : null} ) : null} - {!isWorkspacePresentation && !isDetailGenerationMode && (!isVideoModel || showVideoAttachmentButton) && ( - - )} - {isVideoModel ? (
@@ -10678,49 +10745,24 @@ export function CanvasChatPanel({ />
- ) : ( -
- -
- )} + ) : null}
-
- {!isWorkspacePresentation && activeComposerMode === "image" ? ( - +
+ {!isVideoModel && !isWorkspacePresentation && activeComposerMode === "image" ? ( + ) : null} - {!isWorkspacePresentation && activeComposerMode !== "detail" ? ( - -
- - {triggerComposerModel?.avatarUrl ? ( - {triggerComposerModel.displayName - ) : ( - - {(triggerComposerModel?.displayName || "A").slice(0, 1)} - - )} - - - {triggerComposerModel?.displayName || "Agent模式"} - -
- -
- -
-
+
+ +
diff --git a/components/home/lovart/lovart-sidebar.tsx b/components/home/lovart/lovart-sidebar.tsx index 58210e64b..502f46294 100644 --- a/components/home/lovart/lovart-sidebar.tsx +++ b/components/home/lovart/lovart-sidebar.tsx @@ -31,7 +31,6 @@ type LovartSidebarProps = { onImageEditor: () => void; onNavLink: (url: string) => void; onNewCanvas: () => void; - onProfile: () => void; onRoute: (url: string) => void; onTutorial: (video: TutorialVideo) => void; productPlanningEnabled: boolean; @@ -51,6 +50,7 @@ type LovartSidebarProps = { tutorialEnabled: boolean; tutorialVideos: TutorialVideo[]; videoCanvasEnabled: boolean; + tone?: "light" | "dark"; }; export function LovartSidebar({ @@ -64,7 +64,6 @@ export function LovartSidebar({ onImageEditor, onNavLink, onNewCanvas, - onProfile, onRoute, onTutorial, productPlanningEnabled, @@ -77,14 +76,15 @@ export function LovartSidebar({ tutorialEnabled, tutorialVideos, videoCanvasEnabled, + tone = "light", }: LovartSidebarProps) { 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"; + "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 dark:border-white/10 dark:bg-[#141416]/96 dark:text-white dark:shadow-[0_24px_64px_rgba(0,0,0,0.42)]"; const sidebarButtonClass = (active = false) => [ - "group flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-[#6b7280] transition-all duration-200 hover:bg-black/[0.04] hover:text-[#111827]", + "group flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-[10px] text-[#6b7280] transition-all duration-200 hover:bg-black/[0.04] hover:text-[#111827] dark:text-white/62 dark:hover:bg-white/8 dark:hover:text-white", active - ? "bg-[#111827] text-white shadow-[0_10px_22px_rgba(15,23,42,0.18)]" + ? "bg-[#f1f2f4] text-[#111827] shadow-[inset_0_0_0_1px_rgba(15,23,42,0.04)] dark:bg-white/12 dark:text-white" : "", ] .filter(Boolean) @@ -104,7 +104,10 @@ export function LovartSidebar({ onClick={onClick} title={getItem(key).label} > - {renderIcon(key, { active: options?.active })} + {renderIcon(key, { + active: options?.active, + sizeClassName: "h-[15px] w-[15px]", + })} {getItem(key).label} @@ -124,7 +127,7 @@ export function LovartSidebar({ className={sidebarButtonClass()} title={getItem(key).label} > - {renderIcon(key)} + {renderIcon(key, { sizeClassName: "h-[15px] w-[15px]" })} -
+
{getItem(key).label}
@@ -140,11 +143,11 @@ export function LovartSidebar({ ))}
@@ -153,7 +156,7 @@ export function LovartSidebar({ ) : null; return ( -