Skip to content

Commit b3960ad

Browse files
icecrasher321aadamgoughAdam GoughAdam GoughVikhyath Mondreti
authored
v0.2.2: fix (#588)
* fix(loop-foreach): forEach loop iterations (#581) * fix: working on fixing foreach loop max * fix: removed default of 5 iterations * fix: bun run lint * fix: greptile comments (#581) * fix: removed safety max (#581) --------- Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net> Co-authored-by: Adam Gough <adamgough@Adams-MacBook-Pro.local> * improvement(sockets conn): sockets connection refresh warning (#580) * working conn status * improvement(sockets conn): sockets connection refresh warning * fix styling --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> * fix(variable resolution): use variable references to not have escaping issues (#587) * fix(variable-resolution): don't inject stringified json, use var refs * fix lint * remove unused var --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> --------- Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com> Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net> Co-authored-by: Adam Gough <adamgough@Adams-MacBook-Pro.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
1 parent 805b245 commit b3960ad

File tree

6 files changed

+105
-61
lines changed

6 files changed

+105
-61
lines changed

apps/sim/app/api/function/execute/route.ts

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createLogger } from '@/lib/logs/console-logger'
44

55
export const dynamic = 'force-dynamic'
66
export const runtime = 'nodejs'
7+
export const maxDuration = 60
78

89
const logger = createLogger('FunctionExecuteAPI')
910

@@ -14,71 +15,45 @@ const logger = createLogger('FunctionExecuteAPI')
1415
* @param envVars - Environment variables from the workflow
1516
* @returns Resolved code
1617
*/
17-
/**
18-
* Safely serialize a value to JSON string with proper escaping
19-
* This prevents JavaScript syntax errors when the serialized data is injected into code
20-
*/
21-
function safeJSONStringify(value: any): string {
22-
try {
23-
// Use JSON.stringify with proper escaping
24-
// The key is to let JSON.stringify handle the escaping properly
25-
return JSON.stringify(value)
26-
} catch (error) {
27-
// If JSON.stringify fails (e.g., circular references), return a safe fallback
28-
try {
29-
// Try to create a safe representation by removing circular references
30-
const seen = new WeakSet()
31-
const cleanValue = JSON.parse(
32-
JSON.stringify(value, (key, val) => {
33-
if (typeof val === 'object' && val !== null) {
34-
if (seen.has(val)) {
35-
return '[Circular Reference]'
36-
}
37-
seen.add(val)
38-
}
39-
return val
40-
})
41-
)
42-
return JSON.stringify(cleanValue)
43-
} catch {
44-
// If that also fails, return a safe string representation
45-
return JSON.stringify(String(value))
46-
}
47-
}
48-
}
4918

5019
function resolveCodeVariables(
5120
code: string,
5221
params: Record<string, any>,
5322
envVars: Record<string, string> = {}
54-
): string {
23+
): { resolvedCode: string; contextVariables: Record<string, any> } {
5524
let resolvedCode = code
25+
const contextVariables: Record<string, any> = {}
5626

5727
// Resolve environment variables with {{var_name}} syntax
5828
const envVarMatches = resolvedCode.match(/\{\{([^}]+)\}\}/g) || []
5929
for (const match of envVarMatches) {
6030
const varName = match.slice(2, -2).trim()
6131
// Priority: 1. Environment variables from workflow, 2. Params
6232
const varValue = envVars[varName] || params[varName] || ''
63-
// Use safe JSON stringify to prevent syntax errors
64-
resolvedCode = resolvedCode.replace(
65-
new RegExp(escapeRegExp(match), 'g'),
66-
safeJSONStringify(varValue)
67-
)
33+
34+
// Instead of injecting large JSON directly, create a variable reference
35+
const safeVarName = `__var_${varName.replace(/[^a-zA-Z0-9_]/g, '_')}`
36+
contextVariables[safeVarName] = varValue
37+
38+
// Replace the template with a variable reference
39+
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName)
6840
}
6941

