mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(autofill): added settting to autofill api keys for tools and blocks
* feat(autofill): autofill api keys for tools once they are set once * added setting to settings for envvar autofill, removed from agent block since it depends on the model selected * add in api key autofill for agent blocks based on existing model provider mapping * add tooltips for general settings * resolved pr comments * reorganized files, removed unused imports
This commit is contained in:
+1
-22
@@ -2,29 +2,9 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import {
|
||||
Atom,
|
||||
BotMessageSquare,
|
||||
Brain,
|
||||
BrainCircuit,
|
||||
ChartBar,
|
||||
Code,
|
||||
Database,
|
||||
Eye,
|
||||
HelpCircle,
|
||||
Info,
|
||||
LineChart,
|
||||
MailIcon,
|
||||
NotebookPen,
|
||||
Store,
|
||||
TimerIcon,
|
||||
Trash,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { Eye, HelpCircle, Info, Trash, X } from 'lucide-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import { AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import {
|
||||
@@ -45,7 +25,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { AlertCircle, Copy, Rocket, Store, Terminal, X } from 'lucide-react'
|
||||
import { AlertCircle, Rocket, Store, Terminal, X } from 'lucide-react'
|
||||
import { ErrorIcon } from '@/components/icons'
|
||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Download, Image as ImageIcon } from 'lucide-react'
|
||||
import { Download } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
interface JSONViewProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { ChevronDown, Copy, MoreVertical, Plus, Trash } from 'lucide-react'
|
||||
import { highlight, languages } from 'prismjs'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { PanelRight } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/input'
|
||||
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 { BlockCategory } from '@/blocks/types'
|
||||
import { ToolbarBlock } from './components/toolbar-block/toolbar-block'
|
||||
import { ToolbarTabs } from './components/toolbar-tabs/toolbar-tabs'
|
||||
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { Plus, Trash } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Check, ChevronDown, ExternalLink, RefreshCw, Search, X } from 'lucide-react'
|
||||
import { Check, ChevronDown, ExternalLink, RefreshCw, X } from 'lucide-react'
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
|
||||
+1
-2
@@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { Info, Upload, X } from 'lucide-react'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
+2
-7
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Check, ChevronDown, RefreshCw, Search, X } from 'lucide-react'
|
||||
import { Check, ChevronDown, RefreshCw } from 'lucide-react'
|
||||
import { GmailIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -14,12 +14,7 @@ import {
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
Credential,
|
||||
getProviderIdFromServiceId,
|
||||
getServiceIdFromScopes,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { Credential, getProviderIdFromServiceId, getServiceIdFromScopes } from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '@/app/w/[id]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal'
|
||||
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ import { formatDateTime } from '@/lib/utils'
|
||||
import { getWorkflowWithValues } from '@/stores/workflows'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { workflowSync } from '@/stores/workflows/sync'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
import { ScheduleModal } from './components/schedule-modal'
|
||||
|
||||
+73
-12
@@ -1,15 +1,6 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { PencilIcon, PlusIcon, WrenchIcon, XIcon } from 'lucide-react'
|
||||
import { PlusIcon, WrenchIcon, XIcon } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import {
|
||||
Select,
|
||||
@@ -21,6 +12,8 @@ import {
|
||||
import { OAuthProvider } from '@/lib/oauth'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCustomToolsStore } from '@/stores/custom-tools/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useToolParamsStore } from '@/stores/tool-params/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { getAllBlocks } from '@/blocks'
|
||||
import { getTool } from '@/tools'
|
||||
@@ -125,6 +118,32 @@ const getOperationOptions = (blockType: string): { label: string; id: string }[]
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to initialize tool parameters
|
||||
const initializeToolParams = (
|
||||
toolId: string,
|
||||
params: ToolParam[],
|
||||
toolParamsStore: {
|
||||
resolveParamValue: (toolId: string, paramId: string, instanceId?: string) => string | undefined
|
||||
},
|
||||
isAutoFillEnabled: boolean,
|
||||
instanceId?: string
|
||||
): Record<string, string> => {
|
||||
const initialParams: Record<string, string> = {}
|
||||
|
||||
// Only auto-fill parameters if the setting is enabled
|
||||
if (isAutoFillEnabled) {
|
||||
// For each parameter, check if we have a stored/resolved value
|
||||
params.forEach((param) => {
|
||||
const resolvedValue = toolParamsStore.resolveParamValue(toolId, param.id, instanceId)
|
||||
if (resolvedValue) {
|
||||
initialParams[param.id] = resolvedValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return initialParams
|
||||
}
|
||||
|
||||
export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -133,6 +152,8 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const isWide = useWorkflowStore((state) => state.blocks[blockId]?.isWide)
|
||||
const customTools = useCustomToolsStore((state) => state.getAllTools())
|
||||
const toolParamsStore = useToolParamsStore()
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
|
||||
const toolBlocks = getAllBlocks().filter((block) => block.category === 'tools')
|
||||
|
||||
@@ -166,10 +187,22 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const operationOptions = hasOperations ? getOperationOptions(toolBlock.type) : []
|
||||
const defaultOperation = operationOptions.length > 0 ? operationOptions[0].id : undefined
|
||||
|
||||
const toolId = getToolIdFromBlock(toolBlock.type) || toolBlock.type
|
||||
const requiredParams = toolId ? getRequiredToolParams(toolId) : []
|
||||
|
||||
// Use the helper function to initialize parameters with blockId as instanceId
|
||||
const initialParams = initializeToolParams(
|
||||
toolId,
|
||||
requiredParams,
|
||||
toolParamsStore,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
blockId
|
||||
)
|
||||
|
||||
const newTool: StoredTool = {
|
||||
type: toolBlock.type,
|
||||
title: toolBlock.name,
|
||||
params: {},
|
||||
params: initialParams,
|
||||
isExpanded: true,
|
||||
operation: defaultOperation,
|
||||
}
|
||||
@@ -204,10 +237,25 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get custom tool parameters from schema
|
||||
const toolParams = getCustomToolParams(customTool.schema)
|
||||
|
||||
// Create tool ID for the custom tool
|
||||
const toolId = `custom-${customTool.schema.function.name}`
|
||||
|
||||
// Use the helper function to initialize parameters with blockId as instanceId
|
||||
const initialParams = initializeToolParams(
|
||||
toolId,
|
||||
toolParams,
|
||||
toolParamsStore,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
blockId
|
||||
)
|
||||
|
||||
const newTool: StoredTool = {
|
||||
type: 'custom-tool',
|
||||
title: customTool.title,
|
||||
params: {},
|
||||
params: initialParams,
|
||||
isExpanded: true,
|
||||
schema: customTool.schema,
|
||||
code: customTool.code || '',
|
||||
@@ -270,6 +318,19 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
}
|
||||
|
||||
const handleParamChange = (toolIndex: number, paramId: string, paramValue: string) => {
|
||||
// Store the value in the tool params store for future use
|
||||
const tool = selectedTools[toolIndex]
|
||||
const toolId =
|
||||
tool.type === 'custom-tool'
|
||||
? `custom-${tool.schema?.function?.name || 'tool'}`
|
||||
: getToolIdFromBlock(tool.type) || tool.type
|
||||
|
||||
// Only store non-empty values
|
||||
if (paramValue.trim()) {
|
||||
toolParamsStore.setParam(toolId, paramId, paramValue)
|
||||
}
|
||||
|
||||
// Update the value in the workflow
|
||||
setValue(
|
||||
selectedTools.map((tool, index) =>
|
||||
index === toolIndex
|
||||
|
||||
+184
-4
@@ -1,7 +1,130 @@
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { isEqual } from 'lodash'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useToolParamsStore } from '@/stores/tool-params/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { getProviderFromModel } from '@/providers/utils'
|
||||
|
||||
/**
|
||||
* Helper to handle API key auto-fill for agent blocks
|
||||
*/
|
||||
function handleAgentBlockApiKey(
|
||||
blockId: string,
|
||||
subBlockId: string,
|
||||
modelValue: string | null | undefined,
|
||||
storeValue: any
|
||||
) {
|
||||
// Only proceed if we have a model selected
|
||||
if (!modelValue) return
|
||||
|
||||
// Get the provider for this model
|
||||
const provider = getProviderFromModel(modelValue)
|
||||
|
||||
// Skip if we couldn't determine a provider
|
||||
if (!provider || provider === 'ollama') return
|
||||
|
||||
const toolParamsStore = useToolParamsStore.getState()
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
|
||||
// Try to get a saved API key for this provider
|
||||
const savedValue = toolParamsStore.resolveParamValue(provider, 'apiKey', blockId)
|
||||
|
||||
// If we have a valid API key, use it
|
||||
if (savedValue && savedValue !== '') {
|
||||
// Only update if different from current value
|
||||
if (savedValue !== storeValue) {
|
||||
subBlockStore.setValue(blockId, subBlockId, savedValue)
|
||||
}
|
||||
} else {
|
||||
// No API key found for this provider - ALWAYS clear the field
|
||||
subBlockStore.setValue(blockId, subBlockId, '')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to handle API key auto-fill for non-agent blocks
|
||||
*/
|
||||
function handleStandardBlockApiKey(
|
||||
blockId: string,
|
||||
subBlockId: string,
|
||||
blockType: string | undefined,
|
||||
storeValue: any
|
||||
) {
|
||||
if (!blockType) return
|
||||
|
||||
const toolParamsStore = useToolParamsStore.getState()
|
||||
|
||||
// Only auto-fill if the field is empty
|
||||
if (!storeValue || storeValue === '') {
|
||||
// Pass the blockId as instanceId to check if this specific instance has been cleared
|
||||
const savedValue = toolParamsStore.resolveParamValue(blockType, 'apiKey', blockId)
|
||||
|
||||
if (savedValue && savedValue !== '' && savedValue !== storeValue) {
|
||||
// Auto-fill the API key from the param store
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, savedValue)
|
||||
}
|
||||
}
|
||||
// Handle environment variable references
|
||||
else if (
|
||||
storeValue &&
|
||||
typeof storeValue === 'string' &&
|
||||
storeValue.startsWith('{{') &&
|
||||
storeValue.endsWith('}}')
|
||||
) {
|
||||
// Pass the blockId as instanceId
|
||||
const currentValue = toolParamsStore.resolveParamValue(blockType, 'apiKey', blockId)
|
||||
|
||||
if (currentValue !== storeValue) {
|
||||
// If we got a replacement or null, update the field
|
||||
if (currentValue) {
|
||||
// Replacement found - update to new reference
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, currentValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to store API key values
|
||||
*/
|
||||
function storeApiKeyValue(
|
||||
blockId: string,
|
||||
blockType: string | undefined,
|
||||
modelValue: string | null | undefined,
|
||||
newValue: any,
|
||||
storeValue: any
|
||||
) {
|
||||
if (!blockType) return
|
||||
|
||||
const toolParamsStore = useToolParamsStore.getState()
|
||||
|
||||
// Check if this is an empty value for an API key field that previously had a value
|
||||
// This indicates the user has deliberately cleared the field
|
||||
if (
|
||||
storeValue &&
|
||||
storeValue !== '' &&
|
||||
(newValue === null || newValue === '' || String(newValue).trim() === '')
|
||||
) {
|
||||
// Mark this specific instance as cleared so we don't auto-fill it
|
||||
toolParamsStore.markParamAsCleared(blockId, 'apiKey')
|
||||
return
|
||||
}
|
||||
|
||||
// Only store non-empty values
|
||||
if (!newValue || String(newValue).trim() === '') return
|
||||
|
||||
// For agent blocks, store the API key under the provider name
|
||||
if (blockType === 'agent' && modelValue) {
|
||||
const provider = getProviderFromModel(modelValue)
|
||||
if (provider && provider !== 'ollama') {
|
||||
toolParamsStore.setParam(provider, 'apiKey', String(newValue))
|
||||
}
|
||||
} else {
|
||||
// For other blocks, store under the block type
|
||||
toolParamsStore.setParam(blockType, 'apiKey', String(newValue))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook to get and set values for a sub-block in a workflow.
|
||||
@@ -17,10 +140,13 @@ export function useSubBlockValue<T = any>(
|
||||
subBlockId: string,
|
||||
triggerWorkflowUpdate: boolean = false
|
||||
): readonly [T | null, (value: T) => void] {
|
||||
// Get initial value from workflow store
|
||||
const blockType = useWorkflowStore(
|
||||
useCallback((state) => state.blocks?.[blockId]?.type, [blockId])
|
||||
)
|
||||
|
||||
const initialValue = useWorkflowStore(
|
||||
useCallback(
|
||||
(state) => state.blocks[blockId]?.subBlocks[subBlockId]?.value ?? null,
|
||||
(state) => state.blocks?.[blockId]?.subBlocks?.[subBlockId]?.value ?? null,
|
||||
[blockId, subBlockId]
|
||||
)
|
||||
)
|
||||
@@ -28,11 +154,60 @@ export function useSubBlockValue<T = any>(
|
||||
// Keep a ref to the latest value to prevent unnecessary re-renders
|
||||
const valueRef = useRef<T | null>(null)
|
||||
|
||||
// Get value from subblock store
|
||||
// Previous model reference for detecting model changes
|
||||
const prevModelRef = useRef<string | null>(null)
|
||||
|
||||
// Get value from subblock store - always call this hook unconditionally
|
||||
const storeValue = useSubBlockStore(
|
||||
useCallback((state) => state.getValue(blockId, subBlockId), [blockId, subBlockId])
|
||||
)
|
||||
|
||||
// Check if this is an API key field that could be auto-filled
|
||||
const isApiKey =
|
||||
subBlockId === 'apiKey' || (subBlockId?.toLowerCase().includes('apikey') ?? false)
|
||||
|
||||
// Check if auto-fill environment variables is enabled - always call this hook unconditionally
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
|
||||
// Always call this hook unconditionally - don't wrap it in a condition
|
||||
const modelSubBlockValue = useSubBlockStore((state) =>
|
||||
blockId ? state.getValue(blockId, 'model') : null
|
||||
)
|
||||
|
||||
// Compute the modelValue after the hook call
|
||||
const modelValue = blockType === 'agent' ? (modelSubBlockValue as string) : null
|
||||
|
||||
// When model changes for an agent block's API key, immediately check if we need to clear it
|
||||
useEffect(() => {
|
||||
// Only run for agent blocks with API key fields when model changes
|
||||
if (blockType === 'agent' && isApiKey && modelValue !== prevModelRef.current) {
|
||||
// Update the previous model reference
|
||||
prevModelRef.current = modelValue
|
||||
|
||||
// Handle API key autofill for the new model
|
||||
if (isAutoFillEnvVarsEnabled && modelValue) {
|
||||
handleAgentBlockApiKey(blockId, subBlockId, modelValue, storeValue)
|
||||
}
|
||||
}
|
||||
}, [blockId, subBlockId, blockType, isApiKey, modelValue, isAutoFillEnvVarsEnabled, storeValue])
|
||||
|
||||
// When component mounts, check for existing API key in toolParamsStore
|
||||
useEffect(() => {
|
||||
// Skip autofill if the feature is disabled in settings
|
||||
if (!isAutoFillEnvVarsEnabled) return
|
||||
|
||||
// Only process API key fields
|
||||
if (!isApiKey) return
|
||||
|
||||
// Handle agent blocks differently, they need to use the model to determine provider
|
||||
if (blockType === 'agent') {
|
||||
handleAgentBlockApiKey(blockId, subBlockId, modelValue, storeValue)
|
||||
} else {
|
||||
// Normal handling for non-agent blocks
|
||||
handleStandardBlockApiKey(blockId, subBlockId, blockType, storeValue)
|
||||
}
|
||||
}, [blockId, subBlockId, blockType, storeValue, isApiKey, isAutoFillEnvVarsEnabled, modelValue])
|
||||
|
||||
// Update the ref if the store value changes
|
||||
// This ensures we're always working with the latest value
|
||||
useEffect(() => {
|
||||
@@ -57,6 +232,11 @@ export function useSubBlockValue<T = any>(
|
||||
? JSON.parse(JSON.stringify(newValue))
|
||||
: newValue
|
||||
|
||||
// Handle API key storage for reuse across blocks
|
||||
if (isApiKey && blockType) {
|
||||
storeApiKeyValue(blockId, blockType, modelValue, newValue, storeValue)
|
||||
}
|
||||
|
||||
// Update the subblock store with the new value
|
||||
// The store's setValue method will now trigger the debounced sync automatically
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, valueCopy)
|
||||
@@ -66,7 +246,7 @@ export function useSubBlockValue<T = any>(
|
||||
}
|
||||
}
|
||||
},
|
||||
[blockId, subBlockId, triggerWorkflowUpdate]
|
||||
[blockId, subBlockId, blockType, isApiKey, storeValue, triggerWorkflowUpdate, modelValue]
|
||||
)
|
||||
|
||||
// Return the current value and setter
|
||||
|
||||
+1
-2
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertCircle, Check, Copy, KeySquare, Plus, Trash2 } from 'lucide-react'
|
||||
import { Check, Copy, KeySquare, Plus, Trash2 } from 'lucide-react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -27,7 +27,6 @@ import { Label } from '@/components/ui/label'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const logger = createLogger('ApiKeys')
|
||||
|
||||
|
||||
+118
-11
@@ -1,4 +1,5 @@
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Info } from 'lucide-react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -20,15 +21,26 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { resetAllStores } from '@/stores'
|
||||
|
||||
const TOOLTIPS = {
|
||||
theme: 'Change the visual theme of the application.',
|
||||
debugMode: 'Enable visual debugging information during execution.',
|
||||
autoConnect: 'Automatically connect nodes.',
|
||||
autoFillEnvVars: 'Automatically fill API keys.',
|
||||
resetData: 'Permanently delete all workflows, settings, and stored data.',
|
||||
}
|
||||
|
||||
export function General() {
|
||||
const router = useRouter()
|
||||
const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled)
|
||||
const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect)
|
||||
const isDebugModeEnabled = useGeneralStore((state) => state.isDebugModeEnabled)
|
||||
const toggleDebugMode = useGeneralStore((state) => state.toggleDebugMode)
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
const toggleAutoFillEnvVars = useGeneralStore((state) => state.toggleAutoFillEnvVars)
|
||||
const theme = useGeneralStore((state) => state.theme)
|
||||
const setTheme = useGeneralStore((state) => state.setTheme)
|
||||
|
||||
@@ -43,11 +55,28 @@ export function General() {
|
||||
<h2 className="text-lg font-medium mb-[22px]">General Settings</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Label htmlFor="theme-select" className="font-medium">
|
||||
Theme
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="theme-select" className="font-medium">
|
||||
Theme
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-500 p-1 h-7"
|
||||
aria-label="Learn more about theme settings"
|
||||
>
|
||||
<Info className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px] p-3">
|
||||
<p className="text-sm">{TOOLTIPS.theme}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select value={theme} onValueChange={setTheme}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger id="theme-select" className="w-[180px]">
|
||||
<SelectValue placeholder="Select theme" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -58,9 +87,26 @@ export function General() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Label htmlFor="debug-mode" className="font-medium">
|
||||
Debug mode
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="debug-mode" className="font-medium">
|
||||
Debug mode
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-500 p-1 h-7"
|
||||
aria-label="Learn more about debug mode"
|
||||
>
|
||||
<Info className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px] p-3">
|
||||
<p className="text-sm">{TOOLTIPS.debugMode}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Switch
|
||||
id="debug-mode"
|
||||
checked={isDebugModeEnabled}
|
||||
@@ -68,22 +114,83 @@ export function General() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Label htmlFor="auto-connect" className="font-medium">
|
||||
Auto-connect on drop
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="auto-connect" className="font-medium">
|
||||
Auto-connect on drop
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-500 p-1 h-7"
|
||||
aria-label="Learn more about auto-connect feature"
|
||||
>
|
||||
<Info className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px] p-3">
|
||||
<p className="text-sm">{TOOLTIPS.autoConnect}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Switch
|
||||
id="auto-connect"
|
||||
checked={isAutoConnectEnabled}
|
||||
onCheckedChange={toggleAutoConnect}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="auto-fill-env-vars" className="font-medium">
|
||||
Auto-fill environment variables
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-500 p-1 h-7"
|
||||
aria-label="Learn more about auto-fill environment variables"
|
||||
>
|
||||
<Info className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px] p-3">
|
||||
<p className="text-sm">{TOOLTIPS.autoFillEnvVars}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Switch
|
||||
id="auto-fill-env-vars"
|
||||
checked={isAutoFillEnvVarsEnabled}
|
||||
onCheckedChange={toggleAutoFillEnvVars}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Danger Zone Section */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Label className="font-medium">Reset all data</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">Reset all data</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-gray-500 p-1 h-7"
|
||||
aria-label="Learn more about resetting all data"
|
||||
>
|
||||
<Info className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px] p-3">
|
||||
<p className="text-sm">{TOOLTIPS.resetData}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" size="sm">
|
||||
|
||||
@@ -4,14 +4,13 @@ import { useMemo, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import clsx from 'clsx'
|
||||
import { HelpCircle, Plus, ScrollText, Settings, Store } from 'lucide-react'
|
||||
import { HelpCircle, Plus, ScrollText, 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/workflows/registry/store'
|
||||
import { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
import { HelpModal } from './components/help-modal/help-modal'
|
||||
import { NavItem } from './components/nav-item/nav-item'
|
||||
import { SettingsModal } from './components/settings-modal/settings-modal'
|
||||
import { WorkflowFolders } from './components/workflow-folders/workflow-folders'
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Button } from '@/components/ui/button'
|
||||
import { CopyButton } from '@/components/ui/copy-button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { WorkflowLog } from '@/app/w/logs/stores/types'
|
||||
import { formatDate } from '@/app/w/logs/utils/format-date'
|
||||
import { formatCost } from '@/providers/utils'
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
export interface CustomToolSchema {
|
||||
type: string
|
||||
function: {
|
||||
name: string
|
||||
description?: string
|
||||
parameters: {
|
||||
type: string
|
||||
properties: Record<string, any>
|
||||
required?: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomToolDefinition {
|
||||
id: string
|
||||
title: string
|
||||
schema: CustomToolSchema
|
||||
code: string
|
||||
createdAt: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
interface CustomToolsStore {
|
||||
tools: Record<string, CustomToolDefinition>
|
||||
addTool: (tool: Omit<CustomToolDefinition, 'id' | 'createdAt' | 'updatedAt'>) => string
|
||||
updateTool: (
|
||||
id: string,
|
||||
updates: Partial<Omit<CustomToolDefinition, 'id' | 'createdAt' | 'updatedAt'>>
|
||||
) => boolean
|
||||
removeTool: (id: string) => void
|
||||
getTool: (id: string) => CustomToolDefinition | undefined
|
||||
getAllTools: () => CustomToolDefinition[]
|
||||
}
|
||||
import { CustomToolsStore } from './types'
|
||||
|
||||
export const useCustomToolsStore = create<CustomToolsStore>()(
|
||||
devtools(
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface CustomToolSchema {
|
||||
type: string
|
||||
function: {
|
||||
name: string
|
||||
description?: string
|
||||
parameters: {
|
||||
type: string
|
||||
properties: Record<string, any>
|
||||
required?: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomToolDefinition {
|
||||
id: string
|
||||
title: string
|
||||
schema: CustomToolSchema
|
||||
code: string
|
||||
createdAt: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export interface CustomToolsStore {
|
||||
tools: Record<string, CustomToolDefinition>
|
||||
addTool: (tool: Omit<CustomToolDefinition, 'id' | 'createdAt' | 'updatedAt'>) => string
|
||||
updateTool: (
|
||||
id: string,
|
||||
updates: Partial<Omit<CustomToolDefinition, 'id' | 'createdAt' | 'updatedAt'>>
|
||||
) => boolean
|
||||
removeTool: (id: string) => void
|
||||
getTool: (id: string) => CustomToolDefinition | undefined
|
||||
getAllTools: () => CustomToolDefinition[]
|
||||
}
|
||||
@@ -1,34 +1,5 @@
|
||||
import { create } from 'zustand'
|
||||
import { Executor } from '@/executor'
|
||||
import { ExecutionContext } from '@/executor/types'
|
||||
|
||||
interface ExecutionState {
|
||||
activeBlockIds: Set<string>
|
||||
isExecuting: boolean
|
||||
isDebugging: boolean
|
||||
pendingBlocks: string[]
|
||||
executor: Executor | null
|
||||
debugContext: ExecutionContext | null
|
||||
}
|
||||
|
||||
interface ExecutionActions {
|
||||
setActiveBlocks: (blockIds: Set<string>) => void
|
||||
setIsExecuting: (isExecuting: boolean) => void
|
||||
setIsDebugging: (isDebugging: boolean) => void
|
||||
setPendingBlocks: (blockIds: string[]) => void
|
||||
setExecutor: (executor: Executor | null) => void
|
||||
setDebugContext: (context: ExecutionContext | null) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
const initialState: ExecutionState = {
|
||||
activeBlockIds: new Set(),
|
||||
isExecuting: false,
|
||||
isDebugging: false,
|
||||
pendingBlocks: [],
|
||||
executor: null,
|
||||
debugContext: null,
|
||||
}
|
||||
import { ExecutionActions, ExecutionState, initialState } from './types'
|
||||
|
||||
export const useExecutionStore = create<ExecutionState & ExecutionActions>()((set) => ({
|
||||
...initialState,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Executor } from '@/executor'
|
||||
import { ExecutionContext } from '@/executor/types'
|
||||
|
||||
export interface ExecutionState {
|
||||
activeBlockIds: Set<string>
|
||||
isExecuting: boolean
|
||||
isDebugging: boolean
|
||||
pendingBlocks: string[]
|
||||
executor: Executor | null
|
||||
debugContext: ExecutionContext | null
|
||||
}
|
||||
|
||||
export interface ExecutionActions {
|
||||
setActiveBlocks: (blockIds: Set<string>) => void
|
||||
setIsExecuting: (isExecuting: boolean) => void
|
||||
setIsDebugging: (isDebugging: boolean) => void
|
||||
setPendingBlocks: (blockIds: string[]) => void
|
||||
setExecutor: (executor: Executor | null) => void
|
||||
setDebugContext: (context: ExecutionContext | null) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const initialState: ExecutionState = {
|
||||
activeBlockIds: new Set(),
|
||||
isExecuting: false,
|
||||
isDebugging: false,
|
||||
pendingBlocks: [],
|
||||
executor: null,
|
||||
debugContext: null,
|
||||
}
|
||||
+6
-1
@@ -8,6 +8,7 @@ import { useConsoleStore } from './panel/console/store'
|
||||
import { useVariablesStore } from './panel/variables/store'
|
||||
import { useEnvironmentStore } from './settings/environment/store'
|
||||
import { getSyncManagers, initializeSyncManagers, resetSyncManagers } from './sync-registry'
|
||||
import { useToolParamsStore } from './tool-params/store'
|
||||
import {
|
||||
loadRegistry,
|
||||
loadSubblockValues,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
} from './workflows/persistence'
|
||||
import { useWorkflowRegistry } from './workflows/registry/store'
|
||||
import { useSubBlockStore } from './workflows/subblock/store'
|
||||
import { workflowSync } from './workflows/sync'
|
||||
import { useWorkflowStore } from './workflows/workflow/store'
|
||||
|
||||
const logger = createLogger('Stores')
|
||||
@@ -230,6 +232,7 @@ export {
|
||||
useChatStore,
|
||||
useCustomToolsStore,
|
||||
useVariablesStore,
|
||||
useToolParamsStore,
|
||||
}
|
||||
|
||||
// Helper function to reset all stores
|
||||
@@ -254,6 +257,7 @@ export const resetAllStores = () => {
|
||||
useChatStore.setState({ messages: [], isProcessing: false, error: null })
|
||||
useCustomToolsStore.setState({ tools: {} })
|
||||
useVariablesStore.getState().resetLoaded() // Reset variables store tracking
|
||||
useToolParamsStore.getState().clear()
|
||||
}
|
||||
|
||||
// Helper function to log all store states
|
||||
@@ -269,13 +273,14 @@ export const logAllStores = () => {
|
||||
customTools: useCustomToolsStore.getState(),
|
||||
subBlock: useSubBlockStore.getState(),
|
||||
variables: useVariablesStore.getState(),
|
||||
toolParams: useToolParamsStore.getState(),
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
// Re-export sync managers
|
||||
export { workflowSync } from './sync-registry'
|
||||
export { workflowSync } from './workflows/sync'
|
||||
|
||||
/**
|
||||
* Reinitialize the application after login
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { create } from 'zustand'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { updateOllamaProviderModels } from '@/providers/utils'
|
||||
import { OllamaStore } from './types'
|
||||
|
||||
const logger = createLogger('OllamaStore')
|
||||
|
||||
interface OllamaState {
|
||||
models: string[]
|
||||
setModels: (models: string[]) => void
|
||||
}
|
||||
|
||||
export const useOllamaStore = create<OllamaState>((set) => ({
|
||||
export const useOllamaStore = create<OllamaStore>((set) => ({
|
||||
models: [],
|
||||
setModels: (models) => {
|
||||
logger.info('Updating Ollama models', { models })
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface OllamaStore {
|
||||
models: string[]
|
||||
setModels: (models: string[]) => void
|
||||
}
|
||||
@@ -1,19 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
interface General {
|
||||
isAutoConnectEnabled: boolean
|
||||
isDebugModeEnabled: boolean
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
}
|
||||
|
||||
interface GeneralActions {
|
||||
toggleAutoConnect: () => void
|
||||
toggleDebugMode: () => void
|
||||
setTheme: (theme: 'system' | 'light' | 'dark') => void
|
||||
}
|
||||
|
||||
type GeneralStore = General & GeneralActions
|
||||
import { GeneralStore } from './types'
|
||||
|
||||
export const useGeneralStore = create<GeneralStore>()(
|
||||
devtools(
|
||||
@@ -21,12 +8,15 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
(set) => ({
|
||||
isAutoConnectEnabled: true,
|
||||
isDebugModeEnabled: false,
|
||||
isAutoFillEnvVarsEnabled: true,
|
||||
theme: 'system',
|
||||
toggleAutoConnect: () =>
|
||||
set((state) => ({
|
||||
isAutoConnectEnabled: !state.isAutoConnectEnabled,
|
||||
})),
|
||||
toggleDebugMode: () => set((state) => ({ isDebugModeEnabled: !state.isDebugModeEnabled })),
|
||||
toggleAutoFillEnvVars: () =>
|
||||
set((state) => ({ isAutoFillEnvVarsEnabled: !state.isAutoFillEnvVarsEnabled })),
|
||||
setTheme: (theme: 'system' | 'light' | 'dark') => set({ theme }),
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface General {
|
||||
isAutoConnectEnabled: boolean
|
||||
isDebugModeEnabled: boolean
|
||||
isAutoFillEnvVarsEnabled: boolean
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
}
|
||||
|
||||
export interface GeneralActions {
|
||||
toggleAutoConnect: () => void
|
||||
toggleDebugMode: () => void
|
||||
toggleAutoFillEnvVars: () => void
|
||||
setTheme: (theme: 'system' | 'light' | 'dark') => void
|
||||
}
|
||||
|
||||
export type GeneralStore = General & GeneralActions
|
||||
@@ -0,0 +1,179 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
import { useEnvironmentStore } from '../settings/environment/store'
|
||||
import { useGeneralStore } from '../settings/general/store'
|
||||
import { ToolParamsStore } from './types'
|
||||
import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './utils'
|
||||
|
||||
export const useToolParamsStore = create<ToolParamsStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
params: {},
|
||||
clearedParams: {},
|
||||
|
||||
setParam: (toolId: string, paramId: string, value: string) => {
|
||||
// If setting a non-empty value, we should remove it from clearedParams if it exists
|
||||
if (value.trim() !== '') {
|
||||
set((state) => {
|
||||
const newClearedParams = { ...state.clearedParams }
|
||||
if (newClearedParams[toolId] && newClearedParams[toolId][paramId]) {
|
||||
delete newClearedParams[toolId][paramId]
|
||||
// Clean up empty objects
|
||||
if (Object.keys(newClearedParams[toolId]).length === 0) {
|
||||
delete newClearedParams[toolId]
|
||||
}
|
||||
}
|
||||
|
||||
return { clearedParams: newClearedParams }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the parameter value
|
||||
set((state) => ({
|
||||
params: {
|
||||
...state.params,
|
||||
[toolId]: {
|
||||
...(state.params[toolId] || {}),
|
||||
[paramId]: value,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// For API keys, also store under a normalized tool name for cross-referencing
|
||||
// This allows both blocks and tools to share the same parameters
|
||||
if (paramId.toLowerCase() === 'apikey' || paramId.toLowerCase() === 'api_key') {
|
||||
// Extract the tool name part (e.g., "exa" from "exa-search")
|
||||
const baseTool = toolId.split('-')[0].toLowerCase()
|
||||
|
||||
if (baseTool !== toolId) {
|
||||
// Set the same value for the base tool to enable cross-referencing
|
||||
set((state) => ({
|
||||
params: {
|
||||
...state.params,
|
||||
[baseTool]: {
|
||||
...(state.params[baseTool] || {}),
|
||||
[paramId]: value,
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
markParamAsCleared: (instanceId: string, paramId: string) => {
|
||||
// Mark this specific instance as cleared
|
||||
set((state) => ({
|
||||
clearedParams: {
|
||||
...state.clearedParams,
|
||||
[instanceId]: {
|
||||
...(state.clearedParams[instanceId] || {}),
|
||||
[paramId]: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
isParamCleared: (instanceId: string, paramId: string) => {
|
||||
// Only check this specific instance
|
||||
return !!get().clearedParams[instanceId]?.[paramId]
|
||||
},
|
||||
|
||||
getParam: (toolId: string, paramId: string) => {
|
||||
// Check for direct match first
|
||||
const directValue = get().params[toolId]?.[paramId]
|
||||
if (directValue) return directValue
|
||||
|
||||
// Try base tool name if it's a compound tool ID
|
||||
if (toolId.includes('-')) {
|
||||
const baseTool = toolId.split('-')[0].toLowerCase()
|
||||
return get().params[baseTool]?.[paramId]
|
||||
}
|
||||
|
||||
// Try matching against any stored tool that starts with this ID
|
||||
// This helps match "exa" with "exa-search" etc.
|
||||
const matchingToolIds = Object.keys(get().params).filter(
|
||||
(id) => id.startsWith(toolId) || id.split('-')[0] === toolId
|
||||
)
|
||||
|
||||
for (const id of matchingToolIds) {
|
||||
const value = get().params[id]?.[paramId]
|
||||
if (value) return value
|
||||
}
|
||||
|
||||
return undefined
|
||||
},
|
||||
|
||||
getToolParams: (toolId: string) => {
|
||||
return get().params[toolId] || {}
|
||||
},
|
||||
|
||||
isEnvVarReference,
|
||||
|
||||
resolveParamValue: (toolId: string, paramId: string, instanceId?: string) => {
|
||||
// If this is a specific instance that has been deliberately cleared, don't auto-fill it
|
||||
if (instanceId && get().isParamCleared(instanceId, paramId)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Check if auto-fill environment variables is enabled
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore.getState().isAutoFillEnvVarsEnabled
|
||||
if (!isAutoFillEnvVarsEnabled) {
|
||||
// When auto-fill is disabled, we still return existing stored values, but don't
|
||||
// attempt to resolve environment variables or set new values
|
||||
return get().params[toolId]?.[paramId]
|
||||
}
|
||||
|
||||
const envStore = useEnvironmentStore.getState()
|
||||
|
||||
// First check params store for previously entered value
|
||||
const storedValue = get().getParam(toolId, paramId)
|
||||
|
||||
if (storedValue) {
|
||||
// If the stored value is an environment variable reference like {{EXA_API_KEY}}
|
||||
if (isEnvVarReference(storedValue)) {
|
||||
// Extract variable name from {{VAR_NAME}}
|
||||
const envVarName = extractEnvVarName(storedValue)
|
||||
if (!envVarName) return undefined
|
||||
|
||||
// Check if this environment variable still exists
|
||||
const envValue = envStore.getVariable(envVarName)
|
||||
|
||||
if (envValue) {
|
||||
// Environment variable exists, return the reference
|
||||
return storedValue
|
||||
} else {
|
||||
// Environment variable no longer exists
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// Return the stored value directly if it's not an env var reference
|
||||
return storedValue
|
||||
}
|
||||
|
||||
// If no stored value, try to guess based on parameter name
|
||||
// This handles cases where the user hasn't entered a value yet
|
||||
if (paramId.toLowerCase() === 'apikey' || paramId.toLowerCase() === 'api_key') {
|
||||
const matchingVar = findMatchingEnvVar(toolId)
|
||||
if (matchingVar) {
|
||||
const envReference = `{{${matchingVar}}}`
|
||||
get().setParam(toolId, paramId, envReference)
|
||||
return envReference
|
||||
}
|
||||
}
|
||||
|
||||
// No value found
|
||||
return undefined
|
||||
},
|
||||
|
||||
clear: () => {
|
||||
set({ params: {}, clearedParams: {} })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'tool-params-store',
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface ToolParamsStore {
|
||||
params: Record<string, Record<string, string>>
|
||||
clearedParams: Record<string, Record<string, boolean>>
|
||||
setParam: (toolId: string, paramId: string, value: string) => void
|
||||
markParamAsCleared: (instanceId: string, paramId: string) => void
|
||||
isParamCleared: (instanceId: string, paramId: string) => boolean
|
||||
getParam: (toolId: string, paramId: string) => string | undefined
|
||||
getToolParams: (toolId: string) => Record<string, string>
|
||||
isEnvVarReference: (value: string) => boolean
|
||||
resolveParamValue: (toolId: string, paramId: string, instanceId?: string) => string | undefined
|
||||
clear: () => void
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useEnvironmentStore } from '../settings/environment/store'
|
||||
|
||||
export const isEnvVarReference = (value: string): boolean => {
|
||||
// Check if the value looks like {{ENV_VAR}}
|
||||
return /^\{\{[a-zA-Z0-9_-]+\}\}$/.test(value)
|
||||
}
|
||||
|
||||
export const extractEnvVarName = (value: string): string | null => {
|
||||
if (!isEnvVarReference(value)) return null
|
||||
return value.slice(2, -2)
|
||||
}
|
||||
|
||||
export const generatePossibleEnvVarNames = (toolId: string): string[] => {
|
||||
// Extract base tool name if it's a compound ID
|
||||
const baseTool = toolId.includes('-') ? toolId.split('-')[0] : toolId
|
||||
const toolPrefix = baseTool.toUpperCase()
|
||||
|
||||
return [
|
||||
`${toolPrefix}_API_KEY`,
|
||||
`${toolPrefix.replace(/-/g, '_')}_API_KEY`,
|
||||
`${toolPrefix}_KEY`,
|
||||
`${toolPrefix}_TOKEN`,
|
||||
`${toolPrefix}`,
|
||||
]
|
||||
}
|
||||
|
||||
export const findMatchingEnvVar = (toolId: string): string | null => {
|
||||
const envStore = useEnvironmentStore.getState()
|
||||
const possibleVars = generatePossibleEnvVarNames(toolId)
|
||||
|
||||
for (const varName of possibleVars) {
|
||||
const envValue = envStore.getVariable(varName)
|
||||
if (envValue) {
|
||||
return varName
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { SubBlockConfig } from '@/blocks/types'
|
||||
import { loadSubblockValues, saveSubblockValues } from '../persistence'
|
||||
import { useWorkflowRegistry } from '../registry/store'
|
||||
import { workflowSync } from '../sync'
|
||||
import { SubBlockStore } from './types'
|
||||
|
||||
// Add debounce utility for syncing
|
||||
let syncDebounceTimer: NodeJS.Timeout | null = null
|
||||
@@ -20,18 +21,6 @@ const DEBOUNCE_DELAY = 500 // 500ms delay for sync
|
||||
* (e.g., inputFormat in starter block), the merge function will include it
|
||||
* in the synchronized state to ensure persistence
|
||||
*/
|
||||
interface SubBlockState {
|
||||
workflowValues: Record<string, Record<string, Record<string, any>>> // Store values per workflow ID
|
||||
}
|
||||
|
||||
interface SubBlockStore extends SubBlockState {
|
||||
setValue: (blockId: string, subBlockId: string, value: any) => void
|
||||
getValue: (blockId: string, subBlockId: string) => any
|
||||
clear: () => void
|
||||
initializeFromWorkflow: (workflowId: string, blocks: Record<string, any>) => void
|
||||
// Add debounced sync function
|
||||
syncWithDB: () => void
|
||||
}
|
||||
|
||||
export const useSubBlockStore = create<SubBlockStore>()(
|
||||
devtools(
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface SubBlockState {
|
||||
workflowValues: Record<string, Record<string, Record<string, any>>> // Store values per workflow ID
|
||||
}
|
||||
|
||||
export interface SubBlockStore extends SubBlockState {
|
||||
setValue: (blockId: string, subBlockId: string, value: any) => void
|
||||
getValue: (blockId: string, subBlockId: string) => any
|
||||
clear: () => void
|
||||
initializeFromWorkflow: (workflowId: string, blocks: Record<string, any>) => void
|
||||
// Add debounced sync function
|
||||
syncWithDB: () => void
|
||||
}
|
||||
Reference in New Issue
Block a user