Skip to content

Commit 170eb36

Browse files
authored
feat(subflows): editor, block; fix(copilot): stop, mr (#1877)
1 parent 118c477 commit 170eb36

File tree

21 files changed

+1165
-2180
lines changed

21 files changed

+1165
-2180
lines changed

apps/sim/app/globals.css

Lines changed: 50 additions & 282 deletions
Large diffs are not rendered by default.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/copilot-message/copilot-message.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
317317
>
318318
<div
319319
ref={messageContentRef}
320-
className={`relative whitespace-pre-wrap break-words px-[2px] py-1 font-medium font-sans text-[#0D0D0D] text-sm leading-[1.25rem] dark:text-gray-100 ${isSendingMessage && isLastUserMessage ? 'pr-7' : ''} ${!isExpanded && needsExpansion ? 'max-h-[60px] overflow-hidden' : 'overflow-visible'}`}
320+
className={`relative whitespace-pre-wrap break-words px-[2px] py-1 font-medium font-sans text-[#0D0D0D] text-sm leading-[1.25rem] dark:text-gray-100 ${isSendingMessage && isLastUserMessage && isHoveringMessage ? 'pr-7' : ''} ${!isExpanded && needsExpansion ? 'max-h-[60px] overflow-hidden' : 'overflow-visible'}`}
321321
>
322322
{(() => {
323323
const text = message.content || ''
@@ -374,7 +374,7 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
374374
title='Stop generation'
375375
>
376376
<svg
377-
className='h-[13px] w-[13px]'
377+
className='block h-[13px] w-[13px]'
378378
viewBox='0 0 24 24'
379379
fill='black'
380380
xmlns='http://www.w3.org/2000/svg'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/user-input/user-input.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -695,10 +695,10 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
695695
title='Stop generation'
696696
>
697697
{isAborting ? (
698-
<Loader2 className='h-[13px] w-[13px] animate-spin text-black' />
698+
<Loader2 className='block h-[13px] w-[13px] animate-spin text-black' />
699699
) : (
700700
<svg
701-
className='h-[13px] w-[13px]'
701+
className='block h-[13px] w-[13px]'
702702
viewBox='0 0 24 24'
703703
fill='black'
704704
xmlns='http://www.w3.org/2000/svg'
@@ -719,9 +719,9 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
719719
)}
720720
>
721721
{isLoading ? (
722-
<Loader2 className='h-3.5 w-3.5 animate-spin text-black' />
722+
<Loader2 className='block h-3.5 w-3.5 animate-spin text-black' />
723723
) : (
724-
<ArrowUp className='h-3.5 w-3.5 text-black' strokeWidth={2.25} />
724+
<ArrowUp className='block h-3.5 w-3.5 text-black' strokeWidth={2.25} />
725725
)}
726726
</Button>
727727
)}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { ConnectionBlocks } from './connection-blocks/connection-blocks'
22
export { SubBlock } from './sub-block/sub-block'
3+
export { SubflowEditor } from './subflow-editor/subflow-editor'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
'use client'
2+
3+
import { ChevronUp } from 'lucide-react'
4+
import SimpleCodeEditor from 'react-simple-code-editor'
5+
import { Code as CodeEditor, Combobox, getCodeEditorProps, Input, Label } from '@/components/emcn'
6+
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
7+
import type { BlockState } from '@/stores/workflows/workflow/types'
8+
import type { ConnectedBlock } from '../../hooks/use-block-connections'
9+
import { useSubflowEditor } from '../../hooks/use-subflow-editor'
10+
import { ConnectionBlocks } from '../connection-blocks'
11+
12+
interface SubflowEditorProps {
13+
currentBlock: BlockState
14+
currentBlockId: string
15+
subBlocksRef: React.RefObject<HTMLDivElement | null>
16+
connectionsHeight: number
17+
isResizing: boolean
18+
hasIncomingConnections: boolean
19+
incomingConnections: ConnectedBlock[]
20+
handleConnectionsResizeMouseDown: (e: React.MouseEvent) => void
21+
toggleConnectionsCollapsed: () => void
22+
userCanEdit: boolean
23+
isConnectionsAtMinHeight: boolean
24+
}
25+
26+
/**
27+
* SubflowEditor component for editing loop and parallel blocks
28+
*
29+
* @param props - Component props
30+
* @returns Rendered subflow editor
31+
*/
32+
export function SubflowEditor({
33+
currentBlock,
34+
currentBlockId,
35+
subBlocksRef,
36+
connectionsHeight,
37+
isResizing,
38+
hasIncomingConnections,
39+
incomingConnections,
40+
handleConnectionsResizeMouseDown,
41+
toggleConnectionsCollapsed,
42+
userCanEdit,
43+
isConnectionsAtMinHeight,
44+
}: SubflowEditorProps) {
45+
const {
46+
subflowConfig,
47+
currentType,
48+
isCountMode,
49+
isConditionMode,
50+
inputValue,
51+
editorValue,
52+
typeOptions,
53+
showTagDropdown,
54+
cursorPosition,
55+
editorContainerRef,
56+
handleSubflowTypeChange,
57+
handleSubflowIterationsChange,
58+
handleSubflowIterationsSave,
59+
handleSubflowEditorChange,
60+
handleSubflowTagSelect,
61+
highlightWithReferences,
62+
setShowTagDropdown,
63+
} = useSubflowEditor(currentBlock, currentBlockId)
64+
65+
if (!subflowConfig) return null
66+
67+
return (
68+
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
69+
{/* Subflow Editor Section */}
70+
<div ref={subBlocksRef} className='subblocks-section flex flex-1 flex-col overflow-hidden'>
71+
<div className='flex-1 overflow-y-auto overflow-x-hidden px-[8px] pt-[5px] pb-[8px]'>
72+
{/* Type Selection */}
73+
<div>
74+
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[#E6E6E6] text-[13px] dark:text-[#E6E6E6]'>
75+
{currentBlock.type === 'loop' ? 'Loop Type' : 'Parallel Type'}
76+
</Label>
77+
<Combobox
78+
options={typeOptions}
79+
value={currentType || ''}
80+
onChange={handleSubflowTypeChange}
81+
disabled={!userCanEdit}
82+
placeholder='Select type...'
83+
/>
84+
</div>
85+
86+
{/* Dashed Line Separator */}
87+
<div className='px-[2px] pt-[16px] pb-[10px]'>
88+
<div
89+
className='h-[1.25px]'
90+
style={{
91+
backgroundImage:
92+
'repeating-linear-gradient(to right, #2C2C2C 0px, #2C2C2C 6px, transparent 6px, transparent 12px)',
93+
}}
94+
/>
95+
</div>
96+
97+
{/* Configuration */}
98+
<div>
99+
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[#E6E6E6] text-[13px] dark:text-[#E6E6E6]'>
100+
{isCountMode
101+
? `${currentBlock.type === 'loop' ? 'Loop' : 'Parallel'} Iterations`
102+
: isConditionMode
103+
? 'While Condition'
104+
: `${currentBlock.type === 'loop' ? 'Collection' : 'Parallel'} Items`}
105+
</Label>
106+
107+
{isCountMode ? (
108+
<div>
109+
<Input
110+
type='text'
111+
value={inputValue}
112+
onChange={handleSubflowIterationsChange}
113+
onBlur={handleSubflowIterationsSave}
114+
onKeyDown={(e) => e.key === 'Enter' && handleSubflowIterationsSave()}
115+
disabled={!userCanEdit}
116+
className='mb-[4px]'
117+
/>
118+
<div className='text-[10px] text-muted-foreground'>
119+
Enter a number between 1 and {subflowConfig.maxIterations}
120+
</div>
121+
</div>
122+
) : (
123+
<div ref={editorContainerRef} className='relative'>
124+
<CodeEditor.Container>
125+
<CodeEditor.Content>
126+
<CodeEditor.Placeholder gutterWidth={0} show={editorValue.length === 0}>
127+
{isConditionMode ? '<counter.value> < 10' : "['item1', 'item2', 'item3']"}
128+
</CodeEditor.Placeholder>
129+
130+
<SimpleCodeEditor
131+
value={editorValue}
132+
onValueChange={handleSubflowEditorChange}
133+
highlight={highlightWithReferences}
134+
{...getCodeEditorProps({
135+
isPreview: false,
136+
disabled: !userCanEdit,
137+
})}
138+
/>
139+
140+
{showTagDropdown && (
141+
<TagDropdown
142+
visible={showTagDropdown}
143+
onSelect={handleSubflowTagSelect}
144+
blockId={currentBlockId}
145+
activeSourceBlockId={null}
146+
inputValue={editorValue}
147+
cursorPosition={cursorPosition}
148+
onClose={() => setShowTagDropdown(false)}
149+
inputRef={{
150+
current: editorContainerRef.current?.querySelector(
151+
'textarea'
152+
) as HTMLTextAreaElement,
153+
}}
154+
/>
155+
)}
156+
</CodeEditor.Content>
157+
</CodeEditor.Container>
158+
</div>
159+
)}
160+
</div>
161+
</div>
162+
</div>
163+
164+
{/* Connections Section - Only show when there are connections */}
165+
{hasIncomingConnections && (
166+
<div
167+
className={
168+
'connections-section flex flex-shrink-0 flex-col overflow-hidden border-[#2C2C2C] border-t dark:border-[#2C2C2C]' +
169+
(!isResizing ? ' transition-[height] duration-100 ease-out' : '')
170+
}
171+
style={{ height: `${connectionsHeight}px` }}
172+
>
173+
{/* Resize Handle */}
174+
<div className='relative'>
175+
<div
176+
className='absolute top-[-4px] right-0 left-0 z-30 h-[8px] cursor-ns-resize'
177+
onMouseDown={handleConnectionsResizeMouseDown}
178+
/>
179+
</div>
180+
181+
{/* Connections Header with Chevron */}
182+
<div
183+
className='flex flex-shrink-0 cursor-pointer items-center gap-[8px] px-[10px] pt-[5px] pb-[5px]'
184+
onClick={toggleConnectionsCollapsed}
185+
onKeyDown={(e) => {
186+
if (e.key === 'Enter' || e.key === ' ') {
187+
e.preventDefault()
188+
toggleConnectionsCollapsed()
189+
}
190+
}}
191+
role='button'
192+
tabIndex={0}
193+
aria-label={isConnectionsAtMinHeight ? 'Expand connections' : 'Collapse connections'}
194+
>
195+
<ChevronUp
196+
className={
197+
'h-[14px] w-[14px] transition-transform' +
198+
(!isConnectionsAtMinHeight ? ' rotate-180' : '')
199+
}
200+
/>
201+
<div className='font-medium text-[#E6E6E6] text-[13px] dark:text-[#E6E6E6]'>
202+
Connections
203+
</div>
204+
</div>
205+
206+
{/* Connections Content - Always visible */}
207+
<div className='flex-1 overflow-y-auto overflow-x-hidden px-[6px] pb-[8px]'>
208+
<ConnectionBlocks connections={incomingConnections} currentBlockId={currentBlock.id} />
209+
</div>
210+
</div>
211+
)}
212+
</div>
213+
)
214+
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useCallback, useRef } from 'react'
4-
import { BookOpen, ChevronUp, Crosshair, Settings } from 'lucide-react'
4+
import { BookOpen, ChevronUp, Crosshair, RepeatIcon, Settings, SplitIcon } from 'lucide-react'
55
import { Button, Tooltip } from '@/components/emcn'
66
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
77
import { getSubBlockStableKey } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/utils'
@@ -12,7 +12,7 @@ import { useFocusOnBlock } from '@/hooks/use-focus-on-block'
1212
import { usePanelEditorStore } from '@/stores/panel-new/editor/store'
1313
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1414
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
15-
import { ConnectionBlocks, SubBlock } from './components'
15+
import { ConnectionBlocks, SubBlock, SubflowEditor } from './components'
1616
import {
1717
useBlockConnections,
1818
useConnectionsResize,
@@ -45,6 +45,14 @@ export function Editor() {
4545
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
4646
const title = currentBlock?.name || 'Editor'
4747

48+
// Check if selected block is a subflow (loop or parallel)
49+
const isSubflow =
50+
currentBlock && (currentBlock.type === 'loop' || currentBlock.type === 'parallel')
51+
52+
// Get subflow display properties
53+
const subflowIcon = isSubflow && currentBlock.type === 'loop' ? RepeatIcon : SplitIcon
54+
const subflowBgColor = isSubflow && currentBlock.type === 'loop' ? '#2FB3FF' : '#FEE12B'
55+
4856
// Refs for resize functionality
4957
const subBlocksRef = useRef<HTMLDivElement>(null)
5058

@@ -126,12 +134,15 @@ export function Editor() {
126134
{/* Header */}
127135
<div className='flex flex-shrink-0 items-center justify-between rounded-[4px] bg-[#2A2A2A] px-[12px] py-[8px] dark:bg-[#2A2A2A]'>
128136
<div className='flex min-w-0 flex-1 items-center gap-[8px]'>
129-
{blockConfig && (
137+
{(blockConfig || isSubflow) && (
130138
<div
131139
className='flex h-[18px] w-[18px] items-center justify-center rounded-[4px]'
132-
style={{ backgroundColor: blockConfig.bgColor }}
140+
style={{ backgroundColor: isSubflow ? subflowBgColor : blockConfig?.bgColor }}
133141
>
134-
<IconComponent icon={blockConfig.icon} className='h-[12px] w-[12px] text-[#FFFFFF]' />
142+
<IconComponent
143+
icon={isSubflow ? subflowIcon : blockConfig?.icon}
144+
className='h-[12px] w-[12px] text-[#FFFFFF]'
145+
/>
135146
</div>
136147
)}
137148
<h2
@@ -160,8 +171,8 @@ export function Editor() {
160171
</Tooltip.Content>
161172
</Tooltip.Root>
162173
)}
163-
{/* Mode toggles */}
164-
{currentBlock && hasAdvancedMode && (
174+
{/* Mode toggles - Only show for regular blocks, not subflows */}
175+
{currentBlock && !isSubflow && hasAdvancedMode && (
165176
<Tooltip.Root>
166177
<Tooltip.Trigger asChild>
167178
<Button
@@ -179,7 +190,7 @@ export function Editor() {
179190
</Tooltip.Content>
180191
</Tooltip.Root>
181192
)}
182-
{currentBlock && blockConfig?.docsLink && (
193+
{currentBlock && !isSubflow && blockConfig?.docsLink && (
183194
<Tooltip.Root>
184195
<Tooltip.Trigger asChild>
185196
<Button
@@ -203,6 +214,20 @@ export function Editor() {
203214
<div className='flex flex-1 items-center justify-center text-[#8D8D8D] text-[13px]'>
204215
Select a block to edit
205216
</div>
217+
) : isSubflow ? (
218+
<SubflowEditor
219+
currentBlock={currentBlock}
220+
currentBlockId={currentBlockId}
221+
subBlocksRef={subBlocksRef}
222+
connectionsHeight={connectionsHeight}
223+
isResizing={isResizing}
224+
hasIncomingConnections={hasIncomingConnections}
225+
incomingConnections={incomingConnections}
226+
handleConnectionsResizeMouseDown={handleConnectionsResizeMouseDown}
227+
toggleConnectionsCollapsed={toggleConnectionsCollapsed}
228+
userCanEdit={userPermissions.canEdit}
229+
isConnectionsAtMinHeight={isConnectionsAtMinHeight}
230+
/>
206231
) : (
207232
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
208233
{/* Subblocks Section */}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { useBlockConnections } from './use-block-connections'
22
export { useConnectionsResize } from './use-connections-resize'
33
export { useEditorBlockProperties } from './use-editor-block-properties'
44
export { useEditorSubblockLayout } from './use-editor-subblock-layout'
5+
export { useSubflowEditor } from './use-subflow-editor'

0 commit comments

Comments
 (0)