Skip to content

Commit 26243b9

Browse files
authored
fix(code-subblock): added validation to not parse non-variables as variables in the code subblock (#1240)
* fix(code-subblock): added validation to not parse non-variables as variables in the code subblock * fix wand prompt bar styling * fix error message for available connected blocks to only show connected available blocks, not block ID's * ui
1 parent 3656d3d commit 26243b9

File tree

8 files changed

+124
-68
lines changed

8 files changed

+124
-68
lines changed

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

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ export function WandPromptBar({
8181
<div
8282
ref={promptBarRef}
8383
className={cn(
84-
'-top-20 absolute right-0 left-0',
85-
'rounded-xl border bg-background shadow-lg',
84+
'-translate-y-3 absolute right-0 bottom-full left-0 gap-2',
85+
'rounded-lg border bg-background shadow-lg',
8686
'z-9999999 transition-all duration-150',
8787
isExiting ? 'opacity-0' : 'opacity-100',
8888
className
8989
)}
9090
>
9191
<div className='flex items-center gap-2 p-2'>
92-
<div className={cn('status-indicator ml-1', isStreaming && 'streaming')} />
92+
<div className={cn('status-indicator ml-2 self-center', isStreaming && 'streaming')} />
9393

9494
<div className='relative flex-1'>
9595
<Input
@@ -98,7 +98,7 @@ export function WandPromptBar({
9898
placeholder={placeholder}
9999
className={cn(
100100
'rounded-xl border-0 text-foreground text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0',
101-
isStreaming && 'text-primary',
101+
isStreaming && 'text-foreground/70',
102102
(isLoading || isStreaming) && 'loading-placeholder'
103103
)}
104104
onKeyDown={(e) => {
@@ -111,11 +111,6 @@ export function WandPromptBar({
111111
disabled={isLoading || isStreaming}
112112
autoFocus={!isStreaming}
113113
/>
114-
{isStreaming && (
115-
<div className='pointer-events-none absolute inset-0 h-full w-full overflow-hidden'>
116-
<div className='shimmer-effect' />
117-
</div>
118-
)}
119114
</div>
120115

121116
<Button
@@ -141,14 +136,6 @@ export function WandPromptBar({
141136
</div>
142137

143138
<style jsx global>{`
144-
@keyframes shimmer {
145-
0% {
146-
transform: translateX(-100%);
147-
}
148-
100% {
149-
transform: translateX(100%);
150-
}
151-
}
152139
153140
@keyframes smoke-pulse {
154141
0%,
@@ -164,8 +151,8 @@ export function WandPromptBar({
164151
165152
.status-indicator {
166153
position: relative;
167-
width: 16px;
168-
height: 16px;
154+
width: 12px;
155+
height: 12px;
169156
border-radius: 50%;
170157
overflow: hidden;
171158
background-color: hsl(var(--muted-foreground) / 0.5);
@@ -183,36 +170,20 @@ export function WandPromptBar({
183170
border-radius: 50%;
184171
background: radial-gradient(
185172
circle,
186-
hsl(var(--primary) / 0.7) 0%,
187-
hsl(var(--primary) / 0.2) 60%,
173+
hsl(var(--primary) / 0.9) 0%,
174+
hsl(var(--primary) / 0.4) 60%,
188175
transparent 80%
189176
);
190177
animation: smoke-pulse 1.8s ease-in-out infinite;
178+
opacity: 0.9;
191179
}
192180
193-
.shimmer-effect {
194-
position: absolute;
195-
top: 0;
196-
left: 0;
197-
width: 100%;
198-
height: 100%;
199-
background: linear-gradient(
200-
90deg,
201-
rgba(255, 255, 255, 0) 0%,
202-
rgba(255, 255, 255, 0.4) 50%,
203-
rgba(255, 255, 255, 0) 100%
204-
);
205-
animation: shimmer 2s infinite;
181+
.dark .status-indicator.streaming::before {
182+
background: #6b7280;
183+
opacity: 0.9;
184+
animation: smoke-pulse 1.8s ease-in-out infinite;
206185
}
207186
208-
.dark .shimmer-effect {
209-
background: linear-gradient(
210-
90deg,
211-
rgba(50, 50, 50, 0) 0%,
212-
rgba(80, 80, 80, 0.4) 50%,
213-
rgba(50, 50, 50, 0) 100%
214-
);
215-
}
216187
`}</style>
217188
</div>
218189
)

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,8 @@ IMPORTANT FORMATTING RULES:
404404
<div
405405
className={cn(
406406
'group relative min-h-[100px] rounded-md border border-input bg-background font-mono text-sm transition-colors',
407-
isConnecting && 'ring-2 ring-blue-500 ring-offset-2',
408-
!isValidJson && 'border-destructive bg-destructive/10'
407+
isConnecting && 'ring-2 ring-blue-500 ring-offset-2'
409408
)}
410-
title={!isValidJson ? 'Invalid JSON' : undefined}
411409
onDragOver={(e) => e.preventDefault()}
412410
onDrop={handleDrop}
413411
>
@@ -419,7 +417,7 @@ IMPORTANT FORMATTING RULES:
419417
onClick={isPromptVisible ? hidePromptInline : showPromptInline}
420418
disabled={isAiLoading || isAiStreaming}
421419
aria-label='Generate code with AI'
422-
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-primary hover:shadow'
420+
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow'
423421
>
424422
<Wand2 className='h-4 w-4' />
425423
</Button>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ export function LongInput({
426426
}
427427
disabled={wandHook.isLoading || wandHook.isStreaming || disabled}
428428
aria-label='Generate content with AI'
429-
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-primary hover:shadow'
429+
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow'
430430
>
431431
<Wand2 className='h-4 w-4' />
432432
</Button>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ export function ShortInput({
436436
}
437437
disabled={wandHook.isLoading || wandHook.isStreaming || disabled}
438438
aria-label='Generate content with AI'
439-
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-primary hover:shadow'
439+
className='h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow'
440440
>
441441
<Wand2 className='h-4 w-4' />
442442
</Button>

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'prismjs/components/prism-json'
66
import 'prismjs/themes/prism.css'
77
import { Wand2 } from 'lucide-react'
88
import Editor from 'react-simple-code-editor'
9+
import { Button } from '@/components/ui/button'
910
import { cn } from '@/lib/utils'
1011

1112
interface CodeEditorProps {
@@ -213,19 +214,16 @@ export function CodeEditor({
213214
)}
214215
>
215216
{showWandButton && onWandClick && (
216-
<button
217+
<Button
218+
variant='ghost'
219+
size='icon'
217220
onClick={onWandClick}
218221
disabled={wandButtonDisabled}
219-
className={cn(
220-
'absolute top-2 right-2 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-transparent bg-muted/80 p-0 text-foreground shadow-sm transition-all duration-200',
221-
'hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow',
222-
'opacity-0 transition-opacity group-hover:opacity-100',
223-
wandButtonDisabled && 'cursor-not-allowed opacity-50'
224-
)}
225222
aria-label='Generate with AI'
223+
className='absolute top-2 right-3 z-10 h-8 w-8 rounded-full border border-transparent bg-muted/80 text-muted-foreground opacity-0 shadow-sm transition-all duration-200 hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow group-hover:opacity-100'
226224
>
227225
<Wand2 className='h-4 w-4' />
228-
</button>
226+
</Button>
229227
)}
230228

231229
{!showWandButton && code.split('\n').length > 5 && (

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useMemo, useRef, useState } from 'react'
2-
import { Code, FileJson, Trash2, X } from 'lucide-react'
2+
import { AlertTriangle, Code, FileJson, Trash2, X } from 'lucide-react'
33
import { useParams } from 'next/navigation'
44
import {
55
AlertDialog,
@@ -934,11 +934,18 @@ try {
934934
<Label htmlFor='json-schema' className='font-medium'>
935935
JSON Schema
936936
</Label>
937+
{schemaError &&
938+
!schemaGeneration.isStreaming && ( // Hide schema error while streaming
939+
<Tooltip>
940+
<TooltipTrigger asChild>
941+
<AlertTriangle className='h-4 w-4 cursor-pointer text-destructive' />
942+
</TooltipTrigger>
943+
<TooltipContent side='top'>
944+
<p>Invalid JSON</p>
945+
</TooltipContent>
946+
</Tooltip>
947+
)}
937948
</div>
938-
{schemaError &&
939-
!schemaGeneration.isStreaming && ( // Hide schema error while streaming
940-
<div className='ml-4 break-words text-red-600 text-sm'>{schemaError}</div>
941-
)}
942949
</div>
943950
<CodeEditor
944951
value={jsonSchema}
@@ -975,7 +982,6 @@ try {
975982
}`}
976983
minHeight='360px'
977984
className={cn(
978-
schemaError && !schemaGeneration.isStreaming ? 'border-red-500' : '',
979985
(schemaGeneration.isLoading || schemaGeneration.isStreaming) &&
980986
'cursor-not-allowed opacity-50'
981987
)}

apps/sim/executor/resolver/resolver.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1610,7 +1610,7 @@ describe('InputResolver', () => {
16101610
}
16111611

16121612
expect(() => connectionResolver.resolveInputs(testBlock, contextWithConnections)).toThrow(
1613-
/Available connected blocks:.*Agent Block.*agent-1.*start/
1613+
/Available connected blocks:.*Agent Block.*Start/
16141614
)
16151615
})
16161616

apps/sim/executor/resolver/resolver.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,18 @@ export class InputResolver {
463463
const blockMatches = value.match(/<([^>]+)>/g)
464464
if (!blockMatches) return value
465465

466-
// If we're in an API block body, check each match to see if it looks like XML rather than a reference
466+
// Filter out patterns that are clearly not variable references (e.g., comparison operators)
467+
const validBlockMatches = blockMatches.filter((match) => this.isValidVariableReference(match))
468+
469+
// If no valid matches found after filtering, return original value
470+
if (validBlockMatches.length === 0) {
471+
return value
472+
}
473+
474+
// If we're in an API block body, check each valid match to see if it looks like XML rather than a reference
467475
if (
468476
currentBlock.metadata?.id === 'api' &&
469-
blockMatches.some((match) => {
477+
validBlockMatches.some((match) => {
470478
const innerContent = match.slice(1, -1)
471479
// Patterns that suggest this is XML, not a block reference:
472480
return (
@@ -490,7 +498,7 @@ export class InputResolver {
490498
value.includes('}') &&
491499
value.includes('`')
492500

493-
for (const match of blockMatches) {
501+
for (const match of validBlockMatches) {
494502
// Skip variables - they've already been processed
495503
if (match.startsWith('<variable.')) {
496504
continue
@@ -814,6 +822,63 @@ export class InputResolver {
814822
return resolvedValue
815823
}
816824

825+
/**
826+
* Validates if a match with < and > is actually a variable reference.
827+
* Valid variable references must:
828+
* - Have no space after the opening <
829+
* - Contain a dot (.)
830+
* - Have no spaces until the closing >
831+
* - Not be comparison operators or HTML tags
832+
*
833+
* @param match - The matched string including < and >
834+
* @returns Whether this is a valid variable reference
835+
*/
836+
private isValidVariableReference(match: string): boolean {
837+
const innerContent = match.slice(1, -1)
838+
839+
if (!innerContent.includes('.')) {
840+
return false
841+
}
842+
843+
const dotIndex = innerContent.indexOf('.')
844+
const beforeDot = innerContent.substring(0, dotIndex)
845+
const afterDot = innerContent.substring(dotIndex + 1)
846+
847+
if (afterDot.includes(' ')) {
848+
return false
849+
}
850+
851+
if (
852+
beforeDot.match(/^\s*[<>=!]+\s*$/) ||
853+
beforeDot.match(/\s[<>=!]+\s/) ||
854+
beforeDot.match(/^[<>=!]+\s/)
855+
) {
856+
return false
857+
}
858+
859+
if (innerContent.startsWith(' ')) {
860+
return false
861+
}
862+
863+
if (innerContent.match(/^[a-zA-Z][a-zA-Z0-9]*$/) && !innerContent.includes('.')) {
864+
return false
865+
}
866+
867+
if (innerContent.match(/^[<>=!]+\s/)) {
868+
return false
869+
}
870+
871+
if (beforeDot.match(/[+*/=<>!]/)) {
872+
return false
873+
}
874+
875+
if (afterDot.match(/[+\-*/=<>!]/)) {
876+
return false
877+
}
878+
879+
return true
880+
}
881+
817882
/**
818883
* Determines if a string contains a properly formatted environment variable reference.
819884
* Valid references are either:
@@ -1145,6 +1210,24 @@ export class InputResolver {
11451210
return [...new Set(names)] // Remove duplicates
11461211
}
11471212

1213+
/**
1214+
* Gets user-friendly block names for error messages.
1215+
* Only returns the actual block names that users see in the UI.
1216+
*/
1217+
private getAccessibleBlockNamesForError(currentBlockId: string): string[] {
1218+
const accessibleBlockIds = this.getAccessibleBlocks(currentBlockId)
1219+
const names: string[] = []
1220+
1221+
for (const blockId of accessibleBlockIds) {
1222+
const block = this.blockById.get(blockId)
1223+
if (block?.metadata?.name) {
1224+
names.push(block.metadata.name)
1225+
}
1226+
}
1227+
1228+
return [...new Set(names)] // Remove duplicates
1229+
}
1230+
11481231
/**
11491232
* Checks if a block reference could potentially be valid without throwing errors.
11501233
* Used to filter out non-block patterns like <test> from block reference resolution.
@@ -1197,7 +1280,7 @@ export class InputResolver {
11971280
}
11981281

11991282
if (!sourceBlock) {
1200-
const accessibleNames = this.getAccessibleBlockNames(currentBlockId)
1283+
const accessibleNames = this.getAccessibleBlockNamesForError(currentBlockId)
12011284
return {
12021285
isValid: false,
12031286
errorMessage: `Block "${blockRef}" was not found. Available connected blocks: ${accessibleNames.join(', ')}`,
@@ -1207,7 +1290,7 @@ export class InputResolver {
12071290
// Check if block is accessible (connected)
12081291
const accessibleBlocks = this.getAccessibleBlocks(currentBlockId)
12091292
if (!accessibleBlocks.has(sourceBlock.id)) {
1210-
const accessibleNames = this.getAccessibleBlockNames(currentBlockId)
1293+
const accessibleNames = this.getAccessibleBlockNamesForError(currentBlockId)
12111294
return {
12121295
isValid: false,
12131296
errorMessage: `Block "${blockRef}" is not connected to this block. Available connected blocks: ${accessibleNames.join(', ')}`,

0 commit comments

Comments
 (0)