Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4af6e741e4 | ||
|
|
16611c51de |
@@ -29,7 +29,9 @@
|
||||
try {
|
||||
const parsed = JSON.parse(obj[key])
|
||||
obj[key] = parsed
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse JSON:', e)
|
||||
}
|
||||
}
|
||||
return JSON.stringify(obj, null, 2)
|
||||
} catch {
|
||||
|
||||
@@ -185,8 +185,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(newModule).forEach((k) => delete newModule[k])
|
||||
Object.assign(newModule, $state.snapshot(oldModule))
|
||||
newModule.value = oldModule.value
|
||||
}
|
||||
|
||||
refreshStateStore(flowStore)
|
||||
@@ -499,75 +498,6 @@
|
||||
refreshStateStore(flowStore)
|
||||
}
|
||||
|
||||
setModuleStatus(id, 'modified')
|
||||
},
|
||||
setForLoopOptions: async (id, opts) => {
|
||||
const module = getModule(id)
|
||||
if (!module) {
|
||||
throw new Error('Module not found')
|
||||
}
|
||||
if (module.value.type !== 'forloopflow') {
|
||||
throw new Error('Module is not a forloopflow')
|
||||
}
|
||||
|
||||
// Apply skip_failures if provided
|
||||
if (typeof opts.skip_failures === 'boolean') {
|
||||
module.value.skip_failures = opts.skip_failures
|
||||
}
|
||||
|
||||
// Apply parallel if provided
|
||||
if (typeof opts.parallel === 'boolean') {
|
||||
module.value.parallel = opts.parallel
|
||||
}
|
||||
|
||||
// Handle parallelism
|
||||
if (opts.parallel === false) {
|
||||
// If parallel is disabled, clear parallelism
|
||||
module.value.parallelism = undefined
|
||||
} else if (opts.parallelism !== undefined) {
|
||||
if (opts.parallelism === null) {
|
||||
// Explicitly clear parallelism
|
||||
module.value.parallelism = undefined
|
||||
} else if (module.value.parallel || opts.parallel === true) {
|
||||
// Only set parallelism if parallel is enabled
|
||||
const n = Math.max(1, Math.floor(Math.abs(opts.parallelism)))
|
||||
module.value.parallelism = n
|
||||
}
|
||||
}
|
||||
|
||||
refreshStateStore(flowStore)
|
||||
setModuleStatus(id, 'modified')
|
||||
},
|
||||
setModuleControlOptions: async (id, opts) => {
|
||||
const module = getModule(id)
|
||||
if (!module) {
|
||||
throw new Error('Module not found')
|
||||
}
|
||||
|
||||
// Handle stop_after_if
|
||||
if (typeof opts.stop_after_if === 'boolean') {
|
||||
if (opts.stop_after_if === false) {
|
||||
module.stop_after_if = undefined
|
||||
} else {
|
||||
module.stop_after_if = {
|
||||
expr: opts.stop_after_if_expr ?? '',
|
||||
skip_if_stopped: opts.stop_after_if
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle skip_if
|
||||
if (typeof opts.skip_if === 'boolean') {
|
||||
if (opts.skip_if === false) {
|
||||
module.skip_if = undefined
|
||||
} else {
|
||||
module.skip_if = {
|
||||
expr: opts.skip_if_expr ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshStateStore(flowStore)
|
||||
setModuleStatus(id, 'modified')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ScriptService, type FlowModule, type RawScript, type Script, JobService } from '$lib/gen'
|
||||
import { emitUiIntent } from './uiIntents'
|
||||
import type {
|
||||
ChatCompletionSystemMessageParam,
|
||||
ChatCompletionUserMessageParam
|
||||
@@ -58,23 +57,6 @@ export interface FlowAIChatHelpers {
|
||||
addBranch: (id: string) => Promise<void>
|
||||
removeBranch: (id: string, branchIndex: number) => Promise<void>
|
||||
setForLoopIteratorExpression: (id: string, expression: string) => Promise<void>
|
||||
setForLoopOptions: (
|
||||
id: string,
|
||||
opts: {
|
||||
skip_failures?: boolean | null
|
||||
parallel?: boolean | null
|
||||
parallelism?: number | null
|
||||
}
|
||||
) => Promise<void>
|
||||
setModuleControlOptions: (
|
||||
id: string,
|
||||
opts: {
|
||||
stop_after_if?: boolean | null
|
||||
stop_after_if_expr?: string | null
|
||||
skip_if?: boolean | null
|
||||
skip_if_expr?: string | null
|
||||
}
|
||||
) => Promise<void>
|
||||
setCode: (id: string, code: string) => Promise<void>
|
||||
}
|
||||
|
||||
@@ -214,67 +196,6 @@ const setForLoopIteratorExpressionToolDef = createToolDef(
|
||||
'Set the iterator JavaScript expression for the given forloop step'
|
||||
)
|
||||
|
||||
const setForLoopOptionsSchema = z.object({
|
||||
id: z.string().describe('The id of the forloop step to configure'),
|
||||
skip_failures: z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Whether to skip failures in the loop (null to not change)'),
|
||||
parallel: z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Whether to run iterations in parallel (null to not change)'),
|
||||
parallelism: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Maximum number of parallel iterations (null to not change)')
|
||||
})
|
||||
|
||||
const setForLoopOptionsToolDef = createToolDef(
|
||||
setForLoopOptionsSchema,
|
||||
'set_forloop_options',
|
||||
'Set advanced options for a forloop step: skip_failures, parallel, and parallelism'
|
||||
)
|
||||
|
||||
const setModuleControlOptionsSchema = z.object({
|
||||
id: z.string().describe('The id of the module to configure'),
|
||||
stop_after_if: z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Early stop condition (true to set, false to clear, null to not change)'),
|
||||
stop_after_if_expr: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
'JavaScript expression for early stop condition. Can use `flow_input` or `result`. `result` is the result of the step. `results.<step_id>` is not supported, do not use it. Only used if stop_after_if is true. Example: `flow_input.x > 10` or `result === "failure"`'
|
||||
),
|
||||
skip_if: z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Skip condition (true to set, false to clear, null to not change)'),
|
||||
skip_if_expr: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
'JavaScript expression for skip condition. Can use `flow_input` or `results.<step_id>`. Only used if skip_if is true. Example: `flow_input.x > 10` or `results.a === "failure"`'
|
||||
)
|
||||
})
|
||||
|
||||
const setModuleControlOptionsToolDef = createToolDef(
|
||||
setModuleControlOptionsSchema,
|
||||
'set_module_control_options',
|
||||
'Set control options for any module: stop_after_if (early stop) and skip_if (conditional skip)'
|
||||
)
|
||||
|
||||
const setBranchPredicateSchema = z.object({
|
||||
id: z.string().describe('The id of the branchone step to set the predicates for'),
|
||||
branchIndex: z
|
||||
@@ -637,68 +558,6 @@ export const flowTools: Tool<FlowAIChatHelpers>[] = [
|
||||
return `Forloop '${parsedArgs.id}' iterator expression set`
|
||||
}
|
||||
},
|
||||
{
|
||||
def: setForLoopOptionsToolDef,
|
||||
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
||||
const parsedArgs = setForLoopOptionsSchema.parse(args)
|
||||
await helpers.setForLoopOptions(parsedArgs.id, {
|
||||
skip_failures: parsedArgs.skip_failures,
|
||||
parallel: parsedArgs.parallel,
|
||||
parallelism: parsedArgs.parallelism
|
||||
})
|
||||
helpers.selectStep(parsedArgs.id)
|
||||
|
||||
const message = `Set forloop '${parsedArgs.id}' options`
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: message
|
||||
})
|
||||
return `${message}: ${JSON.stringify(parsedArgs)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
def: setModuleControlOptionsToolDef,
|
||||
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
||||
const parsedArgs = setModuleControlOptionsSchema.parse(args)
|
||||
await helpers.setModuleControlOptions(parsedArgs.id, {
|
||||
stop_after_if: parsedArgs.stop_after_if,
|
||||
stop_after_if_expr: parsedArgs.stop_after_if_expr,
|
||||
skip_if: parsedArgs.skip_if,
|
||||
skip_if_expr: parsedArgs.skip_if_expr
|
||||
})
|
||||
helpers.selectStep(parsedArgs.id)
|
||||
|
||||
// Emit UI intent to show early-stop tab when stop_after_if is configured
|
||||
const modules = helpers.getModules()
|
||||
const module = findModuleById(modules, parsedArgs.id)
|
||||
if (!module) {
|
||||
throw new Error(`Module with id '${parsedArgs.id}' not found in flow.`)
|
||||
}
|
||||
const moduleType = module?.value.type
|
||||
const hasSpecificComponents = ['forloopflow', 'whileloopflow', 'branchall', 'branchone']
|
||||
const prefix = hasSpecificComponents.includes(moduleType) ? `${moduleType}` : 'flow'
|
||||
if (typeof parsedArgs.stop_after_if === 'boolean') {
|
||||
emitUiIntent({
|
||||
kind: 'open_module_tab',
|
||||
componentId: `${prefix}-${parsedArgs.id}`,
|
||||
tab: 'early-stop'
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof parsedArgs.skip_if === 'boolean') {
|
||||
emitUiIntent({
|
||||
kind: 'open_module_tab',
|
||||
componentId: `${prefix}-${parsedArgs.id}`,
|
||||
tab: 'skip'
|
||||
})
|
||||
}
|
||||
|
||||
const message = `Set module '${parsedArgs.id}' control options`
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: message
|
||||
})
|
||||
return `${message}: ${JSON.stringify(parsedArgs)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
def: resourceTypeToolDef,
|
||||
fn: async ({ args, toolId, workspace, toolCallbacks }) => {
|
||||
@@ -931,17 +790,10 @@ When creating new steps, follow this process for EACH step:
|
||||
|
||||
### Special Step Types
|
||||
For special step types, follow these additional steps:
|
||||
- For forloop steps:
|
||||
- Set the iterator expression using set_forloop_iterator_expression
|
||||
- Set advanced options (parallel, parallelism, skip_failures) using set_forloop_options
|
||||
- For forloop steps: Set the iterator expression using set_forloop_iterator_expression
|
||||
- For branchone steps: Set the predicates for each branch using set_branch_predicate
|
||||
- For branchall steps: No additional setup needed
|
||||
|
||||
### Module Control Options
|
||||
For any module type, you can set control flow options using set_module_control_options:
|
||||
- **stop_after_if**: Early stop condition - stops the module if expression evaluates to true. Can use "flow_input" or "result". "result" is the result of the step. "results.<step_id>" is not supported, do not use it. Example: "flow_input.x > 10" or "result === "failure""
|
||||
- **skip_if**: Skip condition - skips the module entirely if expression evaluates to true. Can use "flow_input" or "results.<step_id>". Example: "flow_input.x > 10" or "results.a === "failure""
|
||||
|
||||
### Step Insertion Rules
|
||||
When adding steps, carefully consider the execution order:
|
||||
1. Steps are executed in the order they appear in the flow definition, not in the order they were added
|
||||
@@ -962,7 +814,6 @@ When adding steps, carefully consider the execution order:
|
||||
### JavaScript Expressions
|
||||
For step inputs, forloop iterator expressions and branch predicates, use JavaScript expressions with these variables:
|
||||
- Step results: results.stepid or results.stepid.property_name
|
||||
- Break condition (stop_after_if) in for loops: result (contains the result of the last iteration)
|
||||
- Loop iterator: flow_input.iter.value (inside loops)
|
||||
- Flow inputs: flow_input.property_name
|
||||
- Static values: Use JavaScript syntax (e.g., "hello", true, 3)
|
||||
@@ -971,12 +822,6 @@ Note: These variables are only accessible in step inputs, forloop iterator expre
|
||||
|
||||
For truly static values in step inputs (those not linked to previous steps or loop iterations), prefer using flow inputs by default unless explicitly specified otherwise. This makes the flow more configurable and reusable. For example, instead of hardcoding an email address in a step input, create a flow input for it.
|
||||
|
||||
### For Loop Advanced Options
|
||||
When configuring for-loop steps, consider these options:
|
||||
- **parallel: true** - Run iterations in parallel for independent operations (significantly faster for I/O bound tasks)
|
||||
- **parallelism: N** - Limit concurrent iterations (only applies when parallel=true). Use to prevent overwhelming external APIs
|
||||
- **skip_failures: true** - Continue processing remaining iterations even if some fail. Failed iterations return error objects as results
|
||||
|
||||
### Special Modules
|
||||
- Preprocessor: Runs before the first step when triggered externally
|
||||
- ID: 'preprocessor'
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export type UiIntent = { kind: 'open_module_tab'; componentId: string; tab: string }
|
||||
|
||||
export const uiIntentStore: Writable<UiIntent | null> = writable(null)
|
||||
|
||||
export function emitUiIntent(intent: UiIntent) {
|
||||
uiIntentStore.set(intent)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { uiIntentStore, type UiIntent } from './uiIntents'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
type Handlers = {
|
||||
openTab?: (tab: string) => void
|
||||
}
|
||||
|
||||
export function useUiIntent(componentId: string, handlers: Handlers) {
|
||||
const unsub = uiIntentStore.subscribe((intent: UiIntent | null) => {
|
||||
if (!intent || intent.componentId !== componentId) return
|
||||
|
||||
if (intent.kind === 'open_module_tab') handlers.openTab?.(intent.tab)
|
||||
|
||||
uiIntentStore.set(null)
|
||||
})
|
||||
onDestroy(() => unsub())
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import FlowModuleSkip from './FlowModuleSkip.svelte'
|
||||
import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte'
|
||||
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
||||
|
||||
interface Props {
|
||||
noEditor: boolean
|
||||
@@ -32,12 +31,6 @@
|
||||
})
|
||||
|
||||
let selected = $state('early-stop')
|
||||
|
||||
useUiIntent(`branchall-${flowModule.id}`, {
|
||||
openTab: (tab) => {
|
||||
selected = tab
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col w-full" id="flow-editor-branch-all-wrapper">
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
import FlowModuleMock from './FlowModuleMock.svelte'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import FlowModuleSkip from './FlowModuleSkip.svelte'
|
||||
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
||||
|
||||
interface Props {
|
||||
// import FlowRetries from './FlowRetries.svelte'
|
||||
@@ -40,12 +39,6 @@
|
||||
})
|
||||
|
||||
let selected = $state('early-stop')
|
||||
|
||||
useUiIntent(`branchone-${flowModule.id}`, {
|
||||
openTab: (tab) => {
|
||||
selected = tab
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="h-full" id="flow-editor-branch-one-wrapper">
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
import PropPickerWrapper, { CONNECT } from '../propPicker/PropPickerWrapper.svelte'
|
||||
import type { PropPickerContext } from '$lib/components/prop_picker'
|
||||
import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte'
|
||||
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
||||
|
||||
const { previewArgs, flowStateStore, flowStore, currentEditor } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
@@ -51,13 +50,6 @@
|
||||
let editor: SimpleEditor | undefined = $state(undefined)
|
||||
let selected: string = $state('early-stop')
|
||||
|
||||
// UI Intent handling for AI tool control
|
||||
useUiIntent(`forloopflow-${mod.id}`, {
|
||||
openTab: (tab) => {
|
||||
selected = tab
|
||||
}
|
||||
})
|
||||
|
||||
const { flowPropPickerConfig } = getContext<PropPickerContext>('PropPickerContext')
|
||||
flowPropPickerConfig.set(undefined)
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
import { refreshStateStore } from '$lib/svelte5Utils.svelte'
|
||||
import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte'
|
||||
import AssetsDropdownButton from '$lib/components/assets/AssetsDropdownButton.svelte'
|
||||
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
||||
import { editor as meditor } from 'monaco-editor'
|
||||
|
||||
const {
|
||||
selectedId,
|
||||
@@ -137,13 +135,6 @@
|
||||
|
||||
let assets = $derived((flowModule.value.type === 'rawscript' && flowModule.value.assets) || [])
|
||||
|
||||
// UI Intent handling for AI tool control
|
||||
useUiIntent(`flow-${flowModule.id}`, {
|
||||
openTab: (tab) => {
|
||||
selectAdvanced(tab)
|
||||
}
|
||||
})
|
||||
|
||||
function onModulesChange(savedModule: FlowModule | undefined, flowModule: FlowModule) {
|
||||
// console.log('onModulesChange', savedModule, flowModule)
|
||||
return savedModule?.value?.type === 'rawscript' &&
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import FlowModuleDeleteAfterUse from './FlowModuleDeleteAfterUse.svelte'
|
||||
import FlowModuleSkip from './FlowModuleSkip.svelte'
|
||||
import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte'
|
||||
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
||||
|
||||
const { flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
@@ -39,12 +38,6 @@
|
||||
let job: Job | undefined = $state(undefined)
|
||||
|
||||
let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {})
|
||||
|
||||
useUiIntent(`whileloopflow-${mod.id}`, {
|
||||
openTab: (tab) => {
|
||||
selected = tab
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Drawer bind:open={previewOpen} alwaysOpen size="75%">
|
||||
|
||||
Reference in New Issue
Block a user