Skip to content

Commit ce1fe94

Browse files
fix(chat-deploy): restore output picker interactions
1 parent 65a58a8 commit ce1fe94

3 files changed

Lines changed: 132 additions & 74 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx‎

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,8 @@ const { outputMenuState } = vi.hoisted(() => ({
99
outputMenuState: { includeNestedWorkflow: true },
1010
}))
1111

12-
vi.mock('@sim/emcn', () => ({
13-
cn: (...values: unknown[]) => values.flat().filter(Boolean).join(' '),
14-
Combobox: ({
15-
groups,
16-
multiSelectValues = [],
17-
onMultiSelectChange,
18-
}: {
12+
vi.mock('@sim/emcn', () => {
13+
interface MockComboboxProps {
1914
groups: Array<{
2015
section?: string
2116
sectionElement?: ReactNode
@@ -29,7 +24,13 @@ vi.mock('@sim/emcn', () => ({
2924
}>
3025
multiSelectValues?: string[]
3126
onMultiSelectChange?: (values: string[]) => void
32-
}) => (
27+
}
28+
29+
const MockCombobox = ({
30+
groups,
31+
multiSelectValues = [],
32+
onMultiSelectChange,
33+
}: MockComboboxProps) => (
3334
<div>
3435
{groups.map((group, groupIndex) => (
3536
<div key={group.section ?? groupIndex}>
@@ -59,31 +60,18 @@ vi.mock('@sim/emcn', () => ({
5960
</div>
6061
))}
6162
</div>
62-
),
63-
ChipCombobox: ({
64-
groups,
65-
multiSelectValues,
66-
onMultiSelectChange,
67-
}: {
68-
groups: Array<{ section?: string; items: Array<{ label: string; value: string }> }>
69-
multiSelectValues?: string[]
70-
onMultiSelectChange?: (values: string[]) => void
71-
}) => (
72-
<div data-chip-combobox>
73-
{groups.flatMap((group) =>
74-
group.items.map((option) => (
75-
<button
76-
key={option.value}
77-
type='button'
78-
onClick={() => onMultiSelectChange?.([...(multiSelectValues ?? []), option.value])}
79-
>
80-
{option.label}
81-
</button>
82-
))
83-
)}
84-
</div>
85-
),
86-
}))
63+
)
64+
65+
return {
66+
cn: (...values: unknown[]) => values.flat().filter(Boolean).join(' '),
67+
Combobox: MockCombobox,
68+
ChipCombobox: (props: MockComboboxProps) => (
69+
<div data-chip-combobox>
70+
<MockCombobox {...props} />
71+
</div>
72+
),
73+
}
74+
})
8775

8876
vi.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector }))
8977

@@ -186,29 +174,32 @@ function outputSelect(
186174
workflowId: string,
187175
selectedOutputs: string[],
188176
onOutputSelect: (outputIds: string[]) => void,
189-
valueMode: 'id' | 'label' | 'public' = 'id'
177+
valueMode: 'id' | 'label' | 'public' = 'id',
178+
size: 'sm' | 'md' = 'sm'
190179
) {
191180
return (
192181
<OutputSelect
193182
workflowId={workflowId}
194183
selectedOutputs={selectedOutputs}
195184
onOutputSelect={onOutputSelect}
196185
valueMode={valueMode}
186+
size={size}
197187
/>
198188
)
199189
}
200190

201191
function renderOutputSelect(
202192
selectedOutputs: string[],
203193
onOutputSelect = vi.fn(),
204-
valueMode: 'id' | 'label' | 'public' = 'id'
194+
valueMode: 'id' | 'label' | 'public' = 'id',
195+
size: 'sm' | 'md' = 'sm'
205196
) {
206197
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
207198
container = document.createElement('div')
208199
document.body.appendChild(container)
209200
root = createRoot(container)
210201
act(() => {
211-
root?.render(outputSelect('root', selectedOutputs, onOutputSelect, valueMode))
202+
root?.render(outputSelect('root', selectedOutputs, onOutputSelect, valueMode, size))
212203
})
213204
return onOutputSelect
214205
}
@@ -263,6 +254,35 @@ describe('OutputSelect nested workflow menu', () => {
263254
expect(onOutputSelect).toHaveBeenCalledWith(['child-workflow.agent_answer'])
264255
})
265256

257+
it('keeps every selected output at the top and deselects nested outputs from there', () => {
258+
const onOutputSelect = renderOutputSelect(['child-workflow.agent_answer'])
259+
260+
const sections = [...document.querySelectorAll('[data-section]')]
261+
expect(sections[0]?.textContent).toBe('Selected')
262+
expect(document.body.textContent).toContain('Research / Writer / answer')
263+
264+
clickOption('Research / Writer / answer')
265+
expect(onOutputSelect).toHaveBeenCalledWith([])
266+
})
267+
268+
it('preserves existing selections when choosing a nested output', () => {
269+
const onOutputSelect = renderOutputSelect(['summary_content'])
270+
271+
clickOption('Outputs')
272+
clickOption('answer')
273+
274+
expect(onOutputSelect).toHaveBeenCalledWith(['summary_content', 'child-workflow.agent_answer'])
275+
})
276+
277+
it('selects outputs from the medium chat deployment picker', () => {
278+
const onOutputSelect = renderOutputSelect([], vi.fn(), 'id', 'md')
279+
280+
expect(document.querySelector('[data-chip-combobox]')).not.toBeNull()
281+
clickOption('content')
282+
283+
expect(onOutputSelect).toHaveBeenCalledWith(['summary_content'])
284+
})
285+
266286
it('emits public dot selectors for trigger authoring', () => {
267287
const onOutputSelect = renderOutputSelect([], vi.fn(), 'public')
268288

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx‎

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,23 +250,44 @@ function OutputSelectMenu({
250250
const [menuPath, setMenuPath] = useState<string[]>([])
251251
const activeMenuNode = resolveOutputMenuNode(outputMenu, menuPath)
252252

253-
const validOutputCount = selectedOutputs.filter((val) =>
254-
workflowOutputs.some((output) => output.id === val || output.label === val)
255-
).length
253+
const selectedOutputOptions = selectedOutputs
254+
.map((selectedValue) =>
255+
workflowOutputs.find(
256+
(output) =>
257+
output.id === selectedValue ||
258+
output.label === selectedValue ||
259+
getOutputValue(output, valueMode) === selectedValue
260+
)
261+
)
262+
.filter((output): output is WorkflowOutputOption => output !== undefined)
263+
const validOutputCount = selectedOutputOptions.length
256264
let selectedDisplayText = placeholder
257265
if (validOutputCount === 1) {
258266
selectedDisplayText = '1 output'
259267
} else if (validOutputCount > 1) {
260268
selectedDisplayText = `${validOutputCount} outputs`
261269
}
262270

263-
const normalizedSelectedValues = selectedOutputs
264-
.map((val) => {
265-
const output = workflowOutputs.find((item) => item.id === val || item.label === val)
266-
if (!output) return null
267-
return getOutputValue(output, valueMode)
268-
})
269-
.filter((value): value is string => value !== null)
271+
const normalizedSelectedValues = selectedOutputOptions.map((output) =>
272+
getOutputValue(output, valueMode)
273+
)
274+
const selectedValueSet = new Set(normalizedSelectedValues)
275+
276+
const outputOption = (output: WorkflowOutputOption, label = output.path): ComboboxOption => {
277+
const value = getOutputValue(output, valueMode)
278+
return {
279+
label,
280+
value,
281+
onSelect: () => {
282+
onOutputSelect(
283+
selectedValueSet.has(value)
284+
? normalizedSelectedValues.filter((selectedValue) => selectedValue !== value)
285+
: [...normalizedSelectedValues, value]
286+
)
287+
},
288+
keepOpen: true,
289+
}
290+
}
270291

271292
const folderOption = (node: WorkflowOutputMenuNode): ComboboxOption => ({
272293
label: 'Outputs',
@@ -288,15 +309,14 @@ function OutputSelectMenu({
288309
</div>
289310
),
290311
items: [
291-
...node.outputs.map((output) => ({
292-
label: output.path,
293-
value: getOutputValue(output, valueMode),
294-
})),
312+
...node.outputs
313+
.filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode)))
314+
.map((output) => outputOption(output)),
295315
...(node.children.length > 0 ? [folderOption(node)] : []),
296316
],
297317
})
298318

299-
const comboboxGroups: ComboboxOptionGroup[] = activeMenuNode
319+
const menuGroups: ComboboxOptionGroup[] = activeMenuNode
300320
? [
301321
{
302322
section: activeMenuNode.blockName,
@@ -313,6 +333,18 @@ function OutputSelectMenu({
313333
...activeMenuNode.children.map(outputGroup),
314334
]
315335
: outputMenu.map(outputGroup)
336+
const selectedGroup: ComboboxOptionGroup[] =
337+
selectedOutputOptions.length > 0
338+
? [
339+
{
340+
section: 'Selected',
341+
items: selectedOutputOptions.map((output) =>
342+
outputOption(output, `${output.groupLabel} / ${output.path}`)
343+
),
344+
},
345+
]
346+
: []
347+
const comboboxGroups = [...selectedGroup, ...menuGroups]
316348
const Trigger = size === 'md' ? ChipCombobox : Combobox
317349

318350
return (

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

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,29 +1252,6 @@ export const ToolInput = memo(function ToolInput({
12521252
<UnsupportedToolBadge message={UNSUPPORTED_MCP_TOOL_MESSAGE} />
12531253
) : undefined,
12541254
})
1255-
if (supportsAdvancedMcpServer) {
1256-
actionItems.push({
1257-
label: 'MCP Server (Advanced)',
1258-
value: 'action-mcp-server-advanced',
1259-
icon: Server,
1260-
onSelect: () => {
1261-
setStoreValue([
1262-
...selectedTools.map((tool) => ({ ...tool, isExpanded: false })),
1263-
{
1264-
type: MCP_SERVER_ADVANCED_TOOL_TYPE,
1265-
params: { serverId: '' },
1266-
isExpanded: true,
1267-
usageControl: 'auto',
1268-
},
1269-
])
1270-
setOpen(false)
1271-
},
1272-
disabled: isPreview || disabled || mcpUnsupported,
1273-
suffixElement: mcpUnsupported ? (
1274-
<UnsupportedToolBadge message={UNSUPPORTED_MCP_TOOL_MESSAGE} />
1275-
) : undefined,
1276-
})
1277-
}
12781255
}
12791256
if (actionItems.length > 0) {
12801257
groups.push({ items: actionItems })
@@ -1412,6 +1389,35 @@ export const ToolInput = memo(function ToolInput({
14121389
})
14131390
}
14141391

1392+
if (!permissionConfig.disableMcpTools && supportsAdvancedMcpServer) {
1393+
groups.push({
1394+
section: 'Advanced',
1395+
items: [
1396+
{
1397+
label: 'MCP Server (Advanced)',
1398+
value: 'action-mcp-server-advanced',
1399+
icon: Server,
1400+
onSelect: () => {
1401+
setStoreValue([
1402+
...selectedTools.map((tool) => ({ ...tool, isExpanded: false })),
1403+
{
1404+
type: MCP_SERVER_ADVANCED_TOOL_TYPE,
1405+
params: { serverId: '' },
1406+
isExpanded: true,
1407+
usageControl: 'auto',
1408+
},
1409+
])
1410+
setOpen(false)
1411+
},
1412+
disabled: isPreview || disabled || mcpUnsupported,
1413+
suffixElement: mcpUnsupported ? (
1414+
<UnsupportedToolBadge message={UNSUPPORTED_MCP_TOOL_MESSAGE} />
1415+
) : undefined,
1416+
},
1417+
],
1418+
})
1419+
}
1420+
14151421
return groups
14161422
}, [
14171423
open,

0 commit comments

Comments
 (0)