mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(autofill-env-vars): simplify/remove logic to not cause useEffect overwrites (#726)
* fix(autofill-env-vars): simplify logic to not cause useEffect overwrites * remove useless comments
This commit is contained in:
committed by
GitHub
parent
d7a2c0747c
commit
7b73dfb462
@@ -12,7 +12,7 @@ const logger = createLogger('UserSettingsAPI')
|
||||
const SettingsSchema = z.object({
|
||||
theme: z.enum(['system', 'light', 'dark']).optional(),
|
||||
autoConnect: z.boolean().optional(),
|
||||
autoFillEnvVars: z.boolean().optional(),
|
||||
autoFillEnvVars: z.boolean().optional(), // DEPRECATED: kept for backwards compatibility
|
||||
autoPan: z.boolean().optional(),
|
||||
consoleExpandedByDefault: z.boolean().optional(),
|
||||
telemetryEnabled: z.boolean().optional(),
|
||||
@@ -31,7 +31,7 @@ const SettingsSchema = z.object({
|
||||
const defaultSettings = {
|
||||
theme: 'system',
|
||||
autoConnect: true,
|
||||
autoFillEnvVars: true,
|
||||
autoFillEnvVars: true, // DEPRECATED: kept for backwards compatibility, always true
|
||||
autoPan: true,
|
||||
consoleExpandedByDefault: true,
|
||||
telemetryEnabled: true,
|
||||
@@ -65,7 +65,7 @@ export async function GET() {
|
||||
data: {
|
||||
theme: userSettings.theme,
|
||||
autoConnect: userSettings.autoConnect,
|
||||
autoFillEnvVars: userSettings.autoFillEnvVars,
|
||||
autoFillEnvVars: userSettings.autoFillEnvVars, // DEPRECATED: kept for backwards compatibility
|
||||
autoPan: userSettings.autoPan,
|
||||
consoleExpandedByDefault: userSettings.consoleExpandedByDefault,
|
||||
telemetryEnabled: userSettings.telemetryEnabled,
|
||||
|
||||
@@ -17,7 +17,6 @@ import { cn } from '@/lib/utils'
|
||||
import { getAllBlocks } from '@/blocks'
|
||||
import { getProviderFromModel, supportsToolUsageControl } from '@/providers/utils'
|
||||
import { useCustomToolsStore } from '@/stores/custom-tools/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import {
|
||||
@@ -400,7 +399,6 @@ export function ToolInput({
|
||||
const isWide = useWorkflowStore((state) => state.blocks[blockId]?.isWide)
|
||||
const customTools = useCustomToolsStore((state) => state.getAllTools())
|
||||
const subBlockStore = useSubBlockStore()
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
|
||||
// Get the current model from the 'model' subblock
|
||||
const modelValue = useSubBlockStore.getState().getValue(blockId, 'model')
|
||||
@@ -507,26 +505,13 @@ export function ToolInput({
|
||||
return block.tools.access[0]
|
||||
}
|
||||
|
||||
// Initialize tool parameters with auto-fill if enabled
|
||||
// Initialize tool parameters - no autofill, just return empty params
|
||||
const initializeToolParams = (
|
||||
toolId: string,
|
||||
params: ToolParameterConfig[],
|
||||
instanceId?: string
|
||||
): Record<string, string> => {
|
||||
const initialParams: Record<string, string> = {}
|
||||
|
||||
// Only auto-fill parameters if the setting is enabled
|
||||
if (isAutoFillEnvVarsEnabled) {
|
||||
// For each parameter, check if we have a stored/resolved value
|
||||
params.forEach((param) => {
|
||||
const resolvedValue = subBlockStore.resolveToolParamValue(toolId, param.id, instanceId)
|
||||
if (resolvedValue) {
|
||||
initialParams[param.id] = resolvedValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return initialParams
|
||||
return {}
|
||||
}
|
||||
|
||||
const handleSelectTool = (toolBlock: (typeof toolBlocks)[0]) => {
|
||||
@@ -682,11 +667,6 @@ export function ToolInput({
|
||||
|
||||
const tool = selectedTools[toolIndex]
|
||||
|
||||
// Store the value in the tool params store for future use
|
||||
if (paramValue.trim()) {
|
||||
subBlockStore.setToolParam(tool.toolId, paramId, paramValue)
|
||||
}
|
||||
|
||||
// Update the value in the workflow
|
||||
setStoreValue(
|
||||
selectedTools.map((tool, index) =>
|
||||
|
||||
@@ -3,161 +3,12 @@ import { isEqual } from 'lodash'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { getProviderFromModel } from '@/providers/utils'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
const logger = createLogger('SubBlockValue')
|
||||
|
||||
// Helper function to dispatch collaborative subblock updates
|
||||
const dispatchSubblockUpdate = (blockId: string, subBlockId: string, value: any) => {
|
||||
const event = new CustomEvent('update-subblock-value', {
|
||||
detail: {
|
||||
blockId,
|
||||
subBlockId,
|
||||
value,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to handle API key auto-fill for provider-based blocks
|
||||
* Used for agent, router, evaluator, and any other blocks that use LLM providers
|
||||
*/
|
||||
function handleProviderBasedApiKey(
|
||||
blockId: string,
|
||||
subBlockId: string,
|
||||
modelValue: string | null | undefined,
|
||||
storeValue: any,
|
||||
isModelChange = false
|
||||
) {
|
||||
// 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 subBlockStore = useSubBlockStore.getState()
|
||||
const isAutoFillEnabled = useGeneralStore.getState().isAutoFillEnvVarsEnabled
|
||||
|
||||
// Try to get a saved API key for this provider (only if auto-fill is enabled)
|
||||
const savedValue = isAutoFillEnabled
|
||||
? subBlockStore.resolveToolParamValue(provider, 'apiKey', blockId)
|
||||
: null
|
||||
|
||||
// If we have a valid saved API key and auto-fill is enabled, use it
|
||||
if (savedValue && savedValue !== '' && isAutoFillEnabled) {
|
||||
// Only update if the current value is different to avoid unnecessary updates
|
||||
if (storeValue !== savedValue) {
|
||||
dispatchSubblockUpdate(blockId, subBlockId, savedValue)
|
||||
}
|
||||
} else if (isModelChange && (!storeValue || storeValue === '')) {
|
||||
// Only clear the field when switching models AND the field is already empty
|
||||
// Don't clear existing user-entered values on initial load
|
||||
dispatchSubblockUpdate(blockId, subBlockId, '')
|
||||
}
|
||||
// If no saved value and this is initial load, preserve existing value
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 = subBlockStore.resolveToolParamValue(blockType, 'apiKey', blockId)
|
||||
|
||||
if (savedValue && savedValue !== '' && savedValue !== storeValue) {
|
||||
// Auto-fill the API key from the param store
|
||||
dispatchSubblockUpdate(blockId, subBlockId, savedValue)
|
||||
}
|
||||
}
|
||||
// Handle environment variable references
|
||||
else if (
|
||||
storeValue &&
|
||||
typeof storeValue === 'string' &&
|
||||
storeValue.startsWith('{{') &&
|
||||
storeValue.endsWith('}}')
|
||||
) {
|
||||
// Pass the blockId as instanceId
|
||||
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
|
||||
dispatchSubblockUpdate(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 subBlockStore = useSubBlockStore.getState()
|
||||
|
||||
// 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
|
||||
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 provider-based blocks, store the API key under the provider name
|
||||
if (
|
||||
(blockType === 'agent' || blockType === 'router' || blockType === 'evaluator') &&
|
||||
modelValue
|
||||
) {
|
||||
const provider = getProviderFromModel(modelValue)
|
||||
if (provider && provider !== 'ollama') {
|
||||
subBlockStore.setToolParam(provider, 'apiKey', String(newValue))
|
||||
}
|
||||
} else {
|
||||
// For other blocks, store under the block type
|
||||
subBlockStore.setToolParam(blockType, 'apiKey', String(newValue))
|
||||
}
|
||||
}
|
||||
|
||||
interface UseSubBlockValueOptions {
|
||||
debounceMs?: number
|
||||
isStreaming?: boolean // Explicit streaming state
|
||||
@@ -199,9 +50,6 @@ export function useSubBlockValue<T = any>(
|
||||
// Keep a ref to the latest value to prevent unnecessary re-renders
|
||||
const valueRef = useRef<T | null>(null)
|
||||
|
||||
// Previous model reference for detecting model changes
|
||||
const prevModelRef = useRef<string | null>(null)
|
||||
|
||||
// Streaming refs
|
||||
const lastEmittedValueRef = useRef<T | null>(null)
|
||||
const streamingValueRef = useRef<T | null>(null)
|
||||
@@ -216,9 +64,6 @@ export function useSubBlockValue<T = any>(
|
||||
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
|
||||
@@ -276,6 +121,29 @@ export function useSubBlockValue<T = any>(
|
||||
},
|
||||
}))
|
||||
|
||||
// Handle model changes for provider-based blocks - clear API key when provider changes
|
||||
if (
|
||||
subBlockId === 'model' &&
|
||||
isProviderBasedBlock &&
|
||||
newValue &&
|
||||
typeof newValue === 'string'
|
||||
) {
|
||||
const currentApiKeyValue = useSubBlockStore.getState().getValue(blockId, 'apiKey')
|
||||
|
||||
// Only clear if there's currently an API key value
|
||||
if (currentApiKeyValue && currentApiKeyValue !== '') {
|
||||
const oldModelValue = storeValue as string
|
||||
const oldProvider = oldModelValue ? getProviderFromModel(oldModelValue) : null
|
||||
const newProvider = getProviderFromModel(newValue)
|
||||
|
||||
// Clear API key if provider changed
|
||||
if (oldProvider !== newProvider) {
|
||||
// Use collaborative function to clear the API key
|
||||
collaborativeSetSubblockValue(blockId, 'apiKey', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we're passing the actual value, not a reference that might change
|
||||
const valueCopy =
|
||||
newValue === null
|
||||
@@ -284,11 +152,6 @@ 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)
|
||||
}
|
||||
|
||||
// If streaming, just store the value without emitting
|
||||
if (isStreaming) {
|
||||
streamingValueRef.current = valueCopy
|
||||
@@ -320,61 +183,6 @@ export function useSubBlockValue<T = any>(
|
||||
valueRef.current = storeValue !== undefined ? storeValue : initialValue
|
||||
}, [])
|
||||
|
||||
// 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 different block types
|
||||
if (isProviderBasedBlock) {
|
||||
handleProviderBasedApiKey(blockId, subBlockId, modelValue, storeValue, false)
|
||||
} else {
|
||||
// Normal handling for non-provider blocks
|
||||
handleStandardBlockApiKey(blockId, subBlockId, blockType, storeValue)
|
||||
}
|
||||
}, [
|
||||
blockId,
|
||||
subBlockId,
|
||||
blockType,
|
||||
storeValue,
|
||||
isApiKey,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
modelValue,
|
||||
isProviderBasedBlock,
|
||||
])
|
||||
|
||||
// Monitor for model changes in provider-based blocks
|
||||
useEffect(() => {
|
||||
// Only process API key fields in model-based blocks
|
||||
if (!isApiKey || !isProviderBasedBlock) return
|
||||
|
||||
// Check if the model has changed
|
||||
if (modelValue !== prevModelRef.current) {
|
||||
// Update the previous model reference
|
||||
prevModelRef.current = modelValue
|
||||
|
||||
// Handle API key auto-fill for model changes
|
||||
if (modelValue) {
|
||||
handleProviderBasedApiKey(blockId, subBlockId, modelValue, storeValue, true)
|
||||
} else {
|
||||
// If no model is selected, clear the API key field
|
||||
dispatchSubblockUpdate(blockId, subBlockId, '')
|
||||
}
|
||||
}
|
||||
}, [
|
||||
blockId,
|
||||
subBlockId,
|
||||
blockType,
|
||||
isApiKey,
|
||||
modelValue,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
storeValue,
|
||||
isProviderBasedBlock,
|
||||
])
|
||||
|
||||
// Update the ref if the store value changes
|
||||
// This ensures we're always working with the latest value
|
||||
useEffect(() => {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
|
||||
const TOOLTIPS = {
|
||||
autoConnect: 'Automatically connect nodes.',
|
||||
autoFillEnvVars: 'Automatically fill API keys.',
|
||||
autoPan: 'Automatically pan to active blocks during workflow execution.',
|
||||
consoleExpandedByDefault:
|
||||
'Show console entries expanded by default. When disabled, entries will be collapsed by default.',
|
||||
@@ -30,13 +29,13 @@ export function General() {
|
||||
const error = useGeneralStore((state) => state.error)
|
||||
const theme = useGeneralStore((state) => state.theme)
|
||||
const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled)
|
||||
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
|
||||
|
||||
const isAutoPanEnabled = useGeneralStore((state) => state.isAutoPanEnabled)
|
||||
const isConsoleExpandedByDefault = useGeneralStore((state) => state.isConsoleExpandedByDefault)
|
||||
|
||||
// Loading states
|
||||
const isAutoConnectLoading = useGeneralStore((state) => state.isAutoConnectLoading)
|
||||
const isAutoFillEnvVarsLoading = useGeneralStore((state) => state.isAutoFillEnvVarsLoading)
|
||||
|
||||
const isAutoPanLoading = useGeneralStore((state) => state.isAutoPanLoading)
|
||||
const isConsoleExpandedByDefaultLoading = useGeneralStore(
|
||||
(state) => state.isConsoleExpandedByDefaultLoading
|
||||
@@ -45,7 +44,7 @@ export function General() {
|
||||
|
||||
const setTheme = useGeneralStore((state) => state.setTheme)
|
||||
const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect)
|
||||
const toggleAutoFillEnvVars = useGeneralStore((state) => state.toggleAutoFillEnvVars)
|
||||
|
||||
const toggleAutoPan = useGeneralStore((state) => state.toggleAutoPan)
|
||||
const toggleConsoleExpandedByDefault = useGeneralStore(
|
||||
(state) => state.toggleConsoleExpandedByDefault
|
||||
@@ -69,12 +68,6 @@ export function General() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAutoFillEnvVarsChange = async (checked: boolean) => {
|
||||
if (checked !== isAutoFillEnvVarsEnabled && !isAutoFillEnvVarsLoading) {
|
||||
await toggleAutoFillEnvVars()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAutoPanChange = async (checked: boolean) => {
|
||||
if (checked !== isAutoPanEnabled && !isAutoPanLoading) {
|
||||
await toggleAutoPan()
|
||||
@@ -167,35 +160,7 @@ export function General() {
|
||||
disabled={isLoading || isAutoConnectLoading}
|
||||
/>
|
||||
</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='h-7 p-1 text-gray-500'
|
||||
aria-label='Learn more about auto-fill environment variables'
|
||||
disabled={isLoading || isAutoFillEnvVarsLoading}
|
||||
>
|
||||
<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={handleAutoFillEnvVarsChange}
|
||||
disabled={isLoading || isAutoFillEnvVarsLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between py-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label htmlFor='console-expanded-by-default' className='font-medium'>
|
||||
|
||||
@@ -393,7 +393,7 @@ export const settings = pgTable('settings', {
|
||||
// General settings
|
||||
theme: text('theme').notNull().default('system'),
|
||||
autoConnect: boolean('auto_connect').notNull().default(true),
|
||||
autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true),
|
||||
autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true), // DEPRECATED: autofill feature removed
|
||||
autoPan: boolean('auto_pan').notNull().default(true),
|
||||
consoleExpandedByDefault: boolean('console_expanded_by_default').notNull().default(true),
|
||||
|
||||
|
||||
@@ -219,7 +219,6 @@ export const resetAllStores = () => {
|
||||
})
|
||||
useWorkflowStore.getState().clear()
|
||||
useSubBlockStore.getState().clear()
|
||||
useSubBlockStore.getState().clearToolParams()
|
||||
useEnvironmentStore.setState({
|
||||
variables: {},
|
||||
isLoading: false,
|
||||
|
||||
@@ -17,7 +17,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
|
||||
const store: General = {
|
||||
isAutoConnectEnabled: true,
|
||||
isAutoFillEnvVarsEnabled: true,
|
||||
isAutoPanEnabled: true,
|
||||
isConsoleExpandedByDefault: true,
|
||||
isDebugModeEnabled: false,
|
||||
@@ -28,7 +27,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
error: null,
|
||||
// Individual loading states
|
||||
isAutoConnectLoading: false,
|
||||
isAutoFillEnvVarsLoading: false,
|
||||
isAutoPanLoading: false,
|
||||
isConsoleExpandedByDefaultLoading: false,
|
||||
isThemeLoading: false,
|
||||
@@ -74,17 +72,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
)
|
||||
},
|
||||
|
||||
toggleAutoFillEnvVars: async () => {
|
||||
if (get().isAutoFillEnvVarsLoading) return
|
||||
const newValue = !get().isAutoFillEnvVarsEnabled
|
||||
await updateSettingOptimistic(
|
||||
'autoFillEnvVars',
|
||||
newValue,
|
||||
'isAutoFillEnvVarsLoading',
|
||||
'isAutoFillEnvVarsEnabled'
|
||||
)
|
||||
},
|
||||
|
||||
toggleAutoPan: async () => {
|
||||
if (get().isAutoPanLoading) return
|
||||
const newValue = !get().isAutoPanEnabled
|
||||
@@ -166,7 +153,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
|
||||
set({
|
||||
isAutoConnectEnabled: data.autoConnect,
|
||||
isAutoFillEnvVarsEnabled: data.autoFillEnvVars,
|
||||
isAutoPanEnabled: data.autoPan ?? true, // Default to true if undefined
|
||||
isConsoleExpandedByDefault: data.consoleExpandedByDefault ?? true, // Default to true if undefined
|
||||
theme: data.theme,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export interface General {
|
||||
isAutoConnectEnabled: boolean
|
||||
isAutoFillEnvVarsEnabled: boolean
|
||||
isAutoPanEnabled: boolean
|
||||
isConsoleExpandedByDefault: boolean
|
||||
isDebugModeEnabled: boolean
|
||||
@@ -11,7 +10,6 @@ export interface General {
|
||||
error: string | null
|
||||
// Individual loading states for optimistic updates
|
||||
isAutoConnectLoading: boolean
|
||||
isAutoFillEnvVarsLoading: boolean
|
||||
isAutoPanLoading: boolean
|
||||
isConsoleExpandedByDefaultLoading: boolean
|
||||
isThemeLoading: boolean
|
||||
@@ -20,7 +18,7 @@ export interface General {
|
||||
|
||||
export interface GeneralActions {
|
||||
toggleAutoConnect: () => Promise<void>
|
||||
toggleAutoFillEnvVars: () => Promise<void>
|
||||
|
||||
toggleAutoPan: () => Promise<void>
|
||||
toggleConsoleExpandedByDefault: () => Promise<void>
|
||||
toggleDebugMode: () => void
|
||||
@@ -36,7 +34,6 @@ export type GeneralStore = General & GeneralActions
|
||||
export type UserSettings = {
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
autoConnect: boolean
|
||||
autoFillEnvVars: boolean
|
||||
autoPan: boolean
|
||||
consoleExpandedByDefault: boolean
|
||||
telemetryEnabled: boolean
|
||||
|
||||
@@ -212,7 +212,6 @@ function resetWorkflowStores() {
|
||||
// Reset the subblock store
|
||||
useSubBlockStore.setState({
|
||||
workflowValues: {},
|
||||
toolParams: {},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useEnvironmentStore } from '../../settings/environment/store'
|
||||
import { useGeneralStore } from '../../settings/general/store'
|
||||
import { useWorkflowRegistry } from '../registry/store'
|
||||
// Removed workflowSync import - Socket.IO handles real-time sync
|
||||
import type { SubBlockStore } from './types'
|
||||
import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './utils'
|
||||
|
||||
// Removed debounce sync - Socket.IO handles real-time sync immediately
|
||||
|
||||
@@ -25,9 +22,6 @@ import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './util
|
||||
export const useSubBlockStore = create<SubBlockStore>()(
|
||||
devtools((set, get) => ({
|
||||
workflowValues: {},
|
||||
// Initialize tool params-related state
|
||||
toolParams: {},
|
||||
clearedParams: {},
|
||||
|
||||
setValue: (blockId: string, subBlockId: string, value: any) => {
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
@@ -93,179 +87,5 @@ export const useSubBlockStore = create<SubBlockStore>()(
|
||||
syncWithDB: () => {
|
||||
// No-op: Socket.IO handles real-time sync
|
||||
},
|
||||
|
||||
// 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]?.[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]?.[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
|
||||
}
|
||||
// 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: {} })
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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 {
|
||||
@@ -11,19 +9,4 @@ 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,4 +1,4 @@
|
||||
import { useEnvironmentStore } from '../../settings/environment/store'
|
||||
// DEPRECATED: useEnvironmentStore import removed as autofill functions were removed
|
||||
|
||||
/**
|
||||
* Checks if a value is an environment variable reference in the format {{ENV_VAR}}
|
||||
@@ -15,38 +15,3 @@ 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
|
||||
const toolPrefix = baseTool.toUpperCase()
|
||||
|
||||
return [
|
||||
`${toolPrefix}_API_KEY`,
|
||||
`${toolPrefix.replace(/-/g, '_')}_API_KEY`,
|
||||
`${toolPrefix}_KEY`,
|
||||
`${toolPrefix}_TOKEN`,
|
||||
`${toolPrefix}`,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a matching environment variable for a tool ID
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user