From f080885d2cb5c39af9942247369b6b4334b39e05 Mon Sep 17 00:00:00 2001 From: Mahdi Bagheri Date: Fri, 8 Aug 2025 01:01:26 +0000 Subject: [PATCH] feat(TagImprovement): Improved tag dropdown and outputselect --- .../output-select/output-select.tsx | 79 +++++++++++ apps/sim/blocks/types.ts | 15 ++- apps/sim/components/ui/tag-dropdown.tsx | 124 ++++++++++++++++-- 3 files changed, 205 insertions(+), 13 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx index 184928788a..8994cde165 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx @@ -6,6 +6,7 @@ import { getBlock } from '@/blocks' import { useWorkflowDiffStore } from '@/stores/workflow-diff/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import { useWorkflowRegistry } from '@/stores/workflows/registry/store' interface OutputSelectProps { workflowId: string | null @@ -15,6 +16,78 @@ interface OutputSelectProps { placeholder?: string } +// Helper function to get all current values for a block +const getBlockCurrentValues = (blockId: string, workflowId: string): Record => { + const workflowValues = useSubBlockStore.getState().workflowValues[workflowId] + return workflowValues?.[blockId] || {} +} + +// Evaluate output condition similar to input condition evaluation +const evaluateOutputCondition = ( + condition: { + field: string + value: any + not?: boolean + and?: { field: string; value: any; not?: boolean } + } | undefined, + currentValues: Record +): boolean => { + if (!condition) return true + + // Get the field value + const fieldValue = currentValues[condition.field] + + // Check if the condition value is an array + const isValueMatch = Array.isArray(condition.value) + ? fieldValue != null && + (condition.not + ? !condition.value.includes(fieldValue) + : condition.value.includes(fieldValue)) + : condition.not + ? fieldValue !== condition.value + : fieldValue === condition.value + + // Check both conditions if 'and' is present + const isAndValueMatch = + !condition.and || + (() => { + const andFieldValue = currentValues[condition.and!.field] + return Array.isArray(condition.and!.value) + ? andFieldValue != null && + (condition.and!.not + ? !condition.and!.value.includes(andFieldValue) + : condition.and!.value.includes(andFieldValue)) + : condition.and!.not + ? andFieldValue !== condition.and!.value + : andFieldValue === condition.and!.value + })() + + return isValueMatch && isAndValueMatch +} + +// Filter outputs based on conditions +const filterOutputsByConditions = ( + outputs: Record, + currentValues: Record +): Record => { + const filteredOutputs: Record = {} + + for (const [key, value] of Object.entries(outputs)) { + let shouldInclude = true + + // Check if this output has a condition + if (typeof value === 'object' && value !== null && 'condition' in value) { + shouldInclude = evaluateOutputCondition(value.condition, currentValues) + } + + if (shouldInclude) { + filteredOutputs[key] = value + } + } + + return filteredOutputs +} + export function OutputSelect({ workflowId, selectedOutputs = [], @@ -105,6 +178,12 @@ export function OutputSelect({ outputsToProcess = blockConfig?.outputs || {} } + // Apply conditional filtering to outputs + if (workflowId && Object.keys(outputsToProcess).length > 0) { + const currentValues = getBlockCurrentValues(block.id, workflowId) + outputsToProcess = filterOutputsByConditions(outputsToProcess, currentValues) + } + // Add response outputs if (Object.keys(outputsToProcess).length > 0) { const addOutput = (path: string, outputObj: any, prefix = '') => { diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 8cd1391742..e1f1f5d7d3 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -78,7 +78,20 @@ export type BlockOutput = // Output field definition with optional description export type OutputFieldDefinition = | PrimitiveValueType - | { type: PrimitiveValueType; description?: string } + | { + type: PrimitiveValueType; + description?: string; + condition?: { + field: string + value: string | number | boolean | Array + not?: boolean + and?: { + field: string + value: string | number | boolean | Array | undefined + not?: boolean + } + } + } // Parameter validation rules export interface ParamConfig { diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index 12dbdd6a2e..77ea7708e5 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -46,11 +46,93 @@ export const checkTagTrigger = (text: string, cursorPosition: number): { show: b return { show: false } } +// Helper function to get all current values for a block +const getBlockCurrentValues = (blockId: string): Record => { + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) return {} + + const workflowValues = useSubBlockStore.getState().workflowValues[activeWorkflowId] + return workflowValues?.[blockId] || {} +} + +// Evaluate output condition similar to input condition evaluation +const evaluateOutputCondition = ( + condition: { + field: string + value: any + not?: boolean + and?: { field: string; value: any; not?: boolean } + } | undefined, + currentValues: Record +): boolean => { + if (!condition) return true + + // Get the field value + const fieldValue = currentValues[condition.field] + + // Check if the condition value is an array + const isValueMatch = Array.isArray(condition.value) + ? fieldValue != null && + (condition.not + ? !condition.value.includes(fieldValue) + : condition.value.includes(fieldValue)) + : condition.not + ? fieldValue !== condition.value + : fieldValue === condition.value + + // Check both conditions if 'and' is present + const isAndValueMatch = + !condition.and || + (() => { + const andFieldValue = currentValues[condition.and!.field] + return Array.isArray(condition.and!.value) + ? andFieldValue != null && + (condition.and!.not + ? !condition.and!.value.includes(andFieldValue) + : condition.and!.value.includes(andFieldValue)) + : condition.and!.not + ? andFieldValue !== condition.and!.value + : andFieldValue === condition.and!.value + })() + + return isValueMatch && isAndValueMatch +} + +// Filter outputs based on conditions +const filterOutputsByConditions = ( + outputs: Record, + currentValues: Record +): Record => { + const filteredOutputs: Record = {} + + for (const [key, value] of Object.entries(outputs)) { + let shouldInclude = true + + // Check if this output has a condition + if (typeof value === 'object' && value !== null && 'condition' in value) { + shouldInclude = evaluateOutputCondition(value.condition, currentValues) + } + + if (shouldInclude) { + filteredOutputs[key] = value + } + } + + return filteredOutputs +} + // Generate output paths from block configuration outputs -const generateOutputPaths = (outputs: Record, prefix = ''): string[] => { +const generateOutputPaths = ( + outputs: Record, + prefix = '', + currentValues?: Record +): string[] => { const paths: string[] = [] + + // Filter outputs by conditions if currentValues are provided + const outputsToProcess = currentValues ? filterOutputsByConditions(outputs, currentValues) : outputs - for (const [key, value] of Object.entries(outputs)) { + for (const [key, value] of Object.entries(outputsToProcess)) { const currentPath = prefix ? `${prefix}.${key}` : key if (typeof value === 'string') { @@ -63,7 +145,7 @@ const generateOutputPaths = (outputs: Record, prefix = ''): string[ paths.push(currentPath) } else { // Legacy nested object - recurse - const subPaths = generateOutputPaths(value, currentPath) + const subPaths = generateOutputPaths(value, currentPath, currentValues) paths.push(...subPaths) } } else { @@ -143,8 +225,11 @@ export const TagDropdown: React.FC = ({ const blockName = sourceBlock.name || sourceBlock.type const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(activeSourceBlockId) + // Generate output paths for the mock config - const outputPaths = generateOutputPaths(mockConfig.outputs) + const outputPaths = generateOutputPaths(mockConfig.outputs, '', currentValues) const blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) const blockTagGroups: BlockTagGroup[] = [ @@ -190,7 +275,9 @@ export const TagDropdown: React.FC = ({ ) } else { // Fallback to default evaluator outputs if no metrics are defined - const outputPaths = generateOutputPaths(blockConfig.outputs) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(activeSourceBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } } else if (responseFormat) { @@ -200,7 +287,9 @@ export const TagDropdown: React.FC = ({ blockTags = schemaFields.map((field) => `${normalizedBlockName}.${field.name}`) } else { // Fallback to default if schema extraction failed - const outputPaths = generateOutputPaths(blockConfig.outputs || {}) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(activeSourceBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs || {}, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } } else if (!blockConfig.outputs || Object.keys(blockConfig.outputs).length === 0) { @@ -240,7 +329,9 @@ export const TagDropdown: React.FC = ({ } } else { // Use default block outputs - const outputPaths = generateOutputPaths(blockConfig.outputs || {}) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(activeSourceBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs || {}, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } @@ -424,8 +515,11 @@ export const TagDropdown: React.FC = ({ const blockName = accessibleBlock.name || accessibleBlock.type const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(accessibleBlockId) + // Generate output paths for the mock config - const outputPaths = generateOutputPaths(mockConfig.outputs) + const outputPaths = generateOutputPaths(mockConfig.outputs, '', currentValues) const blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) blockTagGroups.push({ @@ -465,7 +559,9 @@ export const TagDropdown: React.FC = ({ ) } else { // Fallback to default evaluator outputs if no metrics are defined - const outputPaths = generateOutputPaths(blockConfig.outputs) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(accessibleBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } } else if (responseFormat) { @@ -475,7 +571,9 @@ export const TagDropdown: React.FC = ({ blockTags = schemaFields.map((field) => `${normalizedBlockName}.${field.name}`) } else { // Fallback to default if schema extraction failed - const outputPaths = generateOutputPaths(blockConfig.outputs || {}) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(accessibleBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs || {}, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } } else if (!blockConfig.outputs || Object.keys(blockConfig.outputs).length === 0) { @@ -515,7 +613,9 @@ export const TagDropdown: React.FC = ({ } } else { // Use default block outputs - const outputPaths = generateOutputPaths(blockConfig.outputs || {}) + // Get current values for conditional filtering + const currentValues = getBlockCurrentValues(accessibleBlockId) + const outputPaths = generateOutputPaths(blockConfig.outputs || {}, '', currentValues) blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } @@ -892,4 +992,4 @@ export const TagDropdown: React.FC = ({ ) -} +} \ No newline at end of file