From 83c274e8d486c70f8c56768ec0ec4d00c4bc6052 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 23:12:23 +0800 Subject: [PATCH] fix: lock workflow hidden models --- app/(chat)/api/models/route.ts | 142 +++++++++++++----- .../agents/AgentGenerationWorkspaceDarkV2.tsx | 114 +++++++++++--- 2 files changed, 191 insertions(+), 65 deletions(-) diff --git a/app/(chat)/api/models/route.ts b/app/(chat)/api/models/route.ts index 8dcb998b7..50f586bfb 100644 --- a/app/(chat)/api/models/route.ts +++ b/app/(chat)/api/models/route.ts @@ -4,6 +4,7 @@ import { getDefaultAiModel, getEnabledAiModelsWithProvider, getSiteSettings, + getSystemSetting, getUserById, } from "@/lib/db/queries"; import { buildVideoToolCapability } from "@/lib/ai/video-tool-capability"; @@ -13,6 +14,11 @@ import { } from "@/lib/ai/model-classification"; import { ChatSDKError } from "@/lib/errors"; import { isAiModelAgentAuto, isAiModelMultimodal } from "@/lib/db/ai-model-flags"; +import { + AGENT_WORKFLOW_UI_CONFIG_KEY, + getAgentWorkflowBusinessModeSettings, + normalizeAgentWorkflowUiConfig, +} from "@/lib/agents/workflow-ui-config"; function isNanoBananaProModel(model: { displayName?: string | null; modelId?: string | null }) { const displayName = String(model.displayName || "").toLowerCase(); @@ -39,6 +45,28 @@ function sortModelsForDisplay - !isAiModelAgentAuto(model) && - (allowHiddenModels || !model.isHidden) - ) - .map((item) => item.model) - ); - const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id)); - const visibleModelsWithProviderSorted = modelsWithProvider.filter(({ model }) => - visibleModelIdSet.has(model.id) - ).sort((a, b) => { - const left = visibleModelsWithProvider.findIndex((m) => m.id === a.model.id); - const right = visibleModelsWithProvider.findIndex((m) => m.id === b.model.id); - return left - right; - }); - - // 转换为前端需要的格式 - const models = visibleModelsWithProviderSorted.map(({ model, provider }) => ({ + const [siteSettings, agentWorkflowUiConfigSetting] = await Promise.all([ + getSiteSettings(), + getSystemSetting({ key: AGENT_WORKFLOW_UI_CONFIG_KEY }).catch(() => null), + ]); + type ModelWithProvider = (typeof modelsWithProvider)[number]; + const serializeModel = ({ model, provider }: ModelWithProvider) => ({ id: model.id, modelId: model.modelId, displayName: model.displayName, @@ -101,7 +114,42 @@ export async function GET() { }), imageGenConfig: model.imageGenConfig, sortOrder: model.sortOrder ?? 0, - })); + }); + const workflowImageModelById = new Map(); + modelsWithProvider.forEach((entry) => { + const { model, provider } = entry; + if ( + !isAiModelAgentAuto(model) && + isWorkflowImageGenerationModel({ + providerType: provider.type, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) + ) { + workflowImageModelById.set(model.id, entry); + } + }); + const visibleModelsWithProvider = sortModelsForDisplay( + modelsWithProvider + .filter( + ({ model }) => + !isAiModelAgentAuto(model) && + (allowHiddenModels || !model.isHidden) + ) + .map((item) => item.model) + ); + const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id)); + const visibleModelsWithProviderSorted = modelsWithProvider.filter(({ model }) => + visibleModelIdSet.has(model.id) + ).sort((a, b) => { + const left = visibleModelsWithProvider.findIndex((m) => m.id === a.model.id); + const right = visibleModelsWithProvider.findIndex((m) => m.id === b.model.id); + return left - right; + }); + + // 转换为前端需要的格式 + const models = visibleModelsWithProviderSorted.map(serializeModel); const preferredNanoBananaModel = models.find( (model) => isImageGenerationModelDescriptor({ @@ -147,29 +195,43 @@ export async function GET() { const configuredAgentWorkflowModelId = String( siteSettings?.agentWorkflowDefaultModelId || "" ).trim(); - const agentWorkflowDefaultModel = configuredAgentWorkflowModelId - ? models.find( - (model) => - model.id === configuredAgentWorkflowModelId && - isImageGenerationModelDescriptor({ - providerType: model.providerType, - modelId: model.modelId, - capabilities: model.capabilities, - supportsImageGen: model.supportsImageGen, - }) && - !isVideoModelDescriptor({ - providerType: model.providerType, - modelId: model.modelId, - capabilities: model.capabilities, - supportsImageGen: model.supportsImageGen, - }) - ) + const hasConfiguredAgentWorkflowModel = Boolean( + configuredAgentWorkflowModelId && + configuredAgentWorkflowModelId !== "auto" && + configuredAgentWorkflowModelId !== "__auto" + ); + const agentWorkflowDefaultModel = hasConfiguredAgentWorkflowModel + ? workflowImageModelById.get(configuredAgentWorkflowModelId) || null : null; + const workflowUiConfig = normalizeAgentWorkflowUiConfig( + agentWorkflowUiConfigSetting?.value + ); + const workflowBusinessModeSettings = getAgentWorkflowBusinessModeSettings( + workflowUiConfig + ); + const configuredWorkflowModelIds = new Set(); + if (agentWorkflowDefaultModel) { + configuredWorkflowModelIds.add(agentWorkflowDefaultModel.model.id); + } + workflowBusinessModeSettings.modes.forEach((mode) => { + const modelId = String(mode.defaultModelId || "").trim(); + if (modelId && workflowImageModelById.has(modelId)) { + configuredWorkflowModelIds.add(modelId); + } + }); + const agentWorkflowConfiguredModels = Array.from(configuredWorkflowModelIds) + .map((id) => workflowImageModelById.get(id)) + .filter((entry): entry is ModelWithProvider => Boolean(entry)) + .map(serializeModel); return Response.json({ models, defaultModelId: resolvedDefaultModelId, - agentWorkflowDefaultModelId: agentWorkflowDefaultModel?.id || null, + agentWorkflowConfiguredDefaultModelId: hasConfiguredAgentWorkflowModel + ? configuredAgentWorkflowModelId + : null, + agentWorkflowDefaultModelId: agentWorkflowDefaultModel?.model.id || null, + agentWorkflowConfiguredModels, agentModel: agentModelWithProvider ? { id: agentModelWithProvider.model.id, diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index ae2969c34..23113b077 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -157,6 +157,15 @@ interface AgentGenerationWorkspaceDarkV2Props { workflowStylePanel?: ReactNode; } +interface AgentWorkflowModelOption { + id: string; + displayName: string; + providerType: string; + supportsImageGen?: boolean; + isMultimodal?: boolean; + imageGenConfig?: any; +} + interface PickedAttachment extends Attachment {} const MAX_CHAT_TEXT_PART_LENGTH = 2000; @@ -1210,16 +1219,11 @@ export function AgentGenerationWorkspaceDarkV2({ ); const { data: modelsData } = useSWR<{ - models: Array<{ - id: string; - displayName: string; - providerType: string; - supportsImageGen?: boolean; - isMultimodal?: boolean; - imageGenConfig?: any; - }>; + models: AgentWorkflowModelOption[]; defaultModelId?: string | null; + agentWorkflowConfiguredDefaultModelId?: string | null; agentWorkflowDefaultModelId?: string | null; + agentWorkflowConfiguredModels?: AgentWorkflowModelOption[]; agentModel?: any; }>("/api/models", fetcher); @@ -1258,10 +1262,15 @@ export function AgentGenerationWorkspaceDarkV2({ const activeBusinessModeDefaultModelId = !requiresPresetSelection ? String(activeBusinessMode?.defaultModelId || "").trim() || null : null; - const effectiveDefaultModelId = + const lockedWorkflowModelId = activeBusinessModeDefaultModelId || selectedAgentPreset?.defaultModelId || + modelsData?.agentWorkflowConfiguredDefaultModelId || modelsData?.agentWorkflowDefaultModelId || + null; + const isWorkflowModelLocked = Boolean(lockedWorkflowModelId); + const effectiveDefaultModelId = + lockedWorkflowModelId || modelsData?.defaultModelId || null; const presetDrivenResultModeType: AgentGenerationResultModeType = @@ -1353,7 +1362,8 @@ export function AgentGenerationWorkspaceDarkV2({ ); const workflowBrandName = getCurrentWorkflowBrandName(); const shouldAllowModelOverride = - isDirectModelGenerationMode || !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride; + !isWorkflowModelLocked && + (isDirectModelGenerationMode || !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride); const brandModelAttachmentKeys = useMemo(() => { const keys = new Set(); referenceImages @@ -1737,6 +1747,7 @@ export function AgentGenerationWorkspaceDarkV2({ selectedAgentPresetId: string | null; selectedAgentPresetType: AgentPreset["type"] | null; selectedModelId: string; + lockedWorkflowModelId: string | null; imageParams: ImageGenerationParams; isDetailReplacementPreset: boolean; isSkuReplacementPreset: boolean; @@ -1754,6 +1765,7 @@ export function AgentGenerationWorkspaceDarkV2({ selectedAgentPresetId: null, selectedAgentPresetType: null, selectedModelId: "auto", + lockedWorkflowModelId: null, imageParams: { aspectRatio: "auto", imageSize: "2K", @@ -1776,6 +1788,7 @@ export function AgentGenerationWorkspaceDarkV2({ selectedAgentPresetId: selectedAgentPresetId || null, selectedAgentPresetType: selectedAgentPreset?.type || null, selectedModelId, + lockedWorkflowModelId, imageParams, isDetailReplacementPreset, isSkuReplacementPreset, @@ -1807,10 +1820,40 @@ export function AgentGenerationWorkspaceDarkV2({ return [autoOption, ...imageModels]; }, [modelsData?.agentModel?.imageGenConfig, modelsData?.models]); - const currentModel = useMemo( - () => selectableModels.find((model) => model.id === selectedModelId) || selectableModels[0], - [selectableModels, selectedModelId] + const configuredWorkflowModels = useMemo( + () => modelsData?.agentWorkflowConfiguredModels || [], + [modelsData?.agentWorkflowConfiguredModels] ); + const executableModelOptions = useMemo(() => { + const seen = new Set(); + return [...selectableModels, ...configuredWorkflowModels].filter((model) => { + if (seen.has(model.id)) return false; + seen.add(model.id); + return true; + }); + }, [configuredWorkflowModels, selectableModels]); + const executableModelIdSet = useMemo( + () => new Set(executableModelOptions.map((model) => model.id)), + [executableModelOptions] + ); + const lockedWorkflowModel = useMemo( + () => + lockedWorkflowModelId + ? executableModelOptions.find((model) => model.id === lockedWorkflowModelId) || null + : null, + [executableModelOptions, lockedWorkflowModelId] + ); + const currentModel = useMemo( + () => + lockedWorkflowModel || + executableModelOptions.find((model) => model.id === selectedModelId) || + selectableModels[0], + [executableModelOptions, lockedWorkflowModel, selectableModels, selectedModelId] + ); + const lockedWorkflowModelLabel = + lockedWorkflowModel?.displayName || + selectedAgentPreset?.defaultModel?.displayName || + (lockedWorkflowModelId ? `配置模型不可用(${lockedWorkflowModelId})` : "未配置"); const [activeToolStep, setActiveToolStep] = useState("settings"); const hasBrandModelStep = Boolean(brandModelPanel) && modelSlotEnabled; const hasBrandStoreStep = Boolean(brandStorePanel); @@ -1862,9 +1905,10 @@ export function AgentGenerationWorkspaceDarkV2({ ? transportState.selectedAgentPresetId || selectedAgentPresetId || undefined : undefined; const resolvedSelectedModelId = - transportState.selectedModelId !== "auto" + transportState.lockedWorkflowModelId || + (transportState.selectedModelId !== "auto" ? transportState.selectedModelId - : effectiveDefaultModelId || transportState.selectedModelId; + : effectiveDefaultModelId || transportState.selectedModelId); const replacementModePayload = transportState.isDetailReplacementPreset || transportState.isSkuReplacementPreset ? { @@ -1958,6 +2002,10 @@ export function AgentGenerationWorkspaceDarkV2({ }, [historyMessages, setMessages]); useEffect(() => { + if (isWorkflowModelLocked) { + setSelectedModelId((current) => (current === "auto" ? current : "auto")); + return; + } if (normalizedIncomingWorkspaceState.selectedModelId) { const nextModelId = normalizedIncomingWorkspaceState.selectedModelId; setSelectedModelId((current) => @@ -1973,6 +2021,7 @@ export function AgentGenerationWorkspaceDarkV2({ } }, [ effectiveDefaultModelId, + isWorkflowModelLocked, normalizedIncomingWorkspaceState.selectedModelId, ]); @@ -2625,7 +2674,9 @@ export function AgentGenerationWorkspaceDarkV2({ return arePickedAttachmentsEqual(current, nextAttachments) ? current : nextAttachments; }); setSelectedModelId((current) => { - const nextModelId = normalizedIncomingWorkspaceState.selectedModelId || "auto"; + const nextModelId = isWorkflowModelLocked + ? "auto" + : normalizedIncomingWorkspaceState.selectedModelId || "auto"; return current === nextModelId ? current : nextModelId; }); setImageParams((current) => { @@ -2636,7 +2687,7 @@ export function AgentGenerationWorkspaceDarkV2({ }; return areImageGenerationParamsEqual(current, nextParams) ? current : nextParams; }); - }, [normalizedIncomingWorkspaceState]); + }, [isWorkflowModelLocked, normalizedIncomingWorkspaceState]); useEffect(() => { const nextWorkspaceState = { @@ -2987,16 +3038,17 @@ export function AgentGenerationWorkspaceDarkV2({ return; } const resolvedModelId = - selectedModelId !== "auto" + lockedWorkflowModelId || + (selectedModelId !== "auto" ? selectedModelId - : effectiveDefaultModelId || selectedModelId; + : effectiveDefaultModelId || selectedModelId); if (!resolvedModelId) { toast.error("当前风格化未配置模型"); return; } if ( resolvedModelId !== "auto" && - !selectableModels.some((model) => model.id === resolvedModelId) + !executableModelIdSet.has(resolvedModelId) ) { toast.error("当前风格化绑定的模型不存在或已停用,请重新编辑风格化模型配置"); return; @@ -3113,11 +3165,12 @@ export function AgentGenerationWorkspaceDarkV2({ isSelectedBusinessModeUnavailable, isSkuReplacementPreset, effectiveDefaultModelId, + executableModelIdSet, + lockedWorkflowModelId, requiredProductUploadCount, requiresPresetSelection, selectedAgentPreset, selectedAgentPresetId, - selectableModels, selectedModelId, sendMessage, setPendingGeneration, @@ -3670,9 +3723,18 @@ export function AgentGenerationWorkspaceDarkV2({ ? "en" : "zh"; const resolvedModelId = - selectedModelId !== "auto" + lockedWorkflowModelId || + (selectedModelId !== "auto" ? selectedModelId - : effectiveDefaultModelId || undefined; + : effectiveDefaultModelId || undefined); + if ( + resolvedModelId && + resolvedModelId !== "auto" && + !executableModelIdSet.has(resolvedModelId) + ) { + toast.error("当前风格化绑定的模型不存在或已停用,请重新编辑风格化模型配置"); + return; + } detailGenerationCancelRef.current = false; setDetailGenerationRunResults([]); @@ -4918,9 +4980,11 @@ export function AgentGenerationWorkspaceDarkV2({ ) : (
- 当前生图模式已锁定模型: + {isWorkflowModelLocked ? "后台已锁定生图模型:" : "当前生图模式使用默认模型:"} - {selectedAgentPreset?.defaultModel?.displayName || currentModel?.displayName || "未配置"} + {isWorkflowModelLocked + ? lockedWorkflowModelLabel + : currentModel?.displayName || "自动选择"}
)}