split canvas agent mode toggle from agent workspace
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-03-23 20:00:05 +08:00
parent 1c1f7026d5
commit 63a01e6a5a
3 changed files with 97 additions and 9 deletions

View File

@ -248,6 +248,7 @@ type SiteSettings = {
rankingEnabled: boolean;
canvasEnabled: boolean;
agentEnabled: boolean;
canvasAgentModeEnabled: boolean;
agentCategoryTreeConfig: {
productCategories: string[];
audienceTags: string[];
@ -676,6 +677,7 @@ export function AdminSettingsClient() {
rankingEnabled: true,
canvasEnabled: true,
agentEnabled: true,
canvasAgentModeEnabled: true,
agentCategoryTreeConfig: {
productCategories: [],
audienceTags: [],
@ -761,6 +763,7 @@ export function AdminSettingsClient() {
rankingEnabled: siteSettingsData.rankingEnabled ?? true,
canvasEnabled: siteSettingsData.canvasEnabled ?? true,
agentEnabled: (siteSettingsData as any).agentEnabled ?? true,
canvasAgentModeEnabled: (siteSettingsData as any).canvasAgentModeEnabled ?? true,
agentCategoryTreeConfig: {
productCategories: Array.isArray((siteSettingsData as any).agentCategoryTreeConfig?.productCategories)
? (siteSettingsData as any).agentCategoryTreeConfig.productCategories
@ -5594,7 +5597,7 @@ export function AdminSettingsClient() {
<div className="space-y-0.5">
<Label htmlFor="agentEnabled">智能体功能</Label>
<div className="text-xs text-muted-foreground">
开启后,首页侧边栏显示“智能体”入口,用户可进入画布智能体模式;关闭后隐藏入口。
开启后,首页侧边栏显示“智能体”入口,用户可进入独立的智能体工作台;关闭后隐藏该入口。
</div>
</div>
<Switch
@ -5606,6 +5609,22 @@ export function AdminSettingsClient() {
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label htmlFor="canvasAgentModeEnabled">无限画布 Agent模式</Label>
<div className="text-xs text-muted-foreground">
控制无限画布右侧对话框中的 Agent模式 入口,与上面的智能体工作台入口独立。
</div>
</div>
<Switch
id="canvasAgentModeEnabled"
checked={siteSettings.canvasAgentModeEnabled}
onCheckedChange={(checked) =>
setSiteSettings({ ...siteSettings, canvasAgentModeEnabled: checked })
}
/>
</div>
{siteSettings.agentEnabled && (
<div className="rounded-lg border p-4 space-y-4">
<div>

View File

@ -82,6 +82,7 @@ const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides";
const STORAGE_FAILOVER_CONFIG_KEY = "storage_failover_config";
const REALTIME_CANVAS_CONFIG_KEY = "realtime_canvas_config";
const AGENT_CATEGORY_TREE_CONFIG_KEY = "agent_category_tree_config";
const CANVAS_AGENT_MODE_ENABLED_KEY = "canvas_agent_mode_enabled";
function normalizeHomeVariant(value: unknown): "classic" | "lovart" {
return value === "lovart" ? "lovart" : "classic";
@ -196,6 +197,10 @@ function normalizeStorageFailoverConfig(value: unknown): {
};
}
function normalizeCanvasAgentModeEnabled(value: unknown): boolean {
return value !== false;
}
function normalizeAgentCategoryTreeConfig(value: unknown): AgentCategoryTreeConfig {
const raw =
value && typeof value === "object" && !Array.isArray(value)
@ -244,6 +249,7 @@ export async function GET() {
pointsPackagesSetting,
dulupayConfigSetting,
realtimeCanvasConfigSetting,
canvasAgentModeEnabledSetting,
storageFailoverConfigSetting,
ossImageMigrationConfigSetting,
agentPresetLibraryConfigSetting,
@ -340,6 +346,12 @@ export async function GET() {
.where(eq(systemSettings.key, REALTIME_CANVAS_CONFIG_KEY))
.limit(1)
.then(rows => rows[0]),
db
.select()
.from(systemSettings)
.where(eq(systemSettings.key, CANVAS_AGENT_MODE_ENABLED_KEY))
.limit(1)
.then(rows => rows[0]),
db
.select()
.from(systemSettings)
@ -429,6 +441,9 @@ export async function GET() {
: normalizeDulupayForPublic(dulupayConfigSetting?.value);
const realtimeCanvasServiceEnabled =
(realtimeCanvasConfigSetting?.value as { enabled?: boolean } | undefined)?.enabled !== false;
const canvasAgentModeEnabled = normalizeCanvasAgentModeEnabled(
canvasAgentModeEnabledSetting?.value
);
const storageFailoverConfig = normalizeStorageFailoverConfig(
storageFailoverConfigSetting?.value
);
@ -488,6 +503,7 @@ export async function GET() {
rankingEnabled: true,
canvasEnabled: true,
agentEnabled: true,
canvasAgentModeEnabled: true,
agentPresetLibraryConfig: defaultAgentPresetLibraryConfig,
agentCategoryTreeConfig: normalizeAgentCategoryTreeConfig(undefined),
agentSecondaryTypeConfig: defaultAgentSecondaryTypeConfig,
@ -552,6 +568,7 @@ export async function GET() {
pointsPackages,
dulupayConfig,
realtimeCanvasServiceEnabled,
canvasAgentModeEnabled,
storageFailoverConfig,
ossImageMigrationConfig,
});
@ -594,6 +611,7 @@ const updateSchema = z.object({
rankingEnabled: z.boolean().optional(),
canvasEnabled: z.boolean().optional(),
agentEnabled: z.boolean().optional(),
canvasAgentModeEnabled: z.boolean().optional(),
agentPresetLibraryConfig: z
.object({
sections: z
@ -869,6 +887,7 @@ export async function PUT(request: NextRequest) {
dulupayConfig,
storageFailoverConfig,
ossImageMigrationConfig,
canvasAgentModeEnabled,
...siteSettingsPayload
} = data;
@ -1026,6 +1045,26 @@ export async function PUT(request: NextRequest) {
});
}
if (canvasAgentModeEnabled !== undefined) {
const normalized = normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled);
await db
.insert(systemSettings)
.values({
key: CANVAS_AGENT_MODE_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 (agentPresetLibraryConfig !== undefined) {
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
await db
@ -1416,6 +1455,10 @@ export async function PUT(request: NextRequest) {
ossImageMigrationConfig !== undefined
? normalizeOssImageMigrationConfig(ossImageMigrationConfig)
: defaultOssImageMigrationConfig,
canvasAgentModeEnabled:
canvasAgentModeEnabled !== undefined
? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled)
: normalizeCanvasAgentModeEnabled(undefined),
});
} else {
// 创建新设置
@ -1597,6 +1640,26 @@ export async function PUT(request: NextRequest) {
});
}
if (canvasAgentModeEnabled !== undefined) {
const normalized = normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled);
await db
.insert(systemSettings)
.values({
key: CANVAS_AGENT_MODE_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 (agentPresetLibraryConfig !== undefined) {
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
await db
@ -1965,6 +2028,10 @@ export async function PUT(request: NextRequest) {
ossImageMigrationConfig !== undefined
? normalizeOssImageMigrationConfig(ossImageMigrationConfig)
: defaultOssImageMigrationConfig,
canvasAgentModeEnabled:
canvasAgentModeEnabled !== undefined
? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled)
: normalizeCanvasAgentModeEnabled(undefined),
});
}
} catch (error) {

View File

@ -273,6 +273,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
siteLogo?: string | null;
siteLogoDark?: string | null;
agentEnabled?: boolean;
canvasAgentModeEnabled?: boolean;
realtimeCanvasEnabled?: boolean;
realtimeCanvasServiceEnabled?: boolean;
agentWorkflowShowcaseConfig?: AgentWorkflowShowcaseConfig;
@ -306,9 +307,10 @@ export default function CanvasEditorPage({ params }: PageProps) {
const previousBrandForModelRef = useRef("");
const autoSelectedBrandModelBrandRef = useRef("");
const requestedAgentMode = mode === "agent";
const isAgentFeatureEnabled = siteSettingsData?.agentEnabled !== false;
const isAgentWorkspaceEnabled = siteSettingsData?.agentEnabled !== false;
const isCanvasAgentModeEnabled = siteSettingsData?.canvasAgentModeEnabled !== false;
const isAgentMode =
isAgentFeatureEnabled && requestedAgentMode;
isAgentWorkspaceEnabled && requestedAgentMode;
const resolvedBrandModelConfig = useMemo(
() => normalizeAgentWorkflowBrandModelConfig(siteSettingsData?.agentWorkflowBrandModelConfig),
[siteSettingsData?.agentWorkflowBrandModelConfig]
@ -653,7 +655,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
);
useEffect(() => {
if (!requestedAgentMode || !isAgentFeatureEnabled || agentProjectId) return;
if (!requestedAgentMode || !isAgentWorkspaceEnabled || agentProjectId) return;
let cancelled = false;
const createAgentProject = async () => {
@ -686,7 +688,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
return () => {
cancelled = true;
};
}, [agentProjectId, isAgentFeatureEnabled, requestedAgentMode, updateCanvasSearchParams]);
}, [agentProjectId, isAgentWorkspaceEnabled, requestedAgentMode, updateCanvasSearchParams]);
useEffect(() => {
if (!requestedAgentMode || !agentProjectId) return;
@ -1480,7 +1482,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
);
const agentDisabledNotice =
requestedAgentMode && !isAgentFeatureEnabled ? (
requestedAgentMode && !isAgentWorkspaceEnabled ? (
<div className="absolute top-4 left-1/2 z-20 -translate-x-1/2">
<div className="inline-flex items-center gap-2 rounded-full border border-amber-200 bg-amber-50 px-4 py-2 text-sm text-amber-800 shadow-sm dark:border-amber-900/60 dark:bg-amber-950/80 dark:text-amber-200">
<AlertCircle className="h-4 w-4" />
@ -1552,7 +1554,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
<AgentGenerationWorkspaceDarkV2
chatId={agentModeChatId}
projectId={id}
selectedAgentPresetId={isAgentFeatureEnabled ? selectedAgentPresetId : null}
selectedAgentPresetId={isAgentWorkspaceEnabled ? selectedAgentPresetId : null}
selectedBrandModel={selectedBrandModel}
workflowContext={workflowContext}
agentModeState={agentModeState}
@ -1674,7 +1676,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
onOpenFileList={() => setPanelMode('fileList')}
selectedAgentPresetId={null}
chatScope="canvas"
agentEnabled={isAgentFeatureEnabled}
agentEnabled={isCanvasAgentModeEnabled}
/>
</div>
) : (
@ -1725,7 +1727,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
<TooltipContent side="top">平移 (H)</TooltipContent>
</Tooltip>
{isAgentFeatureEnabled ? (
{isAgentWorkspaceEnabled ? (
<Tooltip>
<TooltipTrigger asChild>
<Button