Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ export function WandPromptBar({
<div
ref={promptBarRef}
className={cn(
'-top-20 absolute right-0 left-0',
'rounded-xl border bg-background shadow-lg',
'-translate-y-3 absolute right-0 bottom-full left-0 gap-2',
'rounded-lg border bg-background shadow-lg',
'z-9999999 transition-all duration-150',
isExiting ? 'opacity-0' : 'opacity-100',
className
)}
>
<div className='flex items-center gap-2 p-2'>
<div className={cn('status-indicator ml-1', isStreaming && 'streaming')} />
<div className={cn('status-indicator ml-2 self-center', isStreaming && 'streaming')} />

<div className='relative flex-1'>
<Input
Expand All @@ -98,7 +98,7 @@ export function WandPromptBar({
placeholder={placeholder}
className={cn(
'rounded-xl border-0 text-foreground text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0',
isStreaming && 'text-primary',
isStreaming && 'text-foreground/70',
(isLoading || isStreaming) && 'loading-placeholder'
)}
onKeyDown={(e) => {
Expand All @@ -111,11 +111,6 @@ export function WandPromptBar({
disabled={isLoading || isStreaming}
autoFocus={!isStreaming}
/>
{isStreaming && (
<div className='pointer-events-none absolute inset-0 h-full w-full overflow-hidden'>
<div className='shimmer-effect' />
</div>
)}
</div>

<Button
Expand All @@ -141,14 +136,6 @@ export function WandPromptBar({
</div>

<style jsx global>{`
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}

@keyframes smoke-pulse {
0%,
Expand All @@ -164,8 +151,8 @@ export function WandPromptBar({

.status-indicator {
position: relative;
width: 16px;
height: 16px;
width: 12px;
height: 12px;
border-radius: 50%;
overflow: hidden;
background-color: hsl(var(--muted-foreground) / 0.5);
Expand All @@ -183,36 +170,20 @@ export function WandPromptBar({
border-radius: 50%;
background: radial-gradient(
circle,
hsl(var(--primary) / 0.7) 0%,
hsl(var(--primary) / 0.2) 60%,
hsl(var(--primary) / 0.9) 0%,
hsl(var(--primary) / 0.4) 60%,
transparent 80%
);
animation: smoke-pulse 1.8s ease-in-out infinite;
opacity: 0.9;
}

.shimmer-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.4) 50%,
rgba(255, 255, 255, 0) 100%
);
animation: shimmer 2s infinite;
.dark .status-indicator.streaming::before {
background: #6b7280;
opacity: 0.9;
animation: smoke-pulse 1.8s ease-in-out infinite;
}

.dark .shimmer-effect {
background: linear-gradient(
90deg,
rgba(50, 50, 50, 0) 0%,
rgba(80, 80, 80, 0.4) 50%,
rgba(50, 50, 50, 0) 100%
);
}
`}</style>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,8 @@ IMPORTANT FORMATTING RULES:
<div
className={cn(
'group relative min-h-[100px] rounded-md border border-input bg-background font-mono text-sm transition-colors',
isConnecting && 'ring-2 ring-blue-500 ring-offset-2',
!isValidJson && 'border-destructive bg-destructive/10'
isConnecting && 'ring-2 ring-blue-500 ring-offset-2'
)}
title={!isValidJson ? 'Invalid JSON' : undefined}
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
>
Expand All @@ -419,7 +417,7 @@ IMPORTANT FORMATTING RULES:
onClick={isPromptVisible ? hidePromptInline : showPromptInline}
disabled={isAiLoading || isAiStreaming}
aria-label='Generate code with AI'
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'
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'
>
<Wand2 className='h-4 w-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export function LongInput({
}
disabled={wandHook.isLoading || wandHook.isStreaming || disabled}
aria-label='Generate content with AI'
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'
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'
>
<Wand2 className='h-4 w-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export function ShortInput({
}
disabled={wandHook.isLoading || wandHook.isStreaming || disabled}
aria-label='Generate content with AI'
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'
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'
>
<Wand2 className='h-4 w-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'prismjs/components/prism-json'
import 'prismjs/themes/prism.css'
import { Wand2 } from 'lucide-react'
import Editor from 'react-simple-code-editor'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'

interface CodeEditorProps {
Expand Down Expand Up @@ -213,19 +214,16 @@ export function CodeEditor({
)}
>
{showWandButton && onWandClick && (
<button
<Button
variant='ghost'
size='icon'
onClick={onWandClick}
disabled={wandButtonDisabled}
className={cn(
'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',
'hover:border-primary/20 hover:bg-muted hover:text-foreground hover:shadow',
'opacity-0 transition-opacity group-hover:opacity-100',
wandButtonDisabled && 'cursor-not-allowed opacity-50'
)}
aria-label='Generate with AI'
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'
>
<Wand2 className='h-4 w-4' />
</button>
</Button>
)}

{!showWandButton && code.split('\n').length > 5 && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { Code, FileJson, Trash2, X } from 'lucide-react'
import { AlertTriangle, Code, FileJson, Trash2, X } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
AlertDialog,
Expand Down Expand Up @@ -934,11 +934,18 @@ try {
<Label htmlFor='json-schema' className='font-medium'>
JSON Schema
</Label>
{schemaError &&
!schemaGeneration.isStreaming && ( // Hide schema error while streaming
<Tooltip>
<TooltipTrigger asChild>
<AlertTriangle className='h-4 w-4 cursor-pointer text-destructive' />
</TooltipTrigger>
<TooltipContent side='top'>
<p>Invalid JSON</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider making the tooltip message more specific to the actual error type rather than generic 'Invalid JSON'

</TooltipContent>
</Tooltip>
)}
</div>
{schemaError &&
!schemaGeneration.isStreaming && ( // Hide schema error while streaming
<div className='ml-4 break-words text-red-600 text-sm'>{schemaError}</div>
)}
</div>
<CodeEditor
value={jsonSchema}
Expand Down Expand Up @@ -975,7 +982,6 @@ try {
}`}
minHeight='360px'
className={cn(
schemaError && !schemaGeneration.isStreaming ? 'border-red-500' : '',
(schemaGeneration.isLoading || schemaGeneration.isStreaming) &&
'cursor-not-allowed opacity-50'
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/executor/resolver/resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ describe('InputResolver', () => {
}

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

Expand Down
93 changes: 88 additions & 5 deletions apps/sim/executor/resolver/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,18 @@ export class InputResolver {
const blockMatches = value.match(/<([^>]+)>/g)
if (!blockMatches) return value

// If we're in an API block body, check each match to see if it looks like XML rather than a reference
// Filter out patterns that are clearly not variable references (e.g., comparison operators)
const validBlockMatches = blockMatches.filter((match) => this.isValidVariableReference(match))

// If no valid matches found after filtering, return original value
if (validBlockMatches.length === 0) {
return value
}

// If we're in an API block body, check each valid match to see if it looks like XML rather than a reference
if (
currentBlock.metadata?.id === 'api' &&
blockMatches.some((match) => {
validBlockMatches.some((match) => {
const innerContent = match.slice(1, -1)
// Patterns that suggest this is XML, not a block reference:
return (
Expand All @@ -490,7 +498,7 @@ export class InputResolver {
value.includes('}') &&
value.includes('`')

for (const match of blockMatches) {
for (const match of validBlockMatches) {
// Skip variables - they've already been processed
if (match.startsWith('<variable.')) {
continue
Expand Down Expand Up @@ -814,6 +822,63 @@ export class InputResolver {
return resolvedValue
}

/**
* Validates if a match with < and > is actually a variable reference.
* Valid variable references must:
* - Have no space after the opening <
* - Contain a dot (.)
* - Have no spaces until the closing >
* - Not be comparison operators or HTML tags
*
* @param match - The matched string including < and >
* @returns Whether this is a valid variable reference
*/
private isValidVariableReference(match: string): boolean {
const innerContent = match.slice(1, -1)

if (!innerContent.includes('.')) {
return false
}

const dotIndex = innerContent.indexOf('.')
const beforeDot = innerContent.substring(0, dotIndex)
const afterDot = innerContent.substring(dotIndex + 1)

if (afterDot.includes(' ')) {
return false
}

if (
beforeDot.match(/^\s*[<>=!]+\s*$/) ||
beforeDot.match(/\s[<>=!]+\s/) ||
beforeDot.match(/^[<>=!]+\s/)
) {
return false
}

if (innerContent.startsWith(' ')) {
return false
}

if (innerContent.match(/^[a-zA-Z][a-zA-Z0-9]*$/) && !innerContent.includes('.')) {
return false
}

if (innerContent.match(/^[<>=!]+\s/)) {
return false
}

if (beforeDot.match(/[+*/=<>!]/)) {
return false
}

if (afterDot.match(/[+\-*/=<>!]/)) {
return false
}

return true
}

/**
* Determines if a string contains a properly formatted environment variable reference.
* Valid references are either:
Expand Down Expand Up @@ -1145,6 +1210,24 @@ export class InputResolver {
return [...new Set(names)] // Remove duplicates
}

/**
* Gets user-friendly block names for error messages.
* Only returns the actual block names that users see in the UI.
*/
private getAccessibleBlockNamesForError(currentBlockId: string): string[] {
const accessibleBlockIds = this.getAccessibleBlocks(currentBlockId)
const names: string[] = []

for (const blockId of accessibleBlockIds) {
const block = this.blockById.get(blockId)
if (block?.metadata?.name) {
names.push(block.metadata.name)
}
}

return [...new Set(names)] // Remove duplicates
}

/**
* Checks if a block reference could potentially be valid without throwing errors.
* Used to filter out non-block patterns like <test> from block reference resolution.
Expand Down Expand Up @@ -1197,7 +1280,7 @@ export class InputResolver {
}

if (!sourceBlock) {
const accessibleNames = this.getAccessibleBlockNames(currentBlockId)
const accessibleNames = this.getAccessibleBlockNamesForError(currentBlockId)
return {
isValid: false,
errorMessage: `Block "${blockRef}" was not found. Available connected blocks: ${accessibleNames.join(', ')}`,
Expand All @@ -1207,7 +1290,7 @@ export class InputResolver {
// Check if block is accessible (connected)
const accessibleBlocks = this.getAccessibleBlocks(currentBlockId)
if (!accessibleBlocks.has(sourceBlock.id)) {
const accessibleNames = this.getAccessibleBlockNames(currentBlockId)
const accessibleNames = this.getAccessibleBlockNamesForError(currentBlockId)
return {
isValid: false,
errorMessage: `Block "${blockRef}" is not connected to this block. Available connected blocks: ${accessibleNames.join(', ')}`,
Expand Down