diff --git a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx index 5fc7219b8..9022fc6d1 100644 --- a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx +++ b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx @@ -234,6 +234,27 @@ function collectWorkflowOptionRelations( }); } +function collectWorkflowOptionRelationsFromSteps( + steps: AgentWorkflowStepUiConfig[] +): Array<{ + key: string; + label: string; + depth: number; + stepKey: string; + stepTitle: string; +}> { + return flattenAgentWorkflowSteps(steps).flatMap(({ step }) => + collectWorkflowOptionRelations(step.options || []).map((item) => ({ + ...item, + label: `${step.title} / ${item.label}`, + stepKey: step.key, + stepTitle: step.title, + })) + ); +} + +type WorkflowOptionRelation = ReturnType[number]; + function updateWorkflowOptionTree( options: AgentWorkflowOptionUiConfig[], optionKey: string, @@ -1234,20 +1255,12 @@ export function AgentWorkflowSettingsClient({ (left, right) => left.sortOrder - right.sortOrder ); const canAddChild = depth < AGENT_WORKFLOW_MAX_OPTION_DEPTH; - const optionRelations = collectWorkflowOptionRelations(step.options || []); + const optionRelations = collectWorkflowOptionRelationsFromSteps(config.steps || []); const optionLookup = new Map(optionRelations.map((item) => [item.key, item] as const)); const blockedCandidates = optionRelations.filter((item) => item.key !== option.key); const selectedBlockedItems = (option.blockedOptionKeys || []) .map((blockedKey) => optionLookup.get(blockedKey)) - .filter( - ( - item - ): item is { - key: string; - label: string; - depth: number; - } => Boolean(item) - ); + .filter((item): item is WorkflowOptionRelation => Boolean(item)); return (
1 && "pl-4 sm:pl-6")}> @@ -1367,7 +1380,7 @@ export function AgentWorkflowSettingsClient({

- 选中当前卡片后,下方指定的其它卡片会在前台变灰并不可选。 + 选中当前卡片后,下方指定的其它卡片会在前台变灰并不可选。可跨所有流程节点,并覆盖各节点下的所有层级子卡片。

@@ -1453,7 +1466,7 @@ export function AgentWorkflowSettingsClient({ }) ) : (
- 当前步骤下没有其他可限制卡片 + 当前工作流下没有其他可限制卡片
)}
diff --git a/components/agents/AgentWorkflowPanelDarkV2.tsx b/components/agents/AgentWorkflowPanelDarkV2.tsx index d12ab6721..99fc21a06 100644 --- a/components/agents/AgentWorkflowPanelDarkV2.tsx +++ b/components/agents/AgentWorkflowPanelDarkV2.tsx @@ -12,7 +12,7 @@ import { defaultAgentWorkflowUiConfig, flattenAgentWorkflowSteps, flattenVisibleAgentWorkflowSteps, - getAgentWorkflowBlockedOptionKeys, + getAgentWorkflowBlockedOptionKeysForSelections, getAgentWorkflowBrandStoreEntries, getAgentWorkflowPriceBandOptions, getAgentWorkflowSelectedOptionPath, @@ -405,18 +405,16 @@ export function AgentWorkflowPanelDarkV2({ ]); return Object.fromEntries(entries) as Record; }, [resolvedConfig.steps, selections]); - const blockedOptionKeyMap = useMemo(() => { - const entries = flattenAgentWorkflowSteps(resolvedConfig.steps).map(({ step }) => [ - step.key, + const blockedOptionKeys = useMemo( + () => new Set( - getAgentWorkflowBlockedOptionKeys({ - step, - selectionValue: selections[step.key], + getAgentWorkflowBlockedOptionKeysForSelections({ + config: resolvedConfig, + selections, }) ), - ] as const); - return new Map(entries); - }, [resolvedConfig.steps, selections]); + [resolvedConfig, selections] + ); const [autoAdvanceNotice, setAutoAdvanceNotice] = useState<{ completedStepTitle: string; completedSummary: string; @@ -571,7 +569,6 @@ export function AgentWorkflowPanelDarkV2({ const currentSelectedKey = selectedPathKeys[depth - 1] || ""; const currentSelectedOption = options.find((option) => option.key === currentSelectedKey) || null; - const blockedKeys = blockedOptionKeyMap.get(step.key) || new Set(); const visibleChildren = getEnabledAgentWorkflowOptionTree( currentSelectedOption?.children || [] ); @@ -593,7 +590,7 @@ export function AgentWorkflowPanelDarkV2({
{options.map((option, optionIndex) => { const isDisabled = - currentSelectedKey !== option.key && blockedKeys.has(option.key); + currentSelectedKey !== option.key && blockedOptionKeys.has(option.key); return ( {activeEmbeddedOptionLevel.options.map((option, optionIndex) => { const currentSelectedKey = activeEmbeddedOptionLevel.selectedKey; - const blockedKeys = - blockedOptionKeyMap.get(activeEmbeddedStepEntry.step.key) || - new Set(); const isDisabled = - currentSelectedKey !== option.key && blockedKeys.has(option.key); + currentSelectedKey !== option.key && + blockedOptionKeys.has(option.key); const nextChildren = getEnabledAgentWorkflowOptionTree( option.children || [] ); diff --git a/lib/agents/workflow-ui-config.ts b/lib/agents/workflow-ui-config.ts index 90f30ff30..77d86dfd8 100644 --- a/lib/agents/workflow-ui-config.ts +++ b/lib/agents/workflow-ui-config.ts @@ -1,6 +1,7 @@ import type { AgentWorkflowBrandStoreEntry, AgentWorkflowOptionUiConfig, + AgentWorkflowStepKey, AgentWorkflowStepUiConfig, AgentWorkflowUiConfig, } from "@/types/agent"; @@ -290,6 +291,16 @@ export function countAgentWorkflowOptionTree( return flattenAgentWorkflowOptionTree(options).length; } +function collectWorkflowOptionKeySetFromSteps( + steps: AgentWorkflowStepUiConfig[] +): Set { + return new Set( + flattenAgentWorkflowSteps(steps).flatMap(({ step }) => + flattenAgentWorkflowOptionTree(step.options || []).map(({ option }) => option.key) + ) + ); +} + export function normalizeAgentWorkflowOptionOrdering( options: AgentWorkflowOptionUiConfig[] ): AgentWorkflowOptionUiConfig[] { @@ -385,6 +396,17 @@ function sanitizeBlockedOptionTree( })); } +function sanitizeBlockedWorkflowStepTree( + steps: AgentWorkflowStepUiConfig[], + validKeys: Set +): AgentWorkflowStepUiConfig[] { + return steps.map((step) => ({ + ...step, + options: sanitizeBlockedOptionTree(step.options || [], validKeys), + children: sanitizeBlockedWorkflowStepTree(step.children || [], validKeys), + })); +} + export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiConfig { const raw = value && typeof value === "object" && !Array.isArray(value) @@ -569,8 +591,6 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC normalizeOption(option, optionIndex, stepKey, usedOptionKeys, defaultOptions, 1) ) .sort((left, right) => left.sortOrder - right.sortOrder); - const sanitizedOptions = sanitizeBlockedOptionTree(options); - const hasRawChildren = Array.isArray(stepRecord?.children); const rawChildren = hasRawChildren ? (stepRecord?.children as unknown[]) : []; const defaultChildren = @@ -588,7 +608,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC description: normalizeText(stepRecord?.description, 300) || fallbackDescription, enabled: normalizeBoolean(stepRecord?.enabled, defaultStep?.enabled ?? true), sortOrder: normalizeNumber(stepRecord?.sortOrder, defaultStep?.sortOrder ?? stepIndex), - options: sanitizedOptions, + options, children: depth < AGENT_WORKFLOW_MAX_TREE_DEPTH ? childSource @@ -601,6 +621,8 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC const normalizedSteps = sourceSteps .map((item, stepIndex) => normalizeStep(item, stepIndex, 1)) .sort((left, right) => left.sortOrder - right.sortOrder); + const validOptionKeys = collectWorkflowOptionKeySetFromSteps(normalizedSteps); + const sanitizedSteps = sanitizeBlockedWorkflowStepTree(normalizedSteps, validOptionKeys); return { inputOptions: { @@ -617,7 +639,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC priceBands: normalizedPriceBands, brandStoreEntries: fallbackBrandStoreEntries, }, - steps: normalizedSteps, + steps: sanitizedSteps, }; } @@ -691,6 +713,28 @@ export function getAgentWorkflowBlockedOptionKeys(params: { ); } +export function getAgentWorkflowBlockedOptionKeysForSelections(params: { + config?: AgentWorkflowUiConfig | null; + selections: Partial>; +}) { + const config = params.config || defaultAgentWorkflowUiConfig; + const visibleSteps = flattenVisibleAgentWorkflowSteps( + getEnabledAgentWorkflowStepTree(Array.isArray(config.steps) ? config.steps : []), + params.selections + ).map(({ step }) => step); + + return Array.from( + new Set( + visibleSteps.flatMap((step) => + getAgentWorkflowBlockedOptionKeys({ + step, + selectionValue: params.selections[step.key], + }) + ) + ) + ); +} + export function isAgentWorkflowStepSelectionComplete(params: { step: AgentWorkflowStepUiConfig; selectionValue?: string | null;