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} tutorialEnabled={siteSettings.tutorialEnabled ?? false}
rankingEnabled={siteSettings.rankingEnabled ?? true} rankingEnabled={siteSettings.rankingEnabled ?? true}
productPlanningEnabled={(siteSettings as any).productPlanningEnabled ?? true} productPlanningEnabled={(siteSettings as any).productPlanningEnabled ?? true}
videoCanvasEnabled={(siteSettings as any).videoCanvasEnabled ?? true}
/> />
<div className="flex-1 pl-16">{children}</div> <div className="flex-1 pl-16">{children}</div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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