Skip to content

Commit 9857f78

Browse files
committed
reuse existing isStreaming state for code block llm-generated response format
1 parent d3c1fb4 commit 9857f78

File tree

3 files changed

+69
-149
lines changed

3 files changed

+69
-149
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,6 @@ export function Code({
7373
}
7474
}, [generationType])
7575

76-
const [storeValue, setStoreValue, { isStreaming }] = useSubBlockValue(
77-
blockId,
78-
subBlockId,
79-
false,
80-
{
81-
debounceMs: 150,
82-
onStreamingStart: () => {
83-
logger.debug('Streaming started for code editing', { blockId, subBlockId })
84-
},
85-
onStreamingEnd: () => {
86-
logger.debug('Streaming ended for code editing', { blockId, subBlockId })
87-
},
88-
}
89-
)
90-
9176
const [code, setCode] = useState<string>('')
9277
const [_lineCount, setLineCount] = useState(1)
9378
const [showTags, setShowTags] = useState(false)
@@ -111,53 +96,69 @@ export function Code({
11196
const toggleCollapsed = () => {
11297
setCollapsedValue(blockId, collapsedStateKey, !isCollapsed)
11398
}
99+
100+
// Create refs to hold the handlers
101+
const handleStreamStartRef = useRef<() => void>(() => {})
102+
const handleGeneratedContentRef = useRef<(generatedCode: string) => void>(() => {})
103+
const handleStreamChunkRef = useRef<(chunk: string) => void>(() => {})
104+
105+
// AI Code Generation Hook
106+
const {
107+
isLoading: isAiLoading,
108+
isStreaming: isAiStreaming,
109+
generate: generateCode,
110+
generateStream: generateCodeStream,
111+
cancelGeneration,
112+
isPromptVisible,
113+
showPromptInline,
114+
hidePromptInline,
115+
promptInputValue,
116+
updatePromptValue,
117+
} = useCodeGeneration({
118+
generationType: generationType,
119+
initialContext: code,
120+
onGeneratedContent: (content: string) => handleGeneratedContentRef.current?.(content),
121+
onStreamChunk: (chunk: string) => handleStreamChunkRef.current?.(chunk),
122+
onStreamStart: () => handleStreamStartRef.current?.(),
123+
})
124+
125+
// State management - useSubBlockValue with explicit streaming control
126+
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, {
127+
debounceMs: 150,
128+
isStreaming: isAiStreaming, // Use AI streaming state directly
129+
onStreamingEnd: () => {
130+
logger.debug('AI streaming ended, value persisted', { blockId, subBlockId })
131+
},
132+
})
133+
114134
// Use preview value when in preview mode, otherwise use store value or prop value
115135
const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue
116136

117-
// AI Code Generation Hook
118-
const handleStreamStart = () => {
137+
// Define the handlers now that we have access to setStoreValue
138+
handleStreamStartRef.current = () => {
119139
setCode('')
120-
// No need to manually manage streaming - it's automatic now
140+
// Streaming state is now controlled by isAiStreaming
121141
}
122142

123-
const handleGeneratedContent = (generatedCode: string) => {
143+
handleGeneratedContentRef.current = (generatedCode: string) => {
124144
setCode(generatedCode)
125145
if (!isPreview && !disabled) {
126146
setStoreValue(generatedCode)
147+
// Final value will be persisted when isAiStreaming becomes false
127148
}
128149
}
129150

130-
// Handle streaming chunks directly into the editor
131-
const handleStreamChunk = (chunk: string) => {
151+
handleStreamChunkRef.current = (chunk: string) => {
132152
setCode((currentCode) => {
133153
const newCode = currentCode + chunk
134154
if (!isPreview && !disabled) {
135-
// Just update the value - streaming detection is automatic
155+
// Update the value - it won't be persisted until streaming ends
136156
setStoreValue(newCode)
137157
}
138158
return newCode
139159
})
140160
}
141161

142-
const {
143-
isLoading: isAiLoading,
144-
isStreaming: isAiStreaming,
145-
generate: generateCode,
146-
generateStream: generateCodeStream,
147-
cancelGeneration,
148-
isPromptVisible,
149-
showPromptInline,
150-
hidePromptInline,
151-
promptInputValue,
152-
updatePromptValue,
153-
} = useCodeGeneration({
154-
generationType: generationType,
155-
initialContext: code,
156-
onGeneratedContent: handleGeneratedContent,
157-
onStreamChunk: handleStreamChunk,
158-
onStreamStart: handleStreamStart,
159-
})
160-
161162
// Effects
162163
useEffect(() => {
163164
const valueString = value?.toString() ?? ''

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/response/response-format.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,9 @@ export function ResponseFormat({
5050
isPreview = false,
5151
previewValue,
5252
}: ResponseFormatProps) {
53+
// useSubBlockValue now includes debouncing by default
5354
const [storeValue, setStoreValue] = useSubBlockValue<JSONProperty[]>(blockId, subBlockId, false, {
5455
debounceMs: 200, // Slightly longer debounce for complex structures
55-
onStreamingStart: () => {
56-
console.debug('Starting bulk property operation', { blockId, subBlockId })
57-
},
58-
onStreamingEnd: () => {
59-
console.debug('Ending bulk property operation', { blockId, subBlockId })
60-
},
6156
})
6257

6358
const [showPreview, setShowPreview] = useState(false)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts

Lines changed: 26 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useRef, useState } from 'react'
1+
import { useCallback, useEffect, useRef } from 'react'
22
import { isEqual } from 'lodash'
33
import { createLogger } from '@/lib/logs/console-logger'
44
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
@@ -160,49 +160,28 @@ function storeApiKeyValue(
160160

161161
interface UseSubBlockValueOptions {
162162
debounceMs?: number
163-
streamingThresholdMs?: number
164-
onStreamingStart?: () => void
163+
isStreaming?: boolean // Explicit streaming state
165164
onStreamingEnd?: () => void
166165
}
167166

168167
/**
169168
* Custom hook to get and set values for a sub-block in a workflow.
170169
* Handles complex object values properly by using deep equality comparison.
171-
* Includes automatic debouncing and streaming detection for large text operations.
170+
* Includes automatic debouncing and explicit streaming mode for AI generation.
172171
*
173172
* @param blockId The ID of the block containing the sub-block
174173
* @param subBlockId The ID of the sub-block
175174
* @param triggerWorkflowUpdate Whether to trigger a workflow update when the value changes
176175
* @param options Configuration for debouncing and streaming behavior
177-
* @returns A tuple containing the current value, setter function, and optionally streaming state
176+
* @returns A tuple containing the current value and setter function
178177
*/
179-
export function useSubBlockValue<T = any>(
180-
blockId: string,
181-
subBlockId: string,
182-
triggerWorkflowUpdate?: boolean
183-
): readonly [T | null, (value: T) => void]
184-
185-
export function useSubBlockValue<T = any>(
186-
blockId: string,
187-
subBlockId: string,
188-
triggerWorkflowUpdate: boolean,
189-
options: UseSubBlockValueOptions
190-
): readonly [T | null, (value: T) => void, { isStreaming: boolean }]
191-
192178
export function useSubBlockValue<T = any>(
193179
blockId: string,
194180
subBlockId: string,
195181
triggerWorkflowUpdate = false,
196182
options?: UseSubBlockValueOptions
197-
):
198-
| readonly [T | null, (value: T) => void]
199-
| readonly [T | null, (value: T) => void, { isStreaming: boolean }] {
200-
const {
201-
debounceMs = 100,
202-
streamingThresholdMs = 50,
203-
onStreamingStart,
204-
onStreamingEnd,
205-
} = options || {}
183+
): readonly [T | null, (value: T) => void] {
184+
const { debounceMs = 150, isStreaming = false, onStreamingEnd } = options || {}
206185

207186
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
208187

@@ -223,13 +202,11 @@ export function useSubBlockValue<T = any>(
223202
// Previous model reference for detecting model changes
224203
const prevModelRef = useRef<string | null>(null)
225204

226-
// Debouncing and streaming detection refs
205+
// Debouncing refs
227206
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
228-
const lastUpdateTimeRef = useRef<number>(0)
229-
const updateCountRef = useRef<number>(0)
230-
const streamingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
231207
const lastEmittedValueRef = useRef<T | null>(null)
232-
const [isStreaming, setIsStreaming] = useState(false)
208+
const streamingValueRef = useRef<T | null>(null)
209+
const wasStreamingRef = useRef<boolean>(false)
233210

234211
// Get value from subblock store - always call this hook unconditionally
235212
const storeValue = useSubBlockStore(
@@ -255,15 +232,12 @@ export function useSubBlockValue<T = any>(
255232
// Compute the modelValue based on block type
256233
const modelValue = isProviderBasedBlock ? (modelSubBlockValue as string) : null
257234

258-
// Cleanup timers on unmount
235+
// Cleanup timer on unmount
259236
useEffect(() => {
260237
return () => {
261238
if (debounceTimerRef.current) {
262239
clearTimeout(debounceTimerRef.current)
263240
}
264-
if (streamingTimeoutRef.current) {
265-
clearTimeout(streamingTimeoutRef.current)
266-
}
267241
}
268242
}, [])
269243

@@ -276,59 +250,17 @@ export function useSubBlockValue<T = any>(
276250
[blockId, subBlockId, collaborativeSetSubblockValue]
277251
)
278252

279-
// Detect and handle streaming
280-
const detectStreaming = useCallback(() => {
281-
const now = Date.now()
282-
const timeSinceLastUpdate = now - lastUpdateTimeRef.current
283-
284-
// If updates are coming in rapidly, we're likely streaming
285-
if (timeSinceLastUpdate < streamingThresholdMs) {
286-
updateCountRef.current++
287-
288-
// Start streaming mode after 3 rapid updates
289-
if (updateCountRef.current >= 3 && !isStreaming) {
290-
logger.debug('Streaming detected', {
291-
blockId,
292-
subBlockId,
293-
updateCount: updateCountRef.current,
294-
})
295-
setIsStreaming(true)
296-
onStreamingStart?.()
297-
}
298-
} else {
299-
// Reset counter if updates slow down
300-
updateCountRef.current = 1
301-
}
302-
303-
lastUpdateTimeRef.current = now
304-
305-
// Set up timeout to end streaming
306-
if (streamingTimeoutRef.current) {
307-
clearTimeout(streamingTimeoutRef.current)
308-
}
309-
310-
if (isStreaming) {
311-
streamingTimeoutRef.current = setTimeout(() => {
312-
logger.debug('Ending streaming mode', { blockId, subBlockId })
313-
setIsStreaming(false)
314-
updateCountRef.current = 0
315-
onStreamingEnd?.()
316-
317-
// Emit the final value when streaming ends
318-
if (valueRef.current !== null && valueRef.current !== lastEmittedValueRef.current) {
319-
emitValue(valueRef.current)
320-
}
321-
}, 300) // End streaming 300ms after last update
253+
// Handle streaming mode changes
254+
useEffect(() => {
255+
// If we just exited streaming mode, emit the final value
256+
if (wasStreamingRef.current && !isStreaming && streamingValueRef.current !== null) {
257+
logger.debug('Streaming ended, persisting final value', { blockId, subBlockId })
258+
emitValue(streamingValueRef.current)
259+
streamingValueRef.current = null
260+
onStreamingEnd?.()
322261
}
323-
}, [
324-
blockId,
325-
subBlockId,
326-
isStreaming,
327-
streamingThresholdMs,
328-
onStreamingStart,
329-
onStreamingEnd,
330-
emitValue,
331-
])
262+
wasStreamingRef.current = isStreaming
263+
}, [isStreaming, blockId, subBlockId, emitValue, onStreamingEnd])
332264

333265
// Hook to set a value in the subblock store
334266
const setValue = useCallback(
@@ -366,17 +298,17 @@ export function useSubBlockValue<T = any>(
366298
storeApiKeyValue(blockId, blockType, modelValue, newValue, storeValue)
367299
}
368300

369-
// Detect if we're in a streaming scenario
370-
detectStreaming()
371-
372301
// Clear any existing debounce timer
373302
if (debounceTimerRef.current) {
374303
clearTimeout(debounceTimerRef.current)
304+
debounceTimerRef.current = null
375305
}
376306

377-
// If streaming, don't emit immediately - wait for streaming to end
378-
if (!isStreaming) {
379-
// Detect large changes for automatic bulk operation mode
307+
// If streaming, just store the value without emitting
308+
if (isStreaming) {
309+
streamingValueRef.current = valueCopy
310+
} else {
311+
// Detect large changes for extended debounce
380312
const isLargeChange = detectLargeChange(lastEmittedValueRef.current, valueCopy)
381313
const effectiveDebounceMs = isLargeChange ? debounceMs * 2 : debounceMs
382314

@@ -403,7 +335,6 @@ export function useSubBlockValue<T = any>(
403335
modelValue,
404336
isStreaming,
405337
debounceMs,
406-
detectStreaming,
407338
emitValue,
408339
]
409340
)
@@ -478,13 +409,6 @@ export function useSubBlockValue<T = any>(
478409
}, [storeValue, initialValue])
479410

480411
// Return appropriate tuple based on whether options were provided
481-
if (options) {
482-
return [
483-
storeValue !== undefined ? storeValue : initialValue,
484-
setValue,
485-
{ isStreaming },
486-
] as const
487-
}
488412
return [storeValue !== undefined ? storeValue : initialValue, setValue] as const
489413
}
490414

0 commit comments

Comments
 (0)