diff --git a/frontend/src/lib/components/InputTransformSchemaForm.svelte b/frontend/src/lib/components/InputTransformSchemaForm.svelte index d86e9aa734..5abf5a669f 100644 --- a/frontend/src/lib/components/InputTransformSchemaForm.svelte +++ b/frontend/src/lib/components/InputTransformSchemaForm.svelte @@ -3,7 +3,7 @@ import { VariableService, type InputTransform } from '$lib/gen' import { workspaceStore } from '$lib/stores' import { allTrue } from '$lib/utils' - import { createEventDispatcher, untrack } from 'svelte' + import { untrack } from 'svelte' import { Button } from './common' import StepInputsGen from './copilot/StepInputsGen.svelte' import type { PickableProperties } from './flows/previousResults' @@ -40,8 +40,6 @@ let inputCheck: { [id: string]: boolean } = $state({}) - const dispatch = createEventDispatcher() - $effect(() => { isValid = allTrue(inputCheck) ?? false }) @@ -117,10 +115,6 @@ {noDynamicToggle} {pickableProperties} {enableAi} - on:change={(e) => { - const { argName } = e.detail - dispatch('changeArg', { argName }) - }} /> {/if} diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index e6dda933f4..d64cf7028c 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -22,7 +22,7 @@ flowModuleSchemaMap: FlowModuleSchemaMap | undefined } = $props() - const { flowStore, flowStateStore, selectedId, currentEditor, flowInputsStore } = + const { flowStore, flowStateStore, selectedId, currentEditor } = getContext('FlowEditorContext') const { exprsToSet } = getContext('FlowCopilotContext') ?? {} @@ -185,10 +185,6 @@ flowModuleSchemaMap?.removeAtId(modules, id) } - if ($flowInputsStore) { - delete $flowInputsStore[id] - } - refreshStateStore(flowStore) flowModuleSchemaMap?.updateFlowInputsStore() diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index 71b68cb3a6..d525e58c2b 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -73,9 +73,9 @@ function getModelSpecificConfig( } : { model: modelProvider.model, - temperature: 0, - ...(tools && tools.length > 0 ? { tools } : {}) + temperature: 0 }), + ...(tools && tools.length > 0 ? { tools } : {}), max_completion_tokens: getModelMaxTokens(modelProvider.model) } } diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 9289c990ca..030d0792a1 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 5a536eb7ec..47a16c15fa 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -46,9 +46,6 @@ import { enterpriseLicense } from '$lib/stores' import { isCloudHosted } from '$lib/cloud' import { loadSchemaFromModule } from '../flowInfers' - import { computeFlowStepWarning, initFlowStepWarnings } from '../utils' - import { debounce } from '$lib/utils' - import { dfs } from '../dfs' import FlowModuleSkip from './FlowModuleSkip.svelte' import { type Job, JobService } from '$lib/gen' import { workspaceStore } from '$lib/stores' @@ -65,7 +62,6 @@ flowStore, pathStore, saveDraft, - flowInputsStore, customUi, executionCount } = getContext('FlowEditorContext') @@ -147,15 +143,6 @@ if (inputTransformSchemaForm) { inputTransformSchemaForm.setArgs(input_transforms) - if (!deepEqual(schema, $flowStateStore[flowModule.id]?.schema)) { - $flowInputsStore[flowModule?.id] = { - flowStepWarnings: await initFlowStepWarnings( - flowModule.value, - schema ?? {}, - dfs(flowStore.val.value.modules, (fm) => fm.id) - ) - } - } } else { if ( flowModule.value.type == 'rawscript' || @@ -202,27 +189,6 @@ } } - let debouncedWarning = debounce((argName: string) => { - if ($flowInputsStore) { - computeFlowStepWarning( - argName, - flowModule.value, - $flowInputsStore[flowModule.id].flowStepWarnings ?? {}, - $flowStateStore[$selectedId]?.schema, - dfs(flowStore.val?.value?.modules ?? [], (fm) => fm.id) ?? [] - ).then((flowStepWarnings) => { - $flowInputsStore[flowModule.id].flowStepWarnings = flowStepWarnings - }) - } - }, 100) - - function setFlowInput(argName: string) { - if ($flowInputsStore && flowModule.id && $flowInputsStore?.[flowModule.id] === undefined) { - $flowInputsStore[flowModule.id] = {} - } - debouncedWarning(argName) - } - async function getLastJob() { if ( !$flowStateStore || @@ -545,10 +511,6 @@ } extraLib={stepPropPicker.extraLib} {enableAi} - on:changeArg={(e) => { - const { argName } = e.detail - setFlowInput(argName) - }} /> diff --git a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte index 33b0e7f82e..fa3116631e 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte @@ -19,13 +19,10 @@ import FlowBranchesAllWrapper from './FlowBranchesAllWrapper.svelte' import FlowBranchesOneWrapper from './FlowBranchesOneWrapper.svelte' import FlowWhileLoop from './FlowWhileLoop.svelte' - import { initFlowStepWarnings } from '../utils' - import { dfs } from '../dfs' import type { TriggerContext } from '$lib/components/triggers' import { formatCron } from '$lib/utils' - const { selectedId, flowStateStore, flowInputsStore, flowStore } = - getContext('FlowEditorContext') + const { selectedId, flowStateStore } = getContext('FlowEditorContext') const { triggersState, triggersCount } = getContext('TriggerContext') @@ -106,16 +103,6 @@ flowModule = module $flowStateStore[module.id] = state - - if ($flowInputsStore) { - $flowInputsStore[module?.id] = { - flowStepWarnings: await initFlowStepWarnings( - module?.value, - $flowStateStore[module?.id]?.schema, - dfs(flowStore.val.value.modules, (fm) => fm.id) - ) - } - } } @@ -192,16 +179,6 @@ flowModule = module $flowStateStore[module.id] = state - - if ($flowInputsStore) { - $flowInputsStore[module.id] = { - flowStepWarnings: await initFlowStepWarnings( - module.value, - $flowStateStore[module.id].schema, - dfs(flowStore.val.value.modules, (fm) => fm.id) - ) - } - } }} failureModule={$selectedId === 'failure'} preprocessorModule={$selectedId === 'preprocessor'} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index e69101cb9a..5c30d25c60 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -14,7 +14,7 @@ insertNewPreprocessorModule } from '$lib/components/flows/flowStateUtils.svelte' import type { FlowModule, ScriptLang } from '$lib/gen' - import { emptyFlowModuleState, initFlowStepWarnings } from '../utils' + import { emptyFlowModuleState } from '../utils' import FlowSettingsItem from './FlowSettingsItem.svelte' import FlowConstantsItem from './FlowConstantsItem.svelte' @@ -242,30 +242,6 @@ change: void }>() - export async function updateFlowInputsStore() { - const keys = Object.keys(dependents ?? {}) - - for (const key of keys) { - const module = flowStore.val.value.modules.find((m) => m.id === key) - - if (!module) { - continue - } - - if (!$flowInputsStore) { - $flowInputsStore = {} - } - - $flowInputsStore[module.id] = { - flowStepWarnings: await initFlowStepWarnings( - module.value, - $flowStateStore?.[module.id]?.schema, - dfs(flowStore.val.value.modules, (fm) => fm.id) - ) - } - } - } - export function setExpr(module: FlowModule, expr: string) { if (module.value.type == 'forloopflow') { module.value.iterator = { type: 'javascript', expr } @@ -377,13 +353,8 @@ } else { selectNextId(id) removeAtId(flowStore.val.value.modules, id) - if ($flowInputsStore) { - delete $flowInputsStore[id] - } } refreshStateStore(flowStore) - - updateFlowInputsStore() } if (Object.keys(dependents).length > 0) { diff --git a/frontend/src/lib/components/flows/missingInputWarnings.ts b/frontend/src/lib/components/flows/missingInputWarnings.ts new file mode 100644 index 0000000000..80c934fa75 --- /dev/null +++ b/frontend/src/lib/components/flows/missingInputWarnings.ts @@ -0,0 +1,137 @@ +import type { Schema } from '$lib/common' +import type { InputTransform, OpenFlow } from '$lib/gen' +import { parseOutputs } from '$lib/infer' +import type { Writable } from 'svelte/store' +import type { FlowInput } from './types' +import type { StateStore } from '$lib/utils' +import type { FlowState } from './flowState' +import { dfs } from './dfs' + +function isInputFilled( + inputTransforms: Record, + key: string, + schema: Schema | undefined +): boolean { + const required = schema?.required?.includes(key) ?? false + + if (!required) { + return true + } + + if (inputTransforms.hasOwnProperty(key)) { + const transform = inputTransforms[key] + if ( + transform?.type === 'static' && + (transform?.value === undefined || transform?.value === '' || transform?.value === null) + ) { + return false + } else if ( + transform?.type === 'javascript' && + (transform?.expr === undefined || transform?.expr === '' || transform?.expr === null) + ) { + return false + } + } + + return true +} + +async function isConnectedToMissingModule( + argName: string, + input_transform: InputTransform, + moduleIds: string[] +): Promise { + const val: string = + input_transform.type === 'static' ? String(input_transform.value) : input_transform.expr + + try { + const outputs = await parseOutputs(val, true) + let error: string = '' + + outputs?.forEach(([componentId, id]) => { + if (componentId === 'results') { + if (!moduleIds.includes(id)) { + error += `Input ${argName} is connected to a missing module with id ${id}\n` + } + } + }) + + return error + } catch (e) { + return `Input ${argName} expression is invalid` + } +} + +type FlowStepWarnings = Record + +async function computeFlowStepWarnings( + input: { + input_transforms: Record + id: string + schema: Schema | undefined + }, + moduleIds: string[] = [] +) { + const messages: FlowStepWarnings = {} + const { input_transforms, schema } = input + const keys = Object.keys(input_transforms ?? {}) + const promises = keys.map(async (key) => { + if (!isInputFilled(input_transforms, key, schema)) { + messages[key] = { + message: `Input ${key} is required but not filled`, + type: 'warning' + } + } else { + const errorMessage = await isConnectedToMissingModule(key, input_transforms[key], moduleIds) + if (errorMessage) { + messages[key] = { + message: errorMessage, + type: 'error' + } + } + } + }) + await Promise.all(promises) + + return messages +} + +export async function computeMissingInputWarnings( + flowStore: StateStore, + flowState: FlowState, + flowInputsStore: Writable +) { + const inputs = dfs(flowStore.val.value.modules, (module) => { + if ( + module.value.type === 'script' || + module.value.type === 'rawscript' || + module.value.type === 'flow' + ) { + const schema = flowState[module.id]?.schema + return { + input_transforms: module.value.input_transforms, + id: module.id, + schema + } + } + return undefined + }).filter((x) => x !== undefined) + + const moduleIds = dfs(flowStore.val.value.modules, (module) => module.id) + + const promises = inputs.map(async (input) => { + const warnings = await computeFlowStepWarnings(input, moduleIds) + return [input.id, warnings] as const + }) + const warnings = Object.fromEntries(await Promise.all(promises)) + for (const key in warnings) { + flowInputsStore.update((fi) => { + return { + ...fi, + [key]: { + flowStepWarnings: warnings[key] + } + } + }) + } +} diff --git a/frontend/src/lib/components/flows/utils.ts b/frontend/src/lib/components/flows/utils.ts index b3270331ea..03103c384c 100644 --- a/frontend/src/lib/components/flows/utils.ts +++ b/frontend/src/lib/components/flows/utils.ts @@ -5,8 +5,7 @@ import { type InputTransform, type Job, type RestartedFrom, - type OpenFlow, - type FlowModuleValue + type OpenFlow } from '$lib/gen' import { workspaceStore } from '$lib/stores' import { cleanExpr, emptySchema } from '$lib/utils' @@ -15,8 +14,6 @@ import type { FlowModuleState } from './flowState' import { type PickableProperties, dfs } from './previousResults' import { NEVER_TESTED_THIS_FAR } from './models' import { sendUserToast } from '$lib/toast' -import type { Schema } from '$lib/common' -import { parseOutputs } from '$lib/infer' import type { ExtendedOpenFlow } from './types' function create_context_function_template(eval_string: string, context: Record) { @@ -188,135 +185,6 @@ export function emptyFlowModuleState(): FlowModuleState { } } -export function isInputFilled( - inputTransforms: Record, - key: string, - schema: Schema | undefined -): boolean { - const required = schema?.required?.includes(key) ?? false - - if (!required) { - return true - } - - if (inputTransforms.hasOwnProperty(key)) { - const transform = inputTransforms[key] - if ( - transform?.type === 'static' && - (transform?.value === undefined || transform?.value === '' || transform?.value === null) - ) { - return false - } else if ( - transform?.type === 'javascript' && - (transform?.expr === undefined || transform?.expr === '' || transform?.expr === null) - ) { - return false - } - } - - return true -} - -async function isConnectedToMissingModule( - argName: string, - flowModuleValue: FlowModuleValue, - moduleIds: string[] -): Promise { - const type = flowModuleValue.type - - if (type === 'rawscript' || type === 'script' || type === 'flow') { - const input = flowModuleValue?.input_transforms[argName] - const val: string = input.type === 'static' ? String(input.value) : input.expr - - try { - const outputs = await parseOutputs(val, true) - let error: string = '' - - outputs?.forEach(([componentId, id]) => { - if (componentId === 'results') { - if (!moduleIds.includes(id)) { - error += `Input ${argName} is connected to a missing module with id ${id}\n` - } - } - }) - - return error - } catch (e) { - return `Input ${argName} expression is invalid` - } - } - - return -} - -export async function computeFlowStepWarning( - argName: string, - flowModuleValue: FlowModuleValue, - messages: Record< - string, - { - message: string - type: 'error' | 'warning' - } - >, - schema: Schema | undefined, - moduleIds: string[] = [] -) { - if (messages[argName]) { - delete messages[argName] - } - - const type = flowModuleValue.type - if (type == 'rawscript' || type == 'script' || type == 'flow') { - if (!isInputFilled(flowModuleValue.input_transforms, argName, schema)) { - messages[argName] = { - message: `Input ${argName} is required but not filled`, - type: 'warning' - } - } - - const errorMessage = await isConnectedToMissingModule(argName, flowModuleValue, moduleIds) - - if (errorMessage) { - messages[argName] = { - message: errorMessage, - type: 'error' - } - } else { - if (messages[argName]?.type === 'error') { - delete messages[argName] - } - } - } - - return messages -} - -export async function initFlowStepWarnings( - flowModuleValue: FlowModuleValue, - schema: Schema | undefined, - moduleIds: string[] = [] -) { - const messages: Record< - string, - { - message: string - type: 'error' | 'warning' - } - > = {} - const type = flowModuleValue.type - - if (type == 'rawscript' || type == 'script' || type == 'flow') { - const keys = Object.keys(flowModuleValue.input_transforms ?? {}) - const promises = keys.map(async (key) => { - await computeFlowStepWarning(key, flowModuleValue, messages, schema, moduleIds) - }) - await Promise.all(promises) - } - - return messages -} - export function checkIfParentLoop( flowStore: ExtendedOpenFlow, modId: string