Added prettier, added husky pre-commit hook to ensure formatting

This commit is contained in:
Waleed Latif
2025-02-05 03:10:23 -08:00
parent 578a4a944c
commit 9493055c60
138 changed files with 3597 additions and 3162 deletions
+1
View File
@@ -0,0 +1 @@
npx lint-staged
+37
View File
@@ -0,0 +1,37 @@
# Build output
.next
out
dist
build
# Dependencies
node_modules
# Cache
.cache
.npm
# Misc
.DS_Store
*.pem
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local env files
.env*.local
.env
# Vercel
.vercel
# Coverage
coverage
# PWA files
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/fallback-*.js
+22
View File
@@ -0,0 +1,22 @@
module.exports = {
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 100,
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
plugins: ['prettier-plugin-tailwindcss', '@trivago/prettier-plugin-sort-imports'],
importOrder: [
'^(react/(.*)$)|^(react$)',
'^(next/(.*)$)|^(next$)',
'<THIRD_PARTY_MODULES>',
'^@/components/(.*)$',
'^@/lib/(.*)$',
'^@/stores/(.*)$',
'^@/(.*)$',
'^[./]',
],
importOrderSortSpecifiers: true,
}
+1
View File
@@ -29,6 +29,7 @@ All icons are available as React components and can be imported from `components
import { ApiIcon, CodeIcon, ErrorIcon } from '@/components/icons'
// Usage
;<ApiIcon className="w-6 h-6" />
```
+11 -13
View File
@@ -1,14 +1,14 @@
import { NextResponse } from 'next/server'
import { getTool } from '@/tools'
import { anthropicProvider } from '@/providers/anthropic'
import { googleProvider } from '@/providers/google'
import { openaiProvider } from '@/providers/openai'
import { ProviderConfig } from '@/providers/types'
import { googleProvider } from '@/providers/google'
import { getTool } from '@/tools'
const providers: Record<string, ProviderConfig> = {
'anthropic/chat': anthropicProvider,
'openai/chat': openaiProvider,
'google/chat': googleProvider
'google/chat': googleProvider,
}
export async function POST(request: Request) {
@@ -26,7 +26,7 @@ export async function POST(request: Request) {
const response = await fetch(provider.baseUrl, {
method: 'POST',
headers: provider.headers(apiKey),
body: JSON.stringify(restParams)
body: JSON.stringify(restParams),
})
if (!response.ok) {
@@ -36,7 +36,7 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: await response.json()
output: await response.json(),
})
}
@@ -62,10 +62,10 @@ export async function POST(request: Request) {
const externalResponse = await fetch(url, { method, headers, body })
if (!externalResponse.ok) {
const errorContent = await externalResponse.json().catch(() => ({
message: externalResponse.statusText
const errorContent = await externalResponse.json().catch(() => ({
message: externalResponse.statusText,
}))
// Use the tool's error transformer or a default message
const error = tool.transformError
? tool.transformError(errorContent)
@@ -84,9 +84,7 @@ export async function POST(request: Request) {
if (!result.success) {
throw new Error(
tool.transformError
? tool.transformError(result)
: 'Tool returned an error'
tool.transformError ? tool.transformError(result) : 'Tool returned an error'
)
}
@@ -97,7 +95,7 @@ export async function POST(request: Request) {
} catch (error: any) {
return NextResponse.json({
success: false,
error: error.message || 'Unknown error'
error: error.message || 'Unknown error',
})
}
}
}
+2 -7
View File
@@ -4,15 +4,10 @@ import { ZoomPrevention } from './zoom-prevention'
export const metadata: Metadata = {
title: 'Sim Studio',
description:
'Drag and drop agents to create workflows. Powered by Simplicity',
description: 'Drag and drop agents to create workflows. Powered by Simplicity',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
@@ -1,9 +1,8 @@
import { EdgeProps, getSmoothStepPath } from 'reactflow'
import { X } from 'lucide-react'
import { EdgeProps, getSmoothStepPath } from 'reactflow'
export const CustomEdge = (props: EdgeProps) => {
const isHorizontal =
props.sourcePosition === 'right' || props.sourcePosition === 'left'
const isHorizontal = props.sourcePosition === 'right' || props.sourcePosition === 'left'
// For horizontal handles, we'll add a minimum extension to ensure the path
// always goes outward before going up/down
@@ -1,20 +1,8 @@
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Copy, Play, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Trash2,
Play,
Circle,
CircleOff,
Copy,
ArrowLeftRight,
ArrowUpDown,
} from 'lucide-react'
import { useWorkflowStore } from '@/stores/workflow/store'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useWorkflowStore } from '@/stores/workflow/store'
interface ActionBarProps {
blockId: string
@@ -22,16 +10,10 @@ interface ActionBarProps {
export function ActionBar({ blockId }: ActionBarProps) {
const removeBlock = useWorkflowStore((state) => state.removeBlock)
const toggleBlockEnabled = useWorkflowStore(
(state) => state.toggleBlockEnabled
)
const toggleBlockHandles = useWorkflowStore(
(state) => state.toggleBlockHandles
)
const toggleBlockEnabled = useWorkflowStore((state) => state.toggleBlockEnabled)
const toggleBlockHandles = useWorkflowStore((state) => state.toggleBlockHandles)
const duplicateBlock = useWorkflowStore((state) => state.duplicateBlock)
const isEnabled = useWorkflowStore(
(state) => state.blocks[blockId]?.enabled ?? true
)
const isEnabled = useWorkflowStore((state) => state.blocks[blockId]?.enabled ?? true)
const horizontalHandles = useWorkflowStore(
(state) => state.blocks[blockId]?.horizontalHandles ?? false
)
@@ -63,16 +45,10 @@ export function ActionBar({ blockId }: ActionBarProps) {
onClick={() => toggleBlockEnabled(blockId)}
className="text-gray-500"
>
{isEnabled ? (
<Circle className="h-4 w-4" />
) : (
<CircleOff className="h-4 w-4" />
)}
{isEnabled ? <Circle className="h-4 w-4" /> : <CircleOff className="h-4 w-4" />}
</Button>
</TooltipTrigger>
<TooltipContent side="right">
{isEnabled ? 'Disable Block' : 'Enable Block'}
</TooltipContent>
<TooltipContent side="right">{isEnabled ? 'Disable Block' : 'Enable Block'}</TooltipContent>
</Tooltip>
<Tooltip>
@@ -1,17 +1,13 @@
import { useBlockConnections } from '@/app/w/hooks/use-block-connections'
import { Card } from '@/components/ui/card'
import { useBlockConnections } from '@/app/w/hooks/use-block-connections'
interface ConnectionBlocksProps {
blockId: string
setIsConnecting: (isConnecting: boolean) => void
}
export function ConnectionBlocks({
blockId,
setIsConnecting,
}: ConnectionBlocksProps) {
const { incomingConnections, hasIncomingConnections } =
useBlockConnections(blockId)
export function ConnectionBlocks({ blockId, setIsConnecting }: ConnectionBlocksProps) {
const { incomingConnections, hasIncomingConnections } = useBlockConnections(blockId)
if (!hasIncomingConnections) return null
@@ -51,9 +47,7 @@ export function ConnectionBlocks({
<span className="font-medium leading-none">
{connection.name.replace(/\s+/g, '').toLowerCase()}
</span>
<span className="text-muted-foreground">
.{connection.outputType}
</span>
<span className="text-muted-foreground">.{connection.outputType}</span>
</div>
</Card>
))}
@@ -1,7 +1,7 @@
import { Label } from '@/components/ui/label'
import { Checkbox } from '@/components/ui/checkbox'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { Label } from '@/components/ui/label'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
interface CheckboxListProps {
blockId: string
@@ -11,21 +11,9 @@ interface CheckboxListProps {
layout?: 'full' | 'half'
}
export function CheckboxList({
blockId,
subBlockId,
title,
options,
layout,
}: CheckboxListProps) {
export function CheckboxList({ blockId, subBlockId, title, options, layout }: CheckboxListProps) {
return (
<div
className={cn(
'grid gap-4',
layout === 'half' ? 'grid-cols-2' : 'grid-cols-1',
'pt-1'
)}
>
<div className={cn('grid gap-4', layout === 'half' ? 'grid-cols-2' : 'grid-cols-1', 'pt-1')}>
{options.map((option) => {
const [value, setValue] = useSubBlockValue(blockId, option.id)
return (
@@ -1,11 +1,11 @@
import { useState, useRef, useEffect } from 'react'
import Editor from 'react-simple-code-editor'
import { useEffect, useRef, useState } from 'react'
import { highlight, languages } from 'prismjs'
import 'prismjs/components/prism-javascript'
import 'prismjs/themes/prism.css'
import Editor from 'react-simple-code-editor'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
interface CodeProps {
blockId: string
@@ -19,9 +19,7 @@ export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
const [lineCount, setLineCount] = useState(1)
const [showTags, setShowTags] = useState(false)
const [cursorPosition, setCursorPosition] = useState(0)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(
null
)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
const editorRef = useRef<HTMLDivElement>(null)
// Add new state for tracking visual line heights
@@ -93,10 +91,7 @@ export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
document.body.removeChild(container)
setVisualLineHeights(newVisualLineHeights)
const totalVisualLines = newVisualLineHeights.reduce(
(sum, height) => sum + height,
0
)
const totalVisualLines = newVisualLineHeights.reduce((sum, height) => sum + height, 0)
setLineCount(totalVisualLines)
}
@@ -146,8 +141,7 @@ export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
const dropPosition = textarea?.selectionStart ?? code.length
// Insert '<' at drop position to trigger the dropdown
const newValue =
code.slice(0, dropPosition) + '<' + code.slice(dropPosition)
const newValue = code.slice(0, dropPosition) + '<' + code.slice(dropPosition)
setCode(newValue)
setStoreValue(newValue)
@@ -216,9 +210,7 @@ export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
}
}
}}
highlight={(code) =>
highlight(code, languages.javascript, 'javascript')
}
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
padding={12}
style={{
fontFamily: 'inherit',
@@ -1,3 +1,4 @@
import { useEffect } from 'react'
import {
Select,
SelectContent,
@@ -6,7 +7,6 @@ import {
SelectValue,
} from '@/components/ui/select'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { useEffect } from 'react'
interface DropdownProps {
options: string[]
@@ -15,12 +15,7 @@ interface DropdownProps {
subBlockId: string
}
export function Dropdown({
options,
defaultValue,
blockId,
subBlockId,
}: DropdownProps) {
export function Dropdown({ options, defaultValue, blockId, subBlockId }: DropdownProps) {
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
// Set the value to the first option if it's not set
@@ -1,15 +1,12 @@
import { Textarea } from '@/components/ui/textarea'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { cn } from '@/lib/utils'
import { useState, useRef } from 'react'
import { SubBlockConfig } from '@/blocks/types'
import { useRef, useState } from 'react'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import {
EnvVarDropdown,
checkEnvVarTrigger,
} from '@/components/ui/env-var-dropdown'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { Textarea } from '@/components/ui/textarea'
import { cn } from '@/lib/utils'
import { useWorkflowStore } from '@/stores/workflow/store'
import { SubBlockConfig } from '@/blocks/types'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
interface LongInputProps {
placeholder?: string
@@ -33,9 +30,7 @@ export function LongInput({
const [cursorPosition, setCursorPosition] = useState(0)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const overlayRef = useRef<HTMLDivElement>(null)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(
null
)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
// Handle input changes
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -77,15 +72,11 @@ export function LongInput({
if (data.type !== 'connectionBlock') return
// Get current cursor position or append to end
const dropPosition =
textareaRef.current?.selectionStart ?? value?.toString().length ?? 0
const dropPosition = textareaRef.current?.selectionStart ?? value?.toString().length ?? 0
// Insert '<' at drop position to trigger the dropdown
const currentValue = value?.toString() ?? ''
const newValue =
currentValue.slice(0, dropPosition) +
'<' +
currentValue.slice(dropPosition)
const newValue = currentValue.slice(0, dropPosition) + '<' + currentValue.slice(dropPosition)
// Focus the textarea first
textareaRef.current?.focus()
@@ -1,14 +1,11 @@
import { useEffect, useRef, useState } from 'react'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { Input } from '@/components/ui/input'
import { useState, useRef, useEffect } from 'react'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { cn } from '@/lib/utils'
import { SubBlockConfig } from '@/blocks/types'
import { formatDisplayText } from '@/components/ui/formatted-text'
import {
EnvVarDropdown,
checkEnvVarTrigger,
} from '@/components/ui/env-var-dropdown'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
interface ShortInputProps {
placeholder?: string
@@ -39,9 +36,7 @@ export function ShortInput({
const [cursorPosition, setCursorPosition] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
const overlayRef = useRef<HTMLDivElement>(null)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(
null
)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
// Use either controlled or uncontrolled value
const value = propValue !== undefined ? propValue : storeValue
@@ -104,15 +99,11 @@ export function ShortInput({
if (data.type !== 'connectionBlock') return
// Get current cursor position or append to end
const dropPosition =
inputRef.current?.selectionStart ?? value?.toString().length ?? 0
const dropPosition = inputRef.current?.selectionStart ?? value?.toString().length ?? 0
// Insert '<' at drop position to trigger the dropdown
const currentValue = value?.toString() ?? ''
const newValue =
currentValue.slice(0, dropPosition) +
'<' +
currentValue.slice(dropPosition)
const newValue = currentValue.slice(0, dropPosition) + '<' + currentValue.slice(dropPosition)
// Focus the input first
inputRef.current?.focus()
@@ -151,9 +142,7 @@ export function ShortInput({
// Value display logic
const displayValue =
password && !isFocused
? '•'.repeat(value?.toString().length ?? 0)
: value?.toString() ?? ''
password && !isFocused ? '•'.repeat(value?.toString().length ?? 0) : (value?.toString() ?? '')
// Modify the EnvVarDropdown to use the correct setter
const handleEnvVarSelect = (newValue: string) => {
@@ -32,9 +32,7 @@ export function SliderInput({
<div
className="absolute text-sm text-muted-foreground"
style={{
left: `clamp(0%, ${
((Number(sliderValue) - min) / (max - min)) * 100
}%, 100%)`,
left: `clamp(0%, ${((Number(sliderValue) - min) / (max - min)) * 100}%, 100%)`,
transform: `translateX(-${(() => {
const percentage = ((Number(sliderValue) - min) / (max - min)) * 100
const bias = -25 * Math.sin((percentage * Math.PI) / 50)
@@ -1,6 +1,6 @@
import { Label } from '@/components/ui/label'
import { Switch as UISwitch } from '@/components/ui/switch'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { Label } from '@/components/ui/label'
interface SwitchProps {
blockId: string
@@ -16,10 +16,7 @@ export function Switch({ blockId, subBlockId, title }: SwitchProps) {
<Label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{title}
</Label>
<UISwitch
checked={Boolean(value)}
onCheckedChange={(checked) => setValue(checked)}
/>
<UISwitch checked={Boolean(value)} onCheckedChange={(checked) => setValue(checked)} />
</div>
)
}
@@ -1,9 +1,9 @@
import { useEffect, useRef } from 'react'
import { Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { useRef, useEffect } from 'react'
interface TableProps {
columns: string[]
@@ -18,9 +18,7 @@ interface TableRow {
export function Table({ columns, blockId, subBlockId }: TableProps) {
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
const activePositionRef = useRef<{ rowIndex: number; column: string } | null>(
null
)
const activePositionRef = useRef<{ rowIndex: number; column: string } | null>(null)
const inputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
useEffect(() => {
@@ -57,11 +55,7 @@ export function Table({ columns, blockId, subBlockId }: TableProps) {
},
]
const handleCellChange = (
rowIndex: number,
column: string,
value: string
) => {
const handleCellChange = (rowIndex: number, column: string, value: string) => {
const updatedRows = rows.map((row, idx) =>
idx === rowIndex
? {
@@ -111,10 +105,7 @@ export function Table({ columns, blockId, subBlockId }: TableProps) {
{columns.map((column, cellIndex) => (
<td
key={`${row.id}-${column}`}
className={cn(
'p-1',
cellIndex < columns.length - 1 && 'border-r'
)}
className={cn('p-1', cellIndex < columns.length - 1 && 'border-r')}
>
<Input
ref={(el) => {
@@ -128,9 +119,7 @@ export function Table({ columns, blockId, subBlockId }: TableProps) {
data-column={column}
value={row.cells[column] || ''}
placeholder={column}
onChange={(e) =>
handleCellChange(rowIndex, column, e.target.value)
}
onChange={(e) => handleCellChange(rowIndex, column, e.target.value)}
onFocus={() => {
activePositionRef.current = { rowIndex, column }
}}
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { PlusIcon, XIcon } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Command,
CommandEmpty,
@@ -9,15 +9,11 @@ import {
CommandItem,
CommandList,
} from '@/components/ui/command'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { getAllBlocks } from '@/blocks'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { getAllBlocks } from '@/blocks'
import { getTool } from '@/tools'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
import { ShortInput } from './short-input'
interface ToolInputProps {
@@ -82,9 +78,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
const [open, setOpen] = useState(false)
const toolBlocks = getAllBlocks().filter(
(block) => block.toolbar.category === 'tools'
)
const toolBlocks = getAllBlocks().filter((block) => block.toolbar.category === 'tools')
const selectedTools: StoredTool[] =
Array.isArray(value) && value.length > 0 && typeof value[0] === 'object'
@@ -112,11 +106,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
setValue(selectedTools.filter((tool) => tool.type !== toolType))
}
const handleParamChange = (
toolType: string,
paramId: string,
paramValue: string
) => {
const handleParamChange = (toolType: string, paramId: string, paramValue: string) => {
setValue(
selectedTools.map((tool) =>
tool.type === toolType
@@ -132,13 +122,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
)
}
const IconComponent = ({
icon: Icon,
className,
}: {
icon: any
className?: string
}) => {
const IconComponent = ({ icon: Icon, className }: { icon: any; className?: string }) => {
if (!Icon) return null
return <Icon className={className} />
}
@@ -177,10 +161,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
className="flex items-center justify-center w-6 h-6 rounded"
style={{ backgroundColor: block.toolbar.bgColor }}
>
<IconComponent
icon={block.toolbar.icon}
className="w-4 h-4 text-white"
/>
<IconComponent icon={block.toolbar.icon} className="w-4 h-4 text-white" />
</div>
<span>{block.toolbar.title}</span>
</CommandItem>
@@ -193,9 +174,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.type === tool.type)
const toolId = getToolIdFromBlock(tool.type)
const requiredParams = toolId ? getRequiredToolParams(toolId) : []
@@ -235,10 +214,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
blockId={blockId}
subBlockId={`${subBlockId}-param`}
placeholder={param.description}
password={
param.id.toLowerCase().replace(/\s+/g, '') ===
'apikey'
}
password={param.id.toLowerCase().replace(/\s+/g, '') === 'apikey'}
isConnecting={false}
config={{
id: `${subBlockId}-param`,
@@ -246,9 +222,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
title: param.id,
}}
value={tool.params[param.id] || ''}
onChange={(value) =>
handleParamChange(tool.type, param.id, value)
}
onChange={(value) => handleParamChange(tool.type, param.id, value)}
/>
</div>
</div>
@@ -286,10 +260,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
className="flex items-center justify-center w-6 h-6 rounded"
style={{ backgroundColor: block.toolbar.bgColor }}
>
<IconComponent
icon={block.toolbar.icon}
className="w-4 h-4 text-white"
/>
<IconComponent icon={block.toolbar.icon} className="w-4 h-4 text-white" />
</div>
<span>{block.toolbar.title}</span>
</CommandItem>
@@ -1,11 +1,10 @@
import { useWorkflowStore } from '@/stores/workflow/store'
import { useCallback } from 'react'
import { useWorkflowStore } from '@/stores/workflow/store'
export function useSubBlockValue(blockId: string, subBlockId: string) {
const value = useWorkflowStore(
useCallback(
(state) =>
(state.blocks[blockId]?.subBlocks[subBlockId]?.value) ?? null,
(state) => state.blocks[blockId]?.subBlocks[subBlockId]?.value ?? null,
[blockId, subBlockId]
)
)
@@ -20,4 +19,4 @@ export function useSubBlockValue(blockId: string, subBlockId: string) {
)
return [value, setValue] as const
}
}
@@ -1,14 +1,14 @@
import { SubBlockConfig } from '../../../../../../../blocks/types'
import { Label } from '@/components/ui/label'
import { ShortInput } from './components/short-input'
import { LongInput } from './components/long-input'
import { Dropdown } from './components/dropdown'
import { SliderInput } from './components/slider-input'
import { Table } from './components/table'
import { Code } from './components/code'
import { Switch } from './components/switch'
import { ToolInput } from './components/tool-input'
import { SubBlockConfig } from '../../../../../../../blocks/types'
import { CheckboxList } from './components/checkbox-list'
import { Code } from './components/code'
import { Dropdown } from './components/dropdown'
import { LongInput } from './components/long-input'
import { ShortInput } from './components/short-input'
import { SliderInput } from './components/slider-input'
import { Switch } from './components/switch'
import { Table } from './components/table'
import { ToolInput } from './components/tool-input'
interface SubBlockProps {
blockId: string
@@ -65,35 +65,15 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
subBlockId={config.id}
min={config.min}
max={config.max}
defaultValue={
(config.min || 0) + ((config.max || 100) - (config.min || 0)) / 2
}
defaultValue={(config.min || 0) + ((config.max || 100) - (config.min || 0)) / 2}
/>
)
case 'table':
return (
<Table
blockId={blockId}
subBlockId={config.id}
columns={config.columns ?? []}
/>
)
return <Table blockId={blockId} subBlockId={config.id} columns={config.columns ?? []} />
case 'code':
return (
<Code
blockId={blockId}
subBlockId={config.id}
isConnecting={isConnecting}
/>
)
return <Code blockId={blockId} subBlockId={config.id} isConnecting={isConnecting} />
case 'switch':
return (
<Switch
blockId={blockId}
subBlockId={config.id}
title={config.title ?? ''}
/>
)
return <Switch blockId={blockId} subBlockId={config.id} title={config.title ?? ''} />
case 'tool-input':
return <ToolInput blockId={blockId} subBlockId={config.id} />
case 'checkbox-list':
@@ -1,14 +1,14 @@
import { Card } from '@/components/ui/card'
import { BlockConfig, SubBlockConfig } from '../../../../../blocks/types'
import { SubBlock } from './components/sub-block/sub-block'
import { useEffect, useRef, useState } from 'react'
import { Handle, Position } from 'reactflow'
import { useUpdateNodeInternals } from 'reactflow'
import { Badge } from '@/components/ui/badge'
import { Card } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { useWorkflowStore } from '@/stores/workflow/store'
import { BlockConfig, SubBlockConfig } from '../../../../../blocks/types'
import { ActionBar } from './components/action-bar/action-bar'
import { ConnectionBlocks } from './components/connection-blocks/connection-blocks'
import { useState, useEffect, useRef } from 'react'
import { useWorkflowStore } from '@/stores/workflow/store'
import { Badge } from '@/components/ui/badge'
import { useUpdateNodeInternals } from 'reactflow'
import { SubBlock } from './components/sub-block/sub-block'
interface WorkflowBlockProps {
id: string
@@ -24,19 +24,11 @@ interface SubBlockPosition {
top: number
}
export function WorkflowBlock({
id,
type,
config,
name,
selected,
}: WorkflowBlockProps) {
export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBlockProps) {
const { toolbar, workflow } = config
// Dragging connection state
const [isConnecting, setIsConnecting] = useState(false)
const isEnabled = useWorkflowStore(
(state) => state.blocks[id]?.enabled ?? true
)
const isEnabled = useWorkflowStore((state) => state.blocks[id]?.enabled ?? true)
const horizontalHandles = useWorkflowStore(
(state) => state.blocks[id]?.horizontalHandles ?? false
)
@@ -44,9 +36,7 @@ export function WorkflowBlock({
const [editedName, setEditedName] = useState('')
const updateBlockName = useWorkflowStore((state) => state.updateBlockName)
const blockRef = useRef<HTMLDivElement>(null)
const [subBlockPositions, setSubBlockPositions] = useState<
SubBlockPosition[]
>([])
const [subBlockPositions, setSubBlockPositions] = useState<SubBlockPosition[]>([])
const updateNodeInternals = useUpdateNodeInternals()
// Calculate subblock positions when the component mounts or updates
@@ -198,10 +188,7 @@ export function WorkflowBlock({
)}
</div>
{!isEnabled && (
<Badge
variant="secondary"
className="bg-gray-100 text-gray-500 hover:bg-gray-100"
>
<Badge variant="secondary" className="bg-gray-100 text-gray-500 hover:bg-gray-100">
Disabled
</Badge>
)}
@@ -213,16 +200,10 @@ export function WorkflowBlock({
{row.map((subBlock, blockIndex) => (
<div
key={`${id}-${rowIndex}-${blockIndex}`}
className={`space-y-1 ${
subBlock.layout === 'half' ? 'flex-1' : 'w-full'
}`}
className={`space-y-1 ${subBlock.layout === 'half' ? 'flex-1' : 'w-full'}`}
data-subblock-id={subBlock.id}
>
<SubBlock
blockId={id}
config={subBlock}
isConnecting={isConnecting}
/>
<SubBlock blockId={id} config={subBlock} isConnecting={isConnecting} />
</div>
))}
</div>
@@ -3,19 +3,19 @@
import { useCallback, useEffect, useState } from 'react'
import ReactFlow, {
Background,
NodeTypes,
EdgeTypes,
useReactFlow,
ConnectionLineType,
EdgeTypes,
NodeTypes,
useReactFlow,
} from 'reactflow'
import { getBlock } from '../../../../../blocks'
import { useWorkflowStore } from '@/stores/workflow/store'
import { useNotificationStore } from '@/stores/notifications/store'
import { useWorkflowExecution } from '../../../hooks/use-workflow-execution'
import { NotificationList } from '@/app/w/components/notifications/notifications'
import { WorkflowNode } from '../workflow-node/workflow-node'
import { CustomEdge } from '../custom-edge/custom-edge'
import { initializeStateLogger } from '@/stores/workflow/logger'
import { useWorkflowStore } from '@/stores/workflow/store'
import { NotificationList } from '@/app/w/components/notifications/notifications'
import { getBlock } from '../../../../../blocks'
import { useWorkflowExecution } from '../../../hooks/use-workflow-execution'
import { CustomEdge } from '../custom-edge/custom-edge'
import { WorkflowNode } from '../workflow-node/workflow-node'
// Define custom node and edge types for ReactFlow
const nodeTypes: NodeTypes = {
@@ -29,8 +29,7 @@ export function WorkflowCanvas() {
const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null)
// Store references and state management hooks
const { isExecuting, executionResult, handleRunWorkflow } =
useWorkflowExecution()
const { isExecuting, executionResult, handleRunWorkflow } = useWorkflowExecution()
const {
blocks,
edges,
@@ -152,10 +151,7 @@ export function WorkflowCanvas() {
// Handle keyboard shortcuts for edge deletion
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
(event.key === 'Delete' || event.key === 'Backspace') &&
selectedEdgeId
) {
if ((event.key === 'Delete' || event.key === 'Backspace') && selectedEdgeId) {
removeEdge(selectedEdgeId)
setSelectedEdgeId(null)
}
@@ -1,6 +1,6 @@
import { NodeProps } from 'reactflow'
import { WorkflowBlock } from '../workflow-block/workflow-block'
import { BlockConfig } from '../../../../../blocks/types'
import { WorkflowBlock } from '../workflow-block/workflow-block'
interface WorkflowNodeData {
type: string
@@ -8,13 +8,7 @@ interface WorkflowNodeData {
name: string
}
export const WorkflowNode = ({
data,
id,
xPos,
yPos,
selected,
}: NodeProps<WorkflowNodeData>) => (
export const WorkflowNode = ({ data, id, xPos, yPos, selected }: NodeProps<WorkflowNodeData>) => (
<WorkflowBlock
id={id}
type={data.type}
+2 -9
View File
@@ -1,10 +1,10 @@
'use client'
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { ReactFlowProvider } from 'reactflow'
import 'reactflow/dist/style.css'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useParams, useRouter } from 'next/navigation'
import { WorkflowCanvas } from './components/workflow-canvas/workflow-canvas'
export default function Workflow() {
@@ -66,14 +66,7 @@ export default function Workflow() {
}
validateAndNavigate()
}, [
params.id,
workflows,
setActiveWorkflow,
addWorkflow,
router,
isInitialized,
])
}, [params.id, workflows, setActiveWorkflow, addWorkflow, router, isInitialized])
// Don't render until initial data is loaded
if (!isInitialized) {
@@ -1,16 +1,9 @@
import { useState, useMemo } from 'react'
import { formatDistanceToNow, format } from 'date-fns'
import {
Terminal,
Clock,
Calendar,
CheckCircle2,
AlertCircle,
AlertTriangle,
} from 'lucide-react'
import { useMemo, useState } from 'react'
import { format, formatDistanceToNow } from 'date-fns'
import { AlertCircle, AlertTriangle, Calendar, CheckCircle2, Clock, Terminal } from 'lucide-react'
import { ConsoleEntry as ConsoleEntryType } from '@/stores/console/types'
import { JSONView } from '../json-view/json-view'
import { getBlock } from '@/blocks'
import { JSONView } from '../json-view/json-view'
interface ConsoleEntryProps {
entry: ConsoleEntryType
@@ -46,20 +39,14 @@ export function ConsoleEntry({ entry, consoleWidth }: ConsoleEntryProps) {
return (
<div
className={`border-b border-border transition-colors ${
!entry.error && !entry.warning
? 'hover:bg-accent/50 cursor-pointer'
: ''
!entry.error && !entry.warning ? 'hover:bg-accent/50 cursor-pointer' : ''
}`}
onClick={() =>
!entry.error && !entry.warning && setIsExpanded(!isExpanded)
}
onClick={() => !entry.error && !entry.warning && setIsExpanded(!isExpanded)}
>
<div className="p-4 space-y-4">
<div
className={`${
consoleWidth >= 400
? 'flex items-center justify-between'
: 'grid gap-4 grid-cols-1'
consoleWidth >= 400 ? 'flex items-center justify-between' : 'grid gap-4 grid-cols-1'
}`}
>
{entry.blockName && (
@@ -113,9 +100,7 @@ export function ConsoleEntry({ entry, consoleWidth }: ConsoleEntryProps) {
<AlertTriangle className="h-4 w-4 text-yellow-500 mt-1" />
<div className="flex-1 break-all">
<div className="font-medium">Warning</div>
<pre className="text-sm whitespace-pre-wrap">
{entry.warning}
</pre>
<pre className="text-sm whitespace-pre-wrap">{entry.warning}</pre>
</div>
</div>
)}
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { ChevronRight, ChevronDown } from 'lucide-react'
import { useEffect, useState } from 'react'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { Button } from '@/components/ui/button'
interface JSONViewProps {
@@ -40,11 +40,7 @@ const copyToClipboard = (data: any) => {
navigator.clipboard.writeText(stringified)
}
export const JSONView = ({
data,
level = 0,
initiallyExpanded = false,
}: JSONViewProps) => {
export const JSONView = ({ data, level = 0, initiallyExpanded = false }: JSONViewProps) => {
const [isCollapsed, setIsCollapsed] = useState(!initiallyExpanded)
const [contextMenuPosition, setContextMenuPosition] = useState<{
x: number
@@ -74,15 +70,9 @@ export const JSONView = ({
return (
<span
onContextMenu={handleContextMenu}
className={`${
typeof data === 'string' ? 'text-success' : 'text-info'
} break-all relative`}
className={`${typeof data === 'string' ? 'text-success' : 'text-info'} break-all relative`}
>
{typeof data === 'string' ? (
<TruncatedValue value={stringValue} />
) : (
stringValue
)}
{typeof data === 'string' ? <TruncatedValue value={stringValue} /> : stringValue}
{contextMenuPosition && (
<div
className="fixed z-50 bg-popover border rounded-md shadow-md py-1 min-w-[160px]"
@@ -105,9 +95,7 @@ export const JSONView = ({
const isEmpty = items.length === 0
if (isEmpty) {
return (
<span className="text-muted-foreground">{isArray ? '[]' : '{}'}</span>
)
return <span className="text-muted-foreground">{isArray ? '[]' : '{}'}</span>
}
return (
@@ -119,9 +107,7 @@ export const JSONView = ({
setIsCollapsed(!isCollapsed)
}}
>
<span className="text-xs leading-none mr-1">
{isCollapsed ? '▶' : '▼'}
</span>
<span className="text-xs leading-none mr-1">{isCollapsed ? '▶' : '▼'}</span>
<span>{isArray ? '[' : '{'}</span>
{isCollapsed ? '...' : ''}
</span>
+4 -8
View File
@@ -1,15 +1,11 @@
'use client'
import { useState, useMemo, useEffect } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { PanelLeftClose, Terminal } from 'lucide-react'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { useConsoleStore } from '@/stores/console/store'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useConsoleStore } from '@/stores/console/store'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { ConsoleEntry } from './components/console-entry/console-entry'
@@ -31,10 +31,7 @@ export function HistoryDropdownItem({
onClick={onClick}
>
<Clock
className={cn(
'h-4 w-4',
isFuture ? 'text-muted-foreground/50' : 'text-muted-foreground'
)}
className={cn('h-4 w-4', isFuture ? 'text-muted-foreground/50' : 'text-muted-foreground')}
/>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
@@ -58,12 +55,7 @@ export function HistoryDropdownItem({
</span>
)}
</div>
<p
className={cn(
'text-sm',
isFuture ? 'text-muted-foreground/50' : 'text-foreground'
)}
>
<p className={cn('text-sm', isFuture ? 'text-muted-foreground/50' : 'text-foreground')}>
{action}
</p>
</div>
@@ -1,14 +1,11 @@
import { useEffect, useState } from 'react'
import { formatDistanceToNow } from 'date-fns'
import { Terminal, AlertCircle } from 'lucide-react'
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
import {
NotificationType,
NotificationStore,
} from '@/stores/notifications/types'
import { useNotificationStore } from '@/stores/notifications/store'
import { cn } from '@/lib/utils'
import { AlertCircle, Terminal } from 'lucide-react'
import { ErrorIcon } from '@/components/icons'
import { useState, useEffect } from 'react'
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
import { useNotificationStore } from '@/stores/notifications/store'
import { NotificationStore, NotificationType } from '@/stores/notifications/types'
interface NotificationDropdownItemProps {
id: string
@@ -46,8 +43,7 @@ export function NotificationDropdownItem({
const timeAgo = formatDistanceToNow(timestamp, { addSuffix: true })
// Truncate message if it's too long
const truncatedMessage =
message.length > 50 ? `${message.slice(0, 50)}...` : message
const truncatedMessage = message.length > 50 ? `${message.slice(0, 50)}...` : message
return (
<DropdownMenuItem
@@ -57,9 +53,7 @@ export function NotificationDropdownItem({
<Icon className={cn('h-4 w-4', NotificationColors[type])} />
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span className="text-xs font-medium">
{type === 'error' ? 'Error' : 'Console'}
</span>
<span className="text-xs font-medium">{type === 'error' ? 'Error' : 'Console'}</span>
<span className="text-xs text-muted-foreground">{timeAgo}</span>
</div>
<p className="text-sm text-foreground">{truncatedMessage}</p>
+23 -42
View File
@@ -1,27 +1,9 @@
'use client'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { History, Bell, Play, Trash2, MessageSquare } from 'lucide-react'
import { useNotificationStore } from '@/stores/notifications/store'
import { NotificationDropdownItem } from './components/notification-dropdown-item'
import { useWorkflowStore } from '@/stores/workflow/store'
import { HistoryDropdownItem } from './components/history-dropdown-item'
import { formatDistanceToNow } from 'date-fns'
import { useEffect, useState } from 'react'
import { useWorkflowExecution } from '../../hooks/use-workflow-execution'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useRouter } from 'next/navigation'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { formatDistanceToNow } from 'date-fns'
import { Bell, History, MessageSquare, Play, Trash2 } from 'lucide-react'
import {
AlertDialog,
AlertDialogAction,
@@ -33,14 +15,27 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useNotificationStore } from '@/stores/notifications/store'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useWorkflowStore } from '@/stores/workflow/store'
import { useWorkflowExecution } from '../../hooks/use-workflow-execution'
import { HistoryDropdownItem } from './components/history-dropdown-item'
import { NotificationDropdownItem } from './components/notification-dropdown-item'
export function ControlBar() {
const { notifications, getWorkflowNotifications } = useNotificationStore()
const { history, undo, redo, revertToHistoryState } = useWorkflowStore()
const [isEditing, setIsEditing] = useState(false)
const [editedName, setEditedName] = useState('')
const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } =
useWorkflowRegistry()
const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } = useWorkflowRegistry()
const [, forceUpdate] = useState({})
const { isExecuting, handleRunWorkflow } = useWorkflowExecution()
const router = useRouter()
@@ -167,8 +162,7 @@ export function ControlBar() {
<AlertDialogHeader>
<AlertDialogTitle>Delete Workflow</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this workflow? This action
cannot be undone.
Are you sure you want to delete this workflow? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
@@ -198,10 +192,7 @@ export function ControlBar() {
</DropdownMenuItem>
</DropdownMenuContent>
) : (
<DropdownMenuContent
align="end"
className="w-60 max-h-[300px] overflow-y-auto"
>
<DropdownMenuContent align="end" className="w-60 max-h-[300px] overflow-y-auto">
<>
{[...history.future].reverse().map((entry, index) => (
<HistoryDropdownItem
@@ -210,9 +201,7 @@ export function ControlBar() {
timestamp={entry.timestamp}
onClick={() =>
revertToHistoryState(
history.past.length +
1 +
(history.future.length - 1 - index)
history.past.length + 1 + (history.future.length - 1 - index)
)
}
isFuture={true}
@@ -230,9 +219,7 @@ export function ControlBar() {
key={`past-${entry.timestamp}-${index}`}
action={entry.action}
timestamp={entry.timestamp}
onClick={() =>
revertToHistoryState(history.past.length - 1 - index)
}
onClick={() => revertToHistoryState(history.past.length - 1 - index)}
/>
))}
</>
@@ -255,17 +242,11 @@ export function ControlBar() {
</DropdownMenuItem>
</DropdownMenuContent>
) : (
<DropdownMenuContent
align="end"
className="w-60 max-h-[300px] overflow-y-auto"
>
<DropdownMenuContent align="end" className="w-60 max-h-[300px] overflow-y-auto">
{[...workflowNotifications]
.sort((a, b) => b.timestamp - a.timestamp)
.map((notification) => (
<NotificationDropdownItem
key={notification.id}
{...notification}
/>
<NotificationDropdownItem key={notification.id} {...notification} />
))}
</DropdownMenuContent>
)}
@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react'
import { AlertCircle, Terminal } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { useNotificationStore } from '@/stores/notifications/store'
import { cn } from '@/lib/utils'
import { ErrorIcon } from '@/components/icons'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/utils'
import { useNotificationStore } from '@/stores/notifications/store'
const NOTIFICATION_TIMEOUT = 4000
const FADE_DURATION = 300
@@ -22,9 +22,7 @@ const NotificationColors = {
export function NotificationList() {
const { notifications, hideNotification } = useNotificationStore()
const [fadingNotifications, setFadingNotifications] = useState<Set<string>>(
new Set()
)
const [fadingNotifications, setFadingNotifications] = useState<Set<string>>(new Set())
// Only show visible notifications in the display
const visibleNotifications = notifications.filter((n) => n.isVisible)
@@ -74,9 +72,7 @@ export function NotificationList() {
key={notification.id}
className={cn(
'transition-all duration-300 ease-in-out opacity-0 translate-y-[-100%]',
isFading
? 'animate-notification-fade-out'
: 'animate-notification-slide',
isFading ? 'animate-notification-fade-out' : 'animate-notification-slide',
NotificationColors[notification.type]
)}
>
@@ -89,9 +85,7 @@ export function NotificationList() {
<AlertTitle className="ml-2">
{notification.type === 'error' ? 'Error' : 'Console'}
</AlertTitle>
<AlertDescription className="ml-2">
{notification.message}
</AlertDescription>
<AlertDescription className="ml-2">{notification.message}</AlertDescription>
</Alert>
)
})}
@@ -1,13 +1,9 @@
'use client'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import clsx from 'clsx'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import clsx from 'clsx'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
export function NavItem({
href,
@@ -1,11 +1,6 @@
'use client'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useEffect, useMemo, useRef, useState } from 'react'
import {
AlertDialog,
AlertDialogAction,
@@ -16,10 +11,10 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState, useRef, useEffect, useMemo } from 'react'
import { useEnvironmentStore } from '@/stores/environment/store'
import { EnvironmentVariable as StoreEnvironmentVariable } from '@/stores/environment/types'
@@ -39,9 +34,7 @@ const INITIAL_ENV_VAR: UIEnvironmentVariable = { key: '', value: '' }
export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
const { variables, setVariable, removeVariable } = useEnvironmentStore()
const [envVars, setEnvVars] = useState<UIEnvironmentVariable[]>([])
const [focusedValueIndex, setFocusedValueIndex] = useState<number | null>(
null
)
const [focusedValueIndex, setFocusedValueIndex] = useState<number | null>(null)
const [showUnsavedChanges, setShowUnsavedChanges] = useState(false)
const scrollContainerRef = useRef<HTMLDivElement>(null)
const pendingClose = useRef(false)
@@ -111,15 +104,11 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
useEffect(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop =
scrollContainerRef.current.scrollHeight
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight
}
}, [envVars])
const handleValueFocus = (
index: number,
e: React.FocusEvent<HTMLInputElement>
) => {
const handleValueFocus = (index: number, e: React.FocusEvent<HTMLInputElement>) => {
setFocusedValueIndex(index)
// Always scroll to the start of the input
e.target.scrollLeft = 0
@@ -137,10 +126,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
}
}
const handlePaste = (
e: React.ClipboardEvent<HTMLInputElement>,
index: number
) => {
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>, index: number) => {
const text = e.clipboardData.getData('text').trim()
if (!text) return
@@ -149,9 +135,9 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
e.preventDefault()
const inputType = (e.target as HTMLInputElement).getAttribute(
'data-input-type'
) as 'key' | 'value'
const inputType = (e.target as HTMLInputElement).getAttribute('data-input-type') as
| 'key'
| 'value'
const containsKeyValuePair = text.includes('=')
// Handle single value paste into specific field
@@ -164,11 +150,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
handleKeyValuePaste(lines)
}
const handleSingleValuePaste = (
text: string,
index: number,
inputType: 'key' | 'value'
) => {
const handleSingleValuePaste = (text: string, index: number, inputType: 'key' | 'value') => {
const newEnvVars = [...envVars]
newEnvVars[index][inputType] = text
setEnvVars(newEnvVars)
@@ -182,14 +164,14 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
return {
key: key.trim(),
value,
id: Date.now() + Math.random()
id: Date.now() + Math.random(),
}
})
.filter(({ key, value }) => key && value)
if (parsedVars.length > 0) {
// Merge existing vars with new ones, removing any empty rows at the end
const existingVars = envVars.filter(v => v.key || v.value)
const existingVars = envVars.filter((v) => v.key || v.value)
setEnvVars([...existingVars, ...parsedVars])
}
}
@@ -200,11 +182,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
setEnvVars([...envVars, newVar])
}
const updateEnvVar = (
index: number,
field: 'key' | 'value',
value: string
) => {
const updateEnvVar = (index: number, field: 'key' | 'value', value: string) => {
const newEnvVars = [...envVars]
newEnvVars[index][field] = value
setEnvVars(newEnvVars)
@@ -254,12 +232,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
placeholder="Enter value"
className="allow-scroll"
/>
<Button
variant="ghost"
size="icon"
onClick={() => removeEnvVar(index)}
className="h-10 w-10"
>
<Button variant="ghost" size="icon" onClick={() => removeEnvVar(index)} className="h-10 w-10">
×
</Button>
</div>
@@ -309,10 +282,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
</DialogContent>
</Dialog>
<AlertDialog
open={showUnsavedChanges}
onOpenChange={setShowUnsavedChanges}
>
<AlertDialog open={showUnsavedChanges} onOpenChange={setShowUnsavedChanges}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Unsaved Changes</AlertDialogTitle>
@@ -321,12 +291,8 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>
Discard Changes
</AlertDialogCancel>
<AlertDialogAction onClick={handleSave}>
Save Changes
</AlertDialogAction>
<AlertDialogCancel onClick={handleCancel}>Discard Changes</AlertDialogCancel>
<AlertDialogAction onClick={handleSave}>Save Changes</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
+11 -27
View File
@@ -1,28 +1,18 @@
'use client'
import Link from 'next/link'
import { NavItem } from './components/nav-item/nav-item'
import { Settings, Plus } from 'lucide-react'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { AgentIcon } from '@/components/icons'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { Plus, Settings } from 'lucide-react'
import { AgentIcon } from '@/components/icons'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { NavItem } from './components/nav-item/nav-item'
import { SettingsModal } from './components/settings-modal/settings-modal'
export function Sidebar() {
const WORKFLOW_COLORS = [
'#3972F6',
'#F639DD',
'#F6B539',
'#8139F6',
'#F64439',
]
const WORKFLOW_COLORS = ['#3972F6', '#F639DD', '#F6B539', '#8139F6', '#F64439']
const { workflows, addWorkflow } = useWorkflowRegistry()
const router = useRouter()
const [showSettings, setShowSettings] = useState(false)
@@ -33,9 +23,7 @@ export function Sidebar() {
const lastWorkflow = workflowArray[workflowArray.length - 1]
// Find the index of the last used color, defaulting to first color if undefined
const lastColorIndex = lastWorkflow?.color
? WORKFLOW_COLORS.indexOf(lastWorkflow.color)
: -1
const lastColorIndex = lastWorkflow?.color ? WORKFLOW_COLORS.indexOf(lastWorkflow.color) : -1
// Get next color index, wrapping around to 0 if we reach the end
const nextColorIndex = (lastColorIndex + 1) % WORKFLOW_COLORS.length
@@ -83,11 +71,7 @@ export function Sidebar() {
<nav className="flex-1 overflow-y-auto px-2 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']">
<div className="flex flex-col items-center gap-4">
{Object.values(workflows).map((workflow) => (
<NavItem
key={workflow.id}
href={`/w/${workflow.id}`}
label={workflow.name}
>
<NavItem key={workflow.id} href={`/w/${workflow.id}`} label={workflow.name}>
<div
className="h-4 w-4 rounded-full"
style={{ backgroundColor: workflow.color || '#3972F6' }}
@@ -6,10 +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.type }))
e.dataTransfer.effectAllowed = 'move'
}
@@ -31,9 +28,7 @@ export function ToolbarBlock({ config }: ToolbarBlockProps) {
</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>
<p className="text-sm text-muted-foreground">{config.toolbar.description}</p>
</div>
</div>
)
@@ -1,6 +1,6 @@
'use client'
import { useRef, useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
interface ToolbarTabsProps {
activeTab: 'blocks' | 'tools'
@@ -19,8 +19,7 @@ export function ToolbarTabs({ activeTab, onTabChange }: ToolbarTabsProps) {
const activeRef = activeTab === 'blocks' ? blocksRef : toolsRef
if (activeRef.current) {
const rect = activeRef.current.getBoundingClientRect()
const parentRect =
activeRef.current.parentElement?.getBoundingClientRect()
const parentRect = activeRef.current.parentElement?.getBoundingClientRect()
const offsetLeft = parentRect ? rect.left - parentRect.left : 0
setUnderlineStyle({
+7 -11
View File
@@ -1,18 +1,14 @@
'use client'
import { useState, useMemo } from 'react'
import { useMemo, useState } from 'react'
import { PanelRight, PanelRightClose, Search } from 'lucide-react'
import { Input } from '@/components/ui/input'
import { Search, PanelRightClose, PanelRight } from 'lucide-react'
import { ToolbarTabs } from './components/toolbar-tabs/toolbar-tabs'
import { ToolbarBlock } from './components/toolbar-block/toolbar-block'
import { getBlocksByCategory, getAllBlocks } from '../../../../blocks'
import { BlockCategory, BlockConfig } from '../../../../blocks/types'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { getAllBlocks, getBlocksByCategory } from '../../../../blocks'
import { BlockCategory, BlockConfig } from '../../../../blocks/types'
import { ToolbarBlock } from './components/toolbar-block/toolbar-block'
import { ToolbarTabs } from './components/toolbar-tabs/toolbar-tabs'
export function Toolbar() {
const [activeTab, setActiveTab] = useState<BlockCategory>('blocks')
+6 -6
View File
@@ -1,5 +1,5 @@
import { useWorkflowStore } from '@/stores/workflow/store'
import { shallow } from 'zustand/shallow'
import { useWorkflowStore } from '@/stores/workflow/store'
export interface ConnectedBlock {
id: string
@@ -12,14 +12,14 @@ export function useBlockConnections(blockId: string) {
const { edges, blocks } = useWorkflowStore(
(state) => ({
edges: state.edges,
blocks: state.blocks
blocks: state.blocks,
}),
shallow
)
const incomingConnections = edges
.filter(edge => edge.target === blockId)
.map(edge => {
.filter((edge) => edge.target === blockId)
.map((edge) => {
const sourceBlock = blocks[edge.source]
return {
id: sourceBlock.id,
@@ -31,6 +31,6 @@ export function useBlockConnections(blockId: string) {
return {
incomingConnections,
hasIncomingConnections: incomingConnections.length > 0
hasIncomingConnections: incomingConnections.length > 0,
}
}
}
+40 -25
View File
@@ -1,12 +1,12 @@
import { useCallback, useState } from 'react'
import { useWorkflowStore } from '@/stores/workflow/store'
import { Serializer } from '@/serializer'
import { Executor } from '@/executor'
import { ExecutionResult } from '@/executor/types'
import { useNotificationStore } from '@/stores/notifications/store'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useConsoleStore } from '@/stores/console/store'
import { useEnvironmentStore } from '@/stores/environment/store'
import { useNotificationStore } from '@/stores/notifications/store'
import { useWorkflowRegistry } from '@/stores/workflow/registry'
import { useWorkflowStore } from '@/stores/workflow/store'
import { Executor } from '@/executor'
import { ExecutionResult } from '@/executor/types'
import { Serializer } from '@/serializer'
export function useWorkflowExecution() {
const [isExecuting, setIsExecuting] = useState(false)
@@ -28,25 +28,31 @@ export function useWorkflowExecution() {
try {
// Extract existing block states
const currentBlockStates = Object.entries(blocks).reduce((acc, [id, block]) => {
const responseValue = block.subBlocks?.response?.value
if (responseValue !== undefined) {
acc[id] = { response: responseValue }
}
return acc
}, {} as Record<string, any>)
const currentBlockStates = Object.entries(blocks).reduce(
(acc, [id, block]) => {
const responseValue = block.subBlocks?.response?.value
if (responseValue !== undefined) {
acc[id] = { response: responseValue }
}
return acc
},
{} as Record<string, any>
)
// Get environment variables
const envVars = getAllVariables()
const envVarValues = Object.entries(envVars).reduce((acc, [key, variable]) => {
acc[key] = variable.value
return acc
}, {} as Record<string, string>)
const envVarValues = Object.entries(envVars).reduce(
(acc, [key, variable]) => {
acc[key] = variable.value
return acc
},
{} as Record<string, string>
)
// Execute workflow
const workflow = new Serializer().serializeWorkflow(blocks, edges)
const executor = new Executor(workflow, currentBlockStates, envVarValues)
const result = await executor.execute('my-run-id')
setExecutionResult(result)
@@ -62,7 +68,7 @@ export function useWorkflowExecution() {
workflowId: activeWorkflowId,
timestamp: log.startedAt,
blockName: log.blockTitle,
blockType: log.blockType
blockType: log.blockType,
})
})
}
@@ -75,7 +81,7 @@ export function useWorkflowExecution() {
error: log.error,
durationMs: log.durationMs,
startedAt: log.startedAt,
endedAt: log.endedAt
endedAt: log.endedAt,
})
})
console.groupEnd()
@@ -84,7 +90,7 @@ export function useWorkflowExecution() {
// Show execution result with workflowId
addNotification(
result.success ? 'console' : 'error',
result.success
result.success
? 'Workflow completed successfully'
: `Workflow execution failed: ${result.error}`,
activeWorkflowId
@@ -95,7 +101,7 @@ export function useWorkflowExecution() {
success: false,
output: { response: {} },
error: errorMessage,
logs: []
logs: [],
})
// Add error entry to console
@@ -107,14 +113,23 @@ export function useWorkflowExecution() {
endedAt: new Date().toISOString(),
workflowId: activeWorkflowId,
timestamp: new Date().toISOString(),
blockName: 'Error'
blockName: 'Error',
})
addNotification('error', `Workflow execution failed: ${errorMessage}`, activeWorkflowId)
} finally {
setIsExecuting(false)
}
}, [activeWorkflowId, blocks, edges, addNotification, addConsole, isOpen, toggleConsole, getAllVariables])
}, [
activeWorkflowId,
blocks,
edges,
addNotification,
addConsole,
isOpen,
toggleConsole,
getAllVariables,
])
return { isExecuting, executionResult, handleRunWorkflow }
}
}
+4 -8
View File
@@ -1,14 +1,10 @@
import Providers from './providers'
import { Toolbar } from './components/toolbar/toolbar'
import { Console } from './components/console/console'
import { ControlBar } from './components/control-bar/control-bar'
import { Sidebar } from './components/sidebar/sidebar'
import { Console } from './components/console/console'
import { Toolbar } from './components/toolbar/toolbar'
import Providers from './providers'
export default function WorkspaceLayout({
children,
}: {
children: React.ReactNode
}) {
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
return (
<Providers>
<div className="flex min-h-screen w-full">
+29 -22
View File
@@ -1,7 +1,7 @@
import { BlockConfig } from '../types'
import { AgentIcon } from '@/components/icons'
import { MODEL_TOOLS, ModelType } from '../consts'
import { ToolResponse } from '@/tools/types'
import { MODEL_TOOLS, ModelType } from '../consts'
import { BlockConfig } from '../types'
interface AgentResponse extends ToolResponse {
output: {
@@ -32,7 +32,14 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
category: 'blocks',
},
tools: {
access: ['openai_chat', 'anthropic_chat', 'google_chat', 'xai_chat', 'deepseek_chat', 'deepseek_reasoner'],
access: [
'openai_chat',
'anthropic_chat',
'google_chat',
'xai_chat',
'deepseek_chat',
'deepseek_reasoner',
],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o'
@@ -44,8 +51,8 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
throw new Error(`Invalid model selected: ${model}`)
}
return tool
}
}
},
},
},
workflow: {
inputs: {
@@ -55,7 +62,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
apiKey: { type: 'string', required: true },
responseFormat: { type: 'json', required: false },
temperature: { type: 'number', required: false },
tools: { type: 'json', required: false }
tools: { type: 'json', required: false },
},
outputs: {
response: {
@@ -63,9 +70,9 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
content: 'string',
model: 'string',
tokens: 'any',
toolCalls: 'any'
}
}
toolCalls: 'any',
},
},
},
subBlocks: [
{
@@ -73,21 +80,21 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
title: 'System Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Enter system prompt...'
placeholder: 'Enter system prompt...',
},
{
id: 'context',
title: 'Context',
type: 'short-input',
layout: 'full',
placeholder: 'Enter context or user message...'
placeholder: 'Enter context or user message...',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS)
options: Object.keys(MODEL_TOOLS),
},
{
id: 'temperature',
@@ -99,25 +106,25 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
},
{
id: 'apiKey',
title: "API Key",
type: "short-input",
layout: "full",
placeholder: "Enter your API key",
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false
connectionDroppable: false,
},
{
id: 'tools',
title: 'Tools',
type: 'tool-input',
layout: 'full'
layout: 'full',
},
{
id: 'responseFormat',
title: 'Response Format',
type: 'code',
layout: 'full'
layout: 'full',
},
]
}
}
],
},
}
+7 -7
View File
@@ -1,6 +1,6 @@
import { ApiIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { RequestResponse } from '@/tools/http/request'
import { BlockConfig } from '../types'
export const ApiBlock: BlockConfig<RequestResponse> = {
type: 'api',
@@ -12,23 +12,23 @@ export const ApiBlock: BlockConfig<RequestResponse> = {
category: 'blocks',
},
tools: {
access: ['http_request']
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 }
body: { type: 'json', required: false },
},
outputs: {
response: {
type: {
data: 'any',
status: 'number',
headers: 'json'
}
}
headers: 'json',
},
},
},
subBlocks: [
{
@@ -60,4 +60,4 @@ export const ApiBlock: BlockConfig<RequestResponse> = {
},
],
},
}
}
+10 -10
View File
@@ -1,6 +1,6 @@
import { ConditionalIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { CodeExecutionOutput } from '@/tools/function/execute'
import { BlockConfig } from '../types'
export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
type: 'condition',
@@ -12,19 +12,19 @@ export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
category: 'blocks',
},
tools: {
access: ['function_execute']
access: ['function_execute'],
},
workflow: {
inputs: {
code: { type: 'string', required: true }
code: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string'
}
}
stdout: 'string',
},
},
},
subBlocks: [
{
@@ -32,15 +32,15 @@ export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
title: 'if',
type: 'code',
layout: 'full',
outputHandle: true
outputHandle: true,
},
{
id: 'elseIf',
title: 'else if',
type: 'code',
layout: 'full',
outputHandle: true
}
outputHandle: true,
},
],
},
}
}
+14 -18
View File
@@ -1,6 +1,6 @@
import { BlockConfig } from '../types'
import { CrewAIIcon } from '@/components/icons'
import { VisionResponse } from '@/tools/crewai/vision'
import { BlockConfig } from '../types'
export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
type: 'crewai_vision',
@@ -9,26 +9,26 @@ export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
description: 'Analyze images with vision models',
bgColor: '#FF5A50',
icon: CrewAIIcon,
category: 'tools'
category: 'tools',
},
tools: {
access: ['crewai_vision']
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 }
prompt: { type: 'string', required: false },
},
outputs: {
response: {
type: {
content: 'string',
model: 'any',
tokens: 'any'
}
}
tokens: 'any',
},
},
},
subBlocks: [
{
@@ -36,25 +36,21 @@ export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
title: 'Image URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter publicly accessible image URL'
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'
]
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)'
placeholder: 'Enter prompt for image analysis (optional)',
},
{
id: 'apiKey',
@@ -62,8 +58,8 @@ export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true
password: true,
},
]
}
}
],
},
}
+13 -13
View File
@@ -1,6 +1,6 @@
import { BlockConfig } from '../types'
import { FirecrawlIcon } from '@/components/icons'
import { ScrapeResponse } from '@/tools/firecrawl/scrape'
import { BlockConfig } from '../types'
export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
type: 'firecrawl_scrape',
@@ -9,25 +9,25 @@ export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
description: 'Scrape website content',
bgColor: '#181C1E',
icon: FirecrawlIcon,
category: 'tools'
category: 'tools',
},
tools: {
access: ['firecrawl_scrape']
access: ['firecrawl_scrape'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
url: { type: 'string', required: true },
scrapeOptions: { type: 'json', required: false }
scrapeOptions: { type: 'json', required: false },
},
outputs: {
response: {
type: {
markdown: 'string',
html: 'any',
metadata: 'json'
}
}
metadata: 'json',
},
},
},
subBlocks: [
{
@@ -35,13 +35,13 @@ export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the webpage URL to scrape'
placeholder: 'Enter the webpage URL to scrape',
},
{
id: 'onlyMainContent',
title: 'Only Main Content',
type: 'switch',
layout: 'half'
layout: 'half',
},
{
id: 'apiKey',
@@ -49,8 +49,8 @@ export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Firecrawl API key',
password: true
password: true,
},
]
}
}
],
},
}
+8 -8
View File
@@ -1,6 +1,6 @@
import { CodeIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { CodeExecutionOutput } from '@/tools/function/execute'
import { BlockConfig } from '../types'
export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
type: 'function',
@@ -12,26 +12,26 @@ export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
category: 'blocks',
},
tools: {
access: ['function_execute']
access: ['function_execute'],
},
workflow: {
inputs: {
code: { type: 'string', required: true }
code: { type: 'string', required: true },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string'
}
}
stdout: 'string',
},
},
},
subBlocks: [
{
id: 'code',
type: 'code',
layout: 'full',
}
},
],
},
}
}
+9 -9
View File
@@ -1,6 +1,6 @@
import { RepoInfoResponse } from '@/tools/github/repo'
import { GithubIcon } from '../../components/icons'
import { BlockConfig } from '../types'
import { RepoInfoResponse } from '@/tools/github/repo'
export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
type: 'github_repo_info',
@@ -12,12 +12,12 @@ export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
category: 'tools',
},
tools: {
access: ['github_repoinfo']
access: ['github_repoinfo'],
},
workflow: {
inputs: {
owner: { type: 'string', required: true },
repo: { type: 'string', required: true }
repo: { type: 'string', required: true },
},
outputs: {
response: {
@@ -27,9 +27,9 @@ export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
stars: 'number',
forks: 'number',
openIssues: 'number',
language: 'string'
}
}
language: 'string',
},
},
},
subBlocks: [
{
@@ -52,8 +52,8 @@ export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter GitHub Token',
password: true
}
password: true,
},
],
},
}
}
+15 -15
View File
@@ -1,6 +1,6 @@
import { JinaAIIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { ReadUrlResponse } from '@/tools/jina/reader'
import { BlockConfig } from '../types'
export const JinaBlock: BlockConfig<ReadUrlResponse> = {
type: 'jina_reader',
@@ -9,10 +9,10 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
description: 'Convert website content into text',
bgColor: '#333333',
icon: JinaAIIcon,
category: 'tools'
category: 'tools',
},
tools: {
access: ['jina_readurl']
access: ['jina_readurl'],
},
workflow: {
inputs: {
@@ -20,14 +20,14 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
useReaderLMv2: { type: 'boolean', required: false },
gatherLinks: { type: 'boolean', required: false },
jsonResponse: { type: 'boolean', required: false },
apiKey: { type: 'string', required: true }
apiKey: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string'
}
}
content: 'string',
},
},
},
subBlocks: [
{
@@ -35,7 +35,7 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL to extract content from'
placeholder: 'Enter URL to extract content from',
},
{
id: 'options',
@@ -45,8 +45,8 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
options: [
{ id: 'useReaderLMv2', label: 'Use Reader LM v2' },
{ id: 'gatherLinks', label: 'Gather Links' },
{ id: 'jsonResponse', label: 'JSON Response' }
]
{ id: 'jsonResponse', label: 'JSON Response' },
],
},
{
id: 'apiKey',
@@ -54,8 +54,8 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Jina API key',
password: true
}
]
}
}
password: true,
},
],
},
}
+16 -37
View File
@@ -1,6 +1,6 @@
import { BlockConfig } from '../types'
import { SerperIcon } from '@/components/icons'
import { SearchResponse } from '@/tools/serper/search'
import { BlockConfig } from '../types'
export const SerperBlock: BlockConfig<SearchResponse> = {
type: 'serper_search',
@@ -12,7 +12,7 @@ export const SerperBlock: BlockConfig<SearchResponse> = {
category: 'tools',
},
tools: {
access: ['serper_search']
access: ['serper_search'],
},
workflow: {
inputs: {
@@ -21,14 +21,14 @@ export const SerperBlock: BlockConfig<SearchResponse> = {
num: { type: 'number', required: false },
gl: { type: 'string', required: false },
hl: { type: 'string', required: false },
type: { type: 'string', required: false }
type: { type: 'string', required: false },
},
outputs: {
response: {
type: {
searchResults: 'json'
}
}
searchResults: 'json',
},
},
},
subBlocks: [
{
@@ -36,56 +36,35 @@ export const SerperBlock: BlockConfig<SearchResponse> = {
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your search query...'
placeholder: 'Enter your search query...',
},
{
id: 'type',
title: 'Search Type',
type: 'dropdown',
layout: 'half',
options: ['search', 'news', 'places', 'images']
options: ['search', 'news', 'places', 'images'],
},
{
id: 'num',
title: 'Number of Results',
type: 'dropdown',
layout: 'half',
options: ['10', '20', '30', '40', '50', '100']
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'
]
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'
]
options: ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh'],
},
{
id: 'apiKey',
@@ -93,8 +72,8 @@ export const SerperBlock: BlockConfig<SearchResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Serper API key',
password: true
}
]
}
}
password: true,
},
],
},
}
+15 -15
View File
@@ -1,6 +1,6 @@
import { SlackIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { SlackIcon } from '@/components/icons'
import { SlackMessageResponse } from '@/tools/slack/message'
import { BlockConfig } from '../types'
export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
type: 'slack_message',
@@ -9,24 +9,24 @@ export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
description: 'Send a message to Slack',
bgColor: '#611f69',
icon: SlackIcon,
category: 'tools'
category: 'tools',
},
tools: {
access: ['slack_message']
access: ['slack_message'],
},
workflow: {
inputs: {
apiKey: { type: 'string', required: true },
channel: { type: 'string', required: true },
text: { type: 'string', required: true }
text: { type: 'string', required: true },
},
outputs: {
response: {
type: {
ts: 'string',
channel: 'string'
}
}
channel: 'string',
},
},
},
subBlocks: [
{
@@ -34,14 +34,14 @@ export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
title: 'Channel',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Slack channel (e.g., #general)'
placeholder: 'Enter Slack channel (e.g., #general)',
},
{
id: 'text',
title: 'Message',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your alert message'
placeholder: 'Enter your alert message',
},
{
id: 'apiKey',
@@ -50,8 +50,8 @@ export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
layout: 'full',
placeholder: 'Enter your Slack OAuth token',
password: true,
connectionDroppable: false
}
]
}
}
connectionDroppable: false,
},
],
},
}
+25 -25
View File
@@ -1,7 +1,7 @@
import { BlockConfig } from '../types'
import { TavilyIcon } from '@/components/icons'
import { SearchResponse } from '@/tools/tavily/search'
import { ExtractResponse } from '@/tools/tavily/extract'
import { SearchResponse } from '@/tools/tavily/search'
import { BlockConfig } from '../types'
export const TavilySearchBlock: BlockConfig<SearchResponse> = {
type: 'tavily_search',
@@ -13,22 +13,22 @@ export const TavilySearchBlock: BlockConfig<SearchResponse> = {
category: 'tools',
},
tools: {
access: ['tavily_search']
access: ['tavily_search'],
},
workflow: {
inputs: {
query: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
max_results: { type: 'number', required: false }
max_results: { type: 'number', required: false },
},
outputs: {
response: {
type: {
query: 'string',
results: 'json',
response_time: 'number'
}
}
response_time: 'number',
},
},
},
subBlocks: [
{
@@ -36,14 +36,14 @@ export const TavilySearchBlock: BlockConfig<SearchResponse> = {
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your search query...'
placeholder: 'Enter your search query...',
},
{
id: 'max_results',
title: 'Max Results',
type: 'dropdown',
layout: 'half',
options: ['5', '10', '15', '20']
options: ['5', '10', '15', '20'],
},
{
id: 'apiKey',
@@ -51,10 +51,10 @@ export const TavilySearchBlock: BlockConfig<SearchResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Tavily API key',
password: true
}
]
}
password: true,
},
],
},
}
export const TavilyExtractBlock: BlockConfig<ExtractResponse> = {
@@ -67,22 +67,22 @@ export const TavilyExtractBlock: BlockConfig<ExtractResponse> = {
category: 'tools',
},
tools: {
access: ['tavily_extract']
access: ['tavily_extract'],
},
workflow: {
inputs: {
urls: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
extract_depth: { type: 'string', required: false }
extract_depth: { type: 'string', required: false },
},
outputs: {
response: {
type: {
results: 'json',
failed_results: 'any',
response_time: 'number'
}
}
response_time: 'number',
},
},
},
subBlocks: [
{
@@ -90,14 +90,14 @@ export const TavilyExtractBlock: BlockConfig<ExtractResponse> = {
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL to extract content from...'
placeholder: 'Enter URL to extract content from...',
},
{
id: 'extract_depth',
title: 'Extract Depth',
type: 'dropdown',
layout: 'half',
options: ['basic', 'advanced']
options: ['basic', 'advanced'],
},
{
id: 'apiKey',
@@ -105,8 +105,8 @@ export const TavilyExtractBlock: BlockConfig<ExtractResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Tavily API key',
password: true
}
]
}
}
password: true,
},
],
},
}
+25 -23
View File
@@ -1,16 +1,18 @@
import { TranslateIcon } from '@/components/icons'
import { BlockConfig } from '../types'
import { ChatResponse } from '@/tools/openai/chat'
import { MODEL_TOOLS, ModelType } from '../consts'
import { BlockConfig } from '../types'
const getTranslationPrompt = (targetLanguage: string) => `You are a highly skilled translator. Your task is to translate the given text into ${targetLanguage || 'English'} while:
const getTranslationPrompt = (
targetLanguage: string
) => `You are a highly skilled translator. Your task is to translate the given text into ${targetLanguage || 'English'} while:
1. Preserving the original meaning and nuance
2. Maintaining appropriate formality levels
3. Adapting idioms and cultural references appropriately
4. Preserving formatting and special characters
5. Handling technical terms accurately
Only return the translated text without any explanations or notes. The translation should be natural and fluent in ${targetLanguage || 'English'}.`
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',
@@ -37,25 +39,25 @@ export const TranslateBlock: BlockConfig<ChatResponse> = {
throw new Error(`Invalid model selected: ${model}`)
}
return tool
}
}
return tool
},
},
},
workflow: {
inputs: {
context: { type: 'string', required: true },
targetLanguage: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
systemPrompt: { type: 'string', required: true }
systemPrompt: { type: 'string', required: true },
},
outputs: {
response: {
type: {
content: 'string',
model: 'string',
tokens: 'any'
}
}
tokens: 'any',
},
},
},
subBlocks: [
{
@@ -63,30 +65,30 @@ export const TranslateBlock: BlockConfig<ChatResponse> = {
title: 'Text to Translate',
type: 'long-input',
layout: 'full',
placeholder: 'Enter the text you want to translate'
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.)'
placeholder: 'Enter language (e.g. Spanish, French, etc.)',
},
{
id: 'model',
title: 'Model',
type: 'dropdown',
layout: 'half',
options: Object.keys(MODEL_TOOLS)
options: Object.keys(MODEL_TOOLS),
},
{
id: 'apiKey',
title: "API Key",
type: "short-input",
layout: "full",
placeholder: "Enter your API key",
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false
connectionDroppable: false,
},
{
id: 'systemPrompt',
@@ -96,8 +98,8 @@ export const TranslateBlock: BlockConfig<ChatResponse> = {
hidden: true,
value: (params: Record<string, any>) => {
return getTranslationPrompt(params.targetLanguage || 'English')
}
}
]
}
}
},
},
],
},
}
+3 -3
View File
@@ -1,13 +1,13 @@
export const MODEL_TOOLS = {
'gpt-4o': 'openai_chat',
'o1': 'openai_chat',
o1: 'openai_chat',
'o3-mini': 'openai_chat',
'deepseek-v3': 'deepseek_chat',
'deepseek-r1': 'deepseek_reasoner',
'claude-3-5-sonnet-20241022': 'anthropic_chat',
'gemini-pro': 'google_chat',
'grok-2-latest': 'xai_chat'
'grok-2-latest': 'xai_chat',
} as const
export type ModelType = keyof typeof MODEL_TOOLS
export type ToolType = typeof MODEL_TOOLS[ModelType]
export type ToolType = (typeof MODEL_TOOLS)[ModelType]
+28 -30
View File
@@ -1,25 +1,24 @@
import { BlockConfig } from './types'
// Import blocks
import { AgentBlock } from './blocks/agent'
import { ApiBlock } from './blocks/api'
import { FunctionBlock } from './blocks/function'
import { ConditionBlock } from './blocks/condition'
import { CrewAIVisionBlock } from './blocks/crewai'
import { FirecrawlScrapeBlock } from './blocks/firecrawl'
import { JinaBlock } from './blocks/jina'
import { TranslateBlock } from './blocks/translate'
import { SlackMessageBlock } from './blocks/slack'
import { FunctionBlock } from './blocks/function'
import { GitHubBlock } from './blocks/github'
import { ConditionBlock } from './blocks/condition'
import { JinaBlock } from './blocks/jina'
import { SerperBlock } from './blocks/serper'
import { TavilySearchBlock, TavilyExtractBlock } from './blocks/tavily'
import { SlackMessageBlock } from './blocks/slack'
import { TavilyExtractBlock, TavilySearchBlock } from './blocks/tavily'
import { TranslateBlock } from './blocks/translate'
import { BlockConfig } from './types'
// Export blocks for ease of use
export {
AgentBlock,
ApiBlock,
FunctionBlock,
CrewAIVisionBlock,
export {
AgentBlock,
ApiBlock,
FunctionBlock,
CrewAIVisionBlock,
FirecrawlScrapeBlock,
JinaBlock,
TranslateBlock,
@@ -28,7 +27,7 @@ export {
ConditionBlock,
SerperBlock,
TavilySearchBlock,
TavilyExtractBlock
TavilyExtractBlock,
}
// Registry of all block configurations
@@ -45,29 +44,28 @@ const blocks: Record<string, BlockConfig> = {
condition: ConditionBlock,
serper_search: SerperBlock,
tavily_search: TavilySearchBlock,
tavily_extract: TavilyExtractBlock
tavily_extract: TavilyExtractBlock,
}
// Build a reverse mapping of tools to block types
const toolToBlockType = Object.entries(blocks).reduce((acc, [blockType, config]) => {
config.tools.access.forEach(toolId => {
acc[toolId] = blockType
})
return acc
}, {} as Record<string, string>)
const toolToBlockType = Object.entries(blocks).reduce(
(acc, [blockType, config]) => {
config.tools.access.forEach((toolId) => {
acc[toolId] = blockType
})
return acc
},
{} as Record<string, string>
)
// Helper functions
export const getBlock = (type: string): BlockConfig | undefined =>
blocks[type]
export const getBlock = (type: string): BlockConfig | undefined => blocks[type]
export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
Object.values(blocks).filter(block => block.toolbar.category === category)
Object.values(blocks).filter((block) => block.toolbar.category === category)
export const getAllBlockTypes = (): string[] =>
Object.keys(blocks)
export const getAllBlockTypes = (): string[] => Object.keys(blocks)
export const isValidBlockType = (type: string): type is string =>
type in blocks
export const isValidBlockType = (type: string): type is string => type in blocks
export const getAllBlocks = (): BlockConfig[] =>
Object.values(blocks)
export const getAllBlocks = (): BlockConfig[] => Object.values(blocks)
+28 -16
View File
@@ -3,32 +3,44 @@ import type { JSX } from 'react'
import { ToolResponse } from '@/tools/types'
// Tool output type utilities
export type ExtractToolOutput<T> = T extends ToolResponse
? T['output']
: never
export type ExtractToolOutput<T> = T extends ToolResponse ? T['output'] : never
export type ToolOutputToValueType<T> = T extends Record<string, any>
? {
[K in keyof T]: T[K] extends string ? 'string'
: T[K] extends number ? 'number'
: T[K] extends boolean ? 'boolean'
: T[K] extends object ? 'json'
: 'any'
}
: never
export type ToolOutputToValueType<T> =
T extends Record<string, any>
? {
[K in keyof T]: T[K] extends string
? 'string'
: T[K] extends number
? 'number'
: T[K] extends boolean
? 'boolean'
: T[K] extends object
? 'json'
: 'any'
}
: never
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
export type BlockCategory = 'blocks' | 'tools'
export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'any'
export type BlockOutput =
| PrimitiveValueType
export type BlockOutput =
| PrimitiveValueType
| { [key: string]: PrimitiveValueType | Record<string, any> }
export type ParamType = 'string' | 'number' | 'boolean' | 'json'
export type SubBlockType = 'short-input' | 'long-input' | 'dropdown' | 'slider' | 'table' | 'code' | 'switch' | 'tool-input' | 'checkbox-list'
export type SubBlockType =
| 'short-input'
| 'long-input'
| 'dropdown'
| 'slider'
| 'table'
| 'code'
| 'switch'
| 'tool-input'
| 'checkbox-list'
export type SubBlockLayout = 'full' | 'half'
export interface ParamConfig {
@@ -88,4 +100,4 @@ export interface OutputConfig {
whenFilled: BlockOutput
}
}
}
}
+1 -1
View File
@@ -46,4 +46,4 @@ export function resolveOutputType(
}
return resolvedOutputs
}
}
+1 -1
View File
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
+96 -24
View File
@@ -902,11 +902,26 @@ export function SalesforceIcon(props: SVGProps<SVGSVGElement>) {
export function FirecrawlIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 642 600" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M301 63C299 91 303 122 298 149C295 158 289 165 283 169C274 172 266 170 261 167C253 176 248 183 244 191C230 226 226 263 226 301C216 310 203 317 192 310C179 295 175 277 174 259C161 273 153 288 146 304C141 321 138 336 137 352C140 372 145 388 152 402C161 421 174 435 187 449C181 462 165 453 157 450C158 454 161 458 165 461C195 490 231 500 268 509C240 494 211 471 195 442C179 413 172 378 180 344C191 353 200 362 211 364C223 365 232 361 236 353C247 274 299 214 323 143C322 136 327 140 329 142C354 165 367 191 375 218C387 254 381 294 379 329C393 345 413 334 424 329C429 342 432 352 429 362C427 378 417 388 413 400C422 407 433 403 440 400C432 423 419 442 404 460C383 483 358 501 335 512C379 502 420 491 449 459C443 458 427 464 428 452C443 437 464 423 472 403C482 383 485 362 484 339C482 307 472 280 458 254C459 267 452 276 445 284C434 289 426 279 424 272C415 247 424 220 418 198C415 179 405 165 397 150C370 114 336 86 303 64" fill="rgb(253,76,31)" />
<path d="M324 141C303 214 249 273 244 354C235 359 229 364 223 366C205 367 193 357 182 347C180 350 179 353 180 357C178 374 178 390 182 403C185 421 193 434 200 448C212 465 227 480 243 491C258 500 269 513 285 512C284 508 257 485 252 468C241 450 235 433 233 414C241 415 254 420 263 412C260 387 265 363 273 343C281 323 293 306 310 295C317 289 324 285 330 282C328 307 328 331 329 355C330 368 332 379 338 389C358 394 376 384 388 370C383 386 377 401 371 415C376 414 381 411 385 408C383 421 380 431 376 441C366 467 356 491 334 510C358 499 381 483 400 461C418 442 430 423 440 403C432 404 421 410 413 404C414 386 428 377 427 360C429 349 428 340 424 332C413 336 404 341 392 339C386 338 381 334 379 330C380 292 385 248 371 214C366 195 358 180 349 165C341 155 333 145 323 140" fill="rgb(254,156,69)" />
<path d="M330 284C309 293 289 311 279 332C267 356 261 383 265 411C256 420 242 418 235 412C237 438 245 459 258 479C269 493 281 507 295 513C288 495 265 472 265 446C272 447 281 454 288 444C296 425 303 407 309 388C317 406 321 427 336 443C346 449 358 446 363 438C355 464 348 489 334 511C344 501 352 491 357 480C370 457 379 435 385 412C380 411 376 416 371 418C376 401 382 386 387 371C379 382 369 388 358 391C348 394 337 392 334 383C324 353 328 316 330 285" fill="rgb(254,220,87)" />
<path d="M311 389C303 407 297 426 289 445C282 454 273 450 268 445C267 472 285 492 302 512C299 514 297 514 294 514C297 514 299 514 301 514C314 515 325 512 334 513C341 495 355 467 362 443C357 446 351 448 344 447C337 446 334 441 330 438C320 422 316 406 311 391" fill="rgb(251,250,202)" />
<path d="M187 163C188 181 167 187 164 203C158 215 158 228 159 241C172 233 183 221 188 209C193 194 192 178 188 166" fill="rgb(253,76,31)" />
<path
d="M301 63C299 91 303 122 298 149C295 158 289 165 283 169C274 172 266 170 261 167C253 176 248 183 244 191C230 226 226 263 226 301C216 310 203 317 192 310C179 295 175 277 174 259C161 273 153 288 146 304C141 321 138 336 137 352C140 372 145 388 152 402C161 421 174 435 187 449C181 462 165 453 157 450C158 454 161 458 165 461C195 490 231 500 268 509C240 494 211 471 195 442C179 413 172 378 180 344C191 353 200 362 211 364C223 365 232 361 236 353C247 274 299 214 323 143C322 136 327 140 329 142C354 165 367 191 375 218C387 254 381 294 379 329C393 345 413 334 424 329C429 342 432 352 429 362C427 378 417 388 413 400C422 407 433 403 440 400C432 423 419 442 404 460C383 483 358 501 335 512C379 502 420 491 449 459C443 458 427 464 428 452C443 437 464 423 472 403C482 383 485 362 484 339C482 307 472 280 458 254C459 267 452 276 445 284C434 289 426 279 424 272C415 247 424 220 418 198C415 179 405 165 397 150C370 114 336 86 303 64"
fill="rgb(253,76,31)"
/>
<path
d="M324 141C303 214 249 273 244 354C235 359 229 364 223 366C205 367 193 357 182 347C180 350 179 353 180 357C178 374 178 390 182 403C185 421 193 434 200 448C212 465 227 480 243 491C258 500 269 513 285 512C284 508 257 485 252 468C241 450 235 433 233 414C241 415 254 420 263 412C260 387 265 363 273 343C281 323 293 306 310 295C317 289 324 285 330 282C328 307 328 331 329 355C330 368 332 379 338 389C358 394 376 384 388 370C383 386 377 401 371 415C376 414 381 411 385 408C383 421 380 431 376 441C366 467 356 491 334 510C358 499 381 483 400 461C418 442 430 423 440 403C432 404 421 410 413 404C414 386 428 377 427 360C429 349 428 340 424 332C413 336 404 341 392 339C386 338 381 334 379 330C380 292 385 248 371 214C366 195 358 180 349 165C341 155 333 145 323 140"
fill="rgb(254,156,69)"
/>
<path
d="M330 284C309 293 289 311 279 332C267 356 261 383 265 411C256 420 242 418 235 412C237 438 245 459 258 479C269 493 281 507 295 513C288 495 265 472 265 446C272 447 281 454 288 444C296 425 303 407 309 388C317 406 321 427 336 443C346 449 358 446 363 438C355 464 348 489 334 511C344 501 352 491 357 480C370 457 379 435 385 412C380 411 376 416 371 418C376 401 382 386 387 371C379 382 369 388 358 391C348 394 337 392 334 383C324 353 328 316 330 285"
fill="rgb(254,220,87)"
/>
<path
d="M311 389C303 407 297 426 289 445C282 454 273 450 268 445C267 472 285 492 302 512C299 514 297 514 294 514C297 514 299 514 301 514C314 515 325 512 334 513C341 495 355 467 362 443C357 446 351 448 344 447C337 446 334 441 330 438C320 422 316 406 311 391"
fill="rgb(251,250,202)"
/>
<path
d="M187 163C188 181 167 187 164 203C158 215 158 228 159 241C172 233 183 221 188 209C193 194 192 178 188 166"
fill="rgb(253,76,31)"
/>
</svg>
)
}
@@ -1011,29 +1026,86 @@ export const GithubIcon = (props: SVGProps<SVGSVGElement>) => (
export const SerperIcon = (props: SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 654 600" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M324 38C356 37 389 36 417 47C452 56 484 72 509 94C539 118 561 145 577 176C593 205 601 238 606 271C610 343 590 403 552 452C528 482 499 507 467 523C438 539 404 547 372 552C297 556 235 534 184 492C133 449 103 392 93 330C93 292 89 255 102 224C112 189 128 158 149 132C194 78 255 46 322 38" fill="rgb(71,97,118)" />
<path d="M326 39C286 43 250 55 217 75C185 94 156 120 137 150C100 204 87 266 95 336C107 402 142 462 198 502C249 538 309 556 378 551C415 545 449 533 477 516C511 497 535 472 557 445C592 393 611 333 605 265C595 196 563 140 511 95C484 73 452 57 419 48C390 38 359 38 327 39" fill="rgb(71,97,119)" />
<path d="M342 40C407 42 465 61 513 103C541 126 562 155 576 184C592 217 600 251 600 288C602 357 579 416 535 465C510 493 478 515 445 528C416 541 385 546 352 546C284 548 225 523 178 481C130 436 103 379 96 313C94 244 113 186 151 138C179 103 209 80 245 64C276 50 307 44 340 41" fill="rgb(71,97,119)" />
<path d="M344 42C309 44 277 51 247 64C209 81 180 103 153 136C114 186 95 244 96 312C104 379 131 435 177 480C225 522 284 547 351 546C385 545 416 540 443 528C478 514 509 492 533 466C578 416 601 357 600 289C599 251 591 217 576 187C561 156 541 127 515 105C466 63 409 44 346 41" fill="rgb(71,97,118)" />
<path d="M327 81C378 78 423 89 462 114C511 144 546 196 557 248C567 306 559 363 530 406C498 457 448 492 395 503C338 513 282 506 239 477C192 450 156 402 143 351C126 296 137 235 163 190C198 130 258 89 325 82" fill="rgb(44,56,71)" />
<path d="M329 83C260 89 199 129 165 189C138 235 127 296 144 349C157 401 193 449 237 475C282 505 338 512 393 503C448 491 497 457 529 408C558 363 566 306 557 250C545 196 511 145 464 116C424 91 380 79 330 82" fill="rgb(43,55,70)" />
<path d="M334 87C381 83 423 94 458 117C510 148 544 201 554 258C562 317 551 370 521 412C487 460 440 491 385 500C331 507 281 499 241 473C191 444 157 394 145 339C136 284 143 227 171 186C207 129 265 91 332 87" fill="rgb(41,53,67)" />
<path d="M335 88C267 90 208 129 173 184C144 227 137 284 145 338C158 393 191 443 240 471C281 498 331 506 384 500C439 490 487 459 519 413C550 370 561 317 554 259C543 201 509 149 460 119C424 96 383 85 337 88" fill="rgb(41,53,67)" />
<path d="M347 166C361 164 373 169 387 168C412 180 437 193 447 221C449 232 443 243 434 248C403 245 398 204 365 207C338 206 315 210 297 228C294 238 289 257 303 260C337 280 382 276 417 292C436 300 448 314 455 330C457 349 462 373 449 385C435 408 413 418 391 427C361 429 328 436 304 421C280 413 260 392 250 370C246 356 255 343 268 343C293 360 316 398 356 389C382 390 409 380 416 357C389 295 298 335 260 276C246 256 248 233 258 214C279 184 309 167 346 167" fill="rgb(121,172,205)" />
<path d="M349 168C312 167 280 183 259 212C249 233 247 256 260 274C299 334 390 294 422 354C409 381 382 391 357 389C316 399 293 361 272 342C255 344 247 356 251 368C260 391 280 412 302 420C328 435 361 428 389 428C412 417 434 407 447 386C461 373 456 349 456 332C428 270 351 289 304 262C288 258 293 239 295 229C314 209 338 204 363 204C398 203 403 244 431 249C443 242 448 232 449 222C436 193 412 181 388 172C374 170 363 166 350 167" fill="rgb(125,177,211)" />
<path d="M349 169C386 169 425 185 441 220C444 231 441 240 432 243C409 237 402 209 380 206C347 200 314 201 293 226C290 238 286 256 297 262C332 283 375 281 411 295C431 304 446 317 452 337C455 360 452 383 434 396C415 415 391 421 366 426C338 430 316 422 295 413C276 402 261 385 254 366C254 353 261 343 275 348C290 381 325 398 360 394C388 395 411 382 420 360C425 342 413 334 404 327C359 304 298 318 265 276C253 254 255 235 261 214C280 187 314 173 346 170" fill="rgb(137,195,233)" />
<path d="M349 171C316 173 281 187 263 214C256 235 254 254 266 273C300 316 359 304 401 325C413 333 426 342 422 358C412 382 388 396 363 395C326 399 290 382 278 348C262 345 254 353 253 365C261 384 277 401 292 411C316 421 338 429 365 426C390 420 415 414 432 398C451 383 454 360 453 338C445 317 430 305 413 296C375 282 332 284 299 264C285 257 288 239 291 228C304 212 319 205 336 202C378 193 403 213 423 244C438 244 443 232 441 222C425 186 388 171 352 170" fill="rgb(139,198,236)" />
<path
d="M324 38C356 37 389 36 417 47C452 56 484 72 509 94C539 118 561 145 577 176C593 205 601 238 606 271C610 343 590 403 552 452C528 482 499 507 467 523C438 539 404 547 372 552C297 556 235 534 184 492C133 449 103 392 93 330C93 292 89 255 102 224C112 189 128 158 149 132C194 78 255 46 322 38"
fill="rgb(71,97,118)"
/>
<path
d="M326 39C286 43 250 55 217 75C185 94 156 120 137 150C100 204 87 266 95 336C107 402 142 462 198 502C249 538 309 556 378 551C415 545 449 533 477 516C511 497 535 472 557 445C592 393 611 333 605 265C595 196 563 140 511 95C484 73 452 57 419 48C390 38 359 38 327 39"
fill="rgb(71,97,119)"
/>
<path
d="M342 40C407 42 465 61 513 103C541 126 562 155 576 184C592 217 600 251 600 288C602 357 579 416 535 465C510 493 478 515 445 528C416 541 385 546 352 546C284 548 225 523 178 481C130 436 103 379 96 313C94 244 113 186 151 138C179 103 209 80 245 64C276 50 307 44 340 41"
fill="rgb(71,97,119)"
/>
<path
d="M344 42C309 44 277 51 247 64C209 81 180 103 153 136C114 186 95 244 96 312C104 379 131 435 177 480C225 522 284 547 351 546C385 545 416 540 443 528C478 514 509 492 533 466C578 416 601 357 600 289C599 251 591 217 576 187C561 156 541 127 515 105C466 63 409 44 346 41"
fill="rgb(71,97,118)"
/>
<path
d="M327 81C378 78 423 89 462 114C511 144 546 196 557 248C567 306 559 363 530 406C498 457 448 492 395 503C338 513 282 506 239 477C192 450 156 402 143 351C126 296 137 235 163 190C198 130 258 89 325 82"
fill="rgb(44,56,71)"
/>
<path
d="M329 83C260 89 199 129 165 189C138 235 127 296 144 349C157 401 193 449 237 475C282 505 338 512 393 503C448 491 497 457 529 408C558 363 566 306 557 250C545 196 511 145 464 116C424 91 380 79 330 82"
fill="rgb(43,55,70)"
/>
<path
d="M334 87C381 83 423 94 458 117C510 148 544 201 554 258C562 317 551 370 521 412C487 460 440 491 385 500C331 507 281 499 241 473C191 444 157 394 145 339C136 284 143 227 171 186C207 129 265 91 332 87"
fill="rgb(41,53,67)"
/>
<path
d="M335 88C267 90 208 129 173 184C144 227 137 284 145 338C158 393 191 443 240 471C281 498 331 506 384 500C439 490 487 459 519 413C550 370 561 317 554 259C543 201 509 149 460 119C424 96 383 85 337 88"
fill="rgb(41,53,67)"
/>
<path
d="M347 166C361 164 373 169 387 168C412 180 437 193 447 221C449 232 443 243 434 248C403 245 398 204 365 207C338 206 315 210 297 228C294 238 289 257 303 260C337 280 382 276 417 292C436 300 448 314 455 330C457 349 462 373 449 385C435 408 413 418 391 427C361 429 328 436 304 421C280 413 260 392 250 370C246 356 255 343 268 343C293 360 316 398 356 389C382 390 409 380 416 357C389 295 298 335 260 276C246 256 248 233 258 214C279 184 309 167 346 167"
fill="rgb(121,172,205)"
/>
<path
d="M349 168C312 167 280 183 259 212C249 233 247 256 260 274C299 334 390 294 422 354C409 381 382 391 357 389C316 399 293 361 272 342C255 344 247 356 251 368C260 391 280 412 302 420C328 435 361 428 389 428C412 417 434 407 447 386C461 373 456 349 456 332C428 270 351 289 304 262C288 258 293 239 295 229C314 209 338 204 363 204C398 203 403 244 431 249C443 242 448 232 449 222C436 193 412 181 388 172C374 170 363 166 350 167"
fill="rgb(125,177,211)"
/>
<path
d="M349 169C386 169 425 185 441 220C444 231 441 240 432 243C409 237 402 209 380 206C347 200 314 201 293 226C290 238 286 256 297 262C332 283 375 281 411 295C431 304 446 317 452 337C455 360 452 383 434 396C415 415 391 421 366 426C338 430 316 422 295 413C276 402 261 385 254 366C254 353 261 343 275 348C290 381 325 398 360 394C388 395 411 382 420 360C425 342 413 334 404 327C359 304 298 318 265 276C253 254 255 235 261 214C280 187 314 173 346 170"
fill="rgb(137,195,233)"
/>
<path
d="M349 171C316 173 281 187 263 214C256 235 254 254 266 273C300 316 359 304 401 325C413 333 426 342 422 358C412 382 388 396 363 395C326 399 290 382 278 348C262 345 254 353 253 365C261 384 277 401 292 411C316 421 338 429 365 426C390 420 415 414 432 398C451 383 454 360 453 338C445 317 430 305 413 296C375 282 332 284 299 264C285 257 288 239 291 228C304 212 319 205 336 202C378 193 403 213 423 244C438 244 443 232 441 222C425 186 388 171 352 170"
fill="rgb(139,198,236)"
/>
</svg>
)
export const TavilyIcon = (props: SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M432 291C415 294 418 313 417 326C380 328 342 327 306 328C316 344 312 368 301 381C339 384 377 383 414 384C419 393 415 404 419 412C424 419 431 422 437 421C554 393 539 314 425 290" fill="rgb(248,202,81)" />
<path d="M263 87C260 88 257 89 255 93C237 121 219 147 204 174C203 184 206 191 212 195C222 198 231 196 239 197C241 238 240 277 241 316C257 307 276 309 294 308C296 273 295 234 296 199C309 196 328 200 333 183C314 149 299 103 267 83" fill="rgb(109,164,249)" />
<path d="M314 356L316 354C386 355 457 354 527 355C504 385 469 400 440 421C431 421 424 418 421 411C415 402 420 389 416 383C384 371 284 406 312 358" fill="rgb(250,188,28)" />
<path d="M314 356C281 405 384 369 410 384C422 388 415 402 421 409C425 417 431 420 437 420C469 400 504 384 529 360C456 355 386 356 317 355" fill="rgb(251,186,23)" />
<path d="M264 325C271 325 290 329 283 339C236 384 186 436 139 482C133 481 133 477 131 474C133 477 133 481 135 482C174 490 213 472 250 466C261 447 246 435 235 426C254 406 271 389 289 372C303 352 287 324 266 326" fill="rgb(251,156,158)" />
<path d="M263 327C260 328 256 328 253 330C233 348 216 367 197 384C188 381 183 371 175 368C166 367 161 369 156 372C148 409 133 447 133 482C173 430 281 366 277 323" fill="rgb(248,56,63)" />
<path d="M258 326C235 341 218 365 198 382C186 376 176 360 161 368L160 369L157 369C149 378 150 391 146 401C150 391 149 379 157 370C174 359 185 376 195 385C219 365 238 337 262 325" fill="rgb(242,165,165)" />
<path
d="M432 291C415 294 418 313 417 326C380 328 342 327 306 328C316 344 312 368 301 381C339 384 377 383 414 384C419 393 415 404 419 412C424 419 431 422 437 421C554 393 539 314 425 290"
fill="rgb(248,202,81)"
/>
<path
d="M263 87C260 88 257 89 255 93C237 121 219 147 204 174C203 184 206 191 212 195C222 198 231 196 239 197C241 238 240 277 241 316C257 307 276 309 294 308C296 273 295 234 296 199C309 196 328 200 333 183C314 149 299 103 267 83"
fill="rgb(109,164,249)"
/>
<path
d="M314 356L316 354C386 355 457 354 527 355C504 385 469 400 440 421C431 421 424 418 421 411C415 402 420 389 416 383C384 371 284 406 312 358"
fill="rgb(250,188,28)"
/>
<path
d="M314 356C281 405 384 369 410 384C422 388 415 402 421 409C425 417 431 420 437 420C469 400 504 384 529 360C456 355 386 356 317 355"
fill="rgb(251,186,23)"
/>
<path
d="M264 325C271 325 290 329 283 339C236 384 186 436 139 482C133 481 133 477 131 474C133 477 133 481 135 482C174 490 213 472 250 466C261 447 246 435 235 426C254 406 271 389 289 372C303 352 287 324 266 326"
fill="rgb(251,156,158)"
/>
<path
d="M263 327C260 328 256 328 253 330C233 348 216 367 197 384C188 381 183 371 175 368C166 367 161 369 156 372C148 409 133 447 133 482C173 430 281 366 277 323"
fill="rgb(248,56,63)"
/>
<path
d="M258 326C235 341 218 365 198 382C186 376 176 360 161 368L160 369L157 369C149 378 150 391 146 401C150 391 149 379 157 370C174 359 185 376 195 385C219 365 238 337 262 325"
fill="rgb(242,165,165)"
/>
</svg>
)
+18 -43
View File
@@ -1,10 +1,9 @@
"use client"
'use client'
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import * as React from 'react'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { buttonVariants } from '@/components/ui/button'
import { cn } from '@/lib/utils'
const AlertDialog = AlertDialogPrimitive.Root
@@ -18,7 +17,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
@@ -36,7 +35,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
@@ -45,33 +44,18 @@ const AlertDialogContent = React.forwardRef<
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
)
AlertDialogHeader.displayName = "AlertDialogHeader"
AlertDialogHeader.displayName = 'AlertDialogHeader'
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
AlertDialogFooter.displayName = 'AlertDialogFooter'
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
@@ -79,7 +63,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
className={cn('text-lg font-semibold', className)}
{...props}
/>
))
@@ -91,22 +75,17 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
@@ -116,11 +95,7 @@ const AlertDialogCancel = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
{...props}
/>
))
+21 -32
View File
@@ -1,20 +1,19 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import * as React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
{
variants: {
variant: {
default: "bg-background text-foreground",
default: 'bg-background text-foreground',
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
)
@@ -23,37 +22,27 @@ const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
))
Alert.displayName = "Alert"
Alert.displayName = 'Alert'
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
)
)
AlertTitle.displayName = 'AlertTitle'
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
<div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
))
AlertDescription.displayName = "AlertDescription"
AlertDescription.displayName = 'AlertDescription'
export { Alert, AlertTitle, AlertDescription }
+10 -14
View File
@@ -1,24 +1,22 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import * as React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
)
@@ -28,9 +26,7 @@ export interface BadgeProps
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
export { Badge, badgeVariants }
+48 -63
View File
@@ -1,108 +1,93 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { ChevronRight, MoreHorizontal } from 'lucide-react'
import { cn } from '@/lib/utils'
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
React.ComponentPropsWithoutRef<'nav'> & {
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"
Breadcrumb.displayName = 'Breadcrumb'
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className
)}
{...props}
/>
))
BreadcrumbList.displayName = "BreadcrumbList"
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<'ol'>>(
({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
className
)}
{...props}
/>
)
)
BreadcrumbList.displayName = 'BreadcrumbList'
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
))
BreadcrumbItem.displayName = "BreadcrumbItem"
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<'li'>>(
({ className, ...props }, ref) => (
<li ref={ref} className={cn('inline-flex items-center gap-1.5', className)} {...props} />
)
)
BreadcrumbItem.displayName = 'BreadcrumbItem'
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
React.ComponentPropsWithoutRef<'a'> & {
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
const Comp = asChild ? Slot : 'a'
return (
<Comp
ref={ref}
className={cn("transition-colors hover:text-foreground", className)}
className={cn('transition-colors hover:text-foreground', className)}
{...props}
/>
)
})
BreadcrumbLink.displayName = "BreadcrumbLink"
BreadcrumbLink.displayName = 'BreadcrumbLink'
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
))
BreadcrumbPage.displayName = "BreadcrumbPage"
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<'span'>>(
({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn('font-normal text-foreground', className)}
{...props}
/>
)
)
BreadcrumbPage.displayName = 'BreadcrumbPage'
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<'li'>) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
className={cn('[&>svg]:w-3.5 [&>svg]:h-3.5', className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
)
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
export {
Breadcrumb,
+20 -28
View File
@@ -1,34 +1,30 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { type VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
)
@@ -41,16 +37,12 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
)
}
)
Button.displayName = "Button"
Button.displayName = 'Button'
export { Button, buttonVariants }
+46 -71
View File
@@ -1,79 +1,54 @@
import * as React from "react"
import * as React from 'react'
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)}
{...props}
/>
)
)
Card.displayName = 'Card'
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
)
)
CardHeader.displayName = 'CardHeader'
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
{...props}
/>
)
)
CardTitle.displayName = 'CardTitle'
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
)
)
CardDescription.displayName = 'CardDescription'
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
)
)
CardContent.displayName = 'CardContent'
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
)
)
CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+7 -10
View File
@@ -1,10 +1,9 @@
"use client"
'use client'
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { Check } from 'lucide-react'
import { cn } from '@/lib/utils'
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
@@ -13,14 +12,12 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
+16 -15
View File
@@ -6,9 +6,20 @@ import * as React from 'react'
import { type DialogProps } from '@radix-ui/react-dialog'
import { Command as CommandPrimitive } from 'cmdk'
import { Search } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Dialog, DialogContent } from '@/components/ui/dialog'
import { cn } from '@/lib/utils'
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
@@ -81,11 +92,7 @@ const CommandEmpty = React.forwardRef<
children?: React.ReactNode
}
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
<CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
@@ -140,16 +147,10 @@ const CommandItem = React.forwardRef<
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-muted-foreground',
className
)}
className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
{...props}
/>
)
+15 -34
View File
@@ -1,10 +1,9 @@
"use client"
'use client'
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
const Dialog = DialogPrimitive.Root
@@ -21,7 +20,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
@@ -38,7 +37,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
@@ -53,33 +52,18 @@ const DialogContent = React.forwardRef<
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
)
DialogHeader.displayName = "DialogHeader"
DialogHeader.displayName = 'DialogHeader'
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
@@ -87,10 +71,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...props}
/>
))
@@ -102,7 +83,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
))
+21 -37
View File
@@ -1,10 +1,9 @@
"use client"
'use client'
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { Check, ChevronRight, Circle } from 'lucide-react'
import { cn } from '@/lib/utils'
const DropdownMenu = DropdownMenuPrimitive.Root
@@ -27,8 +26,8 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className
)}
{...props}
@@ -37,8 +36,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@@ -47,14 +45,13 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@@ -65,7 +62,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
@@ -83,8 +80,8 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className
)}
{...props}
@@ -99,7 +96,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
@@ -113,8 +110,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@@ -123,7 +119,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
@@ -146,11 +142,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...props}
/>
))
@@ -162,24 +154,16 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export {
DropdownMenu,
+23 -23
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import { useEnvironmentStore } from '@/stores/environment/store'
import React, { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
import { useEnvironmentStore } from '@/stores/environment/store'
interface EnvVarDropdownProps {
visible: boolean
@@ -12,20 +12,20 @@ interface EnvVarDropdownProps {
onClose?: () => void
}
export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
visible,
export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
visible,
onSelect,
searchTerm = '',
className,
inputValue,
cursorPosition,
onClose
onClose,
}) => {
const envVars = useEnvironmentStore((state) => Object.keys(state.variables))
const [selectedIndex, setSelectedIndex] = useState(0)
// Filter env vars based on search term
const filteredEnvVars = envVars.filter(envVar =>
const filteredEnvVars = envVars.filter((envVar) =>
envVar.toLowerCase().includes(searchTerm.toLowerCase())
)
@@ -38,15 +38,14 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
const handleEnvVarSelect = (envVar: string) => {
const textBeforeCursor = inputValue.slice(0, cursorPosition)
const textAfterCursor = inputValue.slice(cursorPosition)
// Find the position of the last '{{' before cursor
const lastOpenBraces = textBeforeCursor.lastIndexOf('{{')
if (lastOpenBraces === -1) return
const newValue = textBeforeCursor.slice(0, lastOpenBraces) +
'{{' + envVar + '}}' +
textAfterCursor
const newValue =
textBeforeCursor.slice(0, lastOpenBraces) + '{{' + envVar + '}}' + textAfterCursor
onSelect(newValue)
onClose?.()
}
@@ -58,13 +57,11 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
setSelectedIndex(prev =>
prev < filteredEnvVars.length - 1 ? prev + 1 : prev
)
setSelectedIndex((prev) => (prev < filteredEnvVars.length - 1 ? prev + 1 : prev))
break
case 'ArrowUp':
e.preventDefault()
setSelectedIndex(prev => prev > 0 ? prev - 1 : prev)
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev))
break
case 'Enter':
e.preventDefault()
@@ -90,7 +87,7 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
return (
<div
className={cn(
"absolute z-[9999] w-full mt-1 overflow-hidden bg-popover rounded-md border shadow-md",
'absolute z-[9999] w-full mt-1 overflow-hidden bg-popover rounded-md border shadow-md',
className
)}
>
@@ -104,10 +101,10 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
<button
key={envVar}
className={cn(
"w-full px-3 py-1.5 text-sm text-left",
"hover:bg-accent hover:text-accent-foreground",
"focus:bg-accent focus:text-accent-foreground focus:outline-none",
index === selectedIndex && "bg-accent text-accent-foreground"
'w-full px-3 py-1.5 text-sm text-left',
'hover:bg-accent hover:text-accent-foreground',
'focus:bg-accent focus:text-accent-foreground focus:outline-none',
index === selectedIndex && 'bg-accent text-accent-foreground'
)}
onMouseEnter={() => setSelectedIndex(index)}
onMouseDown={(e) => {
@@ -125,7 +122,10 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
}
// Helper function to check for '{{' trigger and get search term
export const checkEnvVarTrigger = (text: string, cursorPosition: number): { show: boolean; searchTerm: string } => {
export const checkEnvVarTrigger = (
text: string,
cursorPosition: number
): { show: boolean; searchTerm: string } => {
if (cursorPosition >= 2) {
const textBeforeCursor = text.slice(0, cursorPosition)
const match = textBeforeCursor.match(/\{\{(\w*)$/)
@@ -134,4 +134,4 @@ export const checkEnvVarTrigger = (text: string, cursorPosition: number): { show
}
}
return { show: false, searchTerm: '' }
}
}
+1 -1
View File
@@ -33,4 +33,4 @@ export function formatDisplayText(text: string): ReactNode[] {
return <span key={index}>{part}</span>
})
}
}
-1
View File
@@ -1,5 +1,4 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
+8 -14
View File
@@ -1,25 +1,19 @@
"use client"
'use client'
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { type VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName
+6 -7
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { cn } from '@/lib/utils'
const Popover = PopoverPrimitive.Root
@@ -12,14 +11,14 @@ const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
+9 -12
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
import { cn } from '@/lib/utils'
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@@ -11,7 +10,7 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
@@ -26,16 +25,14 @@ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
'flex touch-none select-none transition-colors',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className
)}
{...props}
+19 -27
View File
@@ -1,10 +1,9 @@
"use client"
'use client'
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as SelectPrimitive from '@radix-ui/react-select'
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
import { cn } from '@/lib/utils'
const Select = SelectPrimitive.Root
@@ -19,7 +18,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className
)}
{...props}
@@ -38,10 +37,7 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
@@ -55,29 +51,25 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className
)}
position={position}
@@ -86,9 +78,9 @@ const SelectContent = React.forwardRef<
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
)}
>
{children}
@@ -105,7 +97,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props}
/>
))
@@ -118,7 +110,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
@@ -140,7 +132,7 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
))
+23 -43
View File
@@ -1,11 +1,10 @@
"use client"
'use client'
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as SheetPrimitive from '@radix-ui/react-dialog'
import { type VariantProps, cva } from 'class-variance-authority'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
const Sheet = SheetPrimitive.Root
@@ -21,7 +20,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
@@ -31,20 +30,20 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: "right",
side: 'right',
},
}
)
@@ -56,14 +55,10 @@ interface SheetContentProps
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
@@ -74,33 +69,18 @@ const SheetContent = React.forwardRef<
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
)
SheetHeader.displayName = "SheetHeader"
SheetHeader.displayName = 'SheetHeader'
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
SheetFooter.displayName = 'SheetFooter'
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
@@ -108,7 +88,7 @@ const SheetTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
))
@@ -120,7 +100,7 @@ const SheetDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
))
+5 -9
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as SliderPrimitive from '@radix-ui/react-slider'
import { cn } from '@/lib/utils'
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
@@ -11,10 +10,7 @@ const Slider = React.forwardRef<
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
className={cn('relative flex w-full touch-none select-none items-center', className)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
+6 -7
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as SwitchPrimitives from '@radix-ui/react-switch'
import { cn } from '@/lib/utils'
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
@@ -11,7 +10,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className
)}
{...props}
@@ -19,7 +18,7 @@ const Switch = React.forwardRef<
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
)}
/>
</SwitchPrimitives.Root>
+7 -8
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as TabsPrimitive from '@radix-ui/react-tabs'
import { cn } from '@/lib/utils'
const Tabs = TabsPrimitive.Root
@@ -14,7 +13,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className
)}
{...props}
@@ -29,7 +28,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className
)}
{...props}
@@ -44,7 +43,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...props}
+8 -27
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { cn } from '@/lib/utils'
import { useWorkflowStore } from '@/stores/workflow/store'
@@ -62,9 +62,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const blockName = sourceBlock.name || sourceBlock.type
return {
tags: outputPaths.map(
(path) => `${blockName.replace(/\s+/g, '').toLowerCase()}.${path}`
),
tags: outputPaths.map((path) => `${blockName.replace(/\s+/g, '').toLowerCase()}.${path}`),
}
}
@@ -92,9 +90,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const outputPaths = getOutputPaths(sourceBlock.outputs)
const blockName = sourceBlock.name || sourceBlock.type
return outputPaths.map(
(path) => `${blockName.replace(/\s+/g, '').toLowerCase()}.${path}`
)
return outputPaths.map((path) => `${blockName.replace(/\s+/g, '').toLowerCase()}.${path}`)
})
return { tags: sourceTags }
@@ -120,12 +116,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const lastOpenBracket = textBeforeCursor.lastIndexOf('<')
if (lastOpenBracket === -1) return
const newValue =
textBeforeCursor.slice(0, lastOpenBracket) +
'<' +
tag +
'>' +
textAfterCursor
const newValue = textBeforeCursor.slice(0, lastOpenBracket) + '<' + tag + '>' + textAfterCursor
onSelect(newValue)
onClose?.()
@@ -138,9 +129,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
setSelectedIndex((prev) =>
prev < filteredTags.length - 1 ? prev + 1 : prev
)
setSelectedIndex((prev) => (prev < filteredTags.length - 1 ? prev + 1 : prev))
break
case 'ArrowUp':
e.preventDefault()
@@ -177,9 +166,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
>
<div className="py-1">
{filteredTags.length === 0 ? (
<div className="px-3 py-2 text-sm text-muted-foreground">
No matching tags found
</div>
<div className="px-3 py-2 text-sm text-muted-foreground">No matching tags found</div>
) : (
filteredTags.map((tag, index) => (
<button
@@ -206,20 +193,14 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
// Helper function to check for '<' trigger
export const checkTagTrigger = (
text: string,
cursorPosition: number
): { show: boolean } => {
export const checkTagTrigger = (text: string, cursorPosition: number): { show: boolean } => {
if (cursorPosition >= 1) {
const textBeforeCursor = text.slice(0, cursorPosition)
const lastOpenBracket = textBeforeCursor.lastIndexOf('<')
const lastCloseBracket = textBeforeCursor.lastIndexOf('>')
// Show if we have an unclosed '<' that's not part of a completed tag
if (
lastOpenBracket !== -1 &&
(lastCloseBracket === -1 || lastCloseBracket < lastOpenBracket)
) {
if (lastOpenBracket !== -1 && (lastCloseBracket === -1 || lastCloseBracket < lastOpenBracket)) {
return { show: true }
}
}
+17 -19
View File
@@ -1,22 +1,20 @@
import * as React from "react"
import * as React from 'react'
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = 'Textarea'
export { Textarea }
+5 -6
View File
@@ -1,9 +1,8 @@
"use client"
'use client'
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
import * as React from 'react'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
import { cn } from '@/lib/utils'
const TooltipProvider = TooltipPrimitive.Provider
@@ -19,7 +18,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
File diff suppressed because it is too large Load Diff
+121 -110
View File
@@ -1,6 +1,6 @@
/**
* "Executor" for running agentic workflows in parallel.
*
*
* Notes & Features:
* • Uses a layered topological sort to allow parallel block execution for blocks with no remaining dependencies.
* • Each block's inputs are resolved through a template mechanism (e.g., <blockId.property>).
@@ -8,19 +8,13 @@
* • Maintains robust error handling (if a block fails, throws an error for the entire workflow).
* • Returns per-block logs that can be displayed in the UI for better trace/debug.
*/
import { SerializedWorkflow, SerializedBlock } from '@/serializer/types'
import { BlockOutput } from '@/blocks/types'
import {
Tool,
ExecutionContext,
ExecutionResult,
BlockLog
} from './types'
import { tools, executeTool, getTool } from '@/tools'
import { executeProviderRequest } from '@/providers/service'
import { getAllBlocks } from '@/blocks'
import { BlockOutput } from '@/blocks/types'
import { BlockConfig } from '@/blocks/types'
import { executeProviderRequest } from '@/providers/service'
import { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
import { executeTool, getTool, tools } from '@/tools'
import { BlockLog, ExecutionContext, ExecutionResult, Tool } from './types'
export class Executor {
constructor(
@@ -42,9 +36,9 @@ export class Executor {
blockStates: new Map<string, BlockOutput>(),
blockLogs: [],
metadata: {
startTime: startTime.toISOString()
startTime: startTime.toISOString(),
},
environmentVariables: this.environmentVariables
environmentVariables: this.environmentVariables,
}
// Pre-populate block states if initialBlockStates exist
@@ -105,9 +99,7 @@ export class Executor {
}
// Start with all blocks that have inDegree = 0
let layer = blocks
.filter((b) => (inDegree.get(b.id) || 0) === 0)
.map((b) => b.id)
let layer = blocks.filter((b) => (inDegree.get(b.id) || 0) === 0).map((b) => b.id)
// Track the final output from the "last" block or set of blocks
let lastOutput: BlockOutput = { response: {} }
@@ -156,7 +148,7 @@ export class Executor {
// Compute the end time and duration
const end = new Date()
blockLog.endedAt = end.toISOString()
if (blockLog.startedAt) {
const started = new Date(blockLog.startedAt).getTime()
blockLog.durationMs = end.getTime() - started
@@ -216,76 +208,87 @@ export class Executor {
// Special handling for agent blocks that use providers
if (block.metadata?.type === 'agent') {
const model = inputs.model || 'gpt-4o'
const providerId = model.startsWith('gpt') || model.startsWith('o1')
? 'openai'
: model.startsWith('claude')
? 'anthropic'
: model.startsWith('gemini')
? 'google'
: model.startsWith('grok')
? 'xai'
: 'deepseek'
const providerId =
model.startsWith('gpt') || model.startsWith('o1')
? 'openai'
: model.startsWith('claude')
? 'anthropic'
: model.startsWith('gemini')
? 'google'
: model.startsWith('grok')
? 'xai'
: 'deepseek'
// Format tools if they exist
const tools = Array.isArray(inputs.tools) ? inputs.tools.map((tool: any) => {
// Get the tool ID from the block type
const block = getAllBlocks().find((b: BlockConfig) => b.type === tool.type)
const toolId = block?.tools.access[0]
if (!toolId) return null
const tools = Array.isArray(inputs.tools)
? inputs.tools
.map((tool: any) => {
// Get the tool ID from the block type
const block = getAllBlocks().find((b: BlockConfig) => b.type === tool.type)
const toolId = block?.tools.access[0]
if (!toolId) return null
// Get the tool configuration
const toolConfig = getTool(toolId)
if (!toolConfig) return null
// Get the tool configuration
const toolConfig = getTool(toolId)
if (!toolConfig) return null
// Resolve environment variables in tool parameters
const resolvedParams = Object.entries(tool.params || {}).reduce((acc, [key, value]) => {
if (typeof value === 'string') {
// Handle environment variables with {{}} syntax
const envMatches = value.match(/\{\{([^}]+)\}\}/g)
if (envMatches) {
let resolvedValue = value
for (const match of envMatches) {
const envKey = match.slice(2, -2) // remove {{ and }}
const envValue = context.environmentVariables?.[envKey]
if (envValue === undefined) {
throw new Error(`Environment variable "${envKey}" was not found.`)
}
// Resolve environment variables in tool parameters
const resolvedParams = Object.entries(tool.params || {}).reduce(
(acc, [key, value]) => {
if (typeof value === 'string') {
// Handle environment variables with {{}} syntax
const envMatches = value.match(/\{\{([^}]+)\}\}/g)
if (envMatches) {
let resolvedValue = value
for (const match of envMatches) {
const envKey = match.slice(2, -2) // remove {{ and }}
const envValue = context.environmentVariables?.[envKey]
resolvedValue = resolvedValue.replace(match, envValue)
if (envValue === undefined) {
throw new Error(`Environment variable "${envKey}" was not found.`)
}
resolvedValue = resolvedValue.replace(match, envValue)
}
acc[key] = resolvedValue
} else {
acc[key] = value
}
} else {
acc[key] = value
}
return acc
},
{} as Record<string, any>
)
// Return the tool configuration with resolved parameters
return {
id: toolConfig.id,
name: toolConfig.name,
description: toolConfig.description,
params: resolvedParams,
parameters: {
type: 'object',
properties: Object.entries(toolConfig.params).reduce(
(acc, [key, config]) => ({
...acc,
[key]: {
type: config.type === 'json' ? 'object' : config.type,
description: config.description || '',
...(key in resolvedParams && { default: resolvedParams[key] }),
},
}),
{}
),
required: Object.entries(toolConfig.params)
.filter(([_, config]) => config.required)
.map(([key]) => key),
},
}
acc[key] = resolvedValue
} else {
acc[key] = value
}
} else {
acc[key] = value
}
return acc
}, {} as Record<string, any>)
// Return the tool configuration with resolved parameters
return {
id: toolConfig.id,
name: toolConfig.name,
description: toolConfig.description,
params: resolvedParams,
parameters: {
type: 'object',
properties: Object.entries(toolConfig.params).reduce((acc, [key, config]) => ({
...acc,
[key]: {
type: config.type === 'json' ? 'object' : config.type,
description: config.description || '',
...(key in resolvedParams && { default: resolvedParams[key] })
}
}), {}),
required: Object.entries(toolConfig.params)
.filter(([_, config]) => config.required)
.map(([key]) => key)
}
}
}).filter((t): t is NonNullable<typeof t> => t !== null) : []
})
.filter((t): t is NonNullable<typeof t> => t !== null)
: []
const requestPayload = {
model,
@@ -294,7 +297,7 @@ export class Executor {
tools: tools.length > 0 ? tools : undefined,
temperature: inputs.temperature,
maxTokens: inputs.maxTokens,
apiKey: inputs.apiKey
apiKey: inputs.apiKey,
}
// Log the request payload for debugging
@@ -314,13 +317,13 @@ export class Executor {
tokens: response.tokens || {
prompt: 0,
completion: 0,
total: 0
total: 0,
},
toolCalls: {
list: response.toolCalls || [],
count: response.toolCalls?.length || 0
}
}
count: response.toolCalls?.length || 0,
},
},
}
}
@@ -342,7 +345,7 @@ export class Executor {
try {
const result = await executeTool(toolId, validatedParams)
if (!result.success) {
// Ensure we have a meaningful error message
const errorMessage = result.error || `Tool ${toolId} failed with no error message`
@@ -360,9 +363,9 @@ export class Executor {
error: error.message || `Tool ${toolId} failed`,
startedAt: new Date().toISOString(),
endedAt: new Date().toISOString(),
durationMs: 0
durationMs: 0,
}
// Add the log entry
context.blockLogs.push(blockLog as BlockLog)
@@ -375,26 +378,26 @@ export class Executor {
* Validates required parameters for a Tool, or uses defaults if present.
*/
private validateToolParams(tool: Tool, params: Record<string, any>): Record<string, any> {
return Object.entries(tool.params).reduce((acc, [name, config]) => {
if (name in params) {
acc[name] = params[name]
} else if ('default' in config) {
acc[name] = config.default
} else if (config.required) {
throw new Error(`Missing required parameter '${name}'`)
}
return acc
}, {} as Record<string, any>)
return Object.entries(tool.params).reduce(
(acc, [name, config]) => {
if (name in params) {
acc[name] = params[name]
} else if ('default' in config) {
acc[name] = config.default
} else if (config.required) {
throw new Error(`Missing required parameter '${name}'`)
}
return acc
},
{} as Record<string, any>
)
}
/**
* Resolves any template references in a block's config params (e.g., "<someBlockId.response>"),
* pulling from context.blockStates. This is how outputs from one block get wired as inputs to another.
*/
private resolveInputs(
block: SerializedBlock,
context: ExecutionContext
): Record<string, any> {
private resolveInputs(block: SerializedBlock, context: ExecutionContext): Record<string, any> {
const inputs = { ...block.config.params }
// Create quick-lookup for blocks by ID and by normalized name
@@ -402,7 +405,7 @@ export class Executor {
const blockByName = new Map(
this.workflow.blocks.map((b) => [
b.metadata?.title?.toLowerCase().replace(/\s+/g, '') || '',
b
b,
])
)
@@ -432,19 +435,25 @@ export class Executor {
// Check if the referenced block is disabled.
if (sourceBlock.enabled === false) {
throw new Error(`Block "${sourceBlock.metadata?.title}" is disabled, and block "${block.metadata?.title}" depends on it.`)
throw new Error(
`Block "${sourceBlock.metadata?.title}" is disabled, and block "${block.metadata?.title}" depends on it.`
)
}
const sourceState = context.blockStates.get(sourceBlock.id)
if (!sourceState) {
throw new Error(`No state found for block "${sourceBlock.metadata?.title}" (ID: ${sourceBlock.id}).`)
throw new Error(
`No state found for block "${sourceBlock.metadata?.title}" (ID: ${sourceBlock.id}).`
)
}
// Drill into the path
let replacementValue: any = sourceState
for (const part of pathParts) {
if (!replacementValue || typeof replacementValue !== 'object') {
throw new Error(`Invalid path part "${part}" in "${path}" for block "${block.metadata?.title}".`)
throw new Error(
`Invalid path part "${part}" in "${path}" for block "${block.metadata?.title}".`
)
}
replacementValue = replacementValue[part]
}
@@ -459,7 +468,9 @@ export class Executor {
: String(replacementValue)
)
} else {
throw new Error(`No value found at path "${path}" in block "${sourceBlock.metadata?.title}".`)
throw new Error(
`No value found at path "${path}" in block "${sourceBlock.metadata?.title}".`
)
}
}
}
@@ -470,7 +481,7 @@ export class Executor {
for (const match of envMatches) {
const envKey = match.slice(2, -2) // remove {{ and }}
const envValue = this.environmentVariables?.[envKey]
if (envValue === undefined) {
throw new Error(`Environment variable "${envKey}" was not found.`)
}
+1 -1
View File
@@ -84,4 +84,4 @@ export interface Tool<P = any, O = Record<string, any>> {
*/
export interface ToolRegistry {
[key: string]: Tool
}
}
+8 -5
View File
@@ -9,8 +9,11 @@ module.exports = {
},
testMatch: ['**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {
tsconfig: 'tsconfig.json',
}],
}
};
'^.+\\.(ts|tsx)$': [
'ts-jest',
{
tsconfig: 'tsconfig.json',
},
],
},
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
}
+6 -6
View File
@@ -1,7 +1,7 @@
import { BlockState } from '@/stores/workflow/types'
import { Edge } from 'reactflow'
import { Serializer } from '@/serializer'
import { BlockState } from '@/stores/workflow/types'
import { Executor } from '@/executor'
import { Serializer } from '@/serializer'
export interface WorkflowExecutionResult {
success: boolean
@@ -35,20 +35,20 @@ export async function executeWorkflow(
console.log('Workflow executed successfully:', result.output)
return {
success: true,
output: result.output
output: result.output,
}
} else {
console.error('Workflow execution failed:', result.error)
return {
success: false,
error: result.error
error: result.error,
}
}
} catch (error: any) {
console.error('Error executing workflow:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
error: error instanceof Error ? error.message : 'Unknown error occurred',
}
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
/* config options here */
};
}
export default nextConfig;
export default nextConfig
+736 -12
View File
@@ -37,13 +37,18 @@
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/jest": "^29.5.14",
"@types/node": "^20",
"@types/prismjs": "^1.26.5",
"@types/react": "^19",
"@types/react-dom": "^19",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.4.3",
"postcss": "^8",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
@@ -282,13 +287,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz",
"integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.5"
"@babel/types": "^7.26.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -564,17 +569,17 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz",
"integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==",
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/template": "^7.25.9",
"@babel/types": "^7.26.5",
"@babel/types": "^7.26.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -583,9 +588,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz",
"integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3101,6 +3106,41 @@
"tslib": "^2.8.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/traverse": "^7.26.7",
"@babel/types": "^7.26.7",
"javascript-natural-sort": "^0.7.1",
"lodash": "^4.17.21"
},
"engines": {
"node": ">18.12"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "4.x || 5.x"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"svelte": {
"optional": true
}
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -4024,6 +4064,64 @@
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT"
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true,
"license": "MIT",
"dependencies": {
"slice-ansi": "^5.0.0",
"string-width": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true,
"license": "MIT"
},
"node_modules/cli-truncate/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -4675,6 +4773,13 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true,
"license": "MIT"
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -5007,6 +5112,19 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -5058,6 +5176,13 @@
"node": ">=4"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true,
"license": "MIT"
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -5284,6 +5409,19 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
"integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
@@ -5404,6 +5542,22 @@
"node": ">=10.17.0"
}
},
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
"license": "MIT",
"bin": {
"husky": "bin.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/immutable": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
@@ -5692,6 +5846,13 @@
"node": "*"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true,
"license": "MIT"
},
"node_modules/jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
@@ -6467,6 +6628,249 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/lint-staged": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.3.tgz",
"integrity": "sha512-FoH1vOeouNh1pw+90S+cnuoFwRfUD9ijY2GKy5h7HS3OR7JVir2N2xrsa0+Twc1B7cW72L+88geG5cW4wIhn7g==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^5.4.1",
"commander": "^13.1.0",
"debug": "^4.4.0",
"execa": "^8.0.1",
"lilconfig": "^3.1.3",
"listr2": "^8.2.5",
"micromatch": "^4.0.8",
"pidtree": "^0.6.0",
"string-argv": "^0.3.2",
"yaml": "^2.7.0"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
},
"engines": {
"node": ">=18.12.0"
},
"funding": {
"url": "https://opencollective.com/lint-staged"
}
},
"node_modules/lint-staged/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/lint-staged/node_modules/commander": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/lint-staged/node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/lint-staged/node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/lint-staged/node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/listr2": {
"version": "8.2.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz",
"integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/listr2/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true,
"license": "MIT"
},
"node_modules/listr2/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/listr2/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -6480,6 +6884,13 @@
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -6487,6 +6898,118 @@
"dev": true,
"license": "MIT"
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/ansi-escapes": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
"license": "MIT",
"dependencies": {
"environment": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true,
"license": "MIT"
},
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
"integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/slice-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
"integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/log-update/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -6586,6 +7109,19 @@
"node": ">=6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -6972,6 +7508,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidtree": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
"dev": true,
"license": "MIT",
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -7146,6 +7695,101 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
"integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.21.3"
},
"peerDependencies": {
"@ianvs/prettier-plugin-sort-imports": "*",
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"@zackad/prettier-plugin-twig": "*",
"prettier": "^3.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
"prettier-plugin-import-sort": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
"prettier-plugin-multiline-arrays": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-sort-imports": "*",
"prettier-plugin-style-order": "*",
"prettier-plugin-svelte": "*"
},
"peerDependenciesMeta": {
"@ianvs/prettier-plugin-sort-imports": {
"optional": true
},
"@prettier/plugin-pug": {
"optional": true
},
"@shopify/prettier-plugin-liquid": {
"optional": true
},
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"@zackad/prettier-plugin-twig": {
"optional": true
},
"prettier-plugin-astro": {
"optional": true
},
"prettier-plugin-css-order": {
"optional": true
},
"prettier-plugin-import-sort": {
"optional": true
},
"prettier-plugin-jsdoc": {
"optional": true
},
"prettier-plugin-marko": {
"optional": true
},
"prettier-plugin-multiline-arrays": {
"optional": true
},
"prettier-plugin-organize-attributes": {
"optional": true
},
"prettier-plugin-organize-imports": {
"optional": true
},
"prettier-plugin-sort-imports": {
"optional": true
},
"prettier-plugin-style-order": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
}
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -7453,6 +8097,39 @@
"node": ">=10"
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -7463,6 +8140,13 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
"license": "MIT"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7662,6 +8346,36 @@
"node": ">=8"
}
},
"node_modules/slice-ansi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.0.0",
"is-fullwidth-code-point": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -7720,6 +8434,16 @@
"node": ">=10.0.0"
}
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+14 -1
View File
@@ -8,7 +8,10 @@
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch"
"test:watch": "jest --watch",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.5",
@@ -40,15 +43,25 @@
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/jest": "^29.5.14",
"@types/node": "^20",
"@types/prismjs": "^1.26.5",
"@types/react": "^19",
"@types/react-dom": "^19",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.4.3",
"postcss": "^8",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write"
]
}
}
+2 -2
View File
@@ -3,6 +3,6 @@ const config = {
plugins: {
tailwindcss: {},
},
};
}
export default config;
export default config
+59 -45
View File
@@ -1,4 +1,10 @@
import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest, Message } from '../types'
import {
FunctionCallResponse,
Message,
ProviderConfig,
ProviderRequest,
ProviderToolConfig,
} from '../types'
export const anthropicProvider: ProviderConfig = {
id: 'anthropic',
@@ -7,12 +13,12 @@ export const anthropicProvider: ProviderConfig = {
version: '1.0.0',
models: ['claude-3-5-sonnet-20241022'],
defaultModel: 'claude-3-5-sonnet-20241022',
baseUrl: 'https://api.anthropic.com/v1/messages',
headers: (apiKey: string) => ({
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
'anthropic-version': '2023-06-01',
}),
transformToolsToFunctions: (tools: ProviderToolConfig[]) => {
@@ -20,18 +26,21 @@ export const anthropicProvider: ProviderConfig = {
return undefined
}
return tools.map(tool => ({
return tools.map((tool) => ({
name: tool.id,
description: tool.description,
input_schema: {
type: 'object',
properties: tool.parameters.properties,
required: tool.parameters.required
}
required: tool.parameters.required,
},
}))
},
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => {
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
): FunctionCallResponse => {
const rawResponse = response?.output || response
if (!rawResponse?.content) {
throw new Error('No content found in response')
@@ -42,7 +51,7 @@ export const anthropicProvider: ProviderConfig = {
throw new Error('No tool use found in response')
}
const tool = tools?.find(t => t.id === toolUse.name)
const tool = tools?.find((t) => t.id === toolUse.name)
if (!tool) {
throw new Error(`Tool not found: ${toolUse.name}`)
}
@@ -61,48 +70,53 @@ export const anthropicProvider: ProviderConfig = {
name: toolUse.name,
arguments: {
...tool.params,
...input
}
...input,
},
}
},
transformRequest: (request: ProviderRequest, functions?: any) => {
// Transform messages to Anthropic format
const messages = request.messages?.map(msg => {
if (msg.role === 'function') {
return {
role: 'user',
content: [{
type: 'tool_result',
tool_use_id: msg.name,
content: msg.content
}]
const messages =
request.messages?.map((msg) => {
if (msg.role === 'function') {
return {
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: msg.name,
content: msg.content,
},
],
}
}
}
if (msg.function_call) {
return {
role: 'assistant',
content: [{
type: 'tool_use',
id: msg.function_call.name,
name: msg.function_call.name,
input: JSON.parse(msg.function_call.arguments)
}]
if (msg.function_call) {
return {
role: 'assistant',
content: [
{
type: 'tool_use',
id: msg.function_call.name,
name: msg.function_call.name,
input: JSON.parse(msg.function_call.arguments),
},
],
}
}
}
return {
role: msg.role === 'assistant' ? 'assistant' : 'user',
content: msg.content ? [{ type: 'text', text: msg.content }] : []
}
}) || []
return {
role: msg.role === 'assistant' ? 'assistant' : 'user',
content: msg.content ? [{ type: 'text', text: msg.content }] : [],
}
}) || []
// Add context if provided
if (request.context) {
messages.unshift({
role: 'user',
content: [{ type: 'text', text: request.context }]
content: [{ type: 'text', text: request.context }],
})
}
@@ -113,7 +127,7 @@ export const anthropicProvider: ProviderConfig = {
system: request.systemPrompt || '',
max_tokens: parseInt(String(request.maxTokens)) || 1024,
temperature: parseFloat(String(request.temperature ?? 0.7)),
...(functions && { tools: functions })
...(functions && { tools: functions }),
}
return payload
@@ -128,15 +142,15 @@ export const anthropicProvider: ProviderConfig = {
// Get the actual response content
const rawResponse = response.output || response
// Extract text content from the message
let content = ''
const messageContent = rawResponse?.content || rawResponse?.message?.content
if (Array.isArray(messageContent)) {
content = messageContent
.filter(item => item.type === 'text')
.map(item => item.text)
.filter((item) => item.type === 'text')
.map((item) => item.text)
.join('\n')
} else if (typeof messageContent === 'string') {
content = messageContent
@@ -149,9 +163,9 @@ export const anthropicProvider: ProviderConfig = {
tokens: {
prompt: rawResponse.usage.input_tokens,
completion: rawResponse.usage.output_tokens,
total: rawResponse.usage.input_tokens + rawResponse.usage.output_tokens
}
})
total: rawResponse.usage.input_tokens + rawResponse.usage.output_tokens,
},
}),
}
return result
@@ -170,5 +184,5 @@ export const anthropicProvider: ProviderConfig = {
console.error('Error in hasFunctionCall:', error)
return false
}
}
}
},
}
+31 -26
View File
@@ -1,4 +1,4 @@
import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest } from '../types'
import { FunctionCallResponse, ProviderConfig, ProviderRequest, ProviderToolConfig } from '../types'
export const deepseekProvider: ProviderConfig = {
id: 'deepseek',
@@ -7,11 +7,11 @@ export const deepseekProvider: ProviderConfig = {
version: '1.0.0',
models: ['deepseek-chat'],
defaultModel: 'deepseek-chat',
baseUrl: 'https://api.deepseek.com/v1/chat/completions',
headers: (apiKey: string) => ({
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
Authorization: `Bearer ${apiKey}`,
}),
transformToolsToFunctions: (tools: ProviderToolConfig[]) => {
@@ -19,23 +19,26 @@ export const deepseekProvider: ProviderConfig = {
return undefined
}
return tools.map(tool => ({
return tools.map((tool) => ({
type: 'function',
function: {
name: tool.id,
description: tool.description,
parameters: tool.parameters
}
parameters: tool.parameters,
},
}))
},
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => {
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
): FunctionCallResponse => {
const toolCall = response.choices?.[0]?.message?.tool_calls?.[0]
if (!toolCall || !toolCall.function) {
throw new Error('No valid tool call found in response')
}
const tool = tools?.find(t => t.id === toolCall.function.name)
const tool = tools?.find((t) => t.id === toolCall.function.name)
if (!tool) {
throw new Error(`Tool not found: ${toolCall.function.name}`)
}
@@ -54,19 +57,19 @@ export const deepseekProvider: ProviderConfig = {
name: toolCall.function.name,
arguments: {
...tool.params,
...args
}
...args,
},
}
},
transformRequest: (request: ProviderRequest, functions?: any) => {
// Transform messages from internal format to Deepseek format
const messages = (request.messages || []).map(msg => {
const messages = (request.messages || []).map((msg) => {
if (msg.role === 'function') {
return {
role: 'tool',
content: msg.content,
tool_call_id: msg.name
tool_call_id: msg.name,
}
}
@@ -74,14 +77,16 @@ export const deepseekProvider: ProviderConfig = {
return {
role: 'assistant',
content: null,
tool_calls: [{
id: msg.function_call.name,
type: 'function',
function: {
name: msg.function_call.name,
arguments: msg.function_call.arguments
}
}]
tool_calls: [
{
id: msg.function_call.name,
type: 'function',
function: {
name: msg.function_call.name,
arguments: msg.function_call.arguments,
},
},
],
}
}
@@ -93,11 +98,11 @@ export const deepseekProvider: ProviderConfig = {
messages: [
{ role: 'system', content: request.systemPrompt },
...(request.context ? [{ role: 'user', content: request.context }] : []),
...messages
...messages,
],
temperature: request.temperature || 0.7,
max_tokens: request.maxTokens || 1024,
...(functions && { tools: functions })
...(functions && { tools: functions }),
}
return payload
@@ -116,13 +121,13 @@ export const deepseekProvider: ProviderConfig = {
tokens: response.usage && {
prompt: response.usage.prompt_tokens,
completion: response.usage.completion_tokens,
total: response.usage.total_tokens
}
total: response.usage.total_tokens,
},
}
},
hasFunctionCall: (response: any) => {
if (!response) return false
return !!response.choices?.[0]?.message?.tool_calls?.[0]
}
}
},
}
+60 -47
View File
@@ -1,18 +1,18 @@
import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest } from '../types'
import { ToolConfig } from '@/tools/types'
import { FunctionCallResponse, ProviderConfig, ProviderRequest, ProviderToolConfig } from '../types'
export const googleProvider: ProviderConfig = {
id: 'google',
name: 'Google',
description: 'Google\'s Gemini models',
description: "Google's Gemini models",
version: '1.0.0',
models: ['gemini-pro'],
defaultModel: 'gemini-pro',
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent',
headers: (apiKey: string) => ({
'Content-Type': 'application/json',
'x-goog-api-key': apiKey
'x-goog-api-key': apiKey,
}),
transformToolsToFunctions: (tools: ProviderToolConfig[]) => {
@@ -34,8 +34,8 @@ export const googleProvider: ProviderConfig = {
[key]: {
type: 'OBJECT',
description: value.description || '',
properties: transformProperties(value.properties)
}
properties: transformProperties(value.properties),
},
}
}
@@ -44,20 +44,19 @@ export const googleProvider: ProviderConfig = {
...acc,
[key]: {
type: (value.type || 'string').toUpperCase(),
description: value.description || ''
}
description: value.description || '',
},
}
}, {})
}
return {
functionDeclarations: tools.map(tool => {
functionDeclarations: tools.map((tool) => {
// Get properties excluding complex types
const properties = transformProperties(tool.parameters.properties)
// Filter required fields to only include ones that exist in properties
const required = (tool.parameters.required || [])
.filter(field => field in properties)
const required = (tool.parameters.required || []).filter((field) => field in properties)
return {
name: tool.id,
@@ -65,14 +64,17 @@ export const googleProvider: ProviderConfig = {
parameters: {
type: 'OBJECT',
properties,
required
}
required,
},
}
})
}),
}
},
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => {
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
): FunctionCallResponse => {
// Extract function call from Gemini response
const functionCall = response.candidates?.[0]?.content?.parts?.[0]?.functionCall
if (!functionCall) {
@@ -82,7 +84,7 @@ export const googleProvider: ProviderConfig = {
// Log the function call for debugging
console.log('Raw function call from Gemini:', JSON.stringify(functionCall, null, 2))
const tool = tools?.find(t => t.id === functionCall.name)
const tool = tools?.find((t) => t.id === functionCall.name)
if (!tool) {
throw new Error(`Tool not found: ${functionCall.name}`)
}
@@ -105,57 +107,68 @@ export const googleProvider: ProviderConfig = {
name: functionCall.name,
arguments: {
...functionArgs,
apiKey: tool.params.apiKey // Always use the apiKey from tool params
}
apiKey: tool.params.apiKey, // Always use the apiKey from tool params
},
}
},
transformRequest: (request: ProviderRequest, functions?: any) => {
// Combine system prompt and context into a single message if both exist
const initialMessage = request.systemPrompt + (request.context ? `\n\n${request.context}` : '')
const messages = [
{ role: 'user', parts: [{ text: initialMessage }] },
...(request.messages || []).map(msg => {
...(request.messages || []).map((msg) => {
if (msg.role === 'function') {
return {
role: 'user',
parts: [{
functionResponse: {
name: msg.name,
response: {
parts: [
{
functionResponse: {
name: msg.name,
content: JSON.parse(msg.content || '{}')
}
}
}]
response: {
name: msg.name,
content: JSON.parse(msg.content || '{}'),
},
},
},
],
}
}
if (msg.function_call) {
return {
role: 'model',
parts: [{
functionCall: {
name: msg.function_call.name,
args: JSON.parse(msg.function_call.arguments)
}
}]
parts: [
{
functionCall: {
name: msg.function_call.name,
args: JSON.parse(msg.function_call.arguments),
},
},
],
}
}
return {
role: msg.role === 'assistant' ? 'model' : 'user',
parts: [{ text: msg.content || '' }]
parts: [{ text: msg.content || '' }],
}
})
}),
]
// Log the request for debugging
console.log('Gemini request:', JSON.stringify({
messages,
tools: functions ? [{ functionDeclarations: functions.functionDeclarations }] : undefined
}, null, 2))
console.log(
'Gemini request:',
JSON.stringify(
{
messages,
tools: functions ? [{ functionDeclarations: functions.functionDeclarations }] : undefined,
},
null,
2
)
)
return {
contents: messages,
@@ -163,7 +176,7 @@ export const googleProvider: ProviderConfig = {
generationConfig: {
temperature: request.temperature || 0.7,
maxOutputTokens: request.maxTokens || 1024,
}
},
}
},
@@ -173,12 +186,12 @@ export const googleProvider: ProviderConfig = {
tokens: response.usageMetadata && {
prompt: response.usageMetadata.promptTokenCount,
completion: response.usageMetadata.candidatesTokenCount,
total: response.usageMetadata.totalTokenCount
}
total: response.usageMetadata.totalTokenCount,
},
}
},
hasFunctionCall: (response: any) => {
return !!response.candidates?.[0]?.content?.parts?.[0]?.functionCall
}
}
},
}
+25 -20
View File
@@ -1,5 +1,5 @@
import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest } from '../types'
import { ToolConfig } from '@/tools/types'
import { FunctionCallResponse, ProviderConfig, ProviderRequest, ProviderToolConfig } from '../types'
export const openaiProvider: ProviderConfig = {
id: 'openai',
@@ -8,11 +8,11 @@ export const openaiProvider: ProviderConfig = {
version: '1.0.0',
models: ['gpt-4o', 'o1', 'o3-mini'],
defaultModel: 'gpt-4o',
baseUrl: 'https://api.openai.com/v1/chat/completions',
headers: (apiKey: string) => ({
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
Authorization: `Bearer ${apiKey}`,
}),
transformToolsToFunctions: (tools: ProviderToolConfig[]) => {
@@ -20,34 +20,37 @@ export const openaiProvider: ProviderConfig = {
return undefined
}
return tools.map(tool => ({
return tools.map((tool) => ({
name: tool.id,
description: tool.description,
parameters: tool.parameters
parameters: tool.parameters,
}))
},
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => {
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
): FunctionCallResponse => {
const functionCall = response.choices?.[0]?.message?.function_call
if (!functionCall) {
throw new Error('No function call found in response')
}
const tool = tools?.find(t => t.id === functionCall.name)
const tool = tools?.find((t) => t.id === functionCall.name)
const toolParams = tool?.params || {}
return {
name: functionCall.name,
arguments: {
...toolParams,
...JSON.parse(functionCall.arguments)
}
...JSON.parse(functionCall.arguments),
},
}
},
transformRequest: (request: ProviderRequest, functions?: any) => {
console.log('OpenAI transformRequest - Input:', JSON.stringify(request, null, 2))
const isO1Model = request.model?.startsWith('o1')
const isO1Mini = request.model === 'o1-mini'
@@ -64,17 +67,19 @@ export const openaiProvider: ProviderConfig = {
// Add system prompt if present
if (request.systemPrompt) {
allMessages.push(transformMessageRole({
role: 'system',
content: request.systemPrompt
}))
allMessages.push(
transformMessageRole({
role: 'system',
content: request.systemPrompt,
})
)
}
// Add context if present
if (request.context) {
allMessages.push({
role: 'user',
content: request.context
content: request.context,
})
}
@@ -86,7 +91,7 @@ export const openaiProvider: ProviderConfig = {
// Build the request payload
const payload: any = {
model: request.model || 'gpt-4o',
messages: allMessages
messages: allMessages,
}
// Only add parameters supported by the model type
@@ -114,14 +119,14 @@ export const openaiProvider: ProviderConfig = {
transformResponse: (response: any) => {
const output = {
content: response.choices?.[0]?.message?.content || '',
tokens: undefined as any
tokens: undefined as any,
}
if (response.usage) {
output.tokens = {
prompt: response.usage.prompt_tokens,
completion: response.usage.completion_tokens,
total: response.usage.total_tokens
total: response.usage.total_tokens,
}
// Add reasoning_tokens for o1 models if available
@@ -135,5 +140,5 @@ export const openaiProvider: ProviderConfig = {
hasFunctionCall: (response: any) => {
return !!response.choices?.[0]?.message?.function_call
}
}
},
}
+31 -28
View File
@@ -1,9 +1,9 @@
import { ProviderConfig, ProviderRequest, ProviderResponse, TokenInfo } from './types'
import { openaiProvider } from './openai'
import { executeTool, getTool } from '@/tools'
import { anthropicProvider } from './anthropic'
import { googleProvider } from './google'
import { getTool, executeTool } from '@/tools'
import { deepseekProvider } from './deepseek'
import { googleProvider } from './google'
import { openaiProvider } from './openai'
import { ProviderConfig, ProviderRequest, ProviderResponse, TokenInfo } from './types'
import { xAIProvider } from './xai'
// Register providers
@@ -12,7 +12,7 @@ const providers: Record<string, ProviderConfig> = {
anthropic: anthropicProvider,
google: googleProvider,
deepseek: deepseekProvider,
xai: xAIProvider
xai: xAIProvider,
}
export async function executeProviderRequest(
@@ -25,9 +25,10 @@ export async function executeProviderRequest(
}
// Transform tools to provider-specific function format
const functions = request.tools && request.tools.length > 0
? provider.transformToolsToFunctions(request.tools)
: undefined
const functions =
request.tools && request.tools.length > 0
? provider.transformToolsToFunctions(request.tools)
: undefined
// Transform the request using provider-specific logic
const payload = provider.transformRequest(request, functions)
@@ -45,17 +46,17 @@ export async function executeProviderRequest(
try {
while (iterationCount < MAX_ITERATIONS) {
console.log(`Processing iteration ${iterationCount + 1}`)
// Transform the response using provider-specific logic
const transformedResponse = provider.transformResponse(currentResponse)
content = transformedResponse.content
// Update tokens
if (transformedResponse.tokens) {
const newTokens: TokenInfo = {
prompt: (tokens?.prompt ?? 0) + (transformedResponse.tokens?.prompt ?? 0),
completion: (tokens?.completion ?? 0) + (transformedResponse.tokens?.completion ?? 0),
total: (tokens?.total ?? 0) + (transformedResponse.tokens?.total ?? 0)
total: (tokens?.total ?? 0) + (transformedResponse.tokens?.total ?? 0),
}
tokens = newTokens
}
@@ -63,7 +64,7 @@ export async function executeProviderRequest(
// Check for function calls using provider-specific logic
const hasFunctionCall = provider.hasFunctionCall(currentResponse)
console.log('Has function call:', hasFunctionCall)
if (!hasFunctionCall) {
console.log('No function call detected, breaking loop')
break
@@ -94,7 +95,7 @@ export async function executeProviderRequest(
const result = await executeTool(functionCall.name, functionCall.arguments)
console.log('Tool execution result:', result.success)
if (!result.success) {
console.log('Tool execution failed')
break
@@ -109,20 +110,23 @@ export async function executeProviderRequest(
content: null,
function_call: {
name: functionCall.name,
arguments: JSON.stringify(functionCall.arguments)
}
arguments: JSON.stringify(functionCall.arguments),
},
})
currentMessages.push({
role: 'function',
name: functionCall.name,
content: JSON.stringify(result.output)
content: JSON.stringify(result.output),
})
// Prepare the next request
const nextPayload = provider.transformRequest({
...request,
messages: currentMessages
}, functions)
const nextPayload = provider.transformRequest(
{
...request,
messages: currentMessages,
},
functions
)
// Make the next request
currentResponse = await makeProxyRequest(providerId, nextPayload, request.apiKey)
@@ -142,25 +146,25 @@ export async function executeProviderRequest(
model: currentResponse.model,
tokens,
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
toolResults: toolResults.length > 0 ? toolResults : undefined
toolResults: toolResults.length > 0 ? toolResults : undefined,
}
}
async function makeProxyRequest(providerId: string, payload: any, apiKey: string) {
console.log('Making proxy request for provider:', providerId)
const response = await fetch('/api/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
body: JSON.stringify({
toolId: `${providerId}/chat`,
params: {
...payload,
apiKey
}
})
apiKey,
},
}),
})
if (!response.ok) {
@@ -171,5 +175,4 @@ async function makeProxyRequest(providerId: string, payload: any, apiKey: string
const { output } = await response.json()
console.log('Proxy request completed')
return output
}
}
+9 -6
View File
@@ -18,19 +18,22 @@ export interface ProviderConfig {
version: string
models: string[]
defaultModel: string
// Provider-specific configuration
baseUrl: string
headers: (apiKey: string) => Record<string, string>
// Tool calling support
transformToolsToFunctions: (tools: ProviderToolConfig[]) => any
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]) => FunctionCallResponse
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
) => FunctionCallResponse
// Provider-specific request/response transformations
transformRequest: (request: ProviderRequest, functions?: any) => any
transformResponse: (response: any) => TransformedResponse
// Function to check if response contains a function call
hasFunctionCall: (response: any) => boolean
@@ -89,4 +92,4 @@ export interface ProviderRequest {
}
// Map of provider IDs to their configurations
export const providers: Record<string, ProviderConfig> = {}
export const providers: Record<string, ProviderConfig> = {}
+33 -28
View File
@@ -1,4 +1,4 @@
import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest } from '../types'
import { FunctionCallResponse, ProviderConfig, ProviderRequest, ProviderToolConfig } from '../types'
export const xAIProvider: ProviderConfig = {
id: 'xai',
@@ -7,11 +7,11 @@ export const xAIProvider: ProviderConfig = {
version: '1.0.0',
models: ['grok-2-latest'],
defaultModel: 'grok-2-latest',
baseUrl: 'https://api.x.ai/v1/chat/completions',
headers: (apiKey: string) => ({
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
Authorization: `Bearer ${apiKey}`,
}),
transformToolsToFunctions: (tools: ProviderToolConfig[]) => {
@@ -19,24 +19,27 @@ export const xAIProvider: ProviderConfig = {
return undefined
}
return tools.map(tool => ({
return tools.map((tool) => ({
type: 'function',
function: {
name: tool.id,
description: tool.description,
parameters: tool.parameters
}
parameters: tool.parameters,
},
}))
},
transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => {
transformFunctionCallResponse: (
response: any,
tools?: ProviderToolConfig[]
): FunctionCallResponse => {
// xAI returns tool_calls array like OpenAI
const toolCall = response.choices?.[0]?.message?.tool_calls?.[0]
if (!toolCall || !toolCall.function) {
throw new Error('No valid tool call found in response')
}
const tool = tools?.find(t => t.id === toolCall.function.name)
const tool = tools?.find((t) => t.id === toolCall.function.name)
if (!tool) {
throw new Error(`Tool not found: ${toolCall.function.name}`)
}
@@ -55,19 +58,19 @@ export const xAIProvider: ProviderConfig = {
name: toolCall.function.name,
arguments: {
...tool.params,
...args
}
...args,
},
}
},
transformRequest: (request: ProviderRequest, functions?: any) => {
// Convert function messages to tool messages
const messages = (request.messages || []).map(msg => {
const messages = (request.messages || []).map((msg) => {
if (msg.role === 'function') {
return {
role: 'tool',
content: msg.content,
tool_call_id: msg.name // xAI expects tool_call_id for tool results
tool_call_id: msg.name, // xAI expects tool_call_id for tool results
}
}
@@ -75,14 +78,16 @@ export const xAIProvider: ProviderConfig = {
return {
role: 'assistant',
content: null,
tool_calls: [{
id: msg.function_call.name,
type: 'function',
function: {
name: msg.function_call.name,
arguments: msg.function_call.arguments
}
}]
tool_calls: [
{
id: msg.function_call.name,
type: 'function',
function: {
name: msg.function_call.name,
arguments: msg.function_call.arguments,
},
},
],
}
}
@@ -94,14 +99,14 @@ export const xAIProvider: ProviderConfig = {
messages: [
{ role: 'system', content: request.systemPrompt },
...(request.context ? [{ role: 'user', content: request.context }] : []),
...messages
...messages,
],
temperature: request.temperature || 0.7,
max_tokens: request.maxTokens || 1024,
...(functions && {
...(functions && {
tools: functions,
tool_choice: 'auto' // xAI specific parameter
})
tool_choice: 'auto', // xAI specific parameter
}),
}
return payload
@@ -118,13 +123,13 @@ export const xAIProvider: ProviderConfig = {
tokens: response.usage && {
prompt: response.usage.prompt_tokens,
completion: response.usage.completion_tokens,
total: response.usage.total_tokens
}
total: response.usage.total_tokens,
},
}
},
hasFunctionCall: (response: any) => {
if (!response) return false
return !!response.choices?.[0]?.message?.tool_calls?.[0]
}
}
},
}
+111 -111
View File
@@ -1,101 +1,101 @@
import { Edge } from 'reactflow'
import { Serializer } from '../index'
import { SerializedWorkflow } from '../types'
import { BlockState } from '@/stores/workflow/types'
import { BlockOutput } from '@/blocks/types'
import { Edge } from 'reactflow'
import { BlockState } from '@/stores/workflow/types'
import { getBlock } from '@/blocks'
import { BlockOutput } from '@/blocks/types'
import { Serializer } from '../index'
import { SerializedWorkflow } from '../types'
// Mock icons
jest.mock('@/components/icons', () => ({
AgentIcon: () => 'AgentIcon',
ApiIcon: () => 'ApiIcon',
CodeIcon: () => 'CodeIcon',
}))
}))
// Mock blocks
jest.mock('@/blocks', () => ({
getBlock: jest.fn(),
}))
}))
describe('Serializer', () => {
let serializer: Serializer
let serializer: Serializer
beforeEach(() => {
serializer = new Serializer()
serializer = new Serializer()
;(getBlock as jest.Mock).mockReset()
;(getBlock as jest.Mock).mockImplementation((type: string) => {
if (type === 'agent') {
return {
tools: {
access: ['openai.chat'],
config: null
config: null,
},
workflow: {
inputs: {
systemPrompt: { type: 'string', required: false },
context: { type: 'string', required: false },
apiKey: { type: 'string', required: false }
apiKey: { type: 'string', required: false },
},
outputs: {
response: {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
tokens: 'number',
},
} satisfies BlockOutput,
},
subBlocks: [
{ id: 'model', type: 'dropdown' },
{ id: 'systemPrompt', type: 'long-input' },
{ id: 'temperature', type: 'slider' },
{ id: 'responseFormat', type: 'code' }
]
{ id: 'responseFormat', type: 'code' },
],
},
toolbar: {
title: 'Agent Block',
description: 'Use any LLM',
category: 'blocks',
bgColor: '#7F2FFF'
}
bgColor: '#7F2FFF',
},
}
} else if (type === 'api') {
return {
tools: {
access: ['http.request'],
config: null
config: null,
},
workflow: {
inputs: {
url: { type: 'string', required: true },
method: { type: 'string', required: true }
method: { type: 'string', required: true },
},
outputs: {
response: {
response: {
body: 'any',
status: 'number',
headers: 'json'
}
} satisfies BlockOutput
headers: 'json',
},
} satisfies BlockOutput,
},
subBlocks: [
{ id: 'url', type: 'short-input' },
{ id: 'method', type: 'dropdown' }
]
{ id: 'method', type: 'dropdown' },
],
},
toolbar: {
title: 'API Block',
description: 'Make HTTP requests',
category: '',
bgColor: '#00FF00'
}
bgColor: '#00FF00',
},
}
}
return {
tools: {
access: ['test-tool'],
config: null
config: null,
},
workflow: {
inputs: {},
@@ -104,21 +104,21 @@ describe('Serializer', () => {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
tokens: 'number',
},
} satisfies BlockOutput,
},
subBlocks: []
subBlocks: [],
},
toolbar: {
title: 'Test Block',
description: 'A test block',
category: 'test',
bgColor: '#000000'
}
bgColor: '#000000',
},
}
})
})
})
describe('serializeWorkflow', () => {
it('should serialize a workflow with one tool', async () => {
@@ -130,26 +130,26 @@ describe('Serializer', () => {
position: { x: 0, y: 0 },
enabled: true,
subBlocks: {
'model': {
model: {
id: 'model',
type: 'dropdown',
value: 'gpt-4o'
value: 'gpt-4o',
},
'systemPrompt': {
systemPrompt: {
id: 'systemPrompt',
type: 'long-input',
value: 'test'
}
value: 'test',
},
},
outputs: {
response: {
response: {
text: 'string',
status: 'number'
}
} satisfies BlockOutput
}
}
status: 'number',
},
} satisfies BlockOutput,
},
},
}
const workflow = serializer.serializeWorkflow(blocks, [])
@@ -158,22 +158,22 @@ describe('Serializer', () => {
expect(block.config.tool).toBe('openai.chat')
expect(block.config.params).toEqual({
model: 'gpt-4o',
systemPrompt: 'test'
systemPrompt: 'test',
})
expect(block.inputs).toEqual({
systemPrompt: 'string',
context: 'string',
apiKey: 'string'
apiKey: 'string',
})
expect(block.outputs).toEqual({
response: {
response: {
text: 'string',
status: 'number'
}
} satisfies BlockOutput
status: 'number',
},
} satisfies BlockOutput,
})
})
})
it('should handle blocks with minimal configuration', () => {
const blocks: Record<string, BlockState> = {
@@ -184,20 +184,20 @@ describe('Serializer', () => {
position: { x: 0, y: 0 },
enabled: true,
subBlocks: {
'model': {
model: {
id: 'model',
type: 'dropdown',
value: 'gpt-4o'
}
value: 'gpt-4o',
},
},
outputs: {
response: {
response: {
text: 'string'
}
} satisfies BlockOutput
}
}
text: 'string',
},
} satisfies BlockOutput,
},
},
}
const workflow = serializer.serializeWorkflow(blocks, [])
@@ -208,16 +208,16 @@ describe('Serializer', () => {
expect(block.inputs).toEqual({
systemPrompt: 'string',
context: 'string',
apiKey: 'string'
apiKey: 'string',
})
expect(block.outputs).toEqual({
response: {
response: {
text: 'string'
}
} satisfies BlockOutput
text: 'string',
},
} satisfies BlockOutput,
})
})
})
it('should handle complex workflow with multiple blocks', () => {
const blocks: Record<string, BlockState> = {
@@ -228,26 +228,26 @@ describe('Serializer', () => {
position: { x: 100, y: 100 },
enabled: true,
subBlocks: {
'url': {
url: {
id: 'url',
type: 'short-input',
value: 'https://api.data.com'
value: 'https://api.data.com',
},
'method': {
method: {
id: 'method',
type: 'dropdown',
value: 'GET'
}
value: 'GET',
},
},
outputs: {
response: {
response: {
body: 'json',
status: 'number',
headers: 'json'
}
} satisfies BlockOutput
}
headers: 'json',
},
} satisfies BlockOutput,
},
},
'process-1': {
id: 'process-1',
@@ -256,27 +256,27 @@ describe('Serializer', () => {
position: { x: 300, y: 100 },
enabled: true,
subBlocks: {
'model': {
model: {
id: 'model',
type: 'dropdown',
value: 'gpt-4o'
value: 'gpt-4o',
},
'systemPrompt': {
systemPrompt: {
id: 'systemPrompt',
type: 'long-input',
value: 'Process this data'
}
value: 'Process this data',
},
},
outputs: {
response: {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
}
}
tokens: 'number',
},
} satisfies BlockOutput,
},
},
}
const connections: Edge[] = [
@@ -285,8 +285,8 @@ describe('Serializer', () => {
source: 'input-1',
target: 'process-1',
sourceHandle: 'response',
targetHandle: 'context'
}
targetHandle: 'context',
},
]
const workflow = serializer.serializeWorkflow(blocks, connections)
@@ -303,29 +303,29 @@ describe('Serializer', () => {
expect(conn.targetHandle).toBe('context')
// Verify block outputs
const inputBlock = workflow.blocks.find(b => b.id === 'input-1')
const processBlock = workflow.blocks.find(b => b.id === 'process-1')
const inputBlock = workflow.blocks.find((b) => b.id === 'input-1')
const processBlock = workflow.blocks.find((b) => b.id === 'process-1')
expect(inputBlock?.outputs).toEqual({
response: {
response: {
body: 'json',
status: 'number',
headers: 'json'
}
} satisfies BlockOutput
headers: 'json',
},
} satisfies BlockOutput,
})
expect(processBlock?.outputs).toEqual({
response: {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
tokens: 'number',
},
} satisfies BlockOutput,
})
})
})
})
})
describe('deserializeWorkflow', () => {
it('should deserialize a workflow back to blocks and connections', () => {
@@ -339,34 +339,34 @@ describe('Serializer', () => {
tool: 'openai.chat',
params: {
model: 'gpt-4o',
systemPrompt: 'You are helpful'
}
systemPrompt: 'You are helpful',
},
},
inputs: {
systemPrompt: 'string',
context: 'string',
apiKey: 'string'
apiKey: 'string',
},
outputs: {
response: {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
tokens: 'number',
},
} satisfies BlockOutput,
},
metadata: {
title: 'Agent Block',
description: 'Use any LLM',
category: 'blocks',
color: '#7F2FFF',
type: 'agent'
type: 'agent',
},
enabled: true
}
enabled: true,
},
],
connections: []
connections: [],
}
const { blocks } = serializer.deserializeWorkflow(workflow)
@@ -381,10 +381,10 @@ describe('Serializer', () => {
response: {
text: 'string',
model: 'string',
tokens: 'number'
}
} satisfies BlockOutput
tokens: 'number',
},
} satisfies BlockOutput,
})
})
})
})
})
})
})

Some files were not shown because too many files have changed in this diff Show More