Changed block types config for simplicity

This commit is contained in:
Emir Karabeg
2025-02-14 16:39:14 -08:00
parent 0aeeb65bc6
commit 73e4bbc3b4
34 changed files with 1542 additions and 1637 deletions
@@ -39,7 +39,7 @@ interface ToolParam {
// Assumes the first tool in the access array is the tool to be used
// TODO: Switch to getting tools instead of tool blocks once we switch to providers
const getToolIdFromBlock = (blockType: string): string | undefined => {
const block = getAllBlocks().find((block) => block.type === blockType)
const block = getAllBlocks().find((block) => block.id === blockType)
return block?.tools.access[0]
}
@@ -62,7 +62,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
const [open, setOpen] = useState(false)
const isWide = useWorkflowStore((state) => state.blocks[blockId]?.isWide)
const toolBlocks = getAllBlocks().filter((block) => block.toolbar.category === 'tools')
const toolBlocks = getAllBlocks().filter((block) => block.category === 'tools')
const selectedTools: StoredTool[] =
Array.isArray(value) && value.length > 0 && typeof value[0] === 'object'
@@ -71,15 +71,15 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
const handleSelectTool = (toolBlock: (typeof toolBlocks)[0]) => {
// Check if tool already exists
if (selectedTools.some((tool) => tool.type === toolBlock.type)) {
if (selectedTools.some((tool) => tool.type === toolBlock.id)) {
setOpen(false)
return
}
const toolId = getToolIdFromBlock(toolBlock.type)
const toolId = getToolIdFromBlock(toolBlock.id)
const newTool: StoredTool = {
type: toolBlock.type,
title: toolBlock.toolbar.title,
type: toolBlock.id,
title: toolBlock.name,
params: {},
isExpanded: true,
}
@@ -135,12 +135,6 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
return <Icon className={className} />
}
// Helper function to get the icon component for a tool type
const getToolIcon = (type: string) => {
const toolBlock = toolBlocks.find((block) => block.type === type)
return toolBlock?.toolbar.icon
}
return (
<div className="w-full">
{selectedTools.length === 0 ? (
@@ -161,17 +155,17 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
<CommandGroup>
{toolBlocks.map((block) => (
<CommandItem
key={block.type}
key={block.id}
onSelect={() => handleSelectTool(block)}
className="flex items-center gap-2 cursor-pointer"
>
<div
className="flex items-center justify-center w-6 h-6 rounded"
style={{ backgroundColor: block.toolbar.bgColor }}
style={{ backgroundColor: block.bgColor }}
>
<IconComponent icon={block.toolbar.icon} className="w-4 h-4 text-white" />
<IconComponent icon={block.icon} className="w-4 h-4 text-white" />
</div>
<span>{block.toolbar.title}</span>
<span>{block.name}</span>
</CommandItem>
))}
</CommandGroup>
@@ -182,7 +176,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
) : (
<div className="flex flex-wrap gap-2 min-h-[2.5rem] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background">
{selectedTools.map((tool) => {
const toolBlock = toolBlocks.find((block) => block.type === tool.type)
const toolBlock = toolBlocks.find((block) => block.id === tool.type)
const toolId = getToolIdFromBlock(tool.type)
const requiredParams = toolId ? getRequiredToolParams(toolId) : []
@@ -199,12 +193,9 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
<div className="flex items-center gap-2">
<div
className="flex items-center justify-center w-5 h-5 rounded"
style={{ backgroundColor: toolBlock?.toolbar.bgColor }}
style={{ backgroundColor: toolBlock?.bgColor }}
>
<IconComponent
icon={toolBlock?.toolbar.icon}
className="w-3 h-3 text-white"
/>
<IconComponent icon={toolBlock?.icon} className="w-3 h-3 text-white" />
</div>
<span className="text-sm font-medium">{tool.title}</span>
</div>
@@ -278,17 +269,17 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
<CommandGroup>
{toolBlocks.map((block) => (
<CommandItem
key={block.type}
key={block.id}
onSelect={() => handleSelectTool(block)}
className="flex items-center gap-2 cursor-pointer"
>
<div
className="flex items-center justify-center w-6 h-6 rounded"
style={{ backgroundColor: block.toolbar.bgColor }}
style={{ backgroundColor: block.bgColor }}
>
<IconComponent icon={block.toolbar.icon} className="w-4 h-4 text-white" />
<IconComponent icon={block.icon} className="w-4 h-4 text-white" />
</div>
<span>{block.toolbar.title}</span>
<span>{block.name}</span>
</CommandItem>
))}
</CommandGroup>
@@ -21,7 +21,6 @@ interface WorkflowBlockProps {
// Combine both interfaces into a single component
export function WorkflowBlock({ id, data, selected }: NodeProps<WorkflowBlockProps>) {
const { type, config, name } = data
const { toolbar, workflow } = config
// State management
const [isConnecting, setIsConnecting] = useState(false)
@@ -146,7 +145,7 @@ export function WorkflowBlock({ id, data, selected }: NodeProps<WorkflowBlockPro
return rows
}
const subBlockRows = groupSubBlocks(workflow.subBlocks, id)
const subBlockRows = groupSubBlocks(config.subBlocks, id)
// Name editing handlers
const handleNameClick = () => {
@@ -209,9 +208,9 @@ export function WorkflowBlock({ id, data, selected }: NodeProps<WorkflowBlockPro
<div className="flex items-center gap-3">
<div
className="flex items-center justify-center w-7 h-7 rounded"
style={{ backgroundColor: isEnabled ? toolbar.bgColor : 'gray' }}
style={{ backgroundColor: isEnabled ? config.bgColor : 'gray' }}
>
<toolbar.icon className="w-5 h-5 text-white" />
<config.icon className="w-5 h-5 text-white" />
</div>
{isEditing ? (
<input
+2 -2
View File
@@ -227,8 +227,8 @@ function WorkflowContent() {
}
const id = crypto.randomUUID()
const name = `${blockConfig.toolbar.title} ${
Object.values(blocks).filter((b) => b.type === data.type).length + 1
const name = `${blockConfig.name} ${
Object.values(blocks).filter((b) => b.id === data.type).length + 1
}`
addBlock(id, data.type, name, position)
@@ -26,7 +26,7 @@ export function ConsoleEntry({ entry, consoleWidth }: ConsoleEntryProps) {
return getBlock(entry.blockType)
}, [entry.blockType])
const BlockIcon = blockConfig?.toolbar.icon
const BlockIcon = blockConfig?.icon
const statusIcon = entry.error ? (
<AlertCircle className="h-4 w-4 text-destructive" />
@@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { Button } from '@/components/ui/button'
interface JSONViewProps {
@@ -6,7 +6,7 @@ export type ToolbarBlockProps = {
export function ToolbarBlock({ config }: ToolbarBlockProps) {
const handleDragStart = (e: React.DragEvent) => {
e.dataTransfer.setData('application/json', JSON.stringify({ type: config.type }))
e.dataTransfer.setData('application/json', JSON.stringify({ type: config.id }))
e.dataTransfer.effectAllowed = 'move'
}
@@ -18,17 +18,17 @@ export function ToolbarBlock({ config }: ToolbarBlockProps) {
>
<div
className="relative flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg"
style={{ backgroundColor: config.toolbar.bgColor }}
style={{ backgroundColor: config.bgColor }}
>
<config.toolbar.icon
<config.icon
className={`text-white transition-transform duration-200 group-hover:scale-110 ${
config.type === 'agent' ? 'w-[24px] h-[24px]' : 'w-[22px] h-[22px]'
config.id === 'agent' ? 'w-[24px] h-[24px]' : 'w-[22px] h-[22px]'
}`}
/>
</div>
<div className="flex flex-col gap-1">
<h3 className="font-medium leading-none">{config.toolbar.title}</h3>
<p className="text-sm text-muted-foreground">{config.toolbar.description}</p>
<h3 className="font-medium leading-none">{config.name}</h3>
<p className="text-sm text-muted-foreground">{config.description}</p>
</div>
</div>
)
+2 -3
View File
@@ -23,8 +23,7 @@ export function Toolbar() {
const query = searchQuery.toLowerCase()
return getAllBlocks().filter(
(block) =>
block.toolbar.title.toLowerCase().includes(query) ||
block.toolbar.description.toLowerCase().includes(query)
block.name.toLowerCase().includes(query) || block.description.toLowerCase().includes(query)
)
}, [searchQuery, activeTab])
@@ -70,7 +69,7 @@ export function Toolbar() {
<div className="p-4 pb-20">
<div className="flex flex-col gap-3">
{blocks.map((block) => (
<ToolbarBlock key={block.type} config={block} />
<ToolbarBlock key={block.id} config={block} />
))}
</div>
</div>
+2 -2
View File
@@ -67,7 +67,7 @@ export function useWorkflowExecution() {
endedAt: log.endedAt,
workflowId: activeWorkflowId,
timestamp: log.startedAt,
blockName: log.blockTitle,
blockName: log.blockName,
blockType: log.blockType,
})
})
@@ -76,7 +76,7 @@ export function useWorkflowExecution() {
if (result.logs) {
console.group('Detailed Block Logs')
result.logs.forEach((log) => {
console.log(`Block ${log.blockTitle}: Success=${log.success}`, {
console.log(`Block ${log.blockName}: Success=${log.success}`, {
output: log.output,
error: log.error,
durationMs: log.durationMs,
+133 -151
View File
@@ -23,14 +23,64 @@ interface AgentResponse extends ToolResponse {
}
export const AgentBlock: BlockConfig<AgentResponse> = {
type: 'agent',
toolbar: {
title: 'Agent',
description: 'Build an agent',
bgColor: '#7F2FFF',
icon: AgentIcon,
category: 'blocks',
},
id: 'agent',
name: 'Agent',
description: 'Build an agent',
category: 'blocks',
bgColor: '#7F2FFF',
icon: AgentIcon,
subBlocks: [
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Enter system prompt...',
},
{
id: 'context',
title: 'User Prompt',
type: 'short-input',
layout: 'full',
placeholder: 'Enter context or user message...',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'temperature',
title: 'Temperature',
type: 'slider',
layout: 'half',
min: 0,
max: 2,
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'tools',
title: 'Tools',
type: 'tool-input',
layout: 'full',
},
{
id: 'responseFormat',
title: 'Response Format',
type: 'code',
layout: 'full',
},
],
tools: {
access: [
'openai_chat',
@@ -54,165 +104,97 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
},
},
},
workflow: {
inputs: {
systemPrompt: { type: 'string', required: true },
context: { type: 'string', required: false },
model: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
responseFormat: {
type: 'json',
required: false,
description:
'Define the expected response format. If not provided, returns plain text content.',
schema: {
type: 'object',
properties: {
fields: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the field',
},
type: {
type: 'string',
enum: ['string', 'number', 'boolean', 'array', 'object'],
description: 'Type of the field',
},
isArray: {
type: 'boolean',
description: 'Whether this field contains multiple values',
},
items: {
type: 'object',
description: 'Schema for array items (required when isArray is true)',
inputs: {
systemPrompt: { type: 'string', required: true },
context: { type: 'string', required: false },
model: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
responseFormat: {
type: 'json',
required: false,
description:
'Define the expected response format. If not provided, returns plain text content.',
schema: {
type: 'object',
properties: {
fields: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the field',
},
type: {
type: 'string',
enum: ['string', 'number', 'boolean', 'array', 'object'],
description: 'Type of the field',
},
isArray: {
type: 'boolean',
description: 'Whether this field contains multiple values',
},
items: {
type: 'object',
description: 'Schema for array items (required when isArray is true)',
properties: {
type: {
type: 'string',
enum: ['string', 'number', 'boolean', 'object'],
},
properties: {
type: {
type: 'string',
enum: ['string', 'number', 'boolean', 'object'],
},
properties: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: {
type: 'string',
enum: ['string', 'number', 'boolean'],
},
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: {
type: 'string',
enum: ['string', 'number', 'boolean'],
},
required: ['name', 'type'],
},
required: ['name', 'type'],
},
},
},
description: {
type: 'string',
description: 'Description of what this field represents',
},
},
required: ['name', 'type'],
additionalProperties: false,
description: {
type: 'string',
description: 'Description of what this field represents',
},
},
required: ['name', 'type'],
additionalProperties: false,
},
},
required: ['fields'],
},
required: ['fields'],
},
temperature: { type: 'number', required: false },
tools: { type: 'json', required: false },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
toolCalls: 'any',
},
dependsOn: {
subBlockId: 'responseFormat',
condition: {
whenEmpty: {
content: 'string',
model: 'string',
tokens: 'any',
toolCalls: 'any',
},
whenFilled: 'json',
temperature: { type: 'number', required: false },
tools: { type: 'json', required: false },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
toolCalls: 'any',
},
dependsOn: {
subBlockId: 'responseFormat',
condition: {
whenEmpty: {
content: 'string',
model: 'string',
tokens: 'any',
toolCalls: 'any',
},
whenFilled: 'json',
},
},
},
subBlocks: [
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Enter system prompt...',
},
{
id: 'context',
title: 'User Prompt',
type: 'short-input',
layout: 'full',
placeholder: 'Enter context or user message...',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'temperature',
title: 'Temperature',
type: 'slider',
layout: 'half',
min: 0,
max: 2,
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'tools',
title: 'Tools',
type: 'tool-input',
layout: 'full',
},
{
id: 'responseFormat',
title: 'Response Format',
type: 'code',
layout: 'full',
placeholder: `{
"fields": [
{
"name": "sentiment",
"type": "string",
"description": "The sentiment of the text (positive, negative, neutral)"
},
{
"name": "score",
"type": "number",
"description": "Confidence score between 0 and 1"
}
]
}`,
},
],
},
}
+47 -51
View File
@@ -3,61 +3,57 @@ import { RequestResponse } from '@/tools/http/request'
import { BlockConfig } from '../types'
export const ApiBlock: BlockConfig<RequestResponse> = {
type: 'api',
toolbar: {
title: 'API',
description: 'Use any API',
bgColor: '#2F55FF',
icon: ApiIcon,
category: 'blocks',
},
id: 'api',
name: 'API',
description: 'Use any API',
category: 'blocks',
bgColor: '#2F55FF',
icon: ApiIcon,
subBlocks: [
{
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL',
},
{
id: 'method',
title: 'Method',
type: 'dropdown',
layout: 'half',
options: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
},
{
id: 'headers',
title: 'Headers',
type: 'table',
layout: 'full',
columns: ['Key', 'Value'],
},
{
id: 'body',
title: 'Body',
type: 'code',
layout: 'full',
},
],
tools: {
access: ['http_request'],
},
workflow: {
inputs: {
url: { type: 'string', required: true },
method: { type: 'string', required: true },
headers: { type: 'json', required: false },
body: { type: 'json', required: false },
},
outputs: {
response: {
type: {
data: 'any',
status: 'number',
headers: 'json',
},
inputs: {
url: { type: 'string', required: true },
method: { type: 'string', required: true },
headers: { type: 'json', required: false },
body: { type: 'json', required: false },
},
outputs: {
response: {
type: {
data: 'any',
status: 'number',
headers: 'json',
},
},
subBlocks: [
{
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL',
},
{
id: 'method',
title: 'Method',
type: 'dropdown',
layout: 'half',
options: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
},
{
id: 'headers',
title: 'Headers',
type: 'table',
layout: 'full',
columns: ['Key', 'Value'],
},
{
id: 'body',
title: 'Body',
type: 'code',
layout: 'full',
},
],
},
}
+23 -26
View File
@@ -3,36 +3,33 @@ import { CodeExecutionOutput } from '@/tools/function/execute'
import { BlockConfig } from '../types'
export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
type: 'condition',
toolbar: {
title: 'Condition',
description: 'Add a condition',
bgColor: '#FF972F',
icon: ConditionalIcon,
category: 'blocks',
},
id: 'condition',
name: 'Condition',
description: 'Add a condition',
longDescription: 'Add a condition to the workflow',
bgColor: '#FF972F',
icon: ConditionalIcon,
category: 'blocks',
subBlocks: [
{
id: 'conditions',
type: 'condition-input',
layout: 'full',
},
],
tools: {
access: ['function_execute'],
},
workflow: {
inputs: {
code: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
inputs: {
code: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
},
subBlocks: [
{
id: 'conditions',
type: 'condition-input',
layout: 'full',
},
],
},
}
+49 -53
View File
@@ -3,63 +3,59 @@ import { VisionResponse } from '@/tools/crewai/vision'
import { BlockConfig } from '../types'
export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
type: 'crewai_vision',
toolbar: {
title: 'CrewAI Vision Tool',
description: 'Analyze images with vision models',
bgColor: '#FF5A50',
icon: CrewAIIcon,
category: 'tools',
},
id: 'crewai_vision',
name: 'CrewAI Vision',
description: 'Analyze images with vision models',
category: 'tools',
bgColor: '#FF5A50',
icon: CrewAIIcon,
subBlocks: [
{
id: 'imageUrl',
title: 'Image URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter publicly accessible image URL',
},
{
id: 'model',
title: 'Vision Model',
type: 'dropdown',
layout: 'half',
options: ['gpt-4o', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229'],
},
{
id: 'prompt',
title: 'Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Enter prompt for image analysis (optional)',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
},
],
tools: {
access: ['crewai_vision'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
imageUrl: { type: 'string', required: true },
model: { type: 'string', required: false },
prompt: { type: 'string', required: false },
},
outputs: {
response: {
type: {
content: 'string',
model: 'any',
tokens: 'any',
},
inputs: {
apiKey: { type: 'string', required: true },
imageUrl: { type: 'string', required: true },
model: { type: 'string', required: false },
prompt: { type: 'string', required: false },
},
outputs: {
response: {
type: {
content: 'string',
model: 'any',
tokens: 'any',
},
},
subBlocks: [
{
id: 'imageUrl',
title: 'Image URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter publicly accessible image URL',
},
{
id: 'model',
title: 'Vision Model',
type: 'dropdown',
layout: 'half',
options: ['gpt-4o', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229'],
},
{
id: 'prompt',
title: 'Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Enter prompt for image analysis (optional)',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
},
],
},
}
+105 -109
View File
@@ -56,14 +56,60 @@ const generateResponseFormat = (metrics: Metric[]) => ({
})
export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
type: 'evaluator',
toolbar: {
title: 'Evaluator',
description: 'Evaluate content',
bgColor: '#2FA1FF',
icon: ChartBarIcon,
category: 'blocks',
},
id: 'evaluator',
name: 'Evaluator',
description: 'Evaluate content',
category: 'blocks',
bgColor: '#2FA1FF',
icon: ChartBarIcon,
subBlocks: [
{
id: 'metrics',
title: 'Evaluation Metrics',
type: 'eval-input',
layout: 'full',
},
{
id: 'content',
title: 'Content',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the content to evaluate',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
const metrics = params.metrics || []
const content = params.content || ''
const responseFormat = generateResponseFormat(metrics)
return JSON.stringify({
systemPrompt: generateEvaluatorPrompt(metrics, content),
responseFormat,
})
},
},
],
tools: {
access: [
'openai_chat',
@@ -87,116 +133,66 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
},
},
},
workflow: {
inputs: {
metrics: {
type: 'json' as ParamType,
required: true,
description: 'Array of metrics to evaluate against',
schema: {
type: 'array',
properties: {},
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the metric',
},
description: {
type: 'string',
description: 'Description of what this metric measures',
},
range: {
type: 'object',
properties: {
min: {
type: 'number',
description: 'Minimum possible score',
},
max: {
type: 'number',
description: 'Maximum possible score',
},
inputs: {
metrics: {
type: 'json' as ParamType,
required: true,
description: 'Array of metrics to evaluate against',
schema: {
type: 'array',
properties: {},
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the metric',
},
description: {
type: 'string',
description: 'Description of what this metric measures',
},
range: {
type: 'object',
properties: {
min: {
type: 'number',
description: 'Minimum possible score',
},
max: {
type: 'number',
description: 'Maximum possible score',
},
required: ['min', 'max'],
},
required: ['min', 'max'],
},
required: ['name', 'description', 'range'],
},
},
},
model: { type: 'string' as ParamType, required: true },
apiKey: { type: 'string' as ParamType, required: true },
content: { type: 'string' as ParamType, required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
},
dependsOn: {
subBlockId: 'metrics',
condition: {
whenEmpty: {
content: 'string',
model: 'string',
tokens: 'any',
},
whenFilled: 'json',
},
required: ['name', 'description', 'range'],
},
},
},
subBlocks: [
{
id: 'metrics',
title: 'Evaluation Metrics',
type: 'eval-input',
layout: 'full',
model: { type: 'string' as ParamType, required: true },
apiKey: { type: 'string' as ParamType, required: true },
content: { type: 'string' as ParamType, required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
},
{
id: 'content',
title: 'Content',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the content to evaluate',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
const metrics = params.metrics || []
const content = params.content || ''
const responseFormat = generateResponseFormat(metrics)
return JSON.stringify({
systemPrompt: generateEvaluatorPrompt(metrics, content),
responseFormat,
})
dependsOn: {
subBlockId: 'metrics',
condition: {
whenEmpty: {
content: 'string',
model: 'string',
tokens: 'any',
},
whenFilled: 'json',
},
},
],
},
},
}
+41 -45
View File
@@ -2,55 +2,51 @@ import { FirecrawlIcon } from '@/components/icons'
import { ScrapeResponse } from '@/tools/firecrawl/scrape'
import { BlockConfig } from '../types'
export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
type: 'firecrawl_scrape',
toolbar: {
title: 'Firecrawl Scraper',
description: 'Scrape website content',
bgColor: '#181C1E',
icon: FirecrawlIcon,
category: 'tools',
},
export const FirecrawlBlock: BlockConfig<ScrapeResponse> = {
id: 'firecrawl',
name: 'Firecrawl',
description: 'Scrape website content',
category: 'tools',
bgColor: '#181C1E',
icon: FirecrawlIcon,
subBlocks: [
{
id: 'url',
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the webpage URL to scrape',
},
{
id: 'onlyMainContent',
title: 'Only Main Content',
type: 'switch',
layout: 'half',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Firecrawl API key',
password: true,
},
],
tools: {
access: ['firecrawl_scrape'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
url: { type: 'string', required: true },
scrapeOptions: { type: 'json', required: false },
},
outputs: {
response: {
type: {
markdown: 'string',
html: 'any',
metadata: 'json',
},
inputs: {
apiKey: { type: 'string', required: true },
url: { type: 'string', required: true },
scrapeOptions: { type: 'json', required: false },
},
outputs: {
response: {
type: {
markdown: 'string',
html: 'any',
metadata: 'json',
},
},
subBlocks: [
{
id: 'url',
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the webpage URL to scrape',
},
{
id: 'onlyMainContent',
title: 'Only Main Content',
type: 'switch',
layout: 'half',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Firecrawl API key',
password: true,
},
],
},
}
+24 -28
View File
@@ -3,38 +3,34 @@ import { CodeExecutionOutput } from '@/tools/function/execute'
import { BlockConfig } from '../types'
export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
type: 'function',
toolbar: {
title: 'Function',
description: 'Run custom logic',
bgColor: '#FF402F',
icon: CodeIcon,
category: 'blocks',
},
id: 'function',
name: 'Function',
description: 'Run custom logic',
category: 'blocks',
bgColor: '#FF402F',
icon: CodeIcon,
subBlocks: [
{
id: 'code',
type: 'code',
layout: 'full',
},
],
tools: {
access: ['function_execute'],
},
workflow: {
inputs: {
code: { type: 'string', required: true },
timeout: { type: 'number', required: false },
memoryLimit: { type: 'number', required: false },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
inputs: {
code: { type: 'string', required: true },
timeout: { type: 'number', required: false },
memoryLimit: { type: 'number', required: false },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
},
subBlocks: [
{
id: 'code',
type: 'code',
layout: 'full',
},
],
},
}
+43 -47
View File
@@ -3,57 +3,53 @@ import { GithubIcon } from '../../components/icons'
import { BlockConfig } from '../types'
export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
type: 'github_repo_info',
toolbar: {
title: 'GitHub Repository',
description: 'Fetch GitHub repository',
bgColor: '#181C1E',
icon: GithubIcon,
category: 'tools',
},
id: 'github',
name: 'GitHub',
description: 'Fetch GitHub repository',
category: 'tools',
bgColor: '#181C1E',
icon: GithubIcon,
subBlocks: [
{
id: 'owner',
title: 'Repository Owner',
type: 'short-input',
layout: 'half',
placeholder: 'e.g., microsoft',
},
{
id: 'repo',
title: 'Repository Name',
type: 'short-input',
layout: 'half',
placeholder: 'e.g., vscode',
},
{
id: 'apiKey',
title: 'GitHub Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter GitHub Token',
password: true,
},
],
tools: {
access: ['github_repoinfo'],
},
workflow: {
inputs: {
owner: { type: 'string', required: true },
repo: { type: 'string', required: true },
},
outputs: {
response: {
type: {
name: 'string',
description: 'string',
stars: 'number',
forks: 'number',
openIssues: 'number',
language: 'string',
},
inputs: {
owner: { type: 'string', required: true },
repo: { type: 'string', required: true },
},
outputs: {
response: {
type: {
name: 'string',
description: 'string',
stars: 'number',
forks: 'number',
openIssues: 'number',
language: 'string',
},
},
subBlocks: [
{
id: 'owner',
title: 'Repository Owner',
type: 'short-input',
layout: 'half',
placeholder: 'e.g., microsoft',
},
{
id: 'repo',
title: 'Repository Name',
type: 'short-input',
layout: 'half',
placeholder: 'e.g., vscode',
},
{
id: 'apiKey',
title: 'GitHub Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter GitHub Token',
password: true,
},
],
},
}
+98 -102
View File
@@ -3,14 +3,86 @@ import { GmailToolResponse } from '@/tools/gmail/types'
import { BlockConfig } from '../types'
export const GmailBlock: BlockConfig<GmailToolResponse> = {
type: 'gmail_block',
toolbar: {
title: 'Gmail',
description: 'Send, read, and search Gmail messages',
bgColor: '#F14537',
icon: GmailIcon,
category: 'tools',
},
id: 'gmail',
name: 'Gmail',
description: 'Send, read, and search Gmail messages',
category: 'tools',
bgColor: '#F14537',
icon: GmailIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Send Email', id: 'send_gmail' },
{ label: 'Read Email', id: 'read_gmail' },
{ label: 'Search Emails', id: 'search_gmail' },
],
},
// OAuth Token
{
id: 'accessToken',
title: 'Access Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Gmail OAuth token',
password: true,
},
// Send Email Fields
{
id: 'to',
title: 'To',
type: 'short-input',
layout: 'full',
placeholder: 'Recipient email address',
condition: { field: 'operation', value: 'send_gmail' },
},
{
id: 'subject',
title: 'Subject',
type: 'short-input',
layout: 'full',
placeholder: 'Email subject',
condition: { field: 'operation', value: 'send_gmail' },
},
{
id: 'body',
title: 'Body',
type: 'long-input',
layout: 'full',
placeholder: 'Email content',
condition: { field: 'operation', value: 'send_gmail' },
},
// Read Email Fields
{
id: 'messageId',
title: 'Message ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter message ID to read',
condition: { field: 'operation', value: 'read_gmail' },
},
// Search Fields
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search terms',
condition: { field: 'operation', value: 'search_gmail' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: 'Maximum number of results (default: 10)',
condition: { field: 'operation', value: 'search_gmail' },
},
],
tools: {
access: ['gmail_send', 'gmail_read', 'gmail_search'],
config: {
@@ -28,101 +100,25 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
},
},
},
workflow: {
inputs: {
operation: { type: 'string', required: true },
accessToken: { type: 'string', required: true },
// Send operation inputs
to: { type: 'string', required: false },
subject: { type: 'string', required: false },
body: { type: 'string', required: false },
// Read operation inputs
messageId: { type: 'string', required: false },
// Search operation inputs
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
},
outputs: {
response: {
type: {
content: 'string',
metadata: 'json',
},
inputs: {
operation: { type: 'string', required: true },
accessToken: { type: 'string', required: true },
// Send operation inputs
to: { type: 'string', required: false },
subject: { type: 'string', required: false },
body: { type: 'string', required: false },
// Read operation inputs
messageId: { type: 'string', required: false },
// Search operation inputs
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
},
outputs: {
response: {
type: {
content: 'string',
metadata: 'json',
},
},
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Send Email', id: 'send_gmail' },
{ label: 'Read Email', id: 'read_gmail' },
{ label: 'Search Emails', id: 'search_gmail' },
],
},
// OAuth Token
{
id: 'accessToken',
title: 'Access Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Gmail OAuth token',
password: true,
},
// Send Email Fields
{
id: 'to',
title: 'To',
type: 'short-input',
layout: 'full',
placeholder: 'Recipient email address',
condition: { field: 'operation', value: 'send_gmail' },
},
{
id: 'subject',
title: 'Subject',
type: 'short-input',
layout: 'full',
placeholder: 'Email subject',
condition: { field: 'operation', value: 'send_gmail' },
},
{
id: 'body',
title: 'Body',
type: 'long-input',
layout: 'full',
placeholder: 'Email content',
condition: { field: 'operation', value: 'send_gmail' },
},
// Read Email Fields
{
id: 'messageId',
title: 'Message ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter message ID to read',
condition: { field: 'operation', value: 'read_gmail' },
},
// Search Fields
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search terms',
condition: { field: 'operation', value: 'search_gmail' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: 'Maximum number of results (default: 10)',
condition: { field: 'operation', value: 'search_gmail' },
},
],
},
}
+45 -49
View File
@@ -3,59 +3,55 @@ import { ReadUrlResponse } from '@/tools/jina/reader'
import { BlockConfig } from '../types'
export const JinaBlock: BlockConfig<ReadUrlResponse> = {
type: 'jina_reader',
toolbar: {
title: 'Jina Reader',
description: 'Convert website content into text',
bgColor: '#333333',
icon: JinaAIIcon,
category: 'tools',
},
id: 'jina',
name: 'Jina',
description: 'Convert website content into text',
category: 'tools',
bgColor: '#333333',
icon: JinaAIIcon,
subBlocks: [
{
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL to extract content from',
},
{
id: 'options',
title: 'Options',
type: 'checkbox-list',
layout: 'full',
options: [
{ id: 'useReaderLMv2', label: 'Use Reader LM v2' },
{ id: 'gatherLinks', label: 'Gather Links' },
{ id: 'jsonResponse', label: 'JSON Response' },
],
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Jina API key',
password: true,
},
],
tools: {
access: ['jina_readurl'],
},
workflow: {
inputs: {
url: { type: 'string', required: true },
useReaderLMv2: { type: 'boolean', required: false },
gatherLinks: { type: 'boolean', required: false },
jsonResponse: { type: 'boolean', required: false },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
},
inputs: {
url: { type: 'string', required: true },
useReaderLMv2: { type: 'boolean', required: false },
gatherLinks: { type: 'boolean', required: false },
jsonResponse: { type: 'boolean', required: false },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
},
},
subBlocks: [
{
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL to extract content from',
},
{
id: 'options',
title: 'Options',
type: 'checkbox-list',
layout: 'full',
options: [
{ id: 'useReaderLMv2', label: 'Use Reader LM v2' },
{ id: 'gatherLinks', label: 'Gather Links' },
{ id: 'jsonResponse', label: 'JSON Response' },
],
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Jina API key',
password: true,
},
],
},
}
+52 -56
View File
@@ -3,14 +3,47 @@ import { NotionResponse } from '@/tools/notion/read'
import { BlockConfig } from '../types'
export const NotionBlock: BlockConfig<NotionResponse> = {
type: 'notion_reader',
toolbar: {
title: 'Notion',
description: 'Read and write to Notion pages and databases',
bgColor: '#000000',
icon: NotionIcon,
category: 'tools',
},
id: 'notion',
name: 'Notion',
description: 'Read and write to Notion pages and databases',
category: 'tools',
bgColor: '#000000',
icon: NotionIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Page', id: 'read_notion' },
{ label: 'Write Page', id: 'write_notion' },
],
},
{
id: 'pageId',
title: 'Page ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Notion page ID',
},
{
id: 'content',
title: 'Content',
type: 'long-input',
layout: 'full',
placeholder: 'Enter content to write (for write operation)',
condition: { field: 'operation', value: 'write_notion' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Notion API key',
password: true,
},
],
tools: {
access: ['notion_read', 'notion_write'],
config: {
@@ -19,55 +52,18 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
},
},
},
workflow: {
inputs: {
pageId: { type: 'string', required: true },
operation: { type: 'string', required: true },
content: { type: 'string', required: false },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
metadata: 'any',
},
inputs: {
pageId: { type: 'string', required: true },
operation: { type: 'string', required: true },
content: { type: 'string', required: false },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
metadata: 'any',
},
},
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Page', id: 'read_notion' },
{ label: 'Write Page', id: 'write_notion' },
],
},
{
id: 'pageId',
title: 'Page ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Notion page ID',
},
{
id: 'content',
title: 'Content',
type: 'long-input',
layout: 'full',
placeholder: 'Enter content to write (for write operation)',
condition: { field: 'operation', value: 'write_notion' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Notion API key',
password: true,
},
],
},
}
+53 -57
View File
@@ -86,14 +86,47 @@ Remember: Your response must be ONLY the block ID - no additional text, formatti
}
export const RouterBlock: BlockConfig<RouterResponse> = {
type: 'router',
toolbar: {
title: 'Router',
description: 'Route workflow',
bgColor: '#28C43F',
icon: ConnectIcon,
category: 'blocks',
},
id: 'router',
name: 'Router',
description: 'Route workflow',
category: 'blocks',
bgColor: '#28C43F',
icon: ConnectIcon,
subBlocks: [
{
id: 'prompt',
title: 'Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Route to the correct block based on the input...',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
return generateRouterPrompt(params.prompt || '')
},
},
],
tools: {
access: [
'openai_chat',
@@ -117,56 +150,19 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
},
},
},
workflow: {
inputs: {
prompt: { type: 'string', required: true },
model: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
selectedPath: 'json',
},
inputs: {
prompt: { type: 'string', required: true },
model: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
selectedPath: 'json',
},
},
subBlocks: [
{
id: 'prompt',
title: 'Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Route to the correct block based on the input...',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
return generateRouterPrompt(params.prompt || '')
},
},
],
},
}
+63 -67
View File
@@ -3,77 +3,73 @@ import { SearchResponse } from '@/tools/serper/search'
import { BlockConfig } from '../types'
export const SerperBlock: BlockConfig<SearchResponse> = {
type: 'serper_search',
toolbar: {
title: 'Serper Search',
description: 'Search the web using Serper',
bgColor: '#2B3543',
icon: SerperIcon,
category: 'tools',
},
id: 'serper',
name: 'Serper',
description: 'Search the web using Serper',
category: 'tools',
bgColor: '#2B3543',
icon: SerperIcon,
subBlocks: [
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your search query...',
},
{
id: 'type',
title: 'Search Type',
type: 'dropdown',
layout: 'half',
options: ['search', 'news', 'places', 'images'],
},
{
id: 'num',
title: 'Number of Results',
type: 'dropdown',
layout: 'half',
options: ['10', '20', '30', '40', '50', '100'],
},
{
id: 'gl',
title: 'Country',
type: 'dropdown',
layout: 'half',
options: ['US', 'GB', 'CA', 'AU', 'DE', 'FR', 'ES', 'IT', 'JP', 'KR'],
},
{
id: 'hl',
title: 'Language',
type: 'dropdown',
layout: 'half',
options: ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh'],
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Serper API key',
password: true,
},
],
tools: {
access: ['serper_search'],
},
workflow: {
inputs: {
query: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
num: { type: 'number', required: false },
gl: { type: 'string', required: false },
hl: { type: 'string', required: false },
type: { type: 'string', required: false },
},
outputs: {
response: {
type: {
searchResults: 'json',
},
inputs: {
query: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
num: { type: 'number', required: false },
gl: { type: 'string', required: false },
hl: { type: 'string', required: false },
type: { type: 'string', required: false },
},
outputs: {
response: {
type: {
searchResults: 'json',
},
},
subBlocks: [
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your search query...',
},
{
id: 'type',
title: 'Search Type',
type: 'dropdown',
layout: 'half',
options: ['search', 'news', 'places', 'images'],
},
{
id: 'num',
title: 'Number of Results',
type: 'dropdown',
layout: 'half',
options: ['10', '20', '30', '40', '50', '100'],
},
{
id: 'gl',
title: 'Country',
type: 'dropdown',
layout: 'half',
options: ['US', 'GB', 'CA', 'AU', 'DE', 'FR', 'ES', 'IT', 'JP', 'KR'],
},
{
id: 'hl',
title: 'Language',
type: 'dropdown',
layout: 'half',
options: ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh'],
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Serper API key',
password: true,
},
],
},
}
+42 -46
View File
@@ -2,56 +2,52 @@ import { SlackIcon } from '@/components/icons'
import { SlackMessageResponse } from '@/tools/slack/message'
import { BlockConfig } from '../types'
export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
type: 'slack_message',
toolbar: {
title: 'Slack Message',
description: 'Send a message to Slack',
bgColor: '#611f69',
icon: SlackIcon,
category: 'tools',
},
export const SlackBlock: BlockConfig<SlackMessageResponse> = {
id: 'slack',
name: 'Slack',
description: 'Send a message to Slack',
category: 'tools',
bgColor: '#611f69',
icon: SlackIcon,
subBlocks: [
{
id: 'channel',
title: 'Channel',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Slack channel (e.g., #general)',
},
{
id: 'text',
title: 'Message',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your alert message',
},
{
id: 'apiKey',
title: 'OAuth Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Slack OAuth token',
password: true,
connectionDroppable: false,
},
],
tools: {
access: ['slack_message'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
channel: { type: 'string', required: true },
text: { type: 'string', required: true },
},
outputs: {
response: {
type: {
ts: 'string',
channel: 'string',
},
inputs: {
apiKey: { type: 'string', required: true },
channel: { type: 'string', required: true },
text: { type: 'string', required: true },
},
outputs: {
response: {
type: {
ts: 'string',
channel: 'string',
},
},
subBlocks: [
{
id: 'channel',
title: 'Channel',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Slack channel (e.g., #general)',
},
{
id: 'text',
title: 'Message',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your alert message',
},
{
id: 'apiKey',
title: 'OAuth Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Slack OAuth token',
password: true,
connectionDroppable: false,
},
],
},
}
+237 -241
View File
@@ -2,251 +2,247 @@ import { StartIcon } from '@/components/icons'
import { BlockConfig } from '../types'
export const StarterBlock: BlockConfig = {
type: 'starter',
toolbar: {
title: 'Starter',
description: 'Start workflow',
bgColor: '#2FB3FF',
icon: StartIcon,
category: 'blocks',
},
id: 'starter',
name: 'Starter',
description: 'Start workflow',
category: 'blocks',
bgColor: '#2FB3FF',
icon: StartIcon,
subBlocks: [
// Main trigger selector
{
id: 'startWorkflow',
title: 'Start Workflow',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Run manually', id: 'manual' },
{ label: 'On webhook call', id: 'webhook' },
{ label: 'On schedule', id: 'schedule' },
],
value: () => 'manual',
},
// Webhook configuration
{
id: 'webhookPath',
title: 'Webhook Path',
type: 'short-input',
layout: 'full',
placeholder: 'Enter webhook path (e.g., /my-webhook)',
condition: { field: 'startWorkflow', value: 'webhook' },
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
layout: 'full',
placeholder: 'Enter a secret key for webhook security',
password: true,
condition: { field: 'startWorkflow', value: 'webhook' },
},
// Schedule configuration
{
id: 'scheduleType',
title: 'Frequency',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Every X Minutes', id: 'minutes' },
{ label: 'Hourly', id: 'hourly' },
{ label: 'Daily', id: 'daily' },
{ label: 'Weekly', id: 'weekly' },
{ label: 'Monthly', id: 'monthly' },
{ label: 'Custom Cron', id: 'custom' },
],
value: () => 'daily',
condition: { field: 'startWorkflow', value: 'schedule' },
},
// Minutes schedule options
{
id: 'minutesInterval',
title: 'Run Every',
type: 'short-input',
layout: 'full',
placeholder: '15 minutes',
condition: {
field: 'scheduleType',
value: 'minutes',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'minutesStartingAt',
title: 'Starting At',
type: 'short-input',
layout: 'full',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'minutes',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Hourly schedule options
{
id: 'hourlyMinute',
title: 'Start at Minute',
type: 'short-input',
layout: 'full',
placeholder: '00',
condition: {
field: 'scheduleType',
value: 'hourly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Daily schedule options
{
id: 'dailyTime',
title: 'Time',
type: 'short-input',
layout: 'full',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'daily',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Weekly schedule options
{
id: 'weeklyDay',
title: 'Day of Week',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Monday', id: 'MON' },
{ label: 'Tuesday', id: 'TUE' },
{ label: 'Wednesday', id: 'WED' },
{ label: 'Thursday', id: 'THU' },
{ label: 'Friday', id: 'FRI' },
{ label: 'Saturday', id: 'SAT' },
{ label: 'Sunday', id: 'SUN' },
],
value: () => 'MON',
condition: {
field: 'scheduleType',
value: 'weekly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'weeklyDayTime',
title: 'Time',
type: 'short-input',
layout: 'half',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'weekly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Monthly schedule options
{
id: 'monthlyDay',
title: 'Day of Month',
type: 'short-input',
layout: 'half',
placeholder: '1',
condition: {
field: 'scheduleType',
value: 'monthly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'monthlyTime',
title: 'Time',
type: 'short-input',
layout: 'half',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'monthly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Custom cron options
{
id: 'cronExpression',
title: 'Cron Expression',
type: 'short-input',
layout: 'full',
placeholder: '*/15 * * * *',
condition: {
field: 'scheduleType',
value: 'custom',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Timezone configuration (for all schedule types)
{
id: 'timezone',
title: 'Timezone',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'UTC', id: 'UTC' },
{ label: 'US Eastern (UTC-4)', id: 'America/New_York' },
{ label: 'US Central (UTC-5)', id: 'America/Chicago' },
{ label: 'US Mountain (UTC-6)', id: 'America/Denver' },
{ label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' },
{ label: 'London (UTC+1)', id: 'Europe/London' },
{ label: 'Paris (UTC+2)', id: 'Europe/Paris' },
{ label: 'Singapore (UTC+8)', id: 'Asia/Singapore' },
{ label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' },
{ label: 'Sydney (UTC+10)', id: 'Australia/Sydney' },
],
value: () => 'UTC',
condition: { field: 'startWorkflow', value: 'schedule' },
},
],
tools: {
access: [],
},
workflow: {
inputs: {
code: { type: 'string', required: true },
executionMode: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
inputs: {
code: { type: 'string', required: true },
executionMode: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
},
},
subBlocks: [
// Main trigger selector
{
id: 'startWorkflow',
title: 'Start Workflow',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Run manually', id: 'manual' },
{ label: 'On webhook call', id: 'webhook' },
{ label: 'On schedule', id: 'schedule' },
],
value: () => 'manual',
},
// Webhook configuration
{
id: 'webhookPath',
title: 'Webhook Path',
type: 'short-input',
layout: 'full',
placeholder: 'Enter webhook path (e.g., /my-webhook)',
condition: { field: 'startWorkflow', value: 'webhook' },
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
layout: 'full',
placeholder: 'Enter a secret key for webhook security',
password: true,
condition: { field: 'startWorkflow', value: 'webhook' },
},
// Schedule configuration
{
id: 'scheduleType',
title: 'Frequency',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Every X Minutes', id: 'minutes' },
{ label: 'Hourly', id: 'hourly' },
{ label: 'Daily', id: 'daily' },
{ label: 'Weekly', id: 'weekly' },
{ label: 'Monthly', id: 'monthly' },
{ label: 'Custom Cron', id: 'custom' },
],
value: () => 'daily',
condition: { field: 'startWorkflow', value: 'schedule' },
},
// Minutes schedule options
{
id: 'minutesInterval',
title: 'Run Every',
type: 'short-input',
layout: 'full',
placeholder: '15 minutes',
condition: {
field: 'scheduleType',
value: 'minutes',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'minutesStartingAt',
title: 'Starting At',
type: 'short-input',
layout: 'full',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'minutes',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Hourly schedule options
{
id: 'hourlyMinute',
title: 'Start at Minute',
type: 'short-input',
layout: 'full',
placeholder: '00',
condition: {
field: 'scheduleType',
value: 'hourly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Daily schedule options
{
id: 'dailyTime',
title: 'Time',
type: 'short-input',
layout: 'full',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'daily',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Weekly schedule options
{
id: 'weeklyDay',
title: 'Day of Week',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Monday', id: 'MON' },
{ label: 'Tuesday', id: 'TUE' },
{ label: 'Wednesday', id: 'WED' },
{ label: 'Thursday', id: 'THU' },
{ label: 'Friday', id: 'FRI' },
{ label: 'Saturday', id: 'SAT' },
{ label: 'Sunday', id: 'SUN' },
],
value: () => 'MON',
condition: {
field: 'scheduleType',
value: 'weekly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'weeklyDayTime',
title: 'Time',
type: 'short-input',
layout: 'half',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'weekly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Monthly schedule options
{
id: 'monthlyDay',
title: 'Day of Month',
type: 'short-input',
layout: 'half',
placeholder: '1',
condition: {
field: 'scheduleType',
value: 'monthly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
{
id: 'monthlyTime',
title: 'Time',
type: 'short-input',
layout: 'half',
placeholder: '09:00',
condition: {
field: 'scheduleType',
value: 'monthly',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Custom cron options
{
id: 'cronExpression',
title: 'Cron Expression',
type: 'short-input',
layout: 'full',
placeholder: '*/15 * * * *',
condition: {
field: 'scheduleType',
value: 'custom',
and: {
field: 'startWorkflow',
value: 'schedule',
},
},
},
// Timezone configuration (for all schedule types)
{
id: 'timezone',
title: 'Timezone',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'UTC', id: 'UTC' },
{ label: 'US Eastern (UTC-4)', id: 'America/New_York' },
{ label: 'US Central (UTC-5)', id: 'America/Chicago' },
{ label: 'US Mountain (UTC-6)', id: 'America/Denver' },
{ label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' },
{ label: 'London (UTC+1)', id: 'Europe/London' },
{ label: 'Paris (UTC+2)', id: 'Europe/Paris' },
{ label: 'Singapore (UTC+8)', id: 'Asia/Singapore' },
{ label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' },
{ label: 'Sydney (UTC+10)', id: 'Australia/Sydney' },
],
value: () => 'UTC',
condition: { field: 'startWorkflow', value: 'schedule' },
},
],
},
}
+83 -87
View File
@@ -5,14 +5,70 @@ import { BlockConfig } from '../types'
type TavilyResponse = TavilySearchResponse | TavilyExtractResponse
export const TavilyBlock: BlockConfig<TavilyResponse> = {
type: 'tavily_block',
toolbar: {
title: 'Tavily',
description: 'Search and extract information using Tavily AI',
bgColor: '#0066FF',
icon: TavilyIcon,
category: 'tools',
},
id: 'tavily',
name: 'Tavily',
description: 'Search and extract information using Tavily AI',
category: 'tools',
bgColor: '#0066FF',
icon: TavilyIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Search', id: 'tavily_search' },
{ label: 'Extract Content', id: 'tavily_extract' },
],
value: () => 'tavily_search',
},
// API Key (common)
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Tavily API key',
password: true,
},
// Search operation inputs
{
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your search query...',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '5',
condition: { field: 'operation', value: 'tavily_search' },
},
// Extract operation inputs
{
id: 'urls',
title: 'URL',
type: 'long-input',
layout: 'full',
placeholder: 'Enter URL to extract content from...',
condition: { field: 'operation', value: 'tavily_extract' },
},
{
id: 'extract_depth',
title: 'Extract Depth',
type: 'dropdown',
layout: 'full',
options: ['basic', 'advanced'],
value: () => 'basic',
condition: { field: 'operation', value: 'tavily_extract' },
},
],
tools: {
access: ['tavily_search', 'tavily_extract'],
config: {
@@ -28,86 +84,26 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
},
},
},
workflow: {
inputs: {
operation: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
// Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
// Extract operation
urls: { type: 'string', required: false },
extract_depth: { type: 'string', required: false },
},
outputs: {
response: {
type: {
results: 'json',
answer: 'any',
query: 'string',
content: 'string',
title: 'string',
url: 'string',
},
inputs: {
operation: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
// Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
// Extract operation
urls: { type: 'string', required: false },
extract_depth: { type: 'string', required: false },
},
outputs: {
response: {
type: {
results: 'json',
answer: 'any',
query: 'string',
content: 'string',
title: 'string',
url: 'string',
},
},
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Search', id: 'tavily_search' },
{ label: 'Extract Content', id: 'tavily_extract' },
],
value: () => 'tavily_search',
},
// API Key (common)
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Tavily API key',
password: true,
},
// Search operation inputs
{
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your search query...',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '5',
condition: { field: 'operation', value: 'tavily_search' },
},
// Extract operation inputs
{
id: 'urls',
title: 'URL',
type: 'long-input',
layout: 'full',
placeholder: 'Enter URL to extract content from...',
condition: { field: 'operation', value: 'tavily_extract' },
},
{
id: 'extract_depth',
title: 'Extract Depth',
type: 'dropdown',
layout: 'full',
options: ['basic', 'advanced'],
value: () => 'basic',
condition: { field: 'operation', value: 'tavily_extract' },
},
],
},
}
+60 -64
View File
@@ -15,14 +15,54 @@ const getTranslationPrompt = (
Only return the translated text without any explanations or notes. The translation should be natural and fluent in ${targetLanguage || 'English'}.`
export const TranslateBlock: BlockConfig<ChatResponse> = {
type: 'translate',
toolbar: {
title: 'Translate',
description: 'Translate text to any language',
bgColor: '#FF4B4B',
icon: TranslateIcon,
category: 'tools',
},
id: 'translate',
name: 'Translate',
description: 'Translate text to any language',
category: 'tools',
bgColor: '#FF4B4B',
icon: TranslateIcon,
subBlocks: [
{
id: 'context',
title: 'Text to Translate',
type: 'long-input',
layout: 'full',
placeholder: 'Enter the text you want to translate',
},
{
id: 'targetLanguage',
title: 'Translate To',
type: 'short-input',
layout: 'full',
placeholder: 'Enter language (e.g. Spanish, French, etc.)',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
return getTranslationPrompt(params.targetLanguage || 'English')
},
},
],
tools: {
access: ['openai_chat', 'anthropic_chat', 'google_chat'],
config: {
@@ -43,63 +83,19 @@ export const TranslateBlock: BlockConfig<ChatResponse> = {
},
},
},
workflow: {
inputs: {
context: { type: 'string', required: true },
targetLanguage: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
systemPrompt: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
},
inputs: {
context: { type: 'string', required: true },
targetLanguage: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
systemPrompt: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any',
},
},
subBlocks: [
{
id: 'context',
title: 'Text to Translate',
type: 'long-input',
layout: 'full',
placeholder: 'Enter the text you want to translate',
},
{
id: 'targetLanguage',
title: 'Translate To',
type: 'short-input',
layout: 'full',
placeholder: 'Enter language (e.g. Spanish, French, etc.)',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
},
{
id: 'systemPrompt',
title: 'System Prompt',
type: 'code',
layout: 'full',
hidden: true,
value: (params: Record<string, any>) => {
return getTranslationPrompt(params.targetLanguage || 'English')
},
},
],
},
}
+166 -170
View File
@@ -5,14 +5,140 @@ import { BlockConfig } from '../types'
type XResponse = XWriteResponse | XReadResponse | XSearchResponse | XUserResponse
export const XBlock: BlockConfig<XResponse> = {
type: 'x_block',
toolbar: {
title: 'X',
description: 'Interact with X',
bgColor: '#000000', // X's black color
icon: xIcon,
category: 'tools',
},
id: 'x',
name: 'X',
description: 'Interact with X',
category: 'tools',
bgColor: '#000000', // X's black color
icon: xIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Post a New Tweet', id: 'x_write' },
{ label: 'Get Tweet Details', id: 'x_read' },
{ label: 'Search Tweets', id: 'x_search' },
{ label: 'Get User Profile', id: 'x_user' },
],
value: () => 'x_write',
},
// API Key (common)
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your X Bearer token',
password: true,
},
// Write operation inputs
{
id: 'text',
title: 'Tweet Text',
type: 'long-input',
layout: 'full',
placeholder: "What's happening?",
condition: { field: 'operation', value: 'x_write' },
},
{
id: 'replyTo',
title: 'Reply To (Tweet ID)',
type: 'short-input',
layout: 'full',
placeholder: 'Enter tweet ID to reply to',
condition: { field: 'operation', value: 'x_write' },
},
{
id: 'mediaIds',
title: 'Media IDs',
type: 'short-input',
layout: 'full',
placeholder: 'Enter comma-separated media IDs',
condition: { field: 'operation', value: 'x_write' },
},
// Read operation inputs
{
id: 'tweetId',
title: 'Tweet ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter tweet ID to read',
condition: { field: 'operation', value: 'x_read' },
},
{
id: 'includeReplies',
title: 'Include Replies',
type: 'dropdown',
layout: 'full',
options: ['true', 'false'],
value: () => 'false',
condition: { field: 'operation', value: 'x_read' },
},
// Search operation inputs
{
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter search terms (supports X search operators)',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'sortOrder',
title: 'Sort Order',
type: 'dropdown',
layout: 'full',
options: ['recency', 'relevancy'],
value: () => 'recency',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
layout: 'full',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'endTime',
title: 'End Time',
type: 'short-input',
layout: 'full',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
},
// User operation inputs
{
id: 'username',
title: 'Username',
type: 'short-input',
layout: 'full',
placeholder: 'Enter username (without @)',
condition: { field: 'operation', value: 'x_user' },
},
{
id: 'includeRecentTweets',
title: 'Include Recent Tweets',
type: 'dropdown',
layout: 'full',
options: ['true', 'false'],
value: () => 'false',
condition: { field: 'operation', value: 'x_user' },
},
],
tools: {
access: ['x_write', 'x_read', 'x_search', 'x_user'],
config: {
@@ -32,169 +158,39 @@ export const XBlock: BlockConfig<XResponse> = {
},
},
},
workflow: {
inputs: {
operation: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
// Write operation
text: { type: 'string', required: false },
replyTo: { type: 'string', required: false },
mediaIds: { type: 'string', required: false },
poll: { type: 'json', required: false },
// Read operation
tweetId: { type: 'string', required: false },
includeReplies: { type: 'boolean', required: false },
// Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
startTime: { type: 'string', required: false },
endTime: { type: 'string', required: false },
sortOrder: { type: 'string', required: false },
// User operation
username: { type: 'string', required: false },
includeRecentTweets: { type: 'boolean', required: false },
},
outputs: {
response: {
type: {
tweet: 'json',
replies: 'any',
context: 'any',
tweets: 'json',
includes: 'any',
meta: 'json',
user: 'json',
recentTweets: 'any',
},
inputs: {
operation: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
// Write operation
text: { type: 'string', required: false },
replyTo: { type: 'string', required: false },
mediaIds: { type: 'string', required: false },
poll: { type: 'json', required: false },
// Read operation
tweetId: { type: 'string', required: false },
includeReplies: { type: 'boolean', required: false },
// Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
startTime: { type: 'string', required: false },
endTime: { type: 'string', required: false },
sortOrder: { type: 'string', required: false },
// User operation
username: { type: 'string', required: false },
includeRecentTweets: { type: 'boolean', required: false },
},
outputs: {
response: {
type: {
tweet: 'json',
replies: 'any',
context: 'any',
tweets: 'json',
includes: 'any',
meta: 'json',
user: 'json',
recentTweets: 'any',
},
},
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Post a New Tweet', id: 'x_write' },
{ label: 'Get Tweet Details', id: 'x_read' },
{ label: 'Search Tweets', id: 'x_search' },
{ label: 'Get User Profile', id: 'x_user' },
],
value: () => 'x_write',
},
// API Key (common)
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your X Bearer token',
password: true,
},
// Write operation inputs
{
id: 'text',
title: 'Tweet Text',
type: 'long-input',
layout: 'full',
placeholder: "What's happening?",
condition: { field: 'operation', value: 'x_write' },
},
{
id: 'replyTo',
title: 'Reply To (Tweet ID)',
type: 'short-input',
layout: 'full',
placeholder: 'Enter tweet ID to reply to',
condition: { field: 'operation', value: 'x_write' },
},
{
id: 'mediaIds',
title: 'Media IDs',
type: 'short-input',
layout: 'full',
placeholder: 'Enter comma-separated media IDs',
condition: { field: 'operation', value: 'x_write' },
},
// Read operation inputs
{
id: 'tweetId',
title: 'Tweet ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter tweet ID to read',
condition: { field: 'operation', value: 'x_read' },
},
{
id: 'includeReplies',
title: 'Include Replies',
type: 'dropdown',
layout: 'full',
options: ['true', 'false'],
value: () => 'false',
condition: { field: 'operation', value: 'x_read' },
},
// Search operation inputs
{
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter search terms (supports X search operators)',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'sortOrder',
title: 'Sort Order',
type: 'dropdown',
layout: 'full',
options: ['recency', 'relevancy'],
value: () => 'recency',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
layout: 'full',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
},
{
id: 'endTime',
title: 'End Time',
type: 'short-input',
layout: 'full',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
},
// User operation inputs
{
id: 'username',
title: 'Username',
type: 'short-input',
layout: 'full',
placeholder: 'Enter username (without @)',
condition: { field: 'operation', value: 'x_user' },
},
{
id: 'includeRecentTweets',
title: 'Include Recent Tweets',
type: 'dropdown',
layout: 'full',
options: ['true', 'false'],
value: () => 'false',
condition: { field: 'operation', value: 'x_user' },
},
],
},
}
+42 -46
View File
@@ -2,56 +2,52 @@ import { YouTubeIcon } from '@/components/icons'
import { YouTubeSearchResponse } from '@/tools/youtube/search'
import { BlockConfig } from '../types'
export const YouTubeSearchBlock: BlockConfig<YouTubeSearchResponse> = {
type: 'youtube_search',
toolbar: {
title: 'YouTube Search',
description: 'Search for videos on YouTube',
bgColor: '#FF0000',
icon: YouTubeIcon,
category: 'tools',
},
export const YouTubeBlock: BlockConfig<YouTubeSearchResponse> = {
id: 'youtube',
name: 'YouTube',
description: 'Search for videos on YouTube',
category: 'tools',
bgColor: '#FF0000',
icon: YouTubeIcon,
subBlocks: [
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search query',
},
{
id: 'apiKey',
title: 'YouTube API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter YouTube API Key',
password: true,
},
{
id: 'maxResults',
title: 'Max Results',
type: 'slider',
layout: 'half',
min: 0,
max: 20,
},
],
tools: {
access: ['youtube_search'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
query: { type: 'string', required: true },
maxResults: { type: 'number', required: false },
},
outputs: {
response: {
type: {
items: 'json',
totalResults: 'number',
},
inputs: {
apiKey: { type: 'string', required: true },
query: { type: 'string', required: true },
maxResults: { type: 'number', required: false },
},
outputs: {
response: {
type: {
items: 'json',
totalResults: 'number',
},
},
subBlocks: [
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search query',
},
{
id: 'apiKey',
title: 'YouTube API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter YouTube API Key',
password: true,
},
{
id: 'maxResults',
title: 'Max Results',
type: 'slider',
layout: 'half',
min: 0,
max: 20,
},
],
},
}
+20 -20
View File
@@ -4,7 +4,7 @@ import { ApiBlock } from './blocks/api'
import { ConditionBlock } from './blocks/condition'
import { CrewAIVisionBlock } from './blocks/crewai'
import { EvaluatorBlock } from './blocks/evaluator'
import { FirecrawlScrapeBlock } from './blocks/firecrawl'
import { FirecrawlBlock } from './blocks/firecrawl'
import { FunctionBlock } from './blocks/function'
import { GitHubBlock } from './blocks/github'
import { GmailBlock } from './blocks/gmail'
@@ -12,12 +12,12 @@ import { JinaBlock } from './blocks/jina'
import { NotionBlock } from './blocks/notion'
import { RouterBlock } from './blocks/router'
import { SerperBlock } from './blocks/serper'
import { SlackMessageBlock } from './blocks/slack'
import { SlackBlock } from './blocks/slack'
import { StarterBlock } from './blocks/starter'
import { TavilyBlock } from './blocks/tavily'
import { TranslateBlock } from './blocks/translate'
import { XBlock } from './blocks/x'
import { YouTubeSearchBlock } from './blocks/youtube'
import { YouTubeBlock } from './blocks/youtube'
import { BlockConfig } from './types'
// Export blocks for ease of use
@@ -26,17 +26,17 @@ export {
ApiBlock,
FunctionBlock,
CrewAIVisionBlock,
FirecrawlScrapeBlock,
FirecrawlBlock,
JinaBlock,
TranslateBlock,
SlackMessageBlock,
SlackBlock,
GitHubBlock,
ConditionBlock,
SerperBlock,
TavilyBlock,
RouterBlock,
EvaluatorBlock,
YouTubeSearchBlock,
YouTubeBlock,
NotionBlock,
GmailBlock,
XBlock,
@@ -53,27 +53,27 @@ const blocks: Record<string, BlockConfig> = {
router: RouterBlock,
evaluator: EvaluatorBlock,
crewai_vision: CrewAIVisionBlock,
firecrawl_scrape: FirecrawlScrapeBlock,
jina_reader: JinaBlock,
firecrawl: FirecrawlBlock,
jina: JinaBlock,
translate: TranslateBlock,
slack_message: SlackMessageBlock,
github_repo_info: GitHubBlock,
serper_search: SerperBlock,
tavily_block: TavilyBlock,
youtube_search: YouTubeSearchBlock,
notion_reader: NotionBlock,
gmail_block: GmailBlock,
x_block: XBlock,
slack: SlackBlock,
github: GitHubBlock,
serper: SerperBlock,
tavily: TavilyBlock,
youtube: YouTubeBlock,
notion: NotionBlock,
gmail: GmailBlock,
x: XBlock,
}
// Helper functions
export const getBlock = (type: string): BlockConfig | undefined => blocks[type]
export const getBlock = (id: string): BlockConfig | undefined => blocks[id]
export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
Object.values(blocks).filter((block) => block.toolbar.category === category)
Object.values(blocks).filter((block) => block.category === category)
export const getAllBlockTypes = (): string[] => Object.keys(blocks)
export const getAllBlockIds = (): string[] => Object.keys(blocks)
export const isValidBlockType = (type: string): type is string => type in blocks
export const isValidBlockId = (id: string): id is string => id in blocks
export const getAllBlocks = (): BlockConfig[] => Object.values(blocks)
+42 -36
View File
@@ -2,30 +2,35 @@ import type { SVGProps } from 'react'
import type { JSX } from 'react'
import { ToolResponse } from '@/tools/types'
// Basic type definitions for block components
// Basic types
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
export type BlockCategory = 'blocks' | 'tools'
export type ParamType = 'string' | 'number' | 'boolean' | 'json'
export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'any'
// Sub-block configuration types
// Block classification
export type BlockCategory = 'blocks' | 'tools'
// SubBlock types
export type SubBlockType =
| 'short-input'
| 'long-input'
| 'dropdown'
| 'slider'
| 'table'
| 'code'
| 'switch'
| 'tool-input'
| 'checkbox-list'
| 'condition-input'
| 'eval-input'
| 'short-input' // Single line input
| 'long-input' // Multi-line input
| 'dropdown' // Select menu
| 'slider' // Range input
| 'table' // Grid layout
| 'code' // Code editor
| 'switch' // Toggle button
| 'tool-input' // Tool configuration
| 'checkbox-list' // Multiple selection
| 'condition-input' // Conditional logic
| 'eval-input' // Evaluation input
// Component width setting
export type SubBlockLayout = 'full' | 'half'
// Tool output type utilities
// Tool result extraction
export type ExtractToolOutput<T> = T extends ToolResponse ? T['output'] : never
// Convert tool output to types
export type ToolOutputToValueType<T> =
T extends Record<string, any>
? {
@@ -41,11 +46,12 @@ export type ToolOutputToValueType<T> =
}
: never
// Block configuration interfaces and types
// Block output definition
export type BlockOutput =
| PrimitiveValueType
| { [key: string]: PrimitiveValueType | Record<string, any> }
// Parameter validation rules
export interface ParamConfig {
type: ParamType
required: boolean
@@ -64,6 +70,7 @@ export interface ParamConfig {
}
}
// SubBlock configuration
export interface SubBlockConfig {
id: string
title?: string
@@ -88,39 +95,38 @@ export interface SubBlockConfig {
}
}
// Main block definition
export interface BlockConfig<T extends ToolResponse = ToolResponse> {
type: string
toolbar: {
title: string
description: string
bgColor: string
icon: BlockIcon
category: BlockCategory
}
id: string
name: string
description: string
category: BlockCategory
longDescription?: string
bgColor: string
icon: BlockIcon
subBlocks: SubBlockConfig[]
tools: {
access: string[]
config?: {
tool: (params: Record<string, any>) => string
}
}
workflow: {
subBlocks: SubBlockConfig[]
inputs: Record<string, ParamConfig>
outputs: {
response: {
type: ToolOutputToValueType<ExtractToolOutput<T>>
dependsOn?: {
subBlockId: string
condition: {
whenEmpty: ToolOutputToValueType<ExtractToolOutput<T>>
whenFilled: 'json'
}
inputs: Record<string, ParamConfig>
outputs: {
response: {
type: ToolOutputToValueType<ExtractToolOutput<T>>
dependsOn?: {
subBlockId: string
condition: {
whenEmpty: ToolOutputToValueType<ExtractToolOutput<T>>
whenFilled: 'json'
}
}
}
}
}
// Output configuration rules
export interface OutputConfig {
type: BlockOutput
dependsOn?: {
+24 -24
View File
@@ -270,7 +270,7 @@ export class Executor {
context.blockStates.set(blockId, result)
lastOutput = result
if (block.metadata?.type === 'router') {
if (block.metadata?.id === 'router') {
const routerResult = result as {
response: {
content: string
@@ -280,7 +280,7 @@ export class Executor {
}
}
routerDecisions.set(block.id, routerResult.response.selectedPath.blockId)
} else if (block.metadata?.type === 'condition') {
} else if (block.metadata?.id === 'condition') {
const conditionResult = await this.executeConditionalBlock(block, context)
activeConditionalPaths.set(block.id, conditionResult.selectedConditionId)
}
@@ -324,7 +324,7 @@ export class Executor {
// Check if this was the last block in the loop (e.g., a condition block)
const isLoopComplete = executedLoopBlocks.some((blockId) => {
const block = blocks.find((b) => b.id === blockId)
return block?.metadata?.type === 'condition'
return block?.metadata?.id === 'condition'
})
if (hasLoopConnection) {
@@ -374,7 +374,7 @@ export class Executor {
context: ExecutionContext
): Promise<BlockOutput> {
if (block.enabled === false) {
throw new Error(`Cannot execute disabled block: ${block.metadata?.title || block.id}`)
throw new Error(`Cannot execute disabled block: ${block.metadata?.name || block.id}`)
}
const blockLog = this.startBlockLog(block)
@@ -383,7 +383,7 @@ export class Executor {
let output: BlockOutput
// Execute block based on its type.
if (block.metadata?.type === 'router') {
if (block.metadata?.id === 'router') {
const routerOutput = await this.executeRouterBlock(block, context)
output = {
response: {
@@ -393,10 +393,10 @@ export class Executor {
selectedPath: routerOutput.selectedPath,
},
}
} else if (block.metadata?.type === 'evaluator') {
} else if (block.metadata?.id === 'evaluator') {
const evaluatorOutput = await this.executeEvaluatorBlock(block, context)
output = evaluatorOutput
} else if (block.metadata?.type === 'condition') {
} else if (block.metadata?.id === 'condition') {
const conditionResult = await this.executeConditionalBlock(block, context)
output = {
response: {
@@ -409,7 +409,7 @@ export class Executor {
},
},
}
} else if (block.metadata?.type === 'agent') {
} else if (block.metadata?.id === 'agent') {
// Agent block: use a provider request.
let responseFormat: any = undefined
if (inputs.responseFormat) {
@@ -431,7 +431,7 @@ export class Executor {
const formattedTools = Array.isArray(inputs.tools)
? inputs.tools
.map((tool: any) => {
const blockFound = getAllBlocks().find((b: BlockConfig) => b.type === tool.type)
const blockFound = getAllBlocks().find((b: BlockConfig) => b.id === tool.type)
const toolId = blockFound?.tools.access[0]
if (!toolId) return null
@@ -580,8 +580,8 @@ export class Executor {
}
return {
id: targetBlock.id,
type: targetBlock.metadata?.type,
title: targetBlock.metadata?.title,
type: targetBlock.metadata?.id,
title: targetBlock.metadata?.name,
description: targetBlock.metadata?.description,
subBlocks: targetBlock.config.params,
currentState: context.blockStates.get(targetBlock.id),
@@ -729,7 +729,7 @@ export class Executor {
for (const conn of connections) {
// Don't follow connections from other routers
const sourceBlock = this.workflow.blocks.find((b) => b.id === conn.source)
if (sourceBlock?.metadata?.type !== 'router') {
if (sourceBlock?.metadata?.id !== 'router') {
queue.push(conn.target)
}
}
@@ -784,8 +784,8 @@ export class Executor {
if (!sourceBlock) {
throw new Error(`Source block ${sourceBlockId} not found`)
}
const sourceKey = sourceBlock.metadata?.title
? sourceBlock.metadata.title.toLowerCase().replace(/\s+/g, '')
const sourceKey = sourceBlock.metadata?.name
? sourceBlock.metadata.name.toLowerCase().replace(/\s+/g, '')
: 'source'
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
@@ -874,8 +874,8 @@ export class Executor {
result: conditionMet,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
blockType: targetBlock.metadata?.id || '',
blockTitle: targetBlock.metadata?.name || '',
},
selectedConditionId: selectedCondition.id,
},
@@ -892,8 +892,8 @@ export class Executor {
sourceOutput: sourceBlockState,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
blockType: targetBlock.metadata?.id || '',
blockTitle: targetBlock.metadata?.name || '',
},
}
}
@@ -918,7 +918,7 @@ export class Executor {
const blockById = new Map(this.workflow.blocks.map((b) => [b.id, b]))
const blockByName = new Map(
this.workflow.blocks.map((b) => [
b.metadata?.title?.toLowerCase().replace(/\s+/g, '') || '',
b.metadata?.name?.toLowerCase().replace(/\s+/g, '') || '',
b,
])
)
@@ -932,8 +932,8 @@ export class Executor {
blockById,
blockByName,
context.blockStates,
block.metadata?.title || '',
block.metadata?.type || ''
block.metadata?.name || '',
block.metadata?.id || ''
)
// Resolve environment variables
@@ -961,8 +961,8 @@ export class Executor {
private startBlockLog(block: SerializedBlock): BlockLog {
return {
blockId: block.id,
blockTitle: block.metadata?.title || '',
blockType: block.metadata?.type || '',
blockName: block.metadata?.name || '',
blockType: block.metadata?.id || '',
startedAt: new Date().toISOString(),
endedAt: '',
durationMs: 0,
@@ -986,7 +986,7 @@ export class Executor {
) {
const sourceBlock = blocks.find((b) => b.id === conn.source)
if (sourceBlock?.metadata?.type === 'router') {
if (sourceBlock?.metadata?.id === 'router') {
const chosenPath = routerDecisions?.get(sourceBlock.id)
if (conn.target === chosenPath) {
+1 -1
View File
@@ -5,7 +5,7 @@ import { BlockOutput } from '@/blocks/types'
*/
export interface BlockLog {
blockId: string
blockTitle?: string
blockName?: string
blockType?: string
startedAt: string
endedAt: string
+12 -12
View File
@@ -38,8 +38,8 @@ export class Serializer {
// Get inputs from block config
const inputs: Record<string, any> = {}
if (blockConfig.workflow.inputs) {
Object.entries(blockConfig.workflow.inputs).forEach(([key, config]) => {
if (blockConfig.inputs) {
Object.entries(blockConfig.inputs).forEach(([key, config]) => {
inputs[key] = config.type
})
}
@@ -62,11 +62,11 @@ export class Serializer {
: {}),
},
metadata: {
title: block.name,
description: blockConfig.toolbar.description,
category: blockConfig.toolbar.category,
color: blockConfig.toolbar.bgColor,
type: block.type,
id: block.type,
name: block.name,
description: blockConfig.description,
category: blockConfig.category,
color: blockConfig.bgColor,
},
enabled: block.enabled,
}
@@ -86,7 +86,7 @@ export class Serializer {
})
// Then check for any subBlocks with default values
blockConfig.workflow.subBlocks.forEach((subBlockConfig) => {
blockConfig.subBlocks.forEach((subBlockConfig) => {
const id = subBlockConfig.id
if (params[id] === null && subBlockConfig.value) {
// If the value is null and there's a default value function, use it
@@ -125,9 +125,9 @@ export class Serializer {
}
private deserializeBlock(serializedBlock: SerializedBlock): BlockState {
const blockType = serializedBlock.metadata?.type
const blockType = serializedBlock.metadata?.id
if (!blockType) {
throw new Error(`Invalid block type: ${serializedBlock.metadata?.type}`)
throw new Error(`Invalid block type: ${serializedBlock.metadata?.id}`)
}
const blockConfig = getBlock(blockType)
@@ -136,7 +136,7 @@ export class Serializer {
}
const subBlocks: Record<string, any> = {}
blockConfig.workflow.subBlocks.forEach((subBlock) => {
blockConfig.subBlocks.forEach((subBlock) => {
subBlocks[subBlock.id] = {
id: subBlock.id,
type: subBlock.type,
@@ -147,7 +147,7 @@ export class Serializer {
return {
id: serializedBlock.id,
type: blockType,
name: serializedBlock.metadata?.title || blockConfig.toolbar.title,
name: serializedBlock.metadata?.name || blockConfig.name,
position: serializedBlock.position,
subBlocks,
outputs: serializedBlock.outputs,
+2 -2
View File
@@ -29,12 +29,12 @@ export interface SerializedBlock {
inputs: Record<string, ParamType>
outputs: Record<string, BlockOutput>
metadata?: {
title?: string
id: string
name?: string
description?: string
category?: string
icon?: string
color?: string
type: string
}
enabled: boolean
}
+2 -2
View File
@@ -104,7 +104,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
if (!blockConfig) return
const subBlocks: Record<string, SubBlockState> = {}
blockConfig.workflow.subBlocks.forEach((subBlock) => {
blockConfig.subBlocks.forEach((subBlock) => {
const subBlockId = subBlock.id
subBlocks[subBlockId] = {
id: subBlockId,
@@ -113,7 +113,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
})
const outputs = resolveOutputType(blockConfig.workflow.outputs, subBlocks)
const outputs = resolveOutputType(blockConfig.outputs, subBlocks)
const newState = {
blocks: {