Skip to content

Commit 701bf2b

Browse files
improvement(response-copilot): prefer builder mode + fix builder/editor mode conversions (#1648)
* improvement(response-copilot): make it use builder mode over editor mode to prevent json formatting issues * change placeholder text * fix conversion between builder and editor mode
1 parent ba8acbb commit 701bf2b

File tree

3 files changed

+95
-20
lines changed

3 files changed

+95
-20
lines changed

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

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,20 @@ export function Dropdown({
4444

4545
const inputRef = useRef<HTMLInputElement>(null)
4646
const dropdownRef = useRef<HTMLDivElement>(null)
47+
const previousModeRef = useRef<string | null>(null)
4748

4849
// For response dataMode conversion - get builderData and data sub-blocks
49-
const [builderData] = useSubBlockValue<any[]>(blockId, 'builderData')
50-
const [, setData] = useSubBlockValue<string>(blockId, 'data')
50+
const [builderData, setBuilderData] = useSubBlockValue<any[]>(blockId, 'builderData')
51+
const [data, setData] = useSubBlockValue<string>(blockId, 'data')
52+
53+
// Keep refs with latest values to avoid stale closures
54+
const builderDataRef = useRef(builderData)
55+
const dataRef = useRef(data)
56+
57+
useEffect(() => {
58+
builderDataRef.current = builderData
59+
dataRef.current = data
60+
}, [builderData, data])
5161

5262
// Use preview value when in preview mode, otherwise use store value or prop value
5363
const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue
@@ -103,23 +113,89 @@ export function Dropdown({
103113
}
104114
}, [storeInitialized, value, defaultOptionValue, setStoreValue])
105115

116+
// Helper function to normalize variable references in JSON strings
117+
const normalizeVariableReferences = (jsonString: string): string => {
118+
// Replace unquoted variable references with quoted ones
119+
// Pattern: <variable.name> -> "<variable.name>"
120+
return jsonString.replace(/([^"]<[^>]+>)/g, '"$1"')
121+
}
122+
123+
// Helper function to convert JSON string to builder data format
124+
const convertJsonToBuilderData = (jsonString: string): any[] => {
125+
try {
126+
// Always normalize variable references first
127+
const normalizedJson = normalizeVariableReferences(jsonString)
128+
const parsed = JSON.parse(normalizedJson)
129+
130+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
131+
return Object.entries(parsed).map(([key, value]) => {
132+
const fieldType = inferType(value)
133+
const fieldValue =
134+
fieldType === 'object' || fieldType === 'array' ? JSON.stringify(value, null, 2) : value
135+
136+
return {
137+
id: crypto.randomUUID(),
138+
name: key,
139+
type: fieldType,
140+
value: fieldValue,
141+
collapsed: false,
142+
}
143+
})
144+
}
145+
146+
return []
147+
} catch (error) {
148+
return []
149+
}
150+
}
151+
152+
// Helper function to infer field type from value
153+
const inferType = (value: any): 'string' | 'number' | 'boolean' | 'object' | 'array' => {
154+
if (typeof value === 'boolean') return 'boolean'
155+
if (typeof value === 'number') return 'number'
156+
if (Array.isArray(value)) return 'array'
157+
if (typeof value === 'object' && value !== null) return 'object'
158+
return 'string'
159+
}
160+
161+
// Handle data conversion when dataMode changes
162+
useEffect(() => {
163+
if (subBlockId !== 'dataMode' || isPreview || disabled) return
164+
165+
const currentMode = storeValue
166+
const previousMode = previousModeRef.current
167+
168+
// Only convert if the mode actually changed
169+
if (previousMode !== null && previousMode !== currentMode) {
170+
// Builder to Editor mode (structured → json)
171+
if (currentMode === 'json' && previousMode === 'structured') {
172+
const currentBuilderData = builderDataRef.current
173+
if (
174+
currentBuilderData &&
175+
Array.isArray(currentBuilderData) &&
176+
currentBuilderData.length > 0
177+
) {
178+
const jsonString = ResponseBlockHandler.convertBuilderDataToJsonString(currentBuilderData)
179+
setData(jsonString)
180+
}
181+
}
182+
// Editor to Builder mode (json → structured)
183+
else if (currentMode === 'structured' && previousMode === 'json') {
184+
const currentData = dataRef.current
185+
if (currentData && typeof currentData === 'string' && currentData.trim().length > 0) {
186+
const builderArray = convertJsonToBuilderData(currentData)
187+
setBuilderData(builderArray)
188+
}
189+
}
190+
}
191+
192+
// Update the previous mode ref
193+
previousModeRef.current = currentMode
194+
}, [storeValue, subBlockId, isPreview, disabled, setData, setBuilderData])
195+
106196
// Event handlers
107197
const handleSelect = (selectedValue: string) => {
108198
if (!isPreview && !disabled) {
109-
// Handle conversion when switching from Builder to Editor mode in response blocks
110-
if (
111-
subBlockId === 'dataMode' &&
112-
storeValue === 'structured' &&
113-
selectedValue === 'json' &&
114-
builderData &&
115-
Array.isArray(builderData) &&
116-
builderData.length > 0
117-
) {
118-
// Convert builderData to JSON string for editor mode
119-
const jsonString = ResponseBlockHandler.convertBuilderDataToJsonString(builderData)
120-
setData(jsonString)
121-
}
122-
123199
setStoreValue(selectedValue)
124200
}
125201
setOpen(false)

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ export function FieldFormat({
8383
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
8484
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
8585

86-
// Use preview value when in preview mode, otherwise use store value
8786
const value = isPreview ? previewValue : storeValue
88-
const fields: Field[] = value || []
87+
const fields: Field[] = Array.isArray(value) ? value : []
8988

9089
useEffect(() => {
9190
const initial: Record<string, string> = {}
@@ -547,7 +546,7 @@ export function ResponseFormat(
547546
emptyMessage='No response fields defined'
548547
showType={false}
549548
showValue={true}
550-
valuePlaceholder='Enter test value'
549+
valuePlaceholder='Enter return value'
551550
/>
552551
)
553552
}

apps/sim/blocks/blocks/response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const ResponseBlock: BlockConfig<ResponseBlockOutput> = {
1111
docsLink: 'https://docs.sim.ai/blocks/response',
1212
bestPractices: `
1313
- Only use this if the trigger block is the API Trigger.
14-
- Prefer the editor mode over the builder mode.
14+
- Prefer the builder mode over the editor mode.
1515
- This is usually used as the last block in the workflow.
1616
`,
1717
category: 'blocks',

0 commit comments

Comments
 (0)