7042
// Resolve tags with <tag_name> syntax
7143
const tagMatches = resolvedCode.match(/<([a-zA-Z_][a-zA-Z0-9_]*)>/g) || []
7244
for (const match of tagMatches) {
7345
const tagName = match.slice(1, -1).trim()
7446
const tagValue = params[tagName] || ''
75-
resolvedCode = resolvedCode.replace(
76-
new RegExp(escapeRegExp(match), 'g'),
77-
safeJSONStringify(tagValue)
78-
)
47+
48+
// Instead of injecting large JSON directly, create a variable reference
49+
const safeVarName = `__tag_${tagName.replace(/[^a-zA-Z0-9_]/g, '_')}`
50+
contextVariables[safeVarName] = tagValue
51+
52+
// Replace the template with a variable reference
53+
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName)
7954
}
8055

81-
return resolvedCode
56+
return { resolvedCode, contextVariables }
8257
}
8358

8459
/**
@@ -118,7 +93,7 @@ export async function POST(req: NextRequest) {
11893
})
11994

12095
// Resolve variables in the code with workflow environment variables
121-
const resolvedCode = resolveCodeVariables(code, executionParams, envVars)
96+
const { resolvedCode, contextVariables } = resolveCodeVariables(code, executionParams, envVars)
12297

12398
const executionMethod = 'vm' // Default execution method
12499

@@ -280,6 +255,7 @@ export async function POST(req: NextRequest) {
280255
const context = createContext({
281256
params: executionParams,
282257
environmentVariables: envVars,
258+
...contextVariables, // Add resolved variables directly to context
283259
fetch: globalThis.fetch || require('node-fetch').default,
284260
console: {
285261
log: (...args: any[]) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
5+
interface ConnectionStatusProps {
6+
isConnected: boolean
7+
}
8+
9+
export function ConnectionStatus({ isConnected }: ConnectionStatusProps) {
10+
const [showOfflineNotice, setShowOfflineNotice] = useState(false)
11+
12+
useEffect(() => {
13+
let timeoutId: NodeJS.Timeout
14+
15+
if (!isConnected) {
16+
// Show offline notice after 6 seconds of being disconnected
17+
timeoutId = setTimeout(() => {
18+
setShowOfflineNotice(true)
19+
}, 6000) // 6 seconds
20+
} else {
21+
// Hide notice immediately when reconnected
22+
setShowOfflineNotice(false)
23+
}
24+
25+
return () => {
26+
if (timeoutId) {
27+
clearTimeout(timeoutId)
28+
}
29+
}
30+
}, [isConnected])
31+
32+
// Don't render anything if connected or if we haven't been disconnected long enough
33+
if (!showOfflineNotice) {
34+
return null
35+
}
36+
37+
return (
38+
<div className='flex items-center gap-1.5'>
39+
<div className='flex items-center gap-1.5 text-red-600'>
40+
<div className='relative flex items-center justify-center'>
41+
<div className='absolute h-3 w-3 animate-ping rounded-full bg-red-500/20' />
42+
<div className='relative h-2 w-2 rounded-full bg-red-500' />
43+
</div>
44+
<div className='flex flex-col'>
45+
<span className='font-medium text-xs leading-tight'>Connection lost</span>
46+
<span className='text-xs leading-tight opacity-90'>
47+
Changes not saved - please refresh
48+
</span>
49+
</div>
50+
</div>
51+
</div>
52+
)
53+
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/user-avatar-stack/user-avatar-stack.tsx

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

33
import { useMemo } from 'react'
44
import { usePresence } from '../../../../hooks/use-presence'
5+
import { ConnectionStatus } from './components/connection-status/connection-status'
56
import { UserAvatar } from './components/user-avatar/user-avatar'
67

78
interface User {
@@ -25,7 +26,7 @@ export function UserAvatarStack({
2526
className = '',
2627
}: UserAvatarStackProps) {
2728
// Use presence data if no users are provided via props
28-
const { users: presenceUsers } = usePresence()
29+
const { users: presenceUsers, isConnected } = usePresence()
2930
const users = propUsers || presenceUsers
3031

3132
// Memoize the processed users to avoid unnecessary re-renders
@@ -43,10 +44,14 @@ export function UserAvatarStack({
4344
}
4445
}, [users, maxVisible])
4546

47+
// Show connection status component regardless of user count
48+
// This will handle the offline notice when disconnected for 15 seconds
49+
const connectionStatusElement = <ConnectionStatus isConnected={isConnected} />
50+
4651
// Only show presence when there are multiple users (>1)
47-
// Don't render anything if there are no users or only 1 user
52+
// But always show connection status
4853
if (users.length <= 1) {
49-
return null
54+
return connectionStatusElement
5055
}
5156

5257
// Determine spacing based on size
@@ -58,6 +63,9 @@ export function UserAvatarStack({
5863

5964
return (
6065
<div className={`flex items-center ${spacingClass} ${className}`}>
66+
{/* Connection status - always present */}
67+
{connectionStatusElement}
68+
6169
{/* Render visible user avatars */}
6270
{visibleUsers.map((user, index) => (
6371
<UserAvatar

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,6 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
683683
</p>
684684
)}
685685
</div>
686-
<UserAvatarStack className='ml-3' />
687686
</div>
688687
)
689688
}
@@ -1275,8 +1274,10 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
12751274
{/* Left Section - Workflow Info */}
12761275
<div className='pl-4'>{renderWorkflowName()}</div>
12771276

