mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
feat(autofill): consolidated tool-params & sub-block store for one unified store that handles sub-block values (#237)
This commit is contained in:
@@ -13,7 +13,7 @@ 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 { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { getAllBlocks } from '@/blocks'
|
||||
import { getTool } from '@/tools'
|
||||
@@ -122,8 +122,12 @@ const getOperationOptions = (blockType: string): { label: string; id: string }[]
|
||||
const initializeToolParams = (
|
||||
toolId: string,
|
||||
params: ToolParam[],
|
||||
toolParamsStore: {
|
||||
resolveParamValue: (toolId: string, paramId: string, instanceId?: string) => string | undefined
|
||||
subBlockStore: {
|
||||
resolveToolParamValue: (
|
||||
toolId: string,
|
||||
paramId: string,
|
||||
instanceId?: string
|
||||
) => string | undefined
|
||||
},
|
||||
isAutoFillEnabled: boolean,
|
||||
instanceId?: string
|
||||
@@ -134,7 +138,7 @@ const initializeToolParams = (
|
||||
if (isAutoFillEnabled) {
|
||||
// For each parameter, check if we have a stored/resolved value
|
||||
params.forEach((param) => {
|
||||
const resolvedValue = toolParamsStore.resolveParamValue(toolId, param.id, instanceId)
|
||||
const resolvedValue = subBlockStore.resolveToolParamValue(toolId, param.id, instanceId)
|
||||
if (resolvedValue) {
|
||||
initialParams[param.id] = resolvedValue
|
||||
}
|
||||
@@ -152,7 +156,7 @@ 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 subBlockStore = useSubBlockStore()
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
|
||||
const toolBlocks = getAllBlocks().filter((block) => block.category === 'tools')
|
||||
@@ -194,7 +198,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const initialParams = initializeToolParams(
|
||||
toolId,
|
||||
requiredParams,
|
||||
toolParamsStore,
|
||||
subBlockStore,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
blockId
|
||||
)
|
||||
@@ -247,7 +251,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const initialParams = initializeToolParams(
|
||||
toolId,
|
||||
toolParams,
|
||||
toolParamsStore,
|
||||
subBlockStore,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
blockId
|
||||
)
|
||||
@@ -327,7 +331,7 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
|
||||
// Only store non-empty values
|
||||
if (paramValue.trim()) {
|
||||
toolParamsStore.setParam(toolId, paramId, paramValue)
|
||||
subBlockStore.setToolParam(toolId, paramId, paramValue)
|
||||
}
|
||||
|
||||
// Update the value in the workflow
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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'
|
||||
@@ -24,20 +23,19 @@ function handleAgentBlockApiKey(
|
||||
// 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)
|
||||
const savedValue = subBlockStore.resolveToolParamValue(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)
|
||||
}
|
||||
// Always update the value when switching models, even if it appears the same
|
||||
// This handles cases where the field shows masked values but needs to update
|
||||
subBlockStore.setValue(blockId, subBlockId, savedValue)
|
||||
} else {
|
||||
// No API key found for this provider - ALWAYS clear the field
|
||||
// Always clear the field when switching to a model with no API key
|
||||
// Don't wait for user interaction to clear it
|
||||
subBlockStore.setValue(blockId, subBlockId, '')
|
||||
}
|
||||
}
|
||||
@@ -53,16 +51,16 @@ function handleStandardBlockApiKey(
|
||||
) {
|
||||
if (!blockType) return
|
||||
|
||||
const toolParamsStore = useToolParamsStore.getState()
|
||||
const subBlockStore = useSubBlockStore.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)
|
||||
const savedValue = subBlockStore.resolveToolParamValue(blockType, 'apiKey', blockId)
|
||||
|
||||
if (savedValue && savedValue !== '' && savedValue !== storeValue) {
|
||||
// Auto-fill the API key from the param store
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, savedValue)
|
||||
subBlockStore.setValue(blockId, subBlockId, savedValue)
|
||||
}
|
||||
}
|
||||
// Handle environment variable references
|
||||
@@ -73,13 +71,13 @@ function handleStandardBlockApiKey(
|
||||
storeValue.endsWith('}}')
|
||||
) {
|
||||
// Pass the blockId as instanceId
|
||||
const currentValue = toolParamsStore.resolveParamValue(blockType, 'apiKey', blockId)
|
||||
const currentValue = subBlockStore.resolveToolParamValue(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)
|
||||
subBlockStore.setValue(blockId, subBlockId, currentValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,32 +95,39 @@ function storeApiKeyValue(
|
||||
) {
|
||||
if (!blockType) return
|
||||
|
||||
const toolParamsStore = useToolParamsStore.getState()
|
||||
const subBlockStore = useSubBlockStore.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
|
||||
// Check if this is user explicitly clearing a field that had a value
|
||||
// We only want to mark it as cleared if it's a user action, not an automatic
|
||||
// clearing from model switching
|
||||
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')
|
||||
subBlockStore.markParamAsCleared(blockId, 'apiKey')
|
||||
return
|
||||
}
|
||||
|
||||
// Only store non-empty values
|
||||
if (!newValue || String(newValue).trim() === '') return
|
||||
|
||||
// If user enters a value, we should clear any "cleared" flag
|
||||
// to ensure auto-fill will work in the future
|
||||
if (subBlockStore.isParamCleared(blockId, 'apiKey')) {
|
||||
subBlockStore.unmarkParamAsCleared(blockId, 'apiKey')
|
||||
}
|
||||
|
||||
// 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))
|
||||
subBlockStore.setToolParam(provider, 'apiKey', String(newValue))
|
||||
}
|
||||
} else {
|
||||
// For other blocks, store under the block type
|
||||
toolParamsStore.setParam(blockType, 'apiKey', String(newValue))
|
||||
subBlockStore.setToolParam(blockType, 'apiKey', String(newValue))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +189,27 @@ export function useSubBlockValue<T = any>(
|
||||
// Update the previous model reference
|
||||
prevModelRef.current = modelValue
|
||||
|
||||
// Handle API key autofill for the new model
|
||||
if (isAutoFillEnvVarsEnabled && modelValue) {
|
||||
handleAgentBlockApiKey(blockId, subBlockId, modelValue, storeValue)
|
||||
// For agent blocks, always clear the field if needed
|
||||
// But only fill with saved values if auto-fill is enabled
|
||||
if (modelValue) {
|
||||
const provider = getProviderFromModel(modelValue)
|
||||
|
||||
// Skip if we couldn't determine a provider
|
||||
if (!provider || provider === 'ollama') return
|
||||
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
|
||||
// Check if there's a saved value for this provider
|
||||
const savedValue = subBlockStore.resolveToolParamValue(provider, 'apiKey', blockId)
|
||||
|
||||
if (savedValue && savedValue !== '' && isAutoFillEnvVarsEnabled) {
|
||||
// Only auto-fill if the feature is enabled
|
||||
subBlockStore.setValue(blockId, subBlockId, savedValue)
|
||||
} else {
|
||||
// Always clear immediately when switching to a model with no saved key
|
||||
// or when auto-fill is disabled
|
||||
subBlockStore.setValue(blockId, subBlockId, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [blockId, subBlockId, blockType, isApiKey, modelValue, isAutoFillEnvVarsEnabled, storeValue])
|
||||
|
||||
@@ -8,7 +8,6 @@ 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,
|
||||
@@ -18,7 +17,6 @@ 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')
|
||||
@@ -232,7 +230,6 @@ export {
|
||||
useChatStore,
|
||||
useCustomToolsStore,
|
||||
useVariablesStore,
|
||||
useToolParamsStore,
|
||||
}
|
||||
|
||||
// Helper function to reset all stores
|
||||
@@ -246,6 +243,7 @@ export const resetAllStores = () => {
|
||||
})
|
||||
useWorkflowStore.getState().clear()
|
||||
useSubBlockStore.getState().clear()
|
||||
useSubBlockStore.getState().clearToolParams()
|
||||
useNotificationStore.setState({ notifications: [] })
|
||||
useEnvironmentStore.setState({
|
||||
variables: {},
|
||||
@@ -257,7 +255,6 @@ 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
|
||||
@@ -273,7 +270,6 @@ export const logAllStores = () => {
|
||||
customTools: useCustomToolsStore.getState(),
|
||||
subBlock: useSubBlockStore.getState(),
|
||||
variables: useVariablesStore.getState(),
|
||||
toolParams: useToolParamsStore.getState(),
|
||||
}
|
||||
|
||||
return state
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
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',
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
import { SubBlockConfig } from '@/blocks/types'
|
||||
import { useEnvironmentStore } from '../../settings/environment/store'
|
||||
import { useGeneralStore } from '../../settings/general/store'
|
||||
import { loadSubblockValues, saveSubblockValues } from '../persistence'
|
||||
import { useWorkflowRegistry } from '../registry/store'
|
||||
import { workflowSync } from '../sync'
|
||||
import { SubBlockStore } from './types'
|
||||
import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './utils'
|
||||
|
||||
// Add debounce utility for syncing
|
||||
let syncDebounceTimer: NodeJS.Timeout | null = null
|
||||
@@ -27,6 +30,9 @@ export const useSubBlockStore = create<SubBlockStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
workflowValues: {},
|
||||
// Initialize tool params-related state
|
||||
toolParams: {},
|
||||
clearedParams: {},
|
||||
|
||||
setValue: (blockId: string, subBlockId: string, value: any) => {
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
@@ -124,10 +130,189 @@ export const useSubBlockStore = create<SubBlockStore>()(
|
||||
workflowSync.sync()
|
||||
}, DEBOUNCE_DELAY)
|
||||
},
|
||||
|
||||
// Tool params related functionality
|
||||
setToolParam: (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) => ({
|
||||
toolParams: {
|
||||
...state.toolParams,
|
||||
[toolId]: {
|
||||
...(state.toolParams[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) => ({
|
||||
toolParams: {
|
||||
...state.toolParams,
|
||||
[baseTool]: {
|
||||
...(state.toolParams[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,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
unmarkParamAsCleared: (instanceId: string, paramId: string) => {
|
||||
// Remove the cleared flag for this parameter
|
||||
set((state) => {
|
||||
const newClearedParams = { ...state.clearedParams }
|
||||
if (newClearedParams[instanceId] && newClearedParams[instanceId][paramId]) {
|
||||
delete newClearedParams[instanceId][paramId]
|
||||
// Clean up empty objects
|
||||
if (Object.keys(newClearedParams[instanceId]).length === 0) {
|
||||
delete newClearedParams[instanceId]
|
||||
}
|
||||
}
|
||||
return { clearedParams: newClearedParams }
|
||||
})
|
||||
},
|
||||
|
||||
isParamCleared: (instanceId: string, paramId: string) => {
|
||||
// Only check this specific instance
|
||||
return !!get().clearedParams[instanceId]?.[paramId]
|
||||
},
|
||||
|
||||
getToolParam: (toolId: string, paramId: string) => {
|
||||
// Check for direct match first
|
||||
const directValue = get().toolParams[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().toolParams[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().toolParams).filter(
|
||||
(id) => id.startsWith(toolId) || id.split('-')[0] === toolId
|
||||
)
|
||||
|
||||
for (const id of matchingToolIds) {
|
||||
const value = get().toolParams[id]?.[paramId]
|
||||
if (value) return value
|
||||
}
|
||||
|
||||
return undefined
|
||||
},
|
||||
|
||||
getToolParams: (toolId: string) => {
|
||||
return get().toolParams[toolId] || {}
|
||||
},
|
||||
|
||||
isEnvVarReference,
|
||||
|
||||
resolveToolParamValue: (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().toolParams[toolId]?.[paramId]
|
||||
}
|
||||
|
||||
const envStore = useEnvironmentStore.getState()
|
||||
|
||||
// First check params store for previously entered value
|
||||
const storedValue = get().getToolParam(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().setToolParam(toolId, paramId, envReference)
|
||||
return envReference
|
||||
}
|
||||
}
|
||||
|
||||
// No value found
|
||||
return undefined
|
||||
},
|
||||
|
||||
clearToolParams: () => {
|
||||
set({ toolParams: {}, clearedParams: {} })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'subblock-store',
|
||||
partialize: (state) => ({ workflowValues: state.workflowValues }),
|
||||
partialize: (state) => ({
|
||||
workflowValues: state.workflowValues,
|
||||
toolParams: state.toolParams,
|
||||
clearedParams: state.clearedParams,
|
||||
}),
|
||||
// Use default storage
|
||||
storage: {
|
||||
getItem: (name) => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface SubBlockState {
|
||||
workflowValues: Record<string, Record<string, Record<string, any>>> // Store values per workflow ID
|
||||
toolParams: Record<string, Record<string, string>>
|
||||
clearedParams: Record<string, Record<string, boolean>>
|
||||
}
|
||||
|
||||
export interface SubBlockStore extends SubBlockState {
|
||||
@@ -9,4 +11,19 @@ export interface SubBlockStore extends SubBlockState {
|
||||
initializeFromWorkflow: (workflowId: string, blocks: Record<string, any>) => void
|
||||
// Add debounced sync function
|
||||
syncWithDB: () => void
|
||||
|
||||
// Tool params related functions
|
||||
setToolParam: (toolId: string, paramId: string, value: string) => void
|
||||
markParamAsCleared: (instanceId: string, paramId: string) => void
|
||||
unmarkParamAsCleared: (instanceId: string, paramId: string) => void
|
||||
isParamCleared: (instanceId: string, paramId: string) => boolean
|
||||
getToolParam: (toolId: string, paramId: string) => string | undefined
|
||||
getToolParams: (toolId: string) => Record<string, string>
|
||||
isEnvVarReference: (value: string) => boolean
|
||||
resolveToolParamValue: (
|
||||
toolId: string,
|
||||
paramId: string,
|
||||
instanceId?: string
|
||||
) => string | undefined
|
||||
clearToolParams: () => void
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { useEnvironmentStore } from '../settings/environment/store'
|
||||
import { useEnvironmentStore } from '../../settings/environment/store'
|
||||
|
||||
/**
|
||||
* Checks if a value is an environment variable reference in the format {{ENV_VAR}}
|
||||
*/
|
||||
export const isEnvVarReference = (value: string): boolean => {
|
||||
// Check if the value looks like {{ENV_VAR}}
|
||||
return /^\{\{[a-zA-Z0-9_-]+\}\}$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the environment variable name from a reference like {{ENV_VAR}}
|
||||
*/
|
||||
export const extractEnvVarName = (value: string): string | null => {
|
||||
if (!isEnvVarReference(value)) return null
|
||||
return value.slice(2, -2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates possible environment variable names for a tool ID
|
||||
* For example, "exa-search" could map to "EXA_API_KEY", "EXA_KEY", etc.
|
||||
*/
|
||||
export const generatePossibleEnvVarNames = (toolId: string): string[] => {
|
||||
// Extract base tool name if it's a compound ID
|
||||
const baseTool = toolId.includes('-') ? toolId.split('-')[0] : toolId
|
||||
@@ -24,6 +34,9 @@ export const generatePossibleEnvVarNames = (toolId: string): string[] => {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a matching environment variable for a tool ID
|
||||
*/
|
||||
export const findMatchingEnvVar = (toolId: string): string | null => {
|
||||
const envStore = useEnvironmentStore.getState()
|
||||
const possibleVars = generatePossibleEnvVarNames(toolId)
|
||||
Reference in New Issue
Block a user