fix: hide video canvas entry when disabled
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-15 21:25:24 +08:00
parent 71c537fdb7
commit 2a793dcbba
8 changed files with 160 additions and 32 deletions

View File

@ -50,6 +50,7 @@ export default async function AdminLayout({
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
rankingEnabled={siteSettings.rankingEnabled ?? true}
productPlanningEnabled={(siteSettings as any).productPlanningEnabled ?? true}
videoCanvasEnabled={(siteSettings as any).videoCanvasEnabled ?? true}
/>
<div className="flex-1 pl-16">{children}</div>
</div>

View File

@ -482,6 +482,7 @@ type SiteSettings = {
canvasEnabled: boolean;
agentEnabled: boolean;
canvasAgentModeEnabled: boolean;
videoCanvasEnabled: boolean;
videoCanvasSidebarEnabled: boolean;
agentCategoryTreeConfig: {
productCategories: string[];
@ -989,6 +990,7 @@ export function AdminSettingsClient() {
canvasEnabled: true,
agentEnabled: true,
canvasAgentModeEnabled: true,
videoCanvasEnabled: true,
videoCanvasSidebarEnabled: true,
agentCategoryTreeConfig: {
productCategories: [],
@ -1093,6 +1095,10 @@ export function AdminSettingsClient() {
agentEnabled: (siteSettingsData as any).agentEnabled ?? true,
canvasAgentModeEnabled:
(siteSettingsData as any).canvasAgentModeEnabled ?? true,
videoCanvasEnabled:
(siteSettingsData as any).videoCanvasEnabled ??
(siteSettingsData as any).videoCanvasSidebarEnabled ??
true,
videoCanvasSidebarEnabled:
(siteSettingsData as any).videoCanvasSidebarEnabled ?? true,
agentCategoryTreeConfig: {
@ -9432,25 +9438,48 @@ export function AdminSettingsClient() {
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label htmlFor="videoCanvasSidebarEnabled">
视频画布模块侧边栏
<Label htmlFor="videoCanvasEnabled">
视频画布功能
</Label>
<div className="text-xs text-muted-foreground">
开启后,视频画布左侧显示“视频模块”分层侧边栏;关闭后只保留画布本体与节点快捷添加。
开启后,首页和顶部导航显示“视频画布”入口;关闭后隐藏入口按钮。
</div>
</div>
<Switch
id="videoCanvasSidebarEnabled"
checked={siteSettings.videoCanvasSidebarEnabled}
id="videoCanvasEnabled"
checked={siteSettings.videoCanvasEnabled}
onCheckedChange={(checked) =>
setSiteSettings({
...siteSettings,
videoCanvasSidebarEnabled: checked,
videoCanvasEnabled: checked,
})
}
/>
</div>
{siteSettings.videoCanvasEnabled && (
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label htmlFor="videoCanvasSidebarEnabled">
视频画布模块侧边栏
</Label>
<div className="text-xs text-muted-foreground">
开启后,视频画布左侧显示“视频模块”分层侧边栏;关闭后只保留画布本体与节点快捷添加。
</div>
</div>
<Switch
id="videoCanvasSidebarEnabled"
checked={siteSettings.videoCanvasSidebarEnabled}
onCheckedChange={(checked) =>
setSiteSettings({
...siteSettings,
videoCanvasSidebarEnabled: checked,
})
}
/>
</div>
)}
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label htmlFor="aiDetailGenerationEnabled">

View File

@ -80,6 +80,7 @@ async function SidebarWrapper({ children }: { children: React.ReactNode }) {
rankingEnabled={siteSettings.rankingEnabled ?? true}
productPlanningEnabled={(siteSettings as any).productPlanningEnabled ?? true}
agentEnabled={(siteSettings as any).agentEnabled ?? true}
videoCanvasEnabled={(siteSettings as any).videoCanvasEnabled ?? true}
>
{children}
</ChatShell>

View File

@ -109,6 +109,7 @@ const REALTIME_CANVAS_CONFIG_KEY = "realtime_canvas_config";
const LAYER_SPLIT_CONFIG_KEY = "layer_split_config";
const AGENT_CATEGORY_TREE_CONFIG_KEY = "agent_category_tree_config";
const CANVAS_AGENT_MODE_ENABLED_KEY = "canvas_agent_mode_enabled";
const VIDEO_CANVAS_ENABLED_KEY = "video_canvas_enabled";
const VIDEO_CANVAS_SIDEBAR_ENABLED_KEY = "video_canvas_sidebar_enabled";
function normalizeHomeVariant(value: unknown): "classic" | "lovart" {
@ -228,6 +229,17 @@ function normalizeCanvasAgentModeEnabled(value: unknown): boolean {
return value !== false;
}
function normalizeVideoCanvasEnabled(
value: unknown,
fallbackValue: unknown = true
): boolean {
if (value !== undefined && value !== null) {
return value !== false;
}
return fallbackValue !== false;
}
function normalizeVideoCanvasSidebarEnabled(value: unknown): boolean {
return value !== false;
}
@ -289,6 +301,7 @@ export async function GET() {
realtimeCanvasConfigSetting,
layerSplitConfigSetting,
canvasAgentModeEnabledSetting,
videoCanvasEnabledSetting,
videoCanvasSidebarEnabledSetting,
storageFailoverConfigSetting,
ossImageMigrationConfigSetting,
@ -402,6 +415,12 @@ export async function GET() {
.where(eq(systemSettings.key, CANVAS_AGENT_MODE_ENABLED_KEY))
.limit(1)
.then(rows => rows[0]),
db
.select()
.from(systemSettings)
.where(eq(systemSettings.key, VIDEO_CANVAS_ENABLED_KEY))
.limit(1)
.then(rows => rows[0]),
db
.select()
.from(systemSettings)
@ -525,6 +544,10 @@ export async function GET() {
const videoCanvasSidebarEnabled = normalizeVideoCanvasSidebarEnabled(
videoCanvasSidebarEnabledSetting?.value
);
const videoCanvasEnabled = normalizeVideoCanvasEnabled(
videoCanvasEnabledSetting?.value,
videoCanvasSidebarEnabledSetting?.value
);
const storageFailoverConfig = normalizeStorageFailoverConfig(
storageFailoverConfigSetting?.value
);
@ -601,6 +624,7 @@ export async function GET() {
canvasEnabled: true,
agentEnabled: true,
canvasAgentModeEnabled: true,
videoCanvasEnabled: true,
videoCanvasSidebarEnabled: true,
agentPresetLibraryConfig: defaultAgentPresetLibraryConfig,
agentCategoryTreeConfig: normalizeAgentCategoryTreeConfig(undefined),
@ -680,6 +704,7 @@ export async function GET() {
realtimeCanvasServiceEnabled,
layerSplitEnabled,
canvasAgentModeEnabled,
videoCanvasEnabled,
videoCanvasSidebarEnabled,
storageFailoverConfig,
ossImageMigrationConfig,
@ -758,6 +783,7 @@ const updateSchema = z.object({
canvasEnabled: z.boolean().optional(),
agentEnabled: z.boolean().optional(),
canvasAgentModeEnabled: z.boolean().optional(),
videoCanvasEnabled: z.boolean().optional(),
videoCanvasSidebarEnabled: z.boolean().optional(),
agentPresetLibraryConfig: z
.object({
@ -1128,6 +1154,7 @@ export async function PUT(request: NextRequest) {
storageFailoverConfig,
ossImageMigrationConfig,
canvasAgentModeEnabled,
videoCanvasEnabled,
videoCanvasSidebarEnabled,
homeAnimationConfig,
...siteSettingsPayload
@ -1347,6 +1374,26 @@ export async function PUT(request: NextRequest) {
});
}
if (videoCanvasEnabled !== undefined) {
const normalized = normalizeVideoCanvasEnabled(videoCanvasEnabled);
await db
.insert(systemSettings)
.values({
key: VIDEO_CANVAS_ENABLED_KEY,
value: normalized,
updatedAt: new Date(),
updatedBy: session.user.id,
})
.onConflictDoUpdate({
target: systemSettings.key,
set: {
value: normalized,
updatedAt: new Date(),
updatedBy: session.user.id,
},
});
}
if (videoCanvasSidebarEnabled !== undefined) {
const normalized = normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled);
await db
@ -1863,6 +1910,10 @@ export async function PUT(request: NextRequest) {
canvasAgentModeEnabled !== undefined
? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled)
: normalizeCanvasAgentModeEnabled(undefined),
videoCanvasEnabled:
videoCanvasEnabled !== undefined
? normalizeVideoCanvasEnabled(videoCanvasEnabled)
: normalizeVideoCanvasEnabled(undefined, videoCanvasSidebarEnabled),
videoCanvasSidebarEnabled:
videoCanvasSidebarEnabled !== undefined
? normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled)
@ -2417,6 +2468,26 @@ export async function PUT(request: NextRequest) {
});
}
if (videoCanvasEnabled !== undefined) {
const normalized = normalizeVideoCanvasEnabled(videoCanvasEnabled);
await db
.insert(systemSettings)
.values({
key: VIDEO_CANVAS_ENABLED_KEY,
value: normalized,
updatedAt: new Date(),
updatedBy: session.user.id,
})
.onConflictDoUpdate({
target: systemSettings.key,
set: {
value: normalized,
updatedAt: new Date(),
updatedBy: session.user.id,
},
});
}
return NextResponse.json({
...created,
homeVariant: homeVariant ?? normalizeHomeVariant(undefined),
@ -2519,6 +2590,10 @@ export async function PUT(request: NextRequest) {
canvasAgentModeEnabled !== undefined
? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled)
: normalizeCanvasAgentModeEnabled(undefined),
videoCanvasEnabled:
videoCanvasEnabled !== undefined
? normalizeVideoCanvasEnabled(videoCanvasEnabled)
: normalizeVideoCanvasEnabled(undefined, videoCanvasSidebarEnabled),
videoCanvasSidebarEnabled:
videoCanvasSidebarEnabled !== undefined
? normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled)

View File

@ -43,6 +43,7 @@ type ChatShellProps = {
rankingEnabled: boolean;
productPlanningEnabled: boolean;
agentEnabled: boolean;
videoCanvasEnabled: boolean;
};
export function ChatShell({
@ -65,6 +66,7 @@ export function ChatShell({
rankingEnabled,
productPlanningEnabled,
agentEnabled,
videoCanvasEnabled,
}: ChatShellProps) {
const pathname = usePathname();
const isLovartHome = pathname === "/" && homeVariant === "lovart";
@ -97,6 +99,7 @@ export function ChatShell({
toolbox2Name={toolbox2Name}
tutorialEnabled={tutorialEnabled}
tutorialVideos={tutorialVideos}
videoCanvasEnabled={videoCanvasEnabled}
/>
<div className="flex h-svh w-full overflow-hidden pl-16 [--sidebar-offset:4rem]">
<AppSidebar user={user} />

View File

@ -454,6 +454,7 @@ export function LovartHome({
toolbox2Enabled?: boolean;
productPlanningEnabled?: boolean;
agentEnabled?: boolean;
videoCanvasEnabled?: boolean;
tutorialEnabled?: boolean;
tutorialVideoUrl?: string | null;
pointsPackages?: PointsPackage[];
@ -1584,19 +1585,21 @@ export function LovartHome({
<TooltipContent side="right">风格化</TooltipContent>
</Tooltip>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
onClick={() => router.push("/video-canvas")}
title="视频画布"
>
<Clapperboard className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right">视频画布</TooltipContent>
</Tooltip>
{siteSettingsData?.videoCanvasEnabled !== false ? (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
onClick={() => router.push("/video-canvas")}
title="视频画布"
>
<Clapperboard className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right">视频画布</TooltipContent>
</Tooltip>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<button

View File

@ -78,6 +78,7 @@ type TopNavbarProps = {
rankingEnabled?: boolean;
productPlanningEnabled?: boolean;
agentEnabled?: boolean;
videoCanvasEnabled?: boolean;
};
export function TopNavbar({
@ -96,6 +97,7 @@ export function TopNavbar({
rankingEnabled = true,
productPlanningEnabled = true,
agentEnabled = true,
videoCanvasEnabled = true,
}: TopNavbarProps) {
const router = useRouter();
const { resolvedTheme } = useTheme();
@ -604,17 +606,19 @@ export function TopNavbar({
</button>
)}
<button
className={navItemBaseClass}
onClick={() => {
window.location.href = "/video-canvas";
}}
title="视频画布"
type="button"
>
<Clapperboard className="h-5 w-5" />
<span className={navLabelClass}>{getShortLabel("视频画布")}</span>
</button>
{videoCanvasEnabled && (
<button
className={navItemBaseClass}
onClick={() => {
window.location.href = "/video-canvas";
}}
title="视频画布"
type="button"
>
<Clapperboard className="h-5 w-5" />
<span className={navLabelClass}>{getShortLabel("视频画布")}</span>
</button>
)}
<button
className={navItemBaseClass}

View File

@ -4450,6 +4450,7 @@ export async function getSiteSettings() {
rankingEnabled: true,
productPlanningEnabled: true,
agentEnabled: true,
videoCanvasEnabled: true,
canvasDetailAgentModelId: null,
lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠",
lovartPromoActionText: "立即升级",
@ -4457,8 +4458,19 @@ export async function getSiteSettings() {
lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切",
};
try {
const [settings] = await db.select().from(siteSettings).limit(1);
return settings || defaultSettings;
const [settings, videoCanvasEnabledSetting, videoCanvasSidebarEnabledSetting] =
await Promise.all([
db.select().from(siteSettings).limit(1).then((rows) => rows[0]),
getSystemSetting({ key: "video_canvas_enabled" }).catch(() => null),
getSystemSetting({ key: "video_canvas_sidebar_enabled" }).catch(() => null),
]);
const videoCanvasEnabled =
videoCanvasEnabledSetting?.value !== undefined &&
videoCanvasEnabledSetting?.value !== null
? videoCanvasEnabledSetting.value !== false
: videoCanvasSidebarEnabledSetting?.value !== false;
return { ...(settings || defaultSettings), videoCanvasEnabled };
} catch (_error) {
return defaultSettings;
}