mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Added code ability to be dropped in and added ability to prevent drop in block config
This commit is contained in:
@@ -29,6 +29,7 @@ const MATCHING_PAIRS: Record<string, string> = {
|
|||||||
interface CodeProps {
|
interface CodeProps {
|
||||||
blockId: string
|
blockId: string
|
||||||
subBlockId: string
|
subBlockId: string
|
||||||
|
isConnecting: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCodeLines(blockId: string, subBlockId: string) {
|
function useCodeLines(blockId: string, subBlockId: string) {
|
||||||
@@ -72,6 +73,64 @@ function useCodeLines(blockId: string, subBlockId: string) {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleDrop = useCallback(
|
||||||
|
(lineIndex: number, e: React.DragEvent<HTMLTextAreaElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(e.dataTransfer.getData('application/json'))
|
||||||
|
|
||||||
|
const isValidConnectionBlock =
|
||||||
|
data.type === 'connectionBlock' &&
|
||||||
|
data.connectionData.sourceBlockId === blockId
|
||||||
|
|
||||||
|
if (!isValidConnectionBlock) return
|
||||||
|
|
||||||
|
const textarea = e.currentTarget
|
||||||
|
const start = textarea.selectionStart
|
||||||
|
const end = textarea.selectionEnd
|
||||||
|
const currentContent = lines[lineIndex].content
|
||||||
|
|
||||||
|
const connectionName = data.connectionData.name
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
const outputSuffix =
|
||||||
|
data.connectionData.outputType === 'any'
|
||||||
|
? 'res'
|
||||||
|
: data.connectionData.outputType
|
||||||
|
|
||||||
|
const tag = `<${connectionName}.${outputSuffix}>`
|
||||||
|
const newContent =
|
||||||
|
currentContent.substring(0, start) +
|
||||||
|
tag +
|
||||||
|
currentContent.substring(end)
|
||||||
|
|
||||||
|
setLines((prevLines) => {
|
||||||
|
const newLines = [...prevLines]
|
||||||
|
newLines[lineIndex] = {
|
||||||
|
...newLines[lineIndex],
|
||||||
|
content: newContent,
|
||||||
|
}
|
||||||
|
return newLines
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set cursor position after the inserted tag
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = start + tag.length
|
||||||
|
}, 0)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse drop data:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[blockId, lines, setLines]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDragOver = useCallback(
|
||||||
|
(e: React.DragEvent<HTMLTextAreaElement>) => {
|
||||||
|
e.preventDefault() // Required to allow drops
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lines,
|
lines,
|
||||||
setLines,
|
setLines,
|
||||||
@@ -80,10 +139,12 @@ function useCodeLines(blockId: string, subBlockId: string) {
|
|||||||
handleChange,
|
handleChange,
|
||||||
selection,
|
selection,
|
||||||
setSelection,
|
setSelection,
|
||||||
|
handleDrop,
|
||||||
|
handleDragOver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Code({ blockId, subBlockId }: CodeProps) {
|
export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
|
||||||
const {
|
const {
|
||||||
lines,
|
lines,
|
||||||
setLines,
|
setLines,
|
||||||
@@ -92,6 +153,8 @@ export function Code({ blockId, subBlockId }: CodeProps) {
|
|||||||
handleChange,
|
handleChange,
|
||||||
selection,
|
selection,
|
||||||
setSelection,
|
setSelection,
|
||||||
|
handleDrop,
|
||||||
|
handleDragOver,
|
||||||
} = useCodeLines(blockId, subBlockId)
|
} = useCodeLines(blockId, subBlockId)
|
||||||
const textareaRefs = useRef<(HTMLTextAreaElement | null)[]>([])
|
const textareaRefs = useRef<(HTMLTextAreaElement | null)[]>([])
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -591,7 +654,10 @@ export function Code({ blockId, subBlockId }: CodeProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="font-mono text-sm border rounded-md overflow-hidden relative"
|
className={cn(
|
||||||
|
'font-mono text-sm border rounded-md overflow-hidden relative',
|
||||||
|
isConnecting && 'ring-2 ring-blue-500 ring-offset-2'
|
||||||
|
)}
|
||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
>
|
>
|
||||||
<div className="absolute top-0 left-0 z-50 h-full bg-background">
|
<div className="absolute top-0 left-0 z-50 h-full bg-background">
|
||||||
@@ -634,6 +700,8 @@ export function Code({ blockId, subBlockId }: CodeProps) {
|
|||||||
onChange={(e) => handleChange(i, e.target.value)}
|
onChange={(e) => handleChange(i, e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={() => setCurrentLine(i)}
|
onFocus={() => setCurrentLine(i)}
|
||||||
|
onDrop={(e) => handleDrop(i, e)}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
className={cn(
|
className={cn(
|
||||||
textareaClassName,
|
textareaClassName,
|
||||||
'overflow-hidden w-full',
|
'overflow-hidden w-full',
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { Textarea } from '@/components/ui/textarea'
|
|||||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
|
import { SubBlockConfig } from '@/blocks/types'
|
||||||
|
|
||||||
interface LongInputProps {
|
interface LongInputProps {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
blockId: string
|
blockId: string
|
||||||
subBlockId: string
|
subBlockId: string
|
||||||
isConnecting: boolean
|
isConnecting: boolean
|
||||||
|
config: SubBlockConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LongInput({
|
export function LongInput({
|
||||||
@@ -15,6 +17,7 @@ export function LongInput({
|
|||||||
blockId,
|
blockId,
|
||||||
subBlockId,
|
subBlockId,
|
||||||
isConnecting,
|
isConnecting,
|
||||||
|
config,
|
||||||
}: LongInputProps) {
|
}: LongInputProps) {
|
||||||
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
@@ -86,6 +89,7 @@ export function LongInput({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full resize-none placeholder:text-muted-foreground/50 allow-scroll text-transparent caret-foreground break-words whitespace-pre-wrap',
|
'w-full resize-none placeholder:text-muted-foreground/50 allow-scroll text-transparent caret-foreground break-words whitespace-pre-wrap',
|
||||||
isConnecting &&
|
isConnecting &&
|
||||||
|
config?.droppable !== false &&
|
||||||
'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
|
'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
|
||||||
)}
|
)}
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Input } from '@/components/ui/input'
|
|||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { SubBlockConfig } from '@/blocks/types'
|
||||||
|
|
||||||
interface ShortInputProps {
|
interface ShortInputProps {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
@@ -9,6 +10,7 @@ interface ShortInputProps {
|
|||||||
blockId: string
|
blockId: string
|
||||||
subBlockId: string
|
subBlockId: string
|
||||||
isConnecting: boolean
|
isConnecting: boolean
|
||||||
|
config: SubBlockConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShortInput({
|
export function ShortInput({
|
||||||
@@ -17,6 +19,7 @@ export function ShortInput({
|
|||||||
placeholder,
|
placeholder,
|
||||||
password,
|
password,
|
||||||
isConnecting,
|
isConnecting,
|
||||||
|
config,
|
||||||
}: ShortInputProps) {
|
}: ShortInputProps) {
|
||||||
const [isFocused, setIsFocused] = useState(false)
|
const [isFocused, setIsFocused] = useState(false)
|
||||||
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
||||||
@@ -107,6 +110,7 @@ export function ShortInput({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full placeholder:text-muted-foreground/50 allow-scroll text-transparent caret-foreground',
|
'w-full placeholder:text-muted-foreground/50 allow-scroll text-transparent caret-foreground',
|
||||||
isConnecting &&
|
isConnecting &&
|
||||||
|
config?.droppable !== false &&
|
||||||
'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
|
'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
|
||||||
)}
|
)}
|
||||||
placeholder={placeholder ?? ''}
|
placeholder={placeholder ?? ''}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
|||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
password={config.password}
|
password={config.password}
|
||||||
isConnecting={isConnecting}
|
isConnecting={isConnecting}
|
||||||
|
config={config}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'long-input':
|
case 'long-input':
|
||||||
@@ -38,6 +39,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
|||||||
subBlockId={config.id}
|
subBlockId={config.id}
|
||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
isConnecting={isConnecting}
|
isConnecting={isConnecting}
|
||||||
|
config={config}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'dropdown':
|
case 'dropdown':
|
||||||
@@ -71,7 +73,13 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'code':
|
case 'code':
|
||||||
return <Code blockId={blockId} subBlockId={config.id} />
|
return (
|
||||||
|
<Code
|
||||||
|
blockId={blockId}
|
||||||
|
subBlockId={config.id}
|
||||||
|
isConnecting={isConnecting}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'switch':
|
case 'switch':
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ export const AgentBlock: BlockConfig<ChatResponse> = {
|
|||||||
type: "short-input",
|
type: "short-input",
|
||||||
layout: "full",
|
layout: "full",
|
||||||
placeholder: "Enter your API key",
|
placeholder: "Enter your API key",
|
||||||
password: true
|
password: true,
|
||||||
|
droppable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'responseFormat',
|
id: 'responseFormat',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export interface SubBlockConfig {
|
|||||||
columns?: string[]
|
columns?: string[]
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
password?: boolean
|
password?: boolean
|
||||||
|
droppable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockConfig<T extends ToolResponse = ToolResponse> {
|
export interface BlockConfig<T extends ToolResponse = ToolResponse> {
|
||||||
|
|||||||
Reference in New Issue
Block a user