Skip to content

Commit bbd9111

Browse files
committed
added socket event, reused existing utils to persist chat inputs
1 parent d135f3a commit bbd9111

3 files changed

Lines changed: 184 additions & 177 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx

Lines changed: 144 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { AlertCircle, ArrowDownToLine, ArrowUp, MoreVertical, Paperclip, X } from 'lucide-react'
5-
import { useParams } from 'next/navigation'
65
import {
76
Badge,
87
Button,
@@ -14,24 +13,27 @@ import {
1413
PopoverTrigger,
1514
Trash,
1615
} from '@/components/emcn'
16+
import { useSession } from '@/lib/auth-client'
1717
import { createLogger } from '@/lib/logs/console/logger'
1818
import {
1919
extractBlockIdFromOutputId,
2020
extractPathFromOutputId,
2121
parseOutputContentSafely,
2222
} from '@/lib/response-format'
23-
// import { START_BLOCK_RESERVED_FIELDS } from '@/lib/workflows/types'
24-
// import { StartBlockPath, TriggerUtils } from '@/lib/workflows/triggers'
2523
import { cn } from '@/lib/utils'
24+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
25+
import { StartBlockPath, TriggerUtils } from '@/lib/workflows/triggers'
26+
import { START_BLOCK_RESERVED_FIELDS } from '@/lib/workflows/types'
2627
import { useScrollManagement } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
2728
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
2829
import type { BlockLog, ExecutionResult } from '@/executor/types'
2930
import { getChatPosition, useChatStore } from '@/stores/chat/store'
3031
import { useExecutionStore } from '@/stores/execution/store'
32+
import { useOperationQueue } from '@/stores/operation-queue/store'
3133
import { useTerminalConsoleStore } from '@/stores/terminal'
3234
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
33-
// import { useSubBlockStore } from '@/stores/workflows/subblock/store'
34-
// import { useWorkflowStore } from '@/stores/workflows/workflow/store'
35+
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
36+
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3537
import { ChatMessage, OutputSelect } from './components'
3638
import { useChatBoundarySync, useChatDrag, useChatFileUpload, useChatResize } from './hooks'
3739

@@ -128,38 +130,13 @@ const formatOutputContent = (output: any): string => {
128130
return ''
129131
}
130132

131-
// interface StartInputFormatField {
132-
// id?: string
133-
// name?: string
134-
// type?: string
135-
// value?: unknown
136-
// collapsed?: boolean
137-
// }
138-
139-
// /**
140-
// * Normalizes an input format value into a list of valid fields.
141-
// *
142-
// * @param value - Raw input format value from subblock state.
143-
// * @returns Array of fields with non-empty names.
144-
// */
145-
// const normalizeStartInputFormat = (value: unknown): StartInputFormatField[] => {
146-
// if (!Array.isArray(value)) {
147-
// return []
148-
// }
149-
150-
// return value.filter((field): field is StartInputFormatField => {
151-
// if (!field || typeof field !== 'object') {
152-
// return false
153-
// }
154-
155-
// const name = (field as StartInputFormatField).name
156-
// return typeof name === 'string' && name.trim() !== ''
157-
// })
158-
// }
159-
160-
const CHAT_START_EXAMPLE_JSON = `"input": string,
161-
"conversationId": string,
162-
"files": array<File>`
133+
interface StartInputFormatField {
134+
id?: string
135+
name?: string
136+
type?: string
137+
value?: unknown
138+
collapsed?: boolean
139+
}
163140

164141
/**
165142
* Floating chat modal component
@@ -174,12 +151,10 @@ const CHAT_START_EXAMPLE_JSON = `"input": string,
174151
* position across sessions using the floating chat store.
175152
*/
176153
export function Chat() {
177-
const params = useParams()
178-
const workspaceId = params.workspaceId as string
179154
const { activeWorkflowId } = useWorkflowRegistry()
180-
// const blocks = useWorkflowStore((state) => state.blocks)
181-
// const triggerWorkflowUpdate = useWorkflowStore((state) => state.triggerUpdate)
182-
// const setSubBlockValue = useSubBlockStore((state) => state.setValue)
155+
const blocks = useWorkflowStore((state) => state.blocks)
156+
const triggerWorkflowUpdate = useWorkflowStore((state) => state.triggerUpdate)
157+
const setSubBlockValue = useSubBlockStore((state) => state.setValue)
183158

184159
// Chat state (UI and messages from unified store)
185160
const {
@@ -204,6 +179,8 @@ export function Chat() {
204179
const { entries } = useTerminalConsoleStore()
205180
const { isExecuting } = useExecutionStore()
206181
const { handleRunWorkflow } = useWorkflowExecution()
182+
const { data: session } = useSession()
183+
const { addToQueue } = useOperationQueue()
207184

208185
// Local state
209186
const [chatMessage, setChatMessage] = useState('')
@@ -233,65 +210,67 @@ export function Chat() {
233210
/**
234211
* Resolves the unified start block for chat execution, if available.
235212
*/
236-
// const startBlockCandidate = useMemo(() => {
237-
// if (!activeWorkflowId) {
238-
// return null
239-
// }
240-
241-
// if (!blocks || Object.keys(blocks).length === 0) {
242-
// return null
243-
// }
244-
245-
// const candidate = TriggerUtils.findStartBlock(blocks, 'chat')
246-
// if (!candidate || candidate.path !== StartBlockPath.UNIFIED) {
247-
// return null
248-
// }
249-
250-
// return candidate
251-
// }, [activeWorkflowId, blocks])
252-
253-
// const startBlockId = startBlockCandidate?.blockId ?? null
254-
255-
// /**
256-
// * Reads the current input format for the unified start block from the subblock store,
257-
// * falling back to the workflow store if no explicit value is stored yet.
258-
// */
259-
// const startBlockInputFormat = useSubBlockStore((state) => {
260-
// if (!activeWorkflowId || !startBlockId) {
261-
// return null
262-
// }
263-
264-
// const workflowValues = state.workflowValues[activeWorkflowId]
265-
// const fromStore = workflowValues?.[startBlockId]?.inputFormat
266-
// if (fromStore !== undefined && fromStore !== null) {
267-
// return fromStore
268-
// }
269-
270-
// const startBlock = blocks[startBlockId]
271-
// return startBlock?.subBlocks?.inputFormat?.value ?? null
272-
// })
273-
274-
// /**
275-
// * Determines which reserved start inputs are missing from the input format.
276-
// */
277-
// const missingStartReservedFields = useMemo(() => {
278-
// if (!startBlockId) {
279-
// return START_BLOCK_RESERVED_FIELDS
280-
// }
281-
282-
// const normalizedFields = normalizeStartInputFormat(startBlockInputFormat)
283-
// const existingNames = new Set(
284-
// normalizedFields
285-
// .map((field) => field.name)
286-
// .filter((name): name is string => typeof name === 'string' && name.trim() !== '')
287-
// .map((name) => name.trim())
288-
// )
289-
290-
// return START_BLOCK_RESERVED_FIELDS.filter((fieldName) => !existingNames.has(fieldName))
291-
// }, [startBlockId, startBlockInputFormat])
292-
293-
// const shouldShowConfigureStartInputsButton =
294-
// Boolean(startBlockId) && missingStartReservedFields.length > 0
213+
const startBlockCandidate = useMemo(() => {
214+
if (!activeWorkflowId) {
215+
return null
216+
}
217+
218+
if (!blocks || Object.keys(blocks).length === 0) {
219+
return null
220+
}
221+
222+
const candidate = TriggerUtils.findStartBlock(blocks, 'chat')
223+
if (!candidate || candidate.path !== StartBlockPath.UNIFIED) {
224+
return null
225+
}
226+
227+
return candidate
228+
}, [activeWorkflowId, blocks])
229+
230+
const startBlockId = startBlockCandidate?.blockId ?? null
231+
232+
/**
233+
* Reads the current input format for the unified start block from the subblock store,
234+
* falling back to the workflow store if no explicit value is stored yet.
235+
*/
236+
const startBlockInputFormat = useSubBlockStore((state) => {
237+
if (!activeWorkflowId || !startBlockId) {
238+
return null
239+
}
240+
241+
const workflowValues = state.workflowValues[activeWorkflowId]
242+
const fromStore = workflowValues?.[startBlockId]?.inputFormat
243+
if (fromStore !== undefined && fromStore !== null) {
244+
return fromStore
245+
}
246+
247+
const startBlock = blocks[startBlockId]
248+
return startBlock?.subBlocks?.inputFormat?.value ?? null
249+
})
250+
251+
/**
252+
* Determines which reserved start inputs are missing from the input format.
253+
*/
254+
const missingStartReservedFields = useMemo(() => {
255+
if (!startBlockId) {
256+
return START_BLOCK_RESERVED_FIELDS
257+
}
258+
259+
const normalizedFields = normalizeInputFormatValue(startBlockInputFormat)
260+
const existingNames = new Set(
261+
normalizedFields
262+
.map((field) => field.name)
263+
.filter((name): name is string => typeof name === 'string' && name.trim() !== '')
264+
.map((name) => name.trim().toLowerCase())
265+
)
266+
267+
return START_BLOCK_RESERVED_FIELDS.filter(
268+
(fieldName) => !existingNames.has(fieldName.toLowerCase())
269+
)
270+
}, [startBlockId, startBlockInputFormat])
271+
272+
const shouldShowConfigureStartInputsButton =
273+
Boolean(startBlockId) && missingStartReservedFields.length > 0
295274

296275
// Get actual position (default if not set)
297276
const actualPosition = useMemo(
@@ -667,61 +646,67 @@ export function Chat() {
667646
setIsChatOpen(false)
668647
}, [setIsChatOpen])
669648

670-
// /**
671-
// * Adds any missing reserved inputs (input, conversationId, files) to the unified start block.
672-
// */
673-
// const handleConfigureStartInputs = useCallback(() => {
674-
// if (!activeWorkflowId || !startBlockId) {
675-
// logger.warn('Cannot configure start inputs: missing active workflow ID or start block ID')
676-
// return
677-
// }
678-
679-
// try {
680-
// const existingFields = Array.isArray(startBlockInputFormat)
681-
// ? [...startBlockInputFormat]
682-
// : []
683-
684-
// const normalizedExisting = normalizeStartInputFormat(existingFields)
685-
// const existingNames = new Set(
686-
// normalizedExisting
687-
// .map((field) => field.name)
688-
// .filter((name): name is string => typeof name === 'string' && name.trim() !== '')
689-
// .map((name) => name.trim())
690-
// )
691-
692-
// const updatedFields: StartInputFormatField[] = [...existingFields]
693-
694-
// missingStartReservedFields.forEach((fieldName) => {
695-
// if (existingNames.has(fieldName)) {
696-
// return
697-
// }
698-
699-
// const defaultType = fieldName === 'files' ? 'files' : 'string'
700-
701-
// updatedFields.push({
702-
// id: crypto.randomUUID(),
703-
// name: fieldName,
704-
// type: defaultType,
705-
// value: '',
706-
// collapsed: false,
707-
// })
708-
// })
709-
710-
// setSubBlockValue(startBlockId, 'inputFormat', updatedFields)
711-
// triggerWorkflowUpdate()
712-
// } catch (error) {
713-
// logger.error('Failed to configure start block reserved inputs', error)
714-
// }
715-
// }, [
716-
// activeWorkflowId,
717-
// missingStartReservedFields,
718-
// setSubBlockValue,
719-
// startBlockId,
720-
// startBlockInputFormat,
721-
// triggerWorkflowUpdate,
722-
// ])
723-
724-
// Don't render if not open
649+
/**
650+
* Adds any missing reserved inputs (input, conversationId, files) to the unified start block.
651+
*/
652+
const handleConfigureStartInputs = useCallback(() => {
653+
if (!activeWorkflowId || !startBlockId) {
654+
logger.warn('Cannot configure start inputs: missing active workflow ID or start block ID')
655+
return
656+
}
657+
658+
try {
659+
const normalizedExisting = normalizeInputFormatValue(startBlockInputFormat)
660+
661+
const newReservedFields: StartInputFormatField[] = missingStartReservedFields.map(
662+
(fieldName) => {
663+
const defaultType = fieldName === 'files' ? 'files' : 'string'
664+
665+
return {
666+
id: crypto.randomUUID(),
667+
name: fieldName,
668+
type: defaultType,
669+
value: '',
670+
collapsed: false,
671+
}
672+
}
673+
)
674+
675+
const updatedFields: StartInputFormatField[] = [...newReservedFields, ...normalizedExisting]
676+
677+
setSubBlockValue(startBlockId, 'inputFormat', updatedFields)
678+
679+
const userId = session?.user?.id || 'unknown'
680+
addToQueue({
681+
id: crypto.randomUUID(),
682+
operation: {
683+
operation: 'subblock-update',
684+
target: 'subblock',
685+
payload: {
686+
blockId: startBlockId,
687+
subblockId: 'inputFormat',
688+
value: updatedFields,
689+
},
690+
},
691+
workflowId: activeWorkflowId,
692+
userId,
693+
})
694+
695+
triggerWorkflowUpdate()
696+
} catch (error) {
697+
logger.error('Failed to configure start block reserved inputs', error)
698+
}
699+
}, [
700+
activeWorkflowId,
701+
missingStartReservedFields,
702+
setSubBlockValue,
703+
startBlockId,
704+
startBlockInputFormat,
705+
triggerWorkflowUpdate,
706+
session,
707+
addToQueue,
708+
])
709+
725710
if (!isChatOpen) return null
726711

727712
return (
@@ -752,10 +737,10 @@ export function Chat() {
752737
className='ml-auto flex min-w-0 flex-shrink items-center gap-[6px]'
753738
onMouseDown={(e) => e.stopPropagation()}
754739
>
755-
{/* {shouldShowConfigureStartInputsButton && (
740+
{shouldShowConfigureStartInputsButton && (
756741
<Badge
757742
variant='outline'
758-
className='cursor-pointer rounded-[6px] flex-none whitespace-nowrap'
743+
className='flex-none cursor-pointer whitespace-nowrap rounded-[6px]'
759744
title='Add chat inputs to Start block'
760745
onMouseDown={(e) => {
761746
e.stopPropagation()
@@ -764,7 +749,7 @@ export function Chat() {
764749
>
765750
<span className='whitespace-nowrap text-[12px]'>Add inputs</span>
766751
</Badge>
767-
)} */}
752+
)}
768753

769754
<OutputSelect
770755
workflowId={activeWorkflowId}

0 commit comments

Comments
 (0)