1278-
{/* Middle Section - Reserved for future use */}
1279-
<div className='flex-1' />
1277+
{/* Middle Section - Connection Status */}
1278+
<div className='flex flex-1 justify-center'>
1279+
<UserAvatarStack />
1280+
</div>
12801281

12811282
{/* Right Section - Actions */}
12821283
<div className='flex items-center gap-1 pr-4'>

apps/sim/executor/handlers/loop/loop-handler.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ export class LoopBlockHandler implements BlockHandler {
4444
}
4545

4646
const currentIteration = context.loopIterations.get(block.id) || 0
47-
let maxIterations = loop.iterations || DEFAULT_MAX_ITERATIONS
48-
49-
// For forEach loops, we need to check the actual items length
47+
let maxIterations: number
5048
let forEachItems: any[] | Record<string, any> | null = null
5149
if (loop.loopType === 'forEach') {
5250
if (
@@ -71,14 +69,19 @@ export class LoopBlockHandler implements BlockHandler {
7169
)
7270
}
7371

74-
// Adjust max iterations based on actual items
72+
// For forEach, max iterations = items length
7573
const itemsLength = Array.isArray(forEachItems)
7674
? forEachItems.length
7775
: Object.keys(forEachItems).length
78-
maxIterations = Math.min(maxIterations, itemsLength)
76+
77+
maxIterations = itemsLength
78+
7979
logger.info(
80-
`Loop ${block.id} max iterations set to ${maxIterations} based on ${itemsLength} items`
80+
`forEach loop ${block.id} - Items: ${itemsLength}, Max iterations: ${maxIterations}`
8181
)
82+
} else {
83+
maxIterations = loop.iterations || DEFAULT_MAX_ITERATIONS
84+
logger.info(`For loop ${block.id} - Max iterations: ${maxIterations}`)
8285
}
8386

8487
logger.info(

apps/sim/executor/loops.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,26 @@ export class LoopManager {
8181
// Determine the maximum iterations
8282
let maxIterations = loop.iterations || this.defaultIterations
8383

84-
// For forEach loops, check the actual items length
84+
// For forEach loops, use the actual items length
8585
if (loop.loopType === 'forEach' && loop.forEachItems) {
8686
// First check if the items have already been evaluated and stored by the loop handler
8787
const storedItems = context.loopItems.get(`${loopId}_items`)
8888
if (storedItems) {
8989
const itemsLength = Array.isArray(storedItems)
9090
? storedItems.length
9191
: Object.keys(storedItems).length
92-
maxIterations = Math.min(maxIterations, itemsLength)
92+
93+
maxIterations = itemsLength
9394
logger.info(
94-
`Loop ${loopId} using stored items length: ${itemsLength} (max iterations: ${maxIterations})`
95+
`forEach loop ${loopId} - Items: ${itemsLength}, Max iterations: ${maxIterations}`
9596
)
9697
} else {
97-
// Fallback to parsing the forEachItems string if it's not a reference
9898
const itemsLength = this.getItemsLength(loop.forEachItems)
9999
if (itemsLength > 0) {
100-
maxIterations = Math.min(maxIterations, itemsLength)
100+
maxIterations = itemsLength
101+
logger.info(
102+
`forEach loop ${loopId} - Parsed items: ${itemsLength}, Max iterations: ${maxIterations}`
103+
)
101104
}
102105
}
103106
}

0 commit comments

Comments
 (0)