Skip to content

Commit a76e6e9

Browse files
committed
cleanup
1 parent 030c61b commit a76e6e9

File tree

9 files changed

+171
-165
lines changed

9 files changed

+171
-165
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/tools/sub-block-renderer.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ interface ToolSubBlockRendererProps {
2121
}
2222
}
2323

24+
/**
25+
* SubBlock types whose store values are objects/arrays/non-strings.
26+
* tool.params stores strings (via JSON.stringify), so when syncing
27+
* back to the store we parse them to restore the native shape.
28+
*/
29+
const OBJECT_SUBBLOCK_TYPES = new Set(['file-upload', 'table', 'grouped-checkbox-list'])
30+
2431
/**
2532
* Bridges the subblock store with StoredTool.params via a synthetic store key,
2633
* then delegates all rendering to SubBlock for full parity.
27-
*
28-
* Two effects handle bidirectional sync:
29-
* - tool.params → store (external changes)
30-
* - store → tool.params (user interaction)
3134
*/
3235
export function ToolSubBlockRenderer({
3336
blockId,
@@ -44,29 +47,36 @@ export function ToolSubBlockRenderer({
4447
const [storeValue, setStoreValue] = useSubBlockValue(blockId, syntheticId)
4548

4649
const toolParamValue = toolParams?.[effectiveParamId] ?? ''
50+
const isObjectType = OBJECT_SUBBLOCK_TYPES.has(subBlock.type)
4751

48-
/** Tracks the last value we pushed to the store from tool.params to avoid echo loops */
4952
const lastPushedToStoreRef = useRef<string | null>(null)
50-
/** Tracks the last value we synced back to tool.params from the store */
5153
const lastPushedToParamsRef = useRef<string | null>(null)
5254

53-
// Sync tool.params → store: push when the prop value changes (including first mount)
5455
useEffect(() => {
5556
if (!toolParamValue && lastPushedToStoreRef.current === null) {
56-
// Skip initializing the store with an empty value on first mount —
57-
// let the SubBlock component use its own default.
5857
lastPushedToStoreRef.current = toolParamValue
5958
lastPushedToParamsRef.current = toolParamValue
6059
return
6160
}
6261
if (toolParamValue !== lastPushedToStoreRef.current) {
6362
lastPushedToStoreRef.current = toolParamValue
6463
lastPushedToParamsRef.current = toolParamValue
64+
65+
if (isObjectType && typeof toolParamValue === 'string' && toolParamValue) {
66+
try {
67+
const parsed = JSON.parse(toolParamValue)
68+
if (typeof parsed === 'object' && parsed !== null) {
69+
setStoreValue(parsed)
70+
return
71+
}
72+
} catch {
73+
// Not valid JSON — fall through to set as string
74+
}
75+
}
6576
setStoreValue(toolParamValue)
6677
}
67-
}, [toolParamValue, setStoreValue])
78+
}, [toolParamValue, setStoreValue, isObjectType])
6879

69-
// Sync store → tool.params: push when the user changes the value via SubBlock
7080
useEffect(() => {
7181
if (storeValue == null) return
7282
const stringValue = typeof storeValue === 'string' ? storeValue : JSON.stringify(storeValue)
@@ -77,7 +87,6 @@ export function ToolSubBlockRenderer({
7787
}
7888
}, [storeValue, toolIndex, effectiveParamId, onParamChange])
7989

80-
// Suppress SubBlock's "*" required indicator when the LLM can fill the param
8190
const visibility = subBlock.paramVisibility ?? 'user-or-llm'
8291
const isOptionalForUser = visibility !== 'user-only'
8392

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ import {
9797

9898
const logger = createLogger('ToolInput')
9999

100+
/**
101+
* Extracts canonical mode overrides scoped to a specific tool type.
102+
* Canonical modes are stored with `{blockType}:{canonicalId}` keys to prevent
103+
* cross-tool collisions when multiple tools share the same canonicalParamId.
104+
*/
105+
function scopeCanonicalOverrides(
106+
overrides: CanonicalModeOverrides | undefined,
107+
blockType: string | undefined
108+
): CanonicalModeOverrides | undefined {
109+
if (!overrides || !blockType) return undefined
110+
const prefix = `${blockType}:`
111+
let scoped: CanonicalModeOverrides | undefined
112+
for (const [key, val] of Object.entries(overrides)) {
113+
if (key.startsWith(prefix) && val) {
114+
if (!scoped) scoped = {}
115+
scoped[key.slice(prefix.length)] = val
116+
}
117+
}
118+
return scoped
119+
}
120+
100121
/**
101122
* Renders the input for workflow_executor's inputMapping parameter.
102123
* This is a special case that doesn't map to any SubBlockConfig, so it's kept here.
@@ -498,7 +519,6 @@ export const ToolInput = memo(function ToolInput({
498519
const openSettingsModal = useSettingsModalStore((state) => state.openModal)
499520
const mcpDataLoading = mcpLoading || mcpServersLoading
500521

501-
// Fetch workflows for the Workflows section in the dropdown
502522
const { data: workflowsList = [] } = useWorkflows(workspaceId, { syncRegistry: false })
503523
const availableWorkflows = useMemo(
504524
() => workflowsList.filter((w) => w.id !== workflowId),
@@ -1208,7 +1228,6 @@ export const ToolInput = memo(function ToolInput({
12081228
const toolGroups = useMemo((): ComboboxOptionGroup[] => {
12091229
const groups: ComboboxOptionGroup[] = []
12101230

1211-
// Actions group (no section header)
12121231
const actionItems: ComboboxOption[] = []
12131232
if (!permissionConfig.disableCustomTools) {
12141233
actionItems.push({
@@ -1238,7 +1257,6 @@ export const ToolInput = memo(function ToolInput({
12381257
groups.push({ items: actionItems })
12391258
}
12401259

1241-
// Custom Tools section
12421260
if (!permissionConfig.disableCustomTools && customTools.length > 0) {
12431261
groups.push({
12441262
section: 'Custom Tools',
@@ -1268,7 +1286,6 @@ export const ToolInput = memo(function ToolInput({
12681286
})
12691287
}
12701288

1271-
// MCP Tools section
12721289
if (!permissionConfig.disableMcpTools && availableMcpTools.length > 0) {
12731290
groups.push({
12741291
section: 'MCP Tools',
@@ -1306,11 +1323,9 @@ export const ToolInput = memo(function ToolInput({
13061323
})
13071324
}
13081325

1309-
// Split tool blocks into built-in tools and integrations
13101326
const builtInTools = toolBlocks.filter((block) => BUILT_IN_TOOL_TYPES.has(block.type))
13111327
const integrations = toolBlocks.filter((block) => !BUILT_IN_TOOL_TYPES.has(block.type))
13121328

1313-
// Built-in Tools section
13141329
if (builtInTools.length > 0) {
13151330
groups.push({
13161331
section: 'Built-in Tools',
@@ -1328,7 +1343,6 @@ export const ToolInput = memo(function ToolInput({
13281343
})
13291344
}
13301345

1331-
// Integrations section
13321346
if (integrations.length > 0) {
13331347
groups.push({
13341348
section: 'Integrations',
@@ -1410,7 +1424,6 @@ export const ToolInput = memo(function ToolInput({
14101424

14111425
return (
14121426
<div className='w-full space-y-[8px]'>
1413-
{/* Add Tool Combobox - always at top */}
14141427
<Combobox
14151428
options={[]}
14161429
groups={toolGroups}
@@ -1423,10 +1436,8 @@ export const ToolInput = memo(function ToolInput({
14231436
onOpenChange={setOpen}
14241437
/>
14251438

1426-
{/* Selected Tools List */}
14271439
{selectedTools.length > 0 &&
14281440
selectedTools.map((tool, toolIndex) => {
1429-
// Handle custom tools, MCP tools, and workflow tools differently
14301441
const isCustomTool = tool.type === 'custom-tool'
14311442
const isMcpTool = tool.type === 'mcp'
14321443
const isWorkflowTool = tool.type === 'workflow'
@@ -1435,13 +1446,11 @@ export const ToolInput = memo(function ToolInput({
14351446
? toolBlocks.find((block) => block.type === tool.type)
14361447
: null
14371448

1438-
// Get the current tool ID (may change based on operation)
14391449
const currentToolId =
14401450
!isCustomTool && !isMcpTool
14411451
? getToolIdForOperation(tool.type, tool.operation) || tool.toolId || ''
14421452
: tool.toolId || ''
14431453

1444-
// Get tool parameters using the new utility with block type for UI components
14451454
const toolParams =
14461455
!isCustomTool && !isMcpTool && currentToolId
14471456
? getToolParametersConfig(currentToolId, tool.type, {
@@ -1450,7 +1459,8 @@ export const ToolInput = memo(function ToolInput({
14501459
})
14511460
: null
14521461

1453-
// Get subblocks for tool-input (primary source for registry tools)
1462+
const toolScopedOverrides = scopeCanonicalOverrides(canonicalModeOverrides, tool.type)
1463+
14541464
const subBlocksResult: SubBlocksForToolInput | null =
14551465
!isCustomTool && !isMcpTool && currentToolId
14561466
? getSubBlocksForToolInput(
@@ -1460,16 +1470,14 @@ export const ToolInput = memo(function ToolInput({
14601470
operation: tool.operation,
14611471
...tool.params,
14621472
},
1463-
canonicalModeOverrides
1473+
toolScopedOverrides
14641474
)
14651475
: null
14661476

1467-
// Build canonical index for proper dependency resolution
14681477
const toolCanonicalIndex: CanonicalIndex | null = toolBlock?.subBlocks
14691478
? buildCanonicalIndex(toolBlock.subBlocks)
14701479
: null
14711480

1472-
// Build preview context with canonical resolution
14731481
const toolContextValues = toolCanonicalIndex
14741482
? buildPreviewContextValues(tool.params || {}, {
14751483
blockType: tool.type,
@@ -1479,12 +1487,10 @@ export const ToolInput = memo(function ToolInput({
14791487
})
14801488
: tool.params || {}
14811489

1482-
// For custom tools, resolve from reference (new format) or use inline (legacy)
14831490
const resolvedCustomTool = isCustomTool
14841491
? resolveCustomToolFromReference(tool, customTools)
14851492
: null
14861493

1487-
// Derive title and schema from resolved tool or inline data
14881494
const customToolTitle = isCustomTool
14891495
? tool.title || resolvedCustomTool?.title || 'Unknown Tool'
14901496
: null
@@ -1503,8 +1509,6 @@ export const ToolInput = memo(function ToolInput({
15031509
)
15041510
: []
15051511

1506-
// For MCP tools, extract parameters from input schema
1507-
// Use cached schema from tool object if available, otherwise fetch from mcpTools
15081512
const mcpTool = isMcpTool ? mcpTools.find((t) => t.id === tool.toolId) : null
15091513
const mcpToolSchema = isMcpTool ? tool.schema || mcpTool?.inputSchema : null
15101514
const mcpToolParams =
@@ -1521,8 +1525,6 @@ export const ToolInput = memo(function ToolInput({
15211525
)
15221526
: []
15231527

1524-
// Get all parameters to display
1525-
// For registry tools with subBlocks, use the subblock-first approach
15261528
const useSubBlocks = !isCustomTool && !isMcpTool && subBlocksResult?.subBlocks?.length
15271529
const displayParams: ToolParameterConfig[] = isCustomTool
15281530
? customToolParams
@@ -1533,20 +1535,17 @@ export const ToolInput = memo(function ToolInput({
15331535
? subBlocksResult!.subBlocks
15341536
: []
15351537

1536-
// Check if tool requires OAuth
15371538
const requiresOAuth =
15381539
!isCustomTool && !isMcpTool && currentToolId && toolRequiresOAuth(currentToolId)
15391540
const oauthConfig =
15401541
!isCustomTool && !isMcpTool && currentToolId ? getToolOAuthConfig(currentToolId) : null
15411542

1542-
// Determine if tool has expandable body content
15431543
const hasOperations = !isCustomTool && !isMcpTool && hasMultipleOperations(tool.type)
15441544
const hasParams = useSubBlocks
15451545
? displaySubBlocks.length > 0
15461546
: displayParams.filter((param) => evaluateParameterCondition(param, tool)).length > 0
15471547
const hasToolBody = hasOperations || (requiresOAuth && oauthConfig) || hasParams
15481548

1549-
// Only show expansion if tool has body content
15501549
const isExpandedForDisplay = hasToolBody
15511550
? isPreview
15521551
? (previewExpanded[toolIndex] ?? !!tool.isExpanded)
@@ -1717,7 +1716,6 @@ export const ToolInput = memo(function ToolInput({
17171716

17181717
{!isCustomTool && isExpandedForDisplay && (
17191718
<div className='flex flex-col gap-[10px] overflow-visible rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-[8px] py-[8px]'>
1720-
{/* Operation dropdown for tools with multiple operations */}
17211719
{(() => {
17221720
const hasOperations = hasMultipleOperations(tool.type)
17231721
const operationOptions = hasOperations ? getOperationOptions(tool.type) : []
@@ -1743,7 +1741,6 @@ export const ToolInput = memo(function ToolInput({
17431741
) : null
17441742
})()}
17451743

1746-
{/* OAuth credential selector if required */}
17471744
{requiresOAuth && oauthConfig && (
17481745
<div className='relative min-w-0 space-y-[6px]'>
17491746
<div className='font-medium text-[13px] text-[var(--text-primary)]'>
@@ -1768,20 +1765,28 @@ export const ToolInput = memo(function ToolInput({
17681765
</div>
17691766
)}
17701767

1771-
{/* Tool parameters */}
17721768
{(() => {
17731769
const renderedElements: React.ReactNode[] = []
17741770

1775-
// SubBlock-first rendering for registry tools
17761771
if (useSubBlocks && displaySubBlocks.length > 0) {
17771772
const coveredParamIds = new Set(
1778-
displaySubBlocks.map((sb) => sb.canonicalParamId || sb.id)
1773+
displaySubBlocks.flatMap((sb) => {
1774+
const ids = [sb.id]
1775+
if (sb.canonicalParamId) ids.push(sb.canonicalParamId)
1776+
const cId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
1777+
if (cId) {
1778+
const group = toolCanonicalIndex?.groupsById[cId]
1779+
if (group) {
1780+
if (group.basicId) ids.push(group.basicId)
1781+
ids.push(...group.advancedIds)
1782+
}
1783+
}
1784+
return ids
1785+
})
17791786
)
17801787

17811788
displaySubBlocks.forEach((sb) => {
1782-
const effectiveParamId = sb.canonicalParamId || sb.id
1783-
1784-
// Compute canonical toggle for basic/advanced mode switching
1789+
const effectiveParamId = sb.id
17851790
const canonicalId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
17861791
const canonicalGroup = canonicalId
17871792
? toolCanonicalIndex?.groupsById[canonicalId]
@@ -1792,7 +1797,7 @@ export const ToolInput = memo(function ToolInput({
17921797
? resolveCanonicalMode(
17931798
canonicalGroup,
17941799
{ operation: tool.operation, ...tool.params },
1795-
canonicalModeOverrides
1800+
toolScopedOverrides
17961801
)
17971802
: undefined
17981803

@@ -1803,12 +1808,15 @@ export const ToolInput = memo(function ToolInput({
18031808
onToggle: () => {
18041809
const nextMode =
18051810
canonicalMode === 'advanced' ? 'basic' : 'advanced'
1806-
collaborativeSetBlockCanonicalMode(blockId, canonicalId, nextMode)
1811+
collaborativeSetBlockCanonicalMode(
1812+
blockId,
1813+
`${tool.type}:${canonicalId}`,
1814+
nextMode
1815+
)
18071816
},
18081817
}
18091818
: undefined
18101819

1811-
// Ensure title is present for SubBlock's label rendering
18121820
const sbWithTitle = sb.title
18131821
? sb
18141822
: { ...sb, title: formatParameterLabel(effectiveParamId) }
@@ -1829,8 +1837,6 @@ export const ToolInput = memo(function ToolInput({
18291837
)
18301838
})
18311839

1832-
// Render remaining tool params not covered by subblocks
1833-
// (e.g. inputMapping for workflow tools with custom UI)
18341840
const uncoveredParams = displayParams.filter(
18351841
(param) =>
18361842
!coveredParamIds.has(param.id) && evaluateParameterCondition(param, tool)
@@ -1867,8 +1873,6 @@ export const ToolInput = memo(function ToolInput({
18671873
)
18681874
}
18691875

1870-
// Fallback: legacy ToolParameterConfig-based rendering
1871-
// Used for custom tools, MCP tools, and registry tools without subBlocks
18721876
const filteredParams = displayParams.filter((param) =>
18731877
evaluateParameterCondition(param, tool)
18741878
)
@@ -1907,7 +1911,6 @@ export const ToolInput = memo(function ToolInput({
19071911
)
19081912
})}
19091913

1910-
{/* Custom Tool Modal */}
19111914
<CustomToolModal
19121915
open={customToolModalOpen}
19131916
onOpenChange={(open) => {
@@ -1921,11 +1924,9 @@ export const ToolInput = memo(function ToolInput({
19211924
editingToolIndex !== null && selectedTools[editingToolIndex]?.type === 'custom-tool'
19221925
? (() => {
19231926
const storedTool = selectedTools[editingToolIndex]
1924-
// Resolve the full tool definition from reference or inline
19251927
const resolved = resolveCustomToolFromReference(storedTool, customTools)
19261928

19271929
if (resolved) {
1928-
// Find the database ID
19291930
const dbTool = storedTool.customToolId
19301931
? customTools.find((t) => t.id === storedTool.customToolId)
19311932
: customTools.find(
@@ -1939,7 +1940,6 @@ export const ToolInput = memo(function ToolInput({
19391940
}
19401941
}
19411942

1942-
// Fallback to inline definition (legacy format)
19431943
return {
19441944
id: customTools.find(
19451945
(tool) => tool.schema?.function?.name === storedTool.schema?.function?.name

0 commit comments

Comments
 (0)