mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
merge(staging-to-main) (#577)
* feat(agent): agent model dropdown combobox (#572) * feat: agent model dropdown combobox * fix(cli): package type for esm imports, missing realtime (#574) * fix: package type for esm imports, missing realtime calls and use of migrate * chore: bump cli * fix sourceBlock null check * fix(kb): fix kb navigation URLs * fix(csp): update CSP to allow for google drive picker * feat(dropdown): added optional icon to agent model dropdown --------- Co-authored-by: Aditya Tripathi <aditya@climactic.co> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Waleed Latif <walif6@gmail.com> * fix concurrent req check (#576) Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> --------- Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com> Co-authored-by: Aditya Tripathi <aditya@climactic.co> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Waleed Latif <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
This commit is contained in:
committed by
GitHub
parent
9be41b4b73
commit
bf0ea10e62
@@ -29,7 +29,8 @@ export const runtime = 'nodejs'
|
||||
// Define the schema for environment variables
|
||||
const EnvVarsSchema = z.record(z.string())
|
||||
|
||||
// Keep track of running executions to prevent overlap
|
||||
// Keep track of running executions to prevent duplicate requests
|
||||
// Use a combination of workflow ID and request ID to allow concurrent executions with different inputs
|
||||
const runningExecutions = new Set<string>()
|
||||
|
||||
// Custom error class for usage limit exceeded
|
||||
@@ -47,10 +48,14 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
|
||||
const workflowId = workflow.id
|
||||
const executionId = uuidv4()
|
||||
|
||||
// Skip if this workflow is already running
|
||||
if (runningExecutions.has(workflowId)) {
|
||||
logger.warn(`[${requestId}] Workflow is already running: ${workflowId}`)
|
||||
throw new Error('Workflow is already running')
|
||||
// Create a unique execution key combining workflow ID and request ID
|
||||
// This allows concurrent executions of the same workflow with different inputs
|
||||
const executionKey = `${workflowId}:${requestId}`
|
||||
|
||||
// Skip if this exact execution is already running (prevents duplicate requests)
|
||||
if (runningExecutions.has(executionKey)) {
|
||||
logger.warn(`[${requestId}] Execution is already running: ${executionKey}`)
|
||||
throw new Error('Execution is already running')
|
||||
}
|
||||
|
||||
// Check if the user has exceeded their usage limits
|
||||
@@ -86,7 +91,7 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
|
||||
}
|
||||
|
||||
try {
|
||||
runningExecutions.add(workflowId)
|
||||
runningExecutions.add(executionKey)
|
||||
logger.info(`[${requestId}] Starting workflow execution: ${workflowId}`)
|
||||
|
||||
// Use the deployed state if available, otherwise fall back to current state
|
||||
@@ -291,7 +296,7 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
|
||||
await persistExecutionError(workflowId, executionId, error, 'api')
|
||||
throw error
|
||||
} finally {
|
||||
runningExecutions.delete(workflowId)
|
||||
runningExecutions.delete(executionKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +397,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS(request: NextRequest) {
|
||||
export async function OPTIONS(_request: NextRequest) {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Check, ChevronDown } from 'lucide-react'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
|
||||
import { formatDisplayText } from '@/components/ui/formatted-text'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||
|
||||
const logger = createLogger('ComboBox')
|
||||
|
||||
interface ComboBoxProps {
|
||||
options:
|
||||
| Array<string | { label: string; id: string }>
|
||||
| (() => Array<string | { label: string; id: string }>)
|
||||
defaultValue?: string
|
||||
blockId: string
|
||||
subBlockId: string
|
||||
value?: string
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
isConnecting: boolean
|
||||
config: SubBlockConfig
|
||||
}
|
||||
|
||||
export function ComboBox({
|
||||
options,
|
||||
defaultValue,
|
||||
blockId,
|
||||
subBlockId,
|
||||
value: propValue,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
disabled,
|
||||
placeholder = 'Type or select an option...',
|
||||
isConnecting,
|
||||
config,
|
||||
}: ComboBoxProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string>(blockId, subBlockId)
|
||||
const [storeInitialized, setStoreInitialized] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const [showEnvVars, setShowEnvVars] = useState(false)
|
||||
const [showTags, setShowTags] = useState(false)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [cursorPosition, setCursorPosition] = useState(0)
|
||||
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
const reactFlowInstance = useReactFlow()
|
||||
|
||||
// Use preview value when in preview mode, otherwise use store value or prop value
|
||||
const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue
|
||||
|
||||
// Evaluate options if it's a function
|
||||
const evaluatedOptions = useMemo(() => {
|
||||
return typeof options === 'function' ? options() : options
|
||||
}, [options])
|
||||
|
||||
const getOptionValue = (option: string | { label: string; id: string }) => {
|
||||
return typeof option === 'string' ? option : option.id
|
||||
}
|
||||
|
||||
const getOptionLabel = (option: string | { label: string; id: string }) => {
|
||||
return typeof option === 'string' ? option : option.label
|
||||
}
|
||||
|
||||
// Get the default option value (prefer gpt-4o, then provided defaultValue, then first option)
|
||||
const defaultOptionValue = useMemo(() => {
|
||||
if (defaultValue !== undefined) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// For model field, default to gpt-4o if available
|
||||
if (subBlockId === 'model') {
|
||||
const gpt4o = evaluatedOptions.find((opt) => getOptionValue(opt) === 'gpt-4o')
|
||||
if (gpt4o) {
|
||||
return getOptionValue(gpt4o)
|
||||
}
|
||||
}
|
||||
|
||||
if (evaluatedOptions.length > 0) {
|
||||
return getOptionValue(evaluatedOptions[0])
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [defaultValue, evaluatedOptions, getOptionValue, subBlockId])
|
||||
|
||||
// Mark store as initialized on first render
|
||||
useEffect(() => {
|
||||
setStoreInitialized(true)
|
||||
}, [])
|
||||
|
||||
// Only set default value once the store is confirmed to be initialized
|
||||
// and we know the actual value is null/undefined (not just loading)
|
||||
useEffect(() => {
|
||||
if (
|
||||
storeInitialized &&
|
||||
(value === null || value === undefined) &&
|
||||
defaultOptionValue !== undefined
|
||||
) {
|
||||
setStoreValue(defaultOptionValue)
|
||||
}
|
||||
}, [storeInitialized, value, defaultOptionValue, setStoreValue])
|
||||
|
||||
// Filter options based on current value for display
|
||||
const filteredOptions = useMemo(() => {
|
||||
// Always show all options when dropdown is not open
|
||||
if (!open) return evaluatedOptions
|
||||
|
||||
// If no value or value matches an exact option, show all options
|
||||
if (!value) return evaluatedOptions
|
||||
|
||||
const currentValue = value.toString()
|
||||
const exactMatch = evaluatedOptions.find(
|
||||
(opt) => getOptionValue(opt) === currentValue || getOptionLabel(opt) === currentValue
|
||||
)
|
||||
|
||||
// If current value exactly matches an option, show all options (user just selected it)
|
||||
if (exactMatch) return evaluatedOptions
|
||||
|
||||
// Otherwise filter based on current input
|
||||
return evaluatedOptions.filter((option) => {
|
||||
const label = getOptionLabel(option).toLowerCase()
|
||||
const optionValue = getOptionValue(option).toLowerCase()
|
||||
const search = currentValue.toLowerCase()
|
||||
return label.includes(search) || optionValue.includes(search)
|
||||
})
|
||||
}, [evaluatedOptions, value, open, getOptionLabel, getOptionValue])
|
||||
|
||||
// Event handlers
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (disabled) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
const newValue = e.target.value
|
||||
const newCursorPosition = e.target.selectionStart ?? 0
|
||||
|
||||
// Update store value immediately (allow free text)
|
||||
if (!isPreview) {
|
||||
setStoreValue(newValue)
|
||||
}
|
||||
|
||||
setCursorPosition(newCursorPosition)
|
||||
|
||||
// Check for environment variables trigger
|
||||
const envVarTrigger = checkEnvVarTrigger(newValue, newCursorPosition)
|
||||
setShowEnvVars(envVarTrigger.show)
|
||||
setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '')
|
||||
|
||||
// Check for tag trigger
|
||||
const tagTrigger = checkTagTrigger(newValue, newCursorPosition)
|
||||
setShowTags(tagTrigger.show)
|
||||
}
|
||||
|
||||
const handleSelect = (selectedValue: string) => {
|
||||
if (!isPreview && !disabled) {
|
||||
setStoreValue(selectedValue)
|
||||
}
|
||||
setOpen(false)
|
||||
inputRef.current?.blur()
|
||||
}
|
||||
|
||||
const handleDropdownClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (!disabled) {
|
||||
setOpen(!open)
|
||||
if (!open) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsFocused(true)
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
setIsFocused(false)
|
||||
setShowEnvVars(false)
|
||||
setShowTags(false)
|
||||
|
||||
// Delay closing to allow dropdown selection
|
||||
setTimeout(() => {
|
||||
const activeElement = document.activeElement
|
||||
if (!activeElement || !activeElement.closest('.absolute.top-full')) {
|
||||
setOpen(false)
|
||||
}
|
||||
}, 150)
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Escape') {
|
||||
setShowEnvVars(false)
|
||||
setShowTags(false)
|
||||
setOpen(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown' && !open) {
|
||||
setOpen(true)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
// Drag and drop handlers
|
||||
const handleDragOver = (e: React.DragEvent<HTMLInputElement>) => {
|
||||
if (config?.connectionDroppable === false) return
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLInputElement>) => {
|
||||
if (config?.connectionDroppable === false) return
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
const data = JSON.parse(e.dataTransfer.getData('application/json'))
|
||||
if (data.type !== 'connectionBlock') return
|
||||
|
||||
const dropPosition = inputRef.current?.selectionStart ?? value?.toString().length ?? 0
|
||||
const currentValue = value?.toString() ?? ''
|
||||
const newValue = `${currentValue.slice(0, dropPosition)}<${currentValue.slice(dropPosition)}`
|
||||
|
||||
inputRef.current?.focus()
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
setStoreValue(newValue)
|
||||
setCursorPosition(dropPosition + 1)
|
||||
setShowTags(true)
|
||||
|
||||
if (data.connectionData?.sourceBlockId) {
|
||||
setActiveSourceBlockId(data.connectionData.sourceBlockId)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.selectionStart = dropPosition + 1
|
||||
inputRef.current.selectionEnd = dropPosition + 1
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse drop data:', { error })
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll and paste handlers
|
||||
const handleScroll = (e: React.UIEvent<HTMLInputElement>) => {
|
||||
if (overlayRef.current) {
|
||||
overlayRef.current.scrollLeft = e.currentTarget.scrollLeft
|
||||
}
|
||||
}
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
||||
setTimeout(() => {
|
||||
if (inputRef.current && overlayRef.current) {
|
||||
overlayRef.current.scrollLeft = inputRef.current.scrollLeft
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// ReactFlow zoom handler
|
||||
const handleWheel = (e: React.WheelEvent<HTMLInputElement>) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const currentZoom = reactFlowInstance.getZoom()
|
||||
const { x: viewportX, y: viewportY } = reactFlowInstance.getViewport()
|
||||
|
||||
const delta = e.deltaY > 0 ? 1 : -1
|
||||
const zoomFactor = 0.96 ** delta
|
||||
const newZoom = Math.min(Math.max(currentZoom * zoomFactor, 0.1), 1)
|
||||
|
||||
const { x: pointerX, y: pointerY } = reactFlowInstance.screenToFlowPosition({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
})
|
||||
|
||||
const newViewportX = viewportX + (pointerX * currentZoom - pointerX * newZoom)
|
||||
const newViewportY = viewportY + (pointerY * currentZoom - pointerY * newZoom)
|
||||
|
||||
reactFlowInstance.setViewport(
|
||||
{ x: newViewportX, y: newViewportY, zoom: newZoom },
|
||||
{ duration: 0 }
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Environment variable and tag selection handler
|
||||
const handleEnvVarSelect = (newValue: string) => {
|
||||
if (!isPreview) {
|
||||
setStoreValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
if (inputRef.current && overlayRef.current) {
|
||||
overlayRef.current.scrollLeft = inputRef.current.scrollLeft
|
||||
}
|
||||
}, [value])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Element
|
||||
if (
|
||||
inputRef.current &&
|
||||
!inputRef.current.contains(target) &&
|
||||
!target.closest('[data-radix-popper-content-wrapper]') &&
|
||||
!target.closest('.absolute.top-full')
|
||||
) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (open) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}
|
||||
}, [open])
|
||||
|
||||
// Display value with formatting
|
||||
const displayValue = value?.toString() ?? ''
|
||||
|
||||
// Render component
|
||||
return (
|
||||
<div className='relative w-full'>
|
||||
<div className='relative'>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
className={cn(
|
||||
'allow-scroll w-full overflow-auto pr-10 text-transparent caret-foreground placeholder:text-muted-foreground/50',
|
||||
isConnecting &&
|
||||
config?.connectionDroppable !== false &&
|
||||
'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
value={displayValue}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onScroll={handleScroll}
|
||||
onPaste={handlePaste}
|
||||
onWheel={handleWheel}
|
||||
disabled={disabled}
|
||||
autoComplete='off'
|
||||
style={{ overflowX: 'auto' }}
|
||||
/>
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className='pointer-events-none absolute top-0 bottom-0 left-0 flex items-center bg-transparent pr-0 pl-3 text-sm'
|
||||
style={{ right: '42px' }}
|
||||
>
|
||||
<div className='w-full truncate text-foreground' style={{ scrollbarWidth: 'none' }}>
|
||||
{formatDisplayText(displayValue, true)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Chevron button */}
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-1 z-10 h-6 w-6 p-0 hover:bg-transparent'
|
||||
disabled={disabled}
|
||||
onMouseDown={handleDropdownClick}
|
||||
>
|
||||
<ChevronDown
|
||||
className={cn('h-4 w-4 opacity-50 transition-transform', open && 'rotate-180')}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Dropdown */}
|
||||
{open && (
|
||||
<div className='absolute top-full left-0 z-[100] mt-1 w-full min-w-[286px]'>
|
||||
<div className='allow-scroll fade-in-0 zoom-in-95 animate-in rounded-md border bg-popover text-popover-foreground shadow-lg'>
|
||||
<div
|
||||
className='allow-scroll max-h-48 overflow-y-auto p-1'
|
||||
style={{ scrollbarWidth: 'thin' }}
|
||||
>
|
||||
{filteredOptions.length === 0 ? (
|
||||
<div className='py-6 text-center text-muted-foreground text-sm'>
|
||||
No matching options found.
|
||||
</div>
|
||||
) : (
|
||||
filteredOptions.map((option) => {
|
||||
const optionValue = getOptionValue(option)
|
||||
const optionLabel = getOptionLabel(option)
|
||||
const OptionIcon =
|
||||
typeof option === 'object' && 'icon' in option
|
||||
? (option.icon as React.ComponentType<{ className?: string }>)
|
||||
: null
|
||||
const isSelected = displayValue === optionValue || displayValue === optionLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
key={optionValue}
|
||||
onClick={() => handleSelect(optionValue)}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
handleSelect(optionValue)
|
||||
}}
|
||||
className='relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground'
|
||||
>
|
||||
{OptionIcon && <OptionIcon className='mr-2 h-3 w-3 opacity-60' />}
|
||||
<span className='flex-1 truncate'>{optionLabel}</span>
|
||||
{isSelected && <Check className='ml-2 h-4 w-4 flex-shrink-0' />}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EnvVarDropdown
|
||||
visible={showEnvVars}
|
||||
onSelect={handleEnvVarSelect}
|
||||
searchTerm={searchTerm}
|
||||
inputValue={displayValue}
|
||||
cursorPosition={cursorPosition}
|
||||
onClose={() => {
|
||||
setShowEnvVars(false)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
/>
|
||||
<TagDropdown
|
||||
visible={showTags}
|
||||
onSelect={handleEnvVarSelect}
|
||||
blockId={blockId}
|
||||
activeSourceBlockId={activeSourceBlockId}
|
||||
inputValue={displayValue}
|
||||
cursorPosition={cursorPosition}
|
||||
onClose={() => {
|
||||
setShowTags(false)
|
||||
setActiveSourceBlockId(null)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { ChannelSelectorInput } from './components/channel-selector/channel-selector-input'
|
||||
import { CheckboxList } from './components/checkbox-list'
|
||||
import { Code } from './components/code'
|
||||
import { ComboBox } from './components/combobox'
|
||||
import { ConditionInput } from './components/condition-input'
|
||||
import { CredentialSelector } from './components/credential-selector/credential-selector'
|
||||
import { DateInput } from './components/date-input'
|
||||
@@ -114,6 +115,22 @@ export function SubBlock({
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
case 'combobox':
|
||||
return (
|
||||
<div onMouseDown={handleMouseDown}>
|
||||
<ComboBox
|
||||
blockId={blockId}
|
||||
subBlockId={config.id}
|
||||
options={config.options as string[]}
|
||||
placeholder={config.placeholder}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
disabled={isDisabled}
|
||||
isConnecting={isConnecting}
|
||||
config={config}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
case 'slider':
|
||||
return (
|
||||
<SliderInput
|
||||
|
||||
@@ -5,8 +5,10 @@ import {
|
||||
getAllModelProviders,
|
||||
getBaseModelProviders,
|
||||
getHostedModels,
|
||||
getProviderIcon,
|
||||
MODELS_TEMP_RANGE_0_1,
|
||||
MODELS_TEMP_RANGE_0_2,
|
||||
MODELS_WITH_TEMPERATURE_SUPPORT,
|
||||
providers,
|
||||
} from '@/providers/utils'
|
||||
import { useOllamaStore } from '@/stores/ollama/store'
|
||||
@@ -87,12 +89,30 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
|
||||
{
|
||||
id: 'model',
|
||||
title: 'Model',
|
||||
type: 'dropdown',
|
||||
type: 'combobox',
|
||||
layout: 'half',
|
||||
placeholder: 'Type or select a model...',
|
||||
options: () => {
|
||||
const ollamaModels = useOllamaStore.getState().models
|
||||
const baseModels = Object.keys(getBaseModelProviders())
|
||||
return [...baseModels, ...ollamaModels]
|
||||
const allModels = [...baseModels, ...ollamaModels]
|
||||
|
||||
return allModels.map((model) => {
|
||||
const icon = getProviderIcon(model)
|
||||
return { label: model, id: model, ...(icon && { icon }) }
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'temperature',
|
||||
title: 'Temperature',
|
||||
type: 'slider',
|
||||
layout: 'half',
|
||||
min: 0,
|
||||
max: 1,
|
||||
condition: {
|
||||
field: 'model',
|
||||
value: MODELS_TEMP_RANGE_0_1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -111,12 +131,20 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
|
||||
id: 'temperature',
|
||||
title: 'Temperature',
|
||||
type: 'slider',
|
||||
layout: 'half',
|
||||
layout: 'full',
|
||||
min: 0,
|
||||
max: 1,
|
||||
max: 2,
|
||||
condition: {
|
||||
field: 'model',
|
||||
value: MODELS_TEMP_RANGE_0_1,
|
||||
value: [...MODELS_TEMP_RANGE_0_1, ...MODELS_TEMP_RANGE_0_2],
|
||||
not: true,
|
||||
and: {
|
||||
field: 'model',
|
||||
value: Object.keys(getBaseModelProviders()).filter(
|
||||
(model) => !MODELS_WITH_TEMPERATURE_SUPPORT.includes(model)
|
||||
),
|
||||
not: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ export type SubBlockType =
|
||||
| 'short-input' // Single line input
|
||||
| 'long-input' // Multi-line input
|
||||
| 'dropdown' // Select menu
|
||||
| 'combobox' // Searchable dropdown with text input
|
||||
| 'slider' // Range input
|
||||
| 'table' // Grid layout
|
||||
| 'code' // Code editor
|
||||
@@ -92,8 +93,10 @@ export interface SubBlockConfig {
|
||||
mode?: 'basic' | 'advanced' | 'both' // Default is 'both' if not specified
|
||||
options?:
|
||||
| string[]
|
||||
| { label: string; id: string }[]
|
||||
| (() => string[] | { label: string; id: string }[])
|
||||
| { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }[]
|
||||
| (() =>
|
||||
| string[]
|
||||
| { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }[])
|
||||
min?: number
|
||||
max?: number
|
||||
columns?: string[]
|
||||
|
||||
@@ -2953,3 +2953,162 @@ export const ResponseIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const AnthropicIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
fillRule='evenodd'
|
||||
height='1em'
|
||||
viewBox='0 0 24 24'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<title>Anthropic</title>
|
||||
<path d='M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const AzureIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width='18'
|
||||
height='18'
|
||||
viewBox='0 0 18 18'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M5.33492 1.37491C5.44717 1.04229 5.75909 0.818359 6.11014 0.818359H11.25L5.91513 16.6255C5.80287 16.9581 5.49095 17.182 5.13991 17.182H1.13968C0.579936 17.182 0.185466 16.6325 0.364461 16.1022L5.33492 1.37491Z'
|
||||
fill='url(#paint0_linear_6102_134469)'
|
||||
/>
|
||||
<path
|
||||
d='M13.5517 11.4546H5.45126C5.1109 11.4546 4.94657 11.8715 5.19539 12.1037L10.4005 16.9618C10.552 17.1032 10.7515 17.1819 10.9587 17.1819H15.5453L13.5517 11.4546Z'
|
||||
fill='#0078D4'
|
||||
/>
|
||||
<path
|
||||
d='M6.11014 0.818359C5.75909 0.818359 5.44717 1.04229 5.33492 1.37491L0.364461 16.1022C0.185466 16.6325 0.579936 17.182 1.13968 17.182H5.13991C5.49095 17.182 5.80287 16.9581 5.91513 16.6255L6.90327 13.6976L10.4005 16.9617C10.552 17.1032 10.7515 17.1818 10.9588 17.1818H15.5454L13.5517 11.4545H7.66032L11.25 0.818359H6.11014Z'
|
||||
fill='url(#paint1_linear_6102_134469)'
|
||||
/>
|
||||
<path
|
||||
d='M12.665 1.37478C12.5528 1.04217 12.2409 0.818237 11.8898 0.818237H6.13629H6.16254C6.51358 0.818237 6.82551 1.04217 6.93776 1.37478L11.9082 16.1021C12.0872 16.6324 11.6927 17.1819 11.133 17.1819H11.0454H16.8603C17.42 17.1819 17.8145 16.6324 17.6355 16.1021L12.665 1.37478Z'
|
||||
fill='url(#paint2_linear_6102_134469)'
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='paint0_linear_6102_134469'
|
||||
x1='6.07512'
|
||||
y1='1.38476'
|
||||
x2='0.738178'
|
||||
y2='17.1514'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#114A8B' />
|
||||
<stop offset='1' stopColor='#0669BC' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint1_linear_6102_134469'
|
||||
x1='10.3402'
|
||||
y1='11.4564'
|
||||
x2='9.107'
|
||||
y2='11.8734'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stop-opacity='0.3' />
|
||||
<stop offset='0.0711768' stop-opacity='0.2' />
|
||||
<stop offset='0.321031' stop-opacity='0.1' />
|
||||
<stop offset='0.623053' stop-opacity='0.05' />
|
||||
<stop offset='1' stop-opacity='0' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint2_linear_6102_134469'
|
||||
x1='9.45858'
|
||||
y1='1.38467'
|
||||
x2='15.3168'
|
||||
y2='16.9926'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#3CCBF4' />
|
||||
<stop offset='1' stopColor='#2892DF' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const GroqIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
fillRule='evenodd'
|
||||
height='1em'
|
||||
viewBox='0 0 24 24'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<title>Groq</title>
|
||||
<path d='M12.036 2c-3.853-.035-7 3-7.036 6.781-.035 3.782 3.055 6.872 6.908 6.907h2.42v-2.566h-2.292c-2.407.028-4.38-1.866-4.408-4.23-.029-2.362 1.901-4.298 4.308-4.326h.1c2.407 0 4.358 1.915 4.365 4.278v6.305c0 2.342-1.944 4.25-4.323 4.279a4.375 4.375 0 01-3.033-1.252l-1.851 1.818A7 7 0 0012.029 22h.092c3.803-.056 6.858-3.083 6.879-6.816v-6.5C18.907 4.963 15.817 2 12.036 2z' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DeepseekIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg {...props} height='1em' viewBox='0 0 24 24' width='1em' xmlns='http://www.w3.org/2000/svg'>
|
||||
<title>DeepSeek</title>
|
||||
<path
|
||||
d='M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z'
|
||||
fill='#4D6BFE'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const GeminiIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg {...props} height='1em' viewBox='0 0 24 24' width='1em' xmlns='http://www.w3.org/2000/svg'>
|
||||
<title>Gemini</title>
|
||||
<defs>
|
||||
<linearGradient id='lobe-icons-gemini-fill' x1='0%' x2='68.73%' y1='100%' y2='30.395%'>
|
||||
<stop offset='0%' stopColor='#1C7DFF' />
|
||||
<stop offset='52.021%' stopColor='#1C69FF' />
|
||||
<stop offset='100%' stopColor='#F0DCD6' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d='M12 24A14.304 14.304 0 000 12 14.304 14.304 0 0012 0a14.305 14.305 0 0012 12 14.305 14.305 0 00-12 12'
|
||||
fill='url(#lobe-icons-gemini-fill)'
|
||||
fillRule='nonzero'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CerebrasIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
height='1em'
|
||||
viewBox='0 0 24 24'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<title>Cerebras</title>
|
||||
<path
|
||||
clipRule='evenodd'
|
||||
d='M14.121 2.701a9.299 9.299 0 000 18.598V22.7c-5.91 0-10.7-4.791-10.7-10.701S8.21 1.299 14.12 1.299V2.7zm4.752 3.677A7.353 7.353 0 109.42 17.643l-.901 1.074a8.754 8.754 0 01-1.08-12.334 8.755 8.755 0 0112.335-1.08l-.901 1.075zm-2.255.844a5.407 5.407 0 00-5.048 9.563l-.656 1.24a6.81 6.81 0 016.358-12.043l-.654 1.24zM14.12 8.539a3.46 3.46 0 100 6.922v1.402a4.863 4.863 0 010-9.726v1.402z'
|
||||
fill='#F15A29'
|
||||
fillRule='evenodd'
|
||||
/>
|
||||
<path d='M15.407 10.836a2.24 2.24 0 00-.51-.409 1.084 1.084 0 00-.544-.152c-.255 0-.483.047-.684.14a1.58 1.58 0 00-.84.912c-.074.203-.11.416-.11.631 0 .218.036.43.11.631a1.594 1.594 0 00.84.913c.2.093.43.14.684.14.216 0 .417-.046.602-.135.188-.09.35-.225.475-.392l.928 1.006c-.14.14-.3.261-.482.363a3.367 3.367 0 01-1.083.38c-.17.026-.317.04-.44.04a3.315 3.315 0 01-1.182-.21 2.825 2.825 0 01-.961-.597 2.816 2.816 0 01-.644-.929 2.987 2.987 0 01-.238-1.21c0-.444.08-.847.238-1.21.15-.35.368-.666.643-.929.278-.261.605-.464.962-.596a3.315 3.315 0 011.182-.21c.355 0 .712.068 1.072.204.361.138.685.36.944.649l-.962.97z' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const OllamaIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
fill='currentColor'
|
||||
fillRule='evenodd'
|
||||
height='1em'
|
||||
viewBox='0 0 24 24'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<title>Ollama</title>
|
||||
<path d='M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,19 @@
|
||||
* - Provider configurations
|
||||
*/
|
||||
|
||||
import type React from 'react'
|
||||
import {
|
||||
AnthropicIcon,
|
||||
AzureIcon,
|
||||
CerebrasIcon,
|
||||
DeepseekIcon,
|
||||
GeminiIcon,
|
||||
GroqIcon,
|
||||
OllamaIcon,
|
||||
OpenAIIcon,
|
||||
xAIIcon,
|
||||
} from '@/components/icons'
|
||||
|
||||
export interface ModelPricing {
|
||||
input: number // Per 1M tokens
|
||||
cachedInput?: number // Per 1M tokens (if supported)
|
||||
@@ -36,6 +49,7 @@ export interface ProviderDefinition {
|
||||
models: ModelDefinition[]
|
||||
defaultModel: string
|
||||
modelPatterns?: RegExp[]
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,6 +62,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "OpenAI's models",
|
||||
defaultModel: 'gpt-4o',
|
||||
modelPatterns: [/^gpt/, /^o1/],
|
||||
icon: OpenAIIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'gpt-4o',
|
||||
@@ -142,6 +157,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: 'Microsoft Azure OpenAI Service models',
|
||||
defaultModel: 'azure/gpt-4o',
|
||||
modelPatterns: [/^azure\//],
|
||||
icon: AzureIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'azure/gpt-4o',
|
||||
@@ -212,6 +228,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "Anthropic's Claude models",
|
||||
defaultModel: 'claude-sonnet-4-0',
|
||||
modelPatterns: [/^claude/],
|
||||
icon: AnthropicIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'claude-sonnet-4-0',
|
||||
@@ -275,6 +292,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "Google's Gemini models",
|
||||
defaultModel: 'gemini-2.5-pro',
|
||||
modelPatterns: [/^gemini/],
|
||||
icon: GeminiIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
@@ -310,6 +328,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "Deepseek's chat models",
|
||||
defaultModel: 'deepseek-chat',
|
||||
modelPatterns: [],
|
||||
icon: DeepseekIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'deepseek-chat',
|
||||
@@ -356,6 +375,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "xAI's Grok models",
|
||||
defaultModel: 'grok-3-latest',
|
||||
modelPatterns: [/^grok/],
|
||||
icon: xAIIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'grok-3-latest',
|
||||
@@ -391,6 +411,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: 'Cerebras Cloud LLMs',
|
||||
defaultModel: 'cerebras/llama-3.3-70b',
|
||||
modelPatterns: [/^cerebras/],
|
||||
icon: CerebrasIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'cerebras/llama-3.3-70b',
|
||||
@@ -412,6 +433,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: "Groq's LLM models with high-performance inference",
|
||||
defaultModel: 'groq/meta-llama/llama-4-scout-17b-16e-instruct',
|
||||
modelPatterns: [/^groq/],
|
||||
icon: GroqIcon,
|
||||
models: [
|
||||
{
|
||||
id: 'groq/meta-llama/llama-4-scout-17b-16e-instruct',
|
||||
@@ -457,6 +479,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
description: 'Local LLM models via Ollama',
|
||||
defaultModel: '',
|
||||
modelPatterns: [],
|
||||
icon: OllamaIcon,
|
||||
models: [], // Populated dynamically
|
||||
},
|
||||
}
|
||||
|
||||
@@ -178,6 +178,14 @@ export function getProviderModels(providerId: ProviderId): string[] {
|
||||
return getProviderModelsFromDefinitions(providerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider icon for a given model
|
||||
*/
|
||||
export function getProviderIcon(model: string): React.ComponentType<{ className?: string }> | null {
|
||||
const providerId = getProviderFromModel(model)
|
||||
return PROVIDER_DEFINITIONS[providerId]?.icon || null
|
||||
}
|
||||
|
||||
export function generateStructuredOutputInstructions(responseFormat: any): string {
|
||||
// Handle null/undefined input
|
||||
if (!responseFormat) return ''
|
||||
|
||||
@@ -70,9 +70,16 @@ export class Serializer {
|
||||
// For non-custom tools, we determine the tool ID
|
||||
const nonCustomTools = tools.filter((tool: any) => tool.type !== 'custom-tool')
|
||||
if (nonCustomTools.length > 0) {
|
||||
toolId = blockConfig.tools.config?.tool
|
||||
? blockConfig.tools.config.tool(params)
|
||||
: blockConfig.tools.access[0]
|
||||
try {
|
||||
toolId = blockConfig.tools.config?.tool
|
||||
? blockConfig.tools.config.tool(params)
|
||||
: blockConfig.tools.access[0]
|
||||
} catch (error) {
|
||||
logger.warn('Tool selection failed during serialization, using default:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
toolId = blockConfig.tools.access[0]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error processing tools in agent block:', { error })
|
||||
@@ -81,9 +88,16 @@ export class Serializer {
|
||||
}
|
||||
} else {
|
||||
// For non-agent blocks, get tool ID from block config as usual
|
||||
toolId = blockConfig.tools.config?.tool
|
||||
? blockConfig.tools.config.tool(params)
|
||||
: blockConfig.tools.access[0]
|
||||
try {
|
||||
toolId = blockConfig.tools.config?.tool
|
||||
? blockConfig.tools.config.tool(params)
|
||||
: blockConfig.tools.access[0]
|
||||
} catch (error) {
|
||||
logger.warn('Tool selection failed during serialization, using default:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
toolId = blockConfig.tools.access[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Get inputs from block config
|
||||
|
||||
Reference in New Issue
Block a user