mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-06 12:45:07 -05:00
Compare commits
2 Commits
fix/execut
...
fix/cmdk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83d9ee5a2 | ||
|
|
1cfa12538e |
@@ -491,13 +491,6 @@ export function useWorkflowExecution() {
|
||||
updateActiveBlocks(data.blockId, false)
|
||||
setBlockRunStatus(data.blockId, 'error')
|
||||
|
||||
executedBlockIds.add(data.blockId)
|
||||
accumulatedBlockStates.set(data.blockId, {
|
||||
output: { error: data.error },
|
||||
executed: true,
|
||||
executionTime: data.durationMs || 0,
|
||||
})
|
||||
|
||||
accumulatedBlockLogs.push(
|
||||
createBlockLogEntry(data, { success: false, output: {}, error: data.error })
|
||||
)
|
||||
|
||||
@@ -349,15 +349,7 @@ export function PreviewWorkflow({
|
||||
if (block.type === 'loop' || block.type === 'parallel') {
|
||||
const isSelected = selectedBlockId === blockId
|
||||
const dimensions = calculateContainerDimensions(blockId, workflowState.blocks)
|
||||
|
||||
// Check for direct error on the subflow block itself (e.g., loop resolution errors)
|
||||
// before falling back to children-derived status
|
||||
const directExecution = blockExecutionMap.get(blockId)
|
||||
const subflowExecutionStatus: ExecutionStatus | undefined =
|
||||
directExecution?.status === 'error'
|
||||
? 'error'
|
||||
: (getSubflowExecutionStatus(blockId) ??
|
||||
(directExecution ? (directExecution.status as ExecutionStatus) : undefined))
|
||||
const subflowExecutionStatus = getSubflowExecutionStatus(blockId)
|
||||
|
||||
nodeArray.push({
|
||||
id: blockId,
|
||||
|
||||
@@ -79,7 +79,9 @@ export function SearchModal({
|
||||
const router = useRouter()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const openSettingsModal = useSettingsModalStore((state) => state.openModal)
|
||||
const { config: permissionConfig } = usePermissionConfig()
|
||||
|
||||
@@ -142,42 +144,19 @@ export function SearchModal({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (open && inputRef.current) {
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
'value'
|
||||
)?.set
|
||||
if (nativeInputValueSetter) {
|
||||
nativeInputValueSetter.call(inputRef.current, '')
|
||||
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
}
|
||||
inputRef.current.focus()
|
||||
if (open) {
|
||||
setSearch('')
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleSearchChange = useCallback(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const list = document.querySelector('[cmdk-list]')
|
||||
if (list) {
|
||||
list.scrollTop = 0
|
||||
const handleSearchChange = useCallback((value: string) => {
|
||||
setSearch(value)
|
||||
if (listRef.current) {
|
||||
listRef.current.scrollTop = 0
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
onOpenChange(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [open, onOpenChange])
|
||||
|
||||
const handleBlockSelect = useCallback(
|
||||
(block: SearchBlockItem, type: 'block' | 'trigger' | 'tool') => {
|
||||
const enableTriggerMode =
|
||||
@@ -264,7 +243,7 @@ export function SearchModal({
|
||||
{/* Overlay */}
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 z-40 bg-[#E4E4E4]/50 backdrop-blur-[0.75px] transition-opacity duration-100 dark:bg-[#0D0D0D]/50',
|
||||
'fixed inset-0 z-40 bg-[#E4E4E4]/50 transition-opacity duration-100 dark:bg-[#0D0D0D]/50',
|
||||
open ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
)}
|
||||
onClick={() => onOpenChange(false)}
|
||||
@@ -281,16 +260,31 @@ export function SearchModal({
|
||||
'-translate-x-1/2 fixed top-[15%] left-1/2 z-50 w-[500px] overflow-hidden rounded-[12px] border border-[var(--border)] bg-[var(--surface-4)] shadow-lg',
|
||||
open ? 'visible opacity-100' : 'invisible opacity-0'
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
onOpenChange(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Command label='Search' filter={customFilter}>
|
||||
<Command.Input
|
||||
ref={inputRef}
|
||||
autoFocus
|
||||
value={search}
|
||||
onValueChange={handleSearchChange}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
onOpenChange(false)
|
||||
}
|
||||
}}
|
||||
placeholder='Search anything...'
|
||||
className='w-full border-0 border-[var(--border)] border-b bg-transparent px-[12px] py-[10px] font-base text-[15px] text-[var(--text-primary)] placeholder:text-[var(--text-secondary)] focus:outline-none'
|
||||
/>
|
||||
<Command.List className='scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent max-h-[400px] overflow-y-auto p-[8px]'>
|
||||
<Command.List
|
||||
ref={listRef}
|
||||
className='scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent max-h-[400px] overflow-y-auto p-[8px]'
|
||||
>
|
||||
<Command.Empty className='flex items-center justify-center px-[16px] py-[24px] text-[15px] text-[var(--text-subtle)]'>
|
||||
No results found.
|
||||
</Command.Empty>
|
||||
|
||||
@@ -2478,9 +2478,6 @@ describe('EdgeManager', () => {
|
||||
expect(readyNodes).toContain(otherBranchId)
|
||||
expect(readyNodes).not.toContain(sentinelStartId)
|
||||
|
||||
// sentinel_end should NOT be ready - it's on a fully deactivated path
|
||||
expect(readyNodes).not.toContain(sentinelEndId)
|
||||
|
||||
// afterLoop should NOT be ready - its incoming edge from sentinel_end should be deactivated
|
||||
expect(readyNodes).not.toContain(afterLoopId)
|
||||
|
||||
@@ -2548,84 +2545,6 @@ describe('EdgeManager', () => {
|
||||
expect(edgeManager.isNodeReady(afterParallelNode)).toBe(true)
|
||||
})
|
||||
|
||||
it('should not queue loop sentinel-end when upstream condition deactivates entire loop branch', () => {
|
||||
// Regression test for: upstream condition → (if) → ... many blocks ... → sentinel_start → body → sentinel_end
|
||||
// → (else) → exit_block
|
||||
// When condition takes "else", the deep cascade deactivation should NOT queue sentinel_end.
|
||||
// Previously, sentinel_end was flagged as a cascadeTarget (terminal control node) and
|
||||
// spuriously queued, causing it to attempt loop scope initialization and fail.
|
||||
|
||||
const conditionId = 'condition'
|
||||
const intermediateId = 'intermediate'
|
||||
const sentinelStartId = 'sentinel-start'
|
||||
const loopBodyId = 'loop-body'
|
||||
const sentinelEndId = 'sentinel-end'
|
||||
const afterLoopId = 'after-loop'
|
||||
const exitBlockId = 'exit-block'
|
||||
|
||||
const conditionNode = createMockNode(conditionId, [
|
||||
{ target: intermediateId, sourceHandle: 'condition-if' },
|
||||
{ target: exitBlockId, sourceHandle: 'condition-else' },
|
||||
])
|
||||
|
||||
const intermediateNode = createMockNode(
|
||||
intermediateId,
|
||||
[{ target: sentinelStartId }],
|
||||
[conditionId]
|
||||
)
|
||||
|
||||
const sentinelStartNode = createMockNode(
|
||||
sentinelStartId,
|
||||
[{ target: loopBodyId }],
|
||||
[intermediateId]
|
||||
)
|
||||
|
||||
const loopBodyNode = createMockNode(
|
||||
loopBodyId,
|
||||
[{ target: sentinelEndId }],
|
||||
[sentinelStartId]
|
||||
)
|
||||
|
||||
const sentinelEndNode = createMockNode(
|
||||
sentinelEndId,
|
||||
[
|
||||
{ target: sentinelStartId, sourceHandle: 'loop_continue' },
|
||||
{ target: afterLoopId, sourceHandle: 'loop_exit' },
|
||||
],
|
||||
[loopBodyId]
|
||||
)
|
||||
|
||||
const afterLoopNode = createMockNode(afterLoopId, [], [sentinelEndId])
|
||||
const exitBlockNode = createMockNode(exitBlockId, [], [conditionId])
|
||||
|
||||
const nodes = new Map<string, DAGNode>([
|
||||
[conditionId, conditionNode],
|
||||
[intermediateId, intermediateNode],
|
||||
[sentinelStartId, sentinelStartNode],
|
||||
[loopBodyId, loopBodyNode],
|
||||
[sentinelEndId, sentinelEndNode],
|
||||
[afterLoopId, afterLoopNode],
|
||||
[exitBlockId, exitBlockNode],
|
||||
])
|
||||
|
||||
const dag = createMockDAG(nodes)
|
||||
const edgeManager = new EdgeManager(dag)
|
||||
|
||||
const readyNodes = edgeManager.processOutgoingEdges(conditionNode, {
|
||||
selectedOption: 'else',
|
||||
})
|
||||
|
||||
// Only exitBlock should be ready
|
||||
expect(readyNodes).toContain(exitBlockId)
|
||||
|
||||
// Nothing on the deactivated path should be queued
|
||||
expect(readyNodes).not.toContain(intermediateId)
|
||||
expect(readyNodes).not.toContain(sentinelStartId)
|
||||
expect(readyNodes).not.toContain(loopBodyId)
|
||||
expect(readyNodes).not.toContain(sentinelEndId)
|
||||
expect(readyNodes).not.toContain(afterLoopId)
|
||||
})
|
||||
|
||||
it('should still correctly handle normal loop exit (not deactivate when loop runs)', () => {
|
||||
// When a loop actually executes and exits normally, after_loop should become ready
|
||||
const sentinelStartId = 'sentinel-start'
|
||||
|
||||
@@ -71,13 +71,7 @@ export class EdgeManager {
|
||||
|
||||
for (const targetId of cascadeTargets) {
|
||||
if (!readyNodes.includes(targetId) && !activatedTargets.includes(targetId)) {
|
||||
// Only queue cascade terminal control nodes when ALL outgoing edges from the
|
||||
// current node were deactivated (dead-end scenario). When some edges are
|
||||
// activated, terminal control nodes on deactivated branches should NOT be
|
||||
// queued - they will be reached through the normal activated path's completion.
|
||||
// This prevents loop/parallel sentinels on fully deactivated paths (e.g., an
|
||||
// upstream condition took a different branch) from being spuriously executed.
|
||||
if (activatedTargets.length === 0 && this.isTargetReady(targetId)) {
|
||||
if (this.isTargetReady(targetId)) {
|
||||
readyNodes.push(targetId)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user