fix: flow status reactivity improvement (#6402)

This commit is contained in:
Ruben Fiszel
2025-08-19 02:12:57 +00:00
committed by GitHub
parent bcb4812ca2
commit 53715f2c06
64 changed files with 899 additions and 818 deletions
+14 -35
View File
@@ -17,7 +17,7 @@
} from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { setCopilotInfo, userStore, workspaceStore } from '$lib/stores'
import { emptySchema, readFieldsRecursively, sendUserToast } from '$lib/utils'
import { emptySchema, readFieldsRecursively, sendUserToast, type StateStore } from '$lib/utils'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { onDestroy, onMount, setContext, untrack } from 'svelte'
import DarkModeToggle from '$lib/components/sidebar/DarkModeToggle.svelte'
@@ -27,7 +27,7 @@
import FlowModuleSchemaMap from './flows/map/FlowModuleSchemaMap.svelte'
import FlowEditorPanel from './flows/content/FlowEditorPanel.svelte'
import { deepEqual } from 'fast-equals'
import { writable, type Writable } from 'svelte/store'
import { writable } from 'svelte/store'
import type { FlowState } from './flows/flowState'
import { initHistory } from '$lib/history.svelte'
import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types'
@@ -51,7 +51,6 @@
import { TestSteps } from './flows/testSteps.svelte'
import { ModulesTestStates } from './modulesTest.svelte'
import type { GraphModuleState } from './graph'
import { updateDerivedModuleStatesFromTestJobs } from './flows/utils'
let flowCopilotContext: FlowCopilotContext = {
shouldUpdatePropertyType: writable<{
@@ -116,7 +115,6 @@
const flowPreviewContent = $derived(flowPreviewButtons?.getFlowPreviewContent())
const job: Job | undefined = $derived(flowPreviewContent?.getJob())
let showJobStatus = $state(false)
let testModuleId: string | undefined = $state(undefined)
type LastEditScript = {
content: string
@@ -453,7 +451,7 @@
}
}
const flowStateStore = writable({} as FlowState)
const flowStateStore = $state({ val: {} }) as StateStore<FlowState>
const previewArgsStore = $state({ val: {} })
const scriptEditorDrawer = writable(undefined)
@@ -464,8 +462,6 @@
const triggersCount = writable<TriggersCount | undefined>(undefined)
const modulesTestStates = new ModulesTestStates((moduleId) => {
// Update the derived store with test job states
delete $derivedModuleStates[moduleId]
testModuleId = moduleId
showJobStatus = false
})
const outputPickerOpenFns: Record<string, () => void> = $state({})
@@ -538,11 +534,11 @@
}
mod.value.input_transforms = input_transforms
if (!deepEqual(schema, $flowStateStore[mod.id]?.schema)) {
if (!$flowStateStore[mod.id]) {
$flowStateStore[mod.id] = { schema }
if (!deepEqual(schema, flowStateStore.val[mod.id]?.schema)) {
if (!flowStateStore.val[mod.id]) {
flowStateStore.val[mod.id] = { schema }
} else {
$flowStateStore[mod.id].schema = schema
flowStateStore.val[mod.id].schema = schema
}
reload++
}
@@ -586,25 +582,12 @@
$selectedIdStore && untrack(() => inferModuleArgs($selectedIdStore))
})
const localModuleStates: Writable<Record<string, GraphModuleState>> = $derived(
flowPreviewContent?.getLocalModuleStates() ?? writable({})
)
let localModuleStates: Record<string, GraphModuleState> = $state({})
const suspendStatus: Writable<Record<string, { job: Job; nb: number }>> = $derived(
flowPreviewContent?.getSuspendStatus() ?? writable({})
)
let suspendStatus: StateStore<Record<string, { job: Job; nb: number }>> = $state({ val: {} })
// Create a derived store that only shows the module states when showModuleStatus is true
// this store can also be updated
let derivedModuleStates = writable<Record<string, GraphModuleState>>({})
$effect(() => {
derivedModuleStates.update((currentStates) => {
return showJobStatus ? $localModuleStates : currentStates
})
})
$effect(() => {
updateDerivedModuleStatesFromTestJobs(testModuleId, modulesTestStates, derivedModuleStates)
})
let flowModuleSchemaMap: FlowModuleSchemaMap | undefined = $state()
function onJobDone() {
@@ -639,14 +622,9 @@
}
function resetModulesStates() {
derivedModuleStates.set({})
showJobStatus = false
}
const individualStepTests = $derived(
!(showJobStatus && job) && Object.keys($derivedModuleStates).length > 0
)
const flowHasChanged = $derived(flowPreviewContent?.flowHasChanged())
</script>
@@ -785,7 +763,7 @@
bind:this={flowPreviewButtons}
{onJobDone}
onRunPreview={() => {
localModuleStates.set({})
localModuleStates = {}
showJobStatus = true
}}
/>
@@ -800,19 +778,20 @@
disableTutorials
smallErrorHandler={true}
disableStaticInputs
localModuleStates={derivedModuleStates}
{localModuleStates}
onTestUpTo={flowPreviewButtons?.testUpTo}
testModuleStates={modulesTestStates}
isOwner={flowPreviewContent?.getIsOwner?.()}
onTestFlow={flowPreviewButtons?.runPreview}
isRunning={flowPreviewContent?.getIsRunning?.()}
onCancelTestFlow={flowPreviewContent?.cancelTest}
onOpenPreview={flowPreviewButtons?.openPreview}
onHideJobStatus={resetModulesStates}
{individualStepTests}
flowJob={job}
{showJobStatus}
onDelete={(id) => {
delete $derivedModuleStates[id]
delete localModuleStates[id]
delete modulesTestStates.states[id]
}}
{flowHasChanged}
/>
@@ -19,7 +19,7 @@
let mod: any | undefined = $state(undefined)
async function loadSchema() {
try {
const res = await getFirstStepSchema($flowStateStore, flowStore.val)
const res = await getFirstStepSchema(flowStateStore.val, flowStore.val)
schema = res.schema
mod = res.mod
dispatch('connectFirstNode', { connectFirstNode: res.connectFirstNode })
@@ -28,7 +28,7 @@
}
}
$effect(() => {
flowStore.val && $flowStateStore && untrack(() => loadSchema())
flowStore.val && flowStateStore && untrack(() => loadSchema())
})
function handleClick() {
+20 -44
View File
@@ -25,6 +25,7 @@
orderedJsonStringify,
readFieldsRecursively,
replaceFalseWithUndefined,
type StateStore,
type Value
} from '$lib/utils'
import { sendUserToast } from '$lib/toast'
@@ -33,7 +34,7 @@
import AIChangesWarningModal from '$lib/components/copilot/chat/flow/AIChangesWarningModal.svelte'
import { onMount, setContext, untrack, type ComponentType } from 'svelte'
import { writable, type Writable } from 'svelte/store'
import { writable } from 'svelte/store'
import CenteredPage from './CenteredPage.svelte'
import { Badge, Button, UndoRedo } from './common'
import FlowEditor from './flows/FlowEditor.svelte'
@@ -42,7 +43,7 @@
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types'
import { cleanInputs, updateDerivedModuleStatesFromTestJobs } from './flows/utils'
import { cleanInputs } from './flows/utils'
import {
Calendar,
Pen,
@@ -577,13 +578,7 @@
}
let insertButtonOpen = writable<boolean>(false)
let testModuleId: string | undefined = $state(undefined)
let modulesTestStates = new ModulesTestStates((moduleId) => {
// Update the derived store with test job states
delete $derivedModuleStates[moduleId]
testModuleId = moduleId
showJobStatus = false
})
let modulesTestStates = new ModulesTestStates()
let outputPickerOpenFns: Record<string, () => void> = $state({})
let flowEditor: FlowEditor | undefined = $state(undefined)
@@ -933,33 +928,8 @@
}
}
const localModuleStates: Writable<Record<string, GraphModuleState>> = $derived(
flowPreviewContent?.getLocalModuleStates() ?? writable({})
)
const suspendStatus: Writable<Record<string, { job: Job; nb: number }>> = $derived(
flowPreviewContent?.getSuspendStatus() ?? writable({})
)
// Create a derived store that only shows the module states when showModuleStatus is true
// this store can also be updated
let derivedModuleStates = writable<Record<string, GraphModuleState>>({})
$effect(() => {
derivedModuleStates.update((currentStates) => {
return showJobStatus ? $localModuleStates : currentStates
})
})
$effect(() => {
updateDerivedModuleStatesFromTestJobs(testModuleId, modulesTestStates, derivedModuleStates)
})
function resetModulesStates() {
derivedModuleStates.set({})
showJobStatus = false
}
const individualStepTests = $derived(
!(showJobStatus && job) && Object.keys($derivedModuleStates).length > 0
)
let localModuleStates: Record<string, GraphModuleState> = $state({})
let suspendStatus: StateStore<Record<string, { job: Job; nb: number }>> = $state({ val: {} })
const flowHasChanged = $derived(flowPreviewContent?.flowHasChanged())
</script>
@@ -1025,7 +995,7 @@
for (const mod of restoredModules) {
if (mod) {
try {
loadFlowModuleState(mod).then((state) => ($flowStateStore[mod.id] = state))
loadFlowModuleState(mod).then((state) => (flowStateStore.val[mod.id] = state))
} catch (e) {
console.error('Error loading state for restored node', e)
}
@@ -1155,10 +1125,12 @@
showCaptureHint.set(true)
}}
{onJobDone}
bind:localModuleStates
bind:this={flowPreviewButtons}
{loading}
onRunPreview={() => {
localModuleStates.set({})
modulesTestStates.hideJobsInGraph()
localModuleStates = {}
showJobStatus = true
}}
/>
@@ -1185,7 +1157,7 @@
</div>
</div>
<!-- metadata -->
{#if $flowStateStore}
{#if flowStateStore}
<FlowEditor
bind:this={flowEditor}
{disabledFlowInputs}
@@ -1228,18 +1200,22 @@
showFlowAiButton={!disableAi && customUi?.topBar?.aiBuilder != false}
toggleAiChat={() => aiChatManager.toggleOpen()}
onOpenPreview={flowPreviewButtons?.openPreview}
localModuleStates={derivedModuleStates}
localModuleStates={showJobStatus ? localModuleStates : {}}
{showJobStatus}
testModuleStates={modulesTestStates}
isOwner={flowPreviewContent?.getIsOwner()}
onTestFlow={flowPreviewButtons?.runPreview}
isRunning={flowPreviewContent?.getIsRunning()}
onCancelTestFlow={flowPreviewContent?.cancelTest}
onHideJobStatus={resetModulesStates}
{individualStepTests}
onHideJobStatus={() => {
modulesTestStates.hideJobsInGraph()
showJobStatus = false
}}
{job}
{suspendStatus}
{showJobStatus}
onDelete={(id) => {
delete $derivedModuleStates[id]
delete localModuleStates[id]
delete modulesTestStates.states[id]
}}
{flowHasChanged}
/>
@@ -21,13 +21,12 @@
import FlowJobsMenu from './flows/map/FlowJobsMenu.svelte'
import BarsStaggered from './icons/BarsStaggered.svelte'
import type { GraphModuleState } from './graph/model'
import type { Writable } from 'svelte/store'
type RootJobData = Partial<Job>
interface Props {
modules: FlowModule[]
localModuleStates: Writable<Record<string, GraphModuleState>>
localModuleStates: Record<string, GraphModuleState>
rootJob: RootJobData
flowStatus: FlowStatusModule['type'] | undefined
expandedRows: Record<string, boolean>
@@ -122,7 +121,7 @@
}
function hasEmptySubflow(stepId: string, stepType: FlowModuleValue['type'] | undefined): boolean {
const state = $localModuleStates[stepId]
const state = localModuleStates[stepId]
if (!state || !stepType) return false
return (
@@ -172,7 +171,7 @@
}
// Check if this entry itself has an error (but don't flag it - only its parents)
const stepStatus = $localModuleStates[module.id]?.type
const stepStatus = localModuleStates[module.id]?.type
if (stepStatus === 'Failure') {
currentEntryHasError = true
// Don't add the entry itself to parentsWithErrors
@@ -402,7 +401,7 @@
{#if modules.length > 0}
{#each modules as module (module.id)}
{@const isLeafStep = !hasSubflows(module)}
{@const status = $localModuleStates[module.id]?.type}
{@const status = localModuleStates[module.id]?.type}
{@const isRunning = status === 'InProgress' || status === 'WaitingForExecutor'}
{@const hasEmptySubflowValue = hasEmptySubflow(module.id, module.value.type)}
{@const isCollapsible = !hasEmptySubflowValue}
@@ -479,7 +478,7 @@
</span>
{/if}
</span>
{#if !hasEmptySubflowValue && $localModuleStates[module.id]?.flow_jobs && (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow')}
{#if !hasEmptySubflowValue && localModuleStates[module.id]?.flow_jobs && (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow')}
<span
class="text-xs font-mono font-medium inline-flex items-center grow min-w-0 -my-2"
>
@@ -488,18 +487,18 @@
moduleId={module.id}
id={module.id}
{onSelectedIteration}
flowJobsSuccess={$localModuleStates[module.id]
flowJobsSuccess={localModuleStates[module.id]
?.flow_jobs_success}
flowJobs={$localModuleStates[module.id]?.flow_jobs}
selected={$localModuleStates[module.id]?.selectedForloopIndex ??
flowJobs={localModuleStates[module.id]?.flow_jobs}
selected={localModuleStates[module.id]?.selectedForloopIndex ??
0}
selectedManually={$localModuleStates[module.id]
selectedManually={localModuleStates[module.id]
?.selectedForLoopSetManually ?? false}
showIcon={false}
/>
</span>
{#if module.value.type === 'forloopflow'}
{`/${$localModuleStates[module.id]?.iteration_total ?? 0}`}
{`/${localModuleStates[module.id]?.iteration_total ?? 0}`}
{/if}
</span>
{/if}
@@ -507,7 +506,7 @@
</div>
{#if isLeafStep}
{@const jobId = $localModuleStates[module.id]?.job_id}
{@const jobId = localModuleStates[module.id]?.job_id}
<a
href={getJobLink(jobId ?? '')}
class="text-xs text-primary hover:underline font-mono"
@@ -520,24 +519,24 @@
</div>
{#if isCollapsible && isExpanded(module.id, isRunning)}
{@const args = $localModuleStates[module.id]?.args}
{@const logs = $localModuleStates[module.id]?.logs}
{@const result = $localModuleStates[module.id]?.result}
{@const jobId = $localModuleStates[module.id]?.job_id}
{@const args = localModuleStates[module.id]?.args}
{@const logs = localModuleStates[module.id]?.logs}
{@const result = localModuleStates[module.id]?.result}
{@const jobId = localModuleStates[module.id]?.job_id}
<div class="my-1 transition-all duration-200 ease-in-out">
<!-- Show child steps if they exist -->
{#each getSubflows(module) as subflow}
{@const subflowJob = {
id: jobId,
type:
$localModuleStates[module.id]?.type === 'Failure' ||
$localModuleStates[module.id]?.type === 'Success'
localModuleStates[module.id]?.type === 'Failure' ||
localModuleStates[module.id]?.type === 'Success'
? 'CompletedJob'
: ('QueuedJob' as Job['type']),
logs,
result,
args,
success: $localModuleStates[module.id]?.type === 'Success'
success: localModuleStates[module.id]?.type === 'Success'
}}
<div class="border-l mb-2">
<!-- Recursively render child steps using FlowLogViewer -->
@@ -545,7 +544,7 @@
modules={subflow.modules}
{localModuleStates}
rootJob={subflowJob}
flowStatus={$localModuleStates[module.id]?.type}
flowStatus={localModuleStates[module.id]?.type}
{expandedRows}
{allExpanded}
{showResultsInputs}
@@ -1,6 +1,5 @@
<script lang="ts">
import type { Job } from '$lib/gen'
import { type Writable } from 'svelte/store'
import type { GraphModuleState } from './graph'
import FlowLogViewer from './FlowLogViewer.svelte'
import { untrack } from 'svelte'
@@ -9,7 +8,7 @@
interface Props {
job: Job
localModuleStates: Writable<Record<string, GraphModuleState>>
localModuleStates: Record<string, GraphModuleState>
workspaceId: string | undefined
render: boolean
onSelectedIteration: (
@@ -44,7 +43,7 @@
}
function getSelectedIteration(stepId: string): number {
return $localModuleStates[stepId]?.selectedForloopIndex ?? 0
return localModuleStates[stepId]?.selectedForloopIndex ?? 0
}
function toggleExpandAll() {
@@ -176,10 +176,10 @@
<div class="pt-4 grow">
{#if jobId}
<FlowStatusViewer
{flowStateStore}
bind:flowStateStore={flowStateStore.val}
{jobId}
on:jobsLoaded={({ detail }) => {
job = detail
onJobsLoaded={({ job: newJob }) => {
job = newJob
}}
bind:selectedJobStep
/>
@@ -1,7 +1,13 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy'
import { type Job, JobService, type RestartedFrom, type OpenFlow, type ScriptLang } from '$lib/gen'
import {
type Job,
JobService,
type RestartedFrom,
type OpenFlow,
type ScriptLang
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Badge, Button } from './common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
@@ -13,14 +19,13 @@
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
import FlowProgressBar from './flows/FlowProgressBar.svelte'
import { AlertTriangle, ArrowRight, CornerDownLeft, Play, RefreshCw, X } from 'lucide-svelte'
import { emptyString, sendUserToast } from '$lib/utils'
import { emptyString, sendUserToast, type StateStore } from '$lib/utils'
import { dfs } from './flows/dfs'
import { sliceModules } from './flows/flowStateUtils.svelte'
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
import Toggle from './Toggle.svelte'
import JsonInputs from './JsonInputs.svelte'
import FlowHistoryJobPicker from './FlowHistoryJobPicker.svelte'
import { writable, type Writable } from 'svelte/store'
import type { DurationStatus, GraphModuleState } from './graph'
import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte'
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
@@ -39,8 +44,8 @@
rightColumnSelect?: 'timeline' | 'node_status' | 'node_definition' | 'user_states'
branchOrIterationN?: number
scrollTop?: number
localModuleStates?: Writable<Record<string, GraphModuleState>>
localDurationStatuses?: Writable<Record<string, DurationStatus>>
localModuleStates?: Record<string, GraphModuleState>
localDurationStatuses?: Record<string, DurationStatus>
onRunPreview?: () => void
render?: boolean
onJobDone?: () => void
@@ -63,8 +68,8 @@
rightColumnSelect = $bindable('timeline'),
branchOrIterationN = $bindable(0),
scrollTop = $bindable(0),
localModuleStates = $bindable(writable({})),
localDurationStatuses = $bindable(writable({})),
localModuleStates = $bindable({}),
localDurationStatuses = $bindable({}),
onRunPreview,
render = false,
onJobDone,
@@ -77,7 +82,7 @@
let jsonEditor: JsonInputs | undefined = $state(undefined)
let schemaHeight = $state(0)
let isValid: boolean = $state(true)
let suspendStatus: Writable<Record<string, { job: Job; nb: number }>> = $state(writable({}))
let suspendStatus: StateStore<Record<string, { job: Job; nb: number }>> = $state({ val: {} })
let isOwner: boolean = $state(false)
export function test() {
@@ -568,9 +573,9 @@
bind:suspendStatus
hideDownloadInGraph={customUi?.downloadLogs === false}
wideResults
{flowStateStore}
bind:flowStateStore={flowStateStore.val}
{jobId}
on:done={(x) => {
onDone={() => {
isRunning = false
$executionCount = $executionCount + 1
onJobDone?.()
@@ -4,8 +4,7 @@
import FlowPreviewStatus from './preview/FlowPreviewStatus.svelte'
import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte'
import type { FlowStatusModule, Job } from '$lib/gen'
import { emptyString } from '$lib/utils'
import type { Writable } from 'svelte/store'
import { emptyString, type StateStore } from '$lib/utils'
import Badge from './common/badge/Badge.svelte'
interface Props {
@@ -14,8 +13,8 @@
isOwner: boolean
hideFlowResult: boolean
hideDownloadLogs: boolean
innerModules: FlowStatusModule[]
suspendStatus: Writable<Record<string, { job: Job; nb: number }>>
innerModules: FlowStatusModule[] | undefined
suspendStatus: StateStore<Record<string, { job: Job; nb: number }>>
hideJobId?: boolean
extra?: import('svelte').Snippet
result_streams?: Record<string, string | undefined>
@@ -57,9 +56,9 @@
{/if}
{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === 'WaitingForEvents'}
<FlowStatusWaitingForEvents {workspaceId} {job} {isOwner} />
{:else if $suspendStatus && Object.keys($suspendStatus).length > 0}
{:else if suspendStatus.val && Object.keys(suspendStatus.val).length > 0}
<div class="flex gap-2 flex-col">
{#each Object.values($suspendStatus) as suspendCount (suspendCount.job.id)}
{#each Object.values(suspendStatus.val) as suspendCount (suspendCount.job.id)}
<div>
<div class="text-sm">
Flow suspended, waiting for {suspendCount.nb} events
@@ -74,7 +73,7 @@
>
<pre class="w-full">{job.logs}</pre>
</div>
{:else if innerModules?.length > 0}
{:else if innerModules && innerModules?.length > 0}
<div class="flex flex-col gap-1">
{#each innerModules as mod, i (mod.id)}
{#if mod.type == 'InProgress'}
@@ -1,18 +1,17 @@
<script lang="ts">
import { writable, type Writable } from 'svelte/store'
import FlowStatusViewerInner from './FlowStatusViewerInner.svelte'
import type { FlowState } from './flows/flowState'
import { createEventDispatcher, setContext, untrack } from 'svelte'
import { setContext, untrack } from 'svelte'
import type { DurationStatus, FlowStatusViewerContext, GraphModuleState } from './graph'
import { isOwner as loadIsOwner } from '$lib/utils'
import { isOwner as loadIsOwner, type StateStore } from '$lib/utils'
import { userStore, workspaceStore } from '$lib/stores'
import type { Job } from '$lib/gen'
import type { CompletedJob, Job } from '$lib/gen'
interface Props {
jobId: string
initialJob?: Job | undefined
workspaceId?: string | undefined
flowStateStore?: Writable<FlowState>
flowStateStore?: FlowState
selectedJobStep?: string | undefined
hideFlowResult?: boolean
hideTimeline?: boolean
@@ -23,21 +22,24 @@
rightColumnSelect?: 'timeline' | 'node_status' | 'node_definition' | 'user_states'
isOwner?: boolean
wideResults?: boolean
localModuleStates?: Writable<Record<string, GraphModuleState>>
localDurationStatuses?: Writable<Record<string, DurationStatus>>
localModuleStates?: Record<string, GraphModuleState>
localDurationStatuses?: Record<string, DurationStatus>
job?: Job | undefined
render?: boolean
suspendStatus?: any
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
customUi?: {
tagLabel?: string | undefined
}
onStart?: () => void
onJobsLoaded?: ({ job, force }: { job: Job; force: boolean }) => void
onDone?: ({ job }: { job: CompletedJob }) => void
}
let {
jobId,
initialJob = undefined,
workspaceId = undefined,
flowStateStore = writable({}),
flowStateStore = $bindable({}),
selectedJobStep = $bindable(undefined),
hideFlowResult = false,
hideTimeline = false,
@@ -48,17 +50,24 @@
rightColumnSelect = $bindable('timeline'),
isOwner = $bindable(false),
wideResults = false,
localModuleStates = $bindable(writable({})),
localDurationStatuses = $bindable(writable({})),
localModuleStates = $bindable({}),
localDurationStatuses = $bindable({}),
job = $bindable(undefined),
render = true,
suspendStatus = $bindable(writable({})),
customUi
suspendStatus = $bindable({ val: {} }),
customUi,
onStart,
onJobsLoaded,
onDone
}: Props = $props()
let lastJobId: string = jobId
let retryStatus = writable({})
let retryStatus = $state({ val: {} })
let globalRefreshes: Record<string, ((clear, root) => Promise<void>)[]> = $state({})
let globalIterationBounds = $state({})
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
flowStateStore,
suspendStatus,
@@ -77,13 +86,13 @@
async function updateJobId() {
if (jobId !== lastJobId) {
lastJobId = jobId
$retryStatus = {}
$suspendStatus = {}
retryStatus.val = {}
suspendStatus.val = {}
globalRefreshes = {}
globalIterationBounds = {}
}
}
const dispatch = createEventDispatcher()
let lastScriptPath: string | undefined = $state(undefined)
$effect.pre(() => {
@@ -92,25 +101,33 @@
jobId && updateJobId()
})
})
let refreshGlobal = async (moduleId: string, clear: boolean, root: string) => {
let allFns = globalRefreshes?.[moduleId]?.map((x) => x(clear, root)) ?? []
await Promise.all(allFns)
}
let updateGlobalRefresh = (moduleId: string, updateFn: (clear, root) => Promise<void>) => {
globalRefreshes[moduleId] = [...(globalRefreshes[moduleId] ?? []), updateFn]
}
</script>
<FlowStatusViewerInner
{hideFlowResult}
on:jobsLoaded={({ detail }) => {
let { job } = detail
onJobsLoaded={({ job, force }) => {
if (job.script_path != lastScriptPath && job.script_path) {
lastScriptPath = job.script_path
loadOwner(lastScriptPath ?? '')
}
dispatch('jobsLoaded', job)
onJobsLoaded?.({ job, force })
}}
globalModuleStates={[]}
globalDurationStatuses={[]}
{localModuleStates}
{localDurationStatuses}
{globalIterationBounds}
bind:localModuleStates
bind:selectedNode={selectedJobStep}
on:start
on:done
bind:localDurationStatuses
{onStart}
{onDone}
bind:job
{initialJob}
{jobId}
@@ -122,4 +139,6 @@
{customUi}
graphTabOpen={true}
isNodeSelected={true}
{refreshGlobal}
{updateGlobalRefresh}
/>
File diff suppressed because it is too large Load Diff
+23 -23
View File
@@ -1,27 +1,26 @@
<script lang="ts">
import { debounce, displayDate, msToSec } from '$lib/utils'
import { debounce, displayDate, msToSec, readFieldsRecursively } from '$lib/utils'
import { onDestroy, untrack } from 'svelte'
import { getDbClockNow } from '$lib/forLater'
import { Loader2 } from 'lucide-svelte'
import TimelineBar from './TimelineBar.svelte'
import type { Writable } from 'svelte/store'
import WaitTimeWarning from './common/waitTimeWarning/WaitTimeWarning.svelte'
import type { GlobalIterationBounds } from './graph'
interface Props {
selfWaitTime?: number | undefined
aggregateWaitTime?: number | undefined
flowModules: string[]
durationStatuses: Writable<
Record<
string,
{
byJob: Record<string, { created_at?: number; started_at?: number; duration_ms?: number }>
iteration_from?: number
iteration_total?: number
}
>
durationStatuses: Record<
string,
{
byJob: Record<string, { created_at?: number; started_at?: number; duration_ms?: number }>
}
>
flowDone?: boolean
decreaseIterationFrom?: (key: string, amount: number) => void
buildSubflowKey: (key: string) => string
globalIterationBounds: Record<string, GlobalIterationBounds>
}
let {
@@ -29,7 +28,10 @@
aggregateWaitTime = undefined,
flowModules,
durationStatuses,
flowDone = false
flowDone = false,
decreaseIterationFrom,
buildSubflowKey,
globalIterationBounds
}: Props = $props()
let min: undefined | number = $state(undefined)
@@ -43,15 +45,16 @@
>
| undefined = $state(undefined)
let { debounced, clearDebounce } = debounce(() => computeItems($durationStatuses), 30)
let { debounced, clearDebounce } = debounce(() => computeItems(durationStatuses), 30)
$effect(() => {
flowDone != undefined && $durationStatuses && untrack(() => debounced())
readFieldsRecursively(durationStatuses)
flowDone != undefined && durationStatuses && untrack(() => debounced())
})
export function reset() {
min = undefined
max = undefined
items = computeItems($durationStatuses)
items = computeItems(durationStatuses)
}
function computeItems(
@@ -172,20 +175,17 @@
</div>
{/if}
{#each Object.values(flowModules) as k (k)}
{@const iterationFrom = globalIterationBounds[buildSubflowKey(k)]?.iteration_from ?? 0}
<div class="overflow-auto max-h-60 shadow-inner dark:shadow-gray-700 relative">
{#if ($durationStatuses?.[k]?.iteration_from ?? 0) > 0}
{#if iterationFrom > 0}
<div class="w-full flex flex-row-reverse sticky top-0">
<button
class="!text-secondary underline mr-2 text-2xs text-right whitespace-nowrap"
onclick={() => {
let r = $durationStatuses[k]
if (r.iteration_from) {
r.iteration_from -= 20
$durationStatuses = $durationStatuses
}
decreaseIterationFrom?.(k, 20)
}}
>Viewing iterations {$durationStatuses[k].iteration_from} to {$durationStatuses[k]
.iteration_total}. Load more
>Viewing iterations {iterationFrom} to {globalIterationBounds[buildSubflowKey(k)]
?.iteration_total}. Load more
</button>
</div>
{/if}
+1 -1
View File
@@ -1,4 +1,4 @@
<script context="module" lang="ts">
<script module lang="ts">
import pLimit from 'p-limit'
const plimit = pLimit(5)
@@ -101,7 +101,7 @@
let args = $state(<Record<string, any>>{})
onMount(() => {
testSteps?.updateStepArgs(mod.id, $flowStateStore, flowStore?.val, previewArgs?.val)
testSteps?.updateStepArgs(mod.id, flowStateStore, flowStore?.val, previewArgs?.val)
args = testSteps?.getStepArgs(mod.id) ?? { value: {} }
})
</script>
+18 -16
View File
@@ -35,14 +35,13 @@
}
export function loadArgsAndRunTest() {
testSteps?.updateStepArgs(mod.id, $flowStateStore, flowStore?.val, previewArgs?.val)
testSteps?.updateStepArgs(mod.id, flowStateStore, flowStore?.val, previewArgs?.val)
runTest(testSteps.getStepArgs(mod.id)?.value)
}
export async function runTest(args: any) {
// Not defined if JobProgressBar not loaded
if (jobProgressReset) jobProgressReset()
if (modulesTestStates.states[mod.id]) {
modulesTestStates.states[mod.id].cancel = async () => {
await jobLoader?.cancelJob()
@@ -92,16 +91,19 @@
function jobDone(testJob: Job & { result?: any }) {
if (testJob && !testJob.canceled && testJob.type == 'CompletedJob') {
if ($flowStateStore[mod.id]) {
$flowStateStore[mod.id].previewResult = testJob.result
$flowStateStore[mod.id].previewSuccess = testJob.success
$flowStateStore[mod.id].previewJobId = testJob.id
$flowStateStore[mod.id].previewWorkspaceId = testJob.workspace_id
$flowStateStore = $flowStateStore
if (flowStateStore.val[mod.id]) {
flowStateStore.val[mod.id] = {
...flowStateStore.val[mod.id],
previewResult: testJob.result,
previewSuccess: testJob.success,
previewJobId: testJob.id
}
}
stepHistoryLoader?.resetInitial(mod.id)
}
modulesTestStates.states[mod.id].testJob = undefined
if (modulesTestStates.states[mod.id]) {
modulesTestStates.states[mod.id].testJob = testJob
}
}
export function cancelJob() {
@@ -110,16 +112,16 @@
$effect(() => {
// Update testIsLoading to read the state from parent components
testIsLoading = modulesTestStates.states[mod.id]?.loading ?? false
testIsLoading = modulesTestStates.states?.[mod.id]?.loading ?? false
})
$effect(() => {
// Update testJob to read the state from parent components
testJob = modulesTestStates.states[mod.id]?.testJob
testJob = modulesTestStates.states?.[mod.id]?.testJob
})
modulesTestStates.states[mod.id] = {
...(modulesTestStates.states[mod.id] ?? { loading: false }),
...(modulesTestStates.states?.[mod.id] ?? { loading: false }),
loading: testIsLoading,
testJob: testJob
}
@@ -134,13 +136,13 @@
() => modulesTestStates.states[mod.id]?.loading ?? false,
(v) => {
let newLoading = v ?? false
if (modulesTestStates.states[mod.id]?.loading !== newLoading) {
if (modulesTestStates.states && modulesTestStates.states?.[mod.id]?.loading !== newLoading) {
modulesTestStates.states[mod.id] = {
...(modulesTestStates.states[mod.id] ?? {}),
loading: newLoading
...(modulesTestStates.states?.[mod.id] ?? {}),
loading: newLoading,
hiddenInGraph: false
}
}
}
}
bind:job={modulesTestStates.states[mod.id].testJob}
/>
@@ -98,13 +98,13 @@
<FlowStatusViewer
workspaceId={workspace}
{jobId}
on:start={() => {
onStart={() => {
outputs?.jobId.set(jobId)
outputs?.loading.set(true)
}}
on:done={(e) => {
onDone={({ job }) => {
outputs?.loading.set(false)
outputs?.result.set(e?.detail?.result)
outputs?.result.set(job?.result as any)
}}
/>
{:else}
@@ -264,8 +264,8 @@
{#if job?.id}
<FlowStatusViewer
jobId={job?.id}
on:jobsLoaded={({ detail }) => {
job = detail
onJobsLoaded={({ job: newJob }) => {
job = newJob
}}
/>
{:else}
@@ -117,7 +117,7 @@ class AIChatManager {
return (
estimatedTokens >
modelContextWindow -
Math.max(modelContextWindow * MAX_TOKENS_THRESHOLD_PERCENTAGE, MAX_TOKENS_HARD_LIMIT)
Math.max(modelContextWindow * MAX_TOKENS_THRESHOLD_PERCENTAGE, MAX_TOKENS_HARD_LIMIT)
)
}
@@ -547,8 +547,8 @@ class AIChatManager {
onNewToken: (token: string) => {
reply += token
},
onMessageEnd: () => {},
setToolStatus: () => {}
onMessageEnd: () => { },
setToolStatus: () => { }
},
systemMessage
}
@@ -733,8 +733,8 @@ class AIChatManager {
} else {
// Create new tool message with metadata
const newMessage: ToolDisplayMessage = {
role: 'tool',
tool_call_id: id,
role: 'tool',
tool_call_id: id,
content: metadata?.content ?? metadata?.error ?? '',
...(metadata || {})
}
@@ -923,20 +923,20 @@ class AIChatManager {
const module = getModule(id)
if (module && module.value.type === 'rawscript') {
const moduleState: FlowModuleState | undefined = flowStateStore[module.id]
const moduleState: FlowModuleState | undefined = flowStateStore.val[module.id]
const editorRelated =
currentEditor && currentEditor.type === 'script' && currentEditor.stepId === module.id
? {
diffMode: currentEditor.diffMode,
lastDeployedCode: currentEditor.lastDeployedCode,
lastSavedCode: undefined
}
diffMode: currentEditor.diffMode,
lastDeployedCode: currentEditor.lastDeployedCode,
lastSavedCode: undefined
}
: {
diffMode: false,
lastDeployedCode: undefined,
lastSavedCode: undefined
}
diffMode: false,
lastDeployedCode: undefined,
lastSavedCode: undefined
}
return {
args: moduleState?.previewArgs ?? {},
@@ -200,10 +200,10 @@
module.value.input_transforms = input_transforms
refreshStateStore(flowStore)
if ($flowStateStore[id]) {
$flowStateStore[id].schema = schema
if (flowStateStore.val[id]) {
flowStateStore.val[id].schema = schema
} else {
$flowStateStore[id] = {
flowStateStore.val[id] = {
schema
}
}
@@ -305,7 +305,6 @@
}
if (location.type === 'preprocessor' || location.type === 'failure') {
$flowStateStore = $flowStateStore
refreshStateStore(flowStore)
setModuleStatus(location.type, 'added')
@@ -322,7 +321,6 @@
await flowModuleSchemaMap?.addBranch(newModule.id)
}
$flowStateStore = $flowStateStore
refreshStateStore(flowStore)
setModuleStatus(newModule.id, 'added')
@@ -516,7 +514,7 @@
const cleanup = aiChatManager.listenForSelectedIdChanges(
$selectedId,
flowStore.val,
$flowStateStore,
flowStateStore,
$currentEditor
)
return cleanup
+1 -2
View File
@@ -1,6 +1,5 @@
import type { OpenFlow } from '$lib/gen'
import type { StateStore } from '$lib/utils'
import type { Writable } from 'svelte/store'
import type { FlowState } from './flows/flowState'
import type { FlowWithDraftAndDraftTriggers, Trigger } from './triggers/utils'
import type { DiffDrawerI } from './diff_drawer'
@@ -16,7 +15,7 @@ export type FlowBuilderProps = {
initialArgs?: Record<string, any>
loading?: boolean
flowStore: StateStore<OpenFlow>
flowStateStore: Writable<FlowState>
flowStateStore: StateStore<FlowState>
savedFlow?: FlowWithDraftAndDraftTriggers | undefined
diffDrawer?: DiffDrawerI | undefined
customUi?: FlowBuilderWhitelabelCustomUi
@@ -7,7 +7,7 @@
import { getContext, onDestroy, onMount, setContext } from 'svelte'
import type { FlowEditorContext } from './types'
import { writable, type Writable } from 'svelte/store'
import { writable } from 'svelte/store'
import type { PropPickerContext, FlowPropPickerConfig } from '$lib/components/prop_picker'
import type { PickableProperties } from '$lib/components/flows/previousResults'
import type { Flow, Job } from '$lib/gen'
@@ -16,6 +16,8 @@
import { aiChatManager, AIMode } from '../copilot/chat/AIChatManager.svelte'
import type { GraphModuleState } from '../graph'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import type { ModulesTestStates } from '../modulesTest.svelte'
import type { StateStore } from '$lib/utils'
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
interface Props {
@@ -27,6 +29,7 @@
disabledFlowInputs?: boolean
smallErrorHandler?: boolean
newFlow?: boolean
showJobStatus?: boolean
savedFlow?:
| (Flow & {
draft?: Flow | undefined
@@ -40,7 +43,8 @@
aiChatOpen?: boolean
showFlowAiButton?: boolean
toggleAiChat?: () => void
localModuleStates?: Writable<Record<string, GraphModuleState>>
localModuleStates?: Record<string, GraphModuleState>
testModuleStates?: ModulesTestStates
isOwner?: boolean
onTestFlow?: () => void
isRunning?: boolean
@@ -49,8 +53,7 @@
onHideJobStatus?: () => void
individualStepTests?: boolean
job?: Job
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
showJobStatus?: boolean
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
onDelete?: (id: string) => void
flowHasChanged?: boolean
}
@@ -63,6 +66,7 @@
disableSettings = false,
disabledFlowInputs = false,
smallErrorHandler = false,
showJobStatus = false,
newFlow = false,
savedFlow = undefined,
onDeployTrigger = () => {},
@@ -70,7 +74,8 @@
onEditInput = undefined,
forceTestTab,
highlightArg,
localModuleStates = writable({}),
localModuleStates = {},
testModuleStates = undefined,
aiChatOpen,
showFlowAiButton,
toggleAiChat,
@@ -83,7 +88,6 @@
individualStepTests = false,
job,
suspendStatus,
showJobStatus,
onDelete,
flowHasChanged
}: Props = $props()
@@ -134,6 +138,7 @@
{disableSettings}
{smallErrorHandler}
{newFlow}
{showJobStatus}
on:reload
on:generateStep={({ detail }) => {
if (!aiChatManager.open) {
@@ -144,6 +149,7 @@
{onTestUpTo}
{onEditInput}
{localModuleStates}
{testModuleStates}
{aiChatOpen}
{showFlowAiButton}
{toggleAiChat}
@@ -155,7 +161,6 @@
{onHideJobStatus}
{individualStepTests}
flowJob={job}
{showJobStatus}
{suspendStatus}
{onDelete}
{flowHasChanged}
@@ -10,28 +10,34 @@
import { Pen } from 'lucide-svelte'
import PredicateGen from '$lib/components/copilot/PredicateGen.svelte'
export let branch: {
summary?: string
expr: string
modules: Array<FlowModule>
interface Props {
branch: {
summary?: string
expr: string
modules: Array<FlowModule>
}
parentModule: FlowModule
previousModule: FlowModule | undefined
enableAi?: boolean
}
export let parentModule: FlowModule
export let previousModule: FlowModule | undefined
export let enableAi = false
let { branch = $bindable(), parentModule, previousModule, enableAi = false }: Props = $props()
const { previewArgs, flowStateStore, flowStore } =
getContext<FlowEditorContext>('FlowEditorContext')
let editor: SimpleEditor | undefined = undefined
let open = false
$: stepPropPicker = getStepPropPicker(
$flowStateStore,
parentModule,
previousModule,
parentModule.id,
flowStore.val,
previewArgs.val,
false
let editor: SimpleEditor | undefined = $state(undefined)
let open = $state(false)
let stepPropPicker = $derived(
getStepPropPicker(
flowStateStore.val,
parentModule,
previousModule,
parentModule.id,
flowStore.val,
previewArgs.val,
false
)
)
</script>
@@ -35,7 +35,7 @@
m.id,
Object.entries(v.input_transforms)
.map((x) => {
let schema = flowStateStore[m.id]?.schema
let schema = flowStateStore.val[m.id]?.schema
let val: { argName: string; type: string } | undefined = undefined
const [k, inputTransform] = x
@@ -75,7 +75,7 @@
Object.entries(v.input_transforms)
.filter((x) => {
const shouldDisplay = hideOptional
? $flowStateStore[m.id]?.schema?.required?.includes(x[0])
? flowStateStore.val[m.id]?.schema?.required?.includes(x[0])
: true
return x[1].type == 'static' && shouldDisplay
})
@@ -146,7 +146,7 @@
noDynamicToggle
{filter}
class="mt-2"
schema={$flowStateStore[m.id]?.schema ?? {}}
schema={flowStateStore.val[m.id]?.schema ?? {}}
bind:args={steps[index][0]}
/>
</div>
@@ -14,7 +14,7 @@
import { handleSelectTriggerFromKind, type Trigger } from '$lib/components/triggers/utils'
import { computeMissingInputWarnings } from '../missingInputWarnings'
import FlowResult from './FlowResult.svelte'
import type { Writable } from 'svelte/store'
import type { StateStore } from '$lib/utils'
interface Props {
noEditor?: boolean
@@ -32,7 +32,7 @@
onTestFlow?: () => void
job?: Job
isOwner?: boolean
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
onOpenDetails?: () => void
}
@@ -78,7 +78,7 @@
}
$effect(() => {
computeMissingInputWarnings(flowStore, $flowStateStore, flowInputsStore)
computeMissingInputWarnings(flowStore, flowStateStore, flowInputsStore)
})
</script>
@@ -55,7 +55,7 @@
let stepPropPicker = $derived(
getStepPropPicker(
$flowStateStore,
flowStateStore.val,
parentModule,
previousModule,
mod.id,
@@ -72,7 +72,7 @@
let iteratorFieldFocused = $state(false)
let iteratorGen: IteratorGen | undefined = $state(undefined)
let previewIterationArgs = $derived($flowStateStore[mod.id]?.previewArgs ?? {})
let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {})
function setExpr(code: string) {
if (mod.value.type === 'forloopflow') {
@@ -169,11 +169,11 @@
}
}
await tick()
if (!deepEqual(schema, $flowStateStore[flowModule.id]?.schema)) {
if (!$flowStateStore[flowModule.id]) {
$flowStateStore[flowModule.id] = { schema }
if (!deepEqual(schema, flowStateStore.val[flowModule.id]?.schema)) {
if (!flowStateStore.val[flowModule.id]) {
flowStateStore.val[flowModule.id] = { schema }
} else {
$flowStateStore[flowModule.id].schema = schema
flowStateStore.val[flowModule.id].schema = schema
}
}
} catch (e) {
@@ -195,7 +195,7 @@
let lastJobId: string | undefined = undefined
function onSelectedIdChange() {
if (!$flowStateStore?.[$selectedId]?.schema && flowModule) {
if (!flowStateStore?.val?.[$selectedId]?.schema && flowModule) {
reload(flowModule)
}
lastJobId = undefined
@@ -203,31 +203,30 @@
async function getLastJob() {
if (
!$flowStateStore ||
!flowStateStore ||
!flowModule.id ||
$flowStateStore[flowModule.id]?.previewResult === 'never tested this far' ||
!$flowStateStore[flowModule.id]?.previewJobId ||
!$flowStateStore[flowModule.id]?.previewWorkspaceId
flowStateStore.val[flowModule.id]?.previewResult === 'never tested this far' ||
!flowStateStore.val[flowModule.id]?.previewJobId
) {
return
}
if (
lastJobId == $flowStateStore[flowModule.id]?.previewJobId ||
lastJob?.id == $flowStateStore[flowModule.id]?.previewJobId ||
$flowStateStore[flowModule.id]?.previewSuccess == undefined
lastJobId == flowStateStore.val[flowModule.id]?.previewJobId ||
lastJob?.id == flowStateStore.val[flowModule.id]?.previewJobId ||
flowStateStore.val[flowModule.id]?.previewSuccess == undefined
) {
return
}
lastJobId = $flowStateStore[flowModule.id]?.previewJobId
lastJobId = flowStateStore.val[flowModule.id]?.previewJobId
const job = await JobService.getJob({
workspace: $flowStateStore[flowModule.id]?.previewWorkspaceId ?? '',
id: $flowStateStore[flowModule.id]?.previewJobId ?? '',
workspace: $workspaceStore ?? '',
id: flowStateStore.val[flowModule.id]?.previewJobId ?? '',
noCode: true
})
if (job && job.type === 'CompletedJob') {
lastJobId = $flowStateStore[flowModule.id]?.previewJobId
lastJobId = flowStateStore.val[flowModule.id]?.previewJobId
lastJob = job
}
}
@@ -251,9 +250,9 @@
let stepPropPicker = $derived(
$executionCount != undefined && failureModule
? getFailureStepPropPicker($flowStateStore, flowStore.val, previewArgs.val)
? getFailureStepPropPicker(flowStateStore, flowStore.val, previewArgs.val)
: getStepPropPicker(
$flowStateStore,
flowStateStore.val,
parentModule,
previousModule,
flowModule.id,
@@ -269,7 +268,7 @@
$effect(() => {
if (testJob && testJob.type === 'CompletedJob') {
lastJob = $state.snapshot(testJob)
} else if ($workspaceStore && $pathStore && flowModule?.id && $flowStateStore) {
} else if ($workspaceStore && $pathStore && flowModule?.id && flowStateStore) {
untrack(() => getLastJob())
}
})
@@ -361,7 +360,7 @@
on:fork={async () => {
const [module, state] = await fork(flowModule)
flowModule = module
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
}}
on:reload={async () => {
if (flowModule.value.type == 'script') {
@@ -380,14 +379,14 @@
const [module, state] = await createScriptFromInlineScript(
flowModule,
$selectedId,
$flowStateStore[flowModule.id].schema,
flowStateStore.val[flowModule.id].schema,
$pathStore
)
if (flowModule.value.type == 'rawscript') {
module.value.input_transforms = flowModule.value.input_transforms
}
flowModule = module
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
}}
/>
{/snippet}
@@ -539,7 +538,7 @@
class="px-1 xl:px-2"
bind:this={inputTransformSchemaForm}
pickableProperties={stepPropPicker.pickableProperties}
schema={$flowStateStore[$selectedId]?.schema ?? {}}
schema={flowStateStore.val[$selectedId]?.schema ?? {}}
previousModuleId={previousModule?.id}
bind:args={
() => {
@@ -567,7 +566,7 @@
bind:this={modulePreview}
mod={flowModule}
{noEditor}
schema={$flowStateStore[$selectedId]?.schema ?? {}}
schema={flowStateStore.val[$selectedId]?.schema ?? {}}
bind:testJob
bind:testIsLoading
bind:scriptProgress
@@ -23,7 +23,7 @@
let editor: SimpleEditor | undefined = $state(undefined)
let stepPropPicker = $derived(
getStepPropPicker(
$flowStateStore,
flowStateStore.val,
undefined,
undefined,
flowModule.id,
@@ -55,7 +55,7 @@
let isBranchAll = $derived(flowModule.value.type === 'branchall')
let isStopAfterIfEnabled = $derived(Boolean(flowModule.stop_after_if))
let isStopAfterAllIterationsEnabled = $derived(Boolean(flowModule.stop_after_all_iters_if))
let result = $derived($flowStateStore[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR)
let result = $derived(flowStateStore.val[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR)
let parentLoopId = $derived(checkIfParentLoop(flowStore.val))
</script>
@@ -23,7 +23,7 @@
let editor: SimpleEditor | undefined = $state(undefined)
let stepPropPicker = $derived(
getStepPropPicker(
$flowStateStore,
flowStateStore.val,
parentModule,
previousModule,
flowModule.id,
@@ -28,7 +28,7 @@
let editor: SimpleEditor | undefined = $state(undefined)
const result = $flowStateStore[$selectedId]?.previewResult ?? {}
const result = flowStateStore.val[$selectedId]?.previewResult ?? {}
let isSleepEnabled = $derived(Boolean(flowModule.sleep))
</script>
@@ -21,7 +21,7 @@
import AddProperty from '$lib/components/schema/AddProperty.svelte'
const { selectedId, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
const result = $flowStateStore[$selectedId]?.previewResult ?? {}
const result = flowStateStore.val[$selectedId]?.previewResult ?? {}
let editor: SimpleEditor | undefined = $state(undefined)
interface Props {
@@ -106,7 +106,7 @@
}
flowModule = module
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
}
</script>
@@ -145,7 +145,7 @@
const [module, state] = await pickFlow(path, summary, flowModule.id)
flowModule = module
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
}}
/>
{:else}
@@ -182,7 +182,7 @@
}
flowModule = module
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
}}
failureModule={$selectedId === 'failure'}
preprocessorModule={$selectedId === 'preprocessor'}
@@ -2,14 +2,14 @@
import FlowPreviewResult from '$lib/components/FlowPreviewResult.svelte'
import type { Job } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import type { Writable } from 'svelte/store'
import FlowCard from '../common/FlowCard.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import type { StateStore } from '$lib/utils'
interface Props {
job?: Job
isOwner?: boolean
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
noEditor: boolean
onOpenDetails?: () => void
}
@@ -37,7 +37,7 @@
let jobId: string | undefined = $state(undefined)
let job: Job | undefined = $state(undefined)
let previewIterationArgs = $derived($flowStateStore[mod.id]?.previewArgs ?? {})
let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {})
</script>
<Drawer bind:open={previewOpen} alwaysOpen size="75%">
@@ -1,15 +1,14 @@
import type { Schema } from '$lib/common'
import type { Flow, FlowModule } from '$lib/gen'
import type { Writable } from 'svelte/store'
import { loadFlowModuleState } from './flowStateUtils.svelte'
import { emptyFlowModuleState } from './utils'
import type { StateStore } from '$lib/utils'
export type FlowModuleState = {
schema?: Schema
previewResult?: any
previewArgs?: any
previewJobId?: string
previewWorkspaceId?: string
previewSuccess?: boolean
}
@@ -21,7 +20,7 @@ export type FlowState = Record<string, FlowModuleState>
* We also hold the data of the results of a test job, ran by the user.
*/
export async function initFlowState(flow: Flow, flowStateStore: Writable<FlowState>) {
export async function initFlowState(flow: Flow, flowStateStore: StateStore<FlowState>) {
const modulesState: FlowState = {}
await mapFlowModules(flow.value.modules, modulesState)
@@ -30,10 +29,10 @@ export async function initFlowState(flow: Flow, flowStateStore: Writable<FlowSta
? await loadFlowModuleState(flow.value.failure_module)
: emptyFlowModuleState()
flowStateStore.set({
flowStateStore.val = {
...modulesState,
failure: failureModule
})
}
}
/**
@@ -11,7 +11,7 @@ import {
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/scripts'
import { get, type Writable } from 'svelte/store'
import { get } from 'svelte/store'
import type { FlowModuleState, FlowState } from './flowState'
import { emptyFlowModuleState } from './utils'
import { NEVER_TESTED_THIS_FAR } from './models'
@@ -260,11 +260,8 @@ export async function createScriptFromInlineScript(
return pickScript(availablePath, flowModule.summary ?? '', flowModule.id, hash)
}
export function deleteFlowStateById(id: string, flowStateStore: Writable<FlowState>) {
flowStateStore.update((fss) => {
delete fss[id]
return fss
})
export function deleteFlowStateById(id: string, flowStateStore: FlowState) {
delete flowStateStore.val[id]
}
export function sliceModules(
@@ -298,7 +295,7 @@ export function sliceModules(
export async function insertNewPreprocessorModule(
flowStore: StateStore<ExtendedOpenFlow>,
flowStateStore: Writable<FlowState>,
flowStateStore: FlowState,
inlineScript?: {
language: RawScript['language']
},
@@ -323,15 +320,12 @@ export async function insertNewPreprocessorModule(
flowStore.val.value.preprocessor_module = module
flowStateStore.update((fss) => {
fss[module.id] = state
return fss
})
flowStateStore.val[module.id] = state
}
export async function insertNewFailureModule(
flowStore: StateStore<ExtendedOpenFlow>,
flowStateStore: Writable<FlowState>,
flowStateStore: FlowState,
inlineScript?: {
language: RawScript['language']
subkind: 'pgsql' | 'flow'
@@ -361,8 +355,5 @@ export async function insertNewFailureModule(
flowStore.val.value.failure_module = module
flowStateStore.update((fss) => {
fss[module.id] = state
return fss
})
flowStateStore.val[module.id] = state
}
@@ -1,5 +1,5 @@
import type { Flow, OpenFlow } from '$lib/gen'
import { writable, type Writable } from 'svelte/store'
import { writable } from 'svelte/store'
import { initFlowState, type FlowState } from './flowState'
import { sendUserToast } from '$lib/toast'
import type { StateStore } from '$lib/utils'
@@ -11,7 +11,7 @@ export const importFlowStore = writable<Flow | undefined>(undefined)
export async function initFlow(
flow: Flow,
flowStore: StateStore<Flow>,
flowStateStore: Writable<FlowState>
flowStateStore: StateStore<FlowState>
) {
await initFlowState(flow, flowStateStore)
flowStore.val = flow
@@ -9,14 +9,21 @@
import type { FlowEditorContext } from '../types'
import { Play } from 'lucide-svelte'
import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte'
import type { GraphModuleState } from '$lib/components/graph'
interface Props {
loading?: boolean
onRunPreview?: () => void
onJobDone?: () => void
localModuleStates?: Record<string, GraphModuleState>
}
let { loading = false, onRunPreview, onJobDone }: Props = $props()
let {
loading = false,
onRunPreview,
onJobDone,
localModuleStates = $bindable({})
}: Props = $props()
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -157,6 +164,7 @@
bind:selectedJobStepType
bind:branchOrIterationN
bind:rightColumnSelect
bind:localModuleStates
on:close={() => {
// keep the data in the preview content
deferContent = true
@@ -59,7 +59,7 @@
<span transition:fade={{ duration: 100 }} class="text-xs">Test flow</span>
{/if}
</Button>
{#if wide && (flowPreviewJob || individualStepTests)}
{#if wide && flowPreviewJob}
<div
class="flex flex-row items-center shadow-sm rounded-md mt-1"
in:fade={{ duration: 100, delay: 200 }}
@@ -70,6 +70,7 @@
let isOpen = $state(false)
$effect(() => {
filter
isOpen && flowJobs && untrack(() => updateItems())
})
</script>
@@ -150,7 +151,8 @@
class={twMerge(
'text-primary text-xs w-full text-left py-1 pl-2 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center',
items[idx].success == false ? 'text-red-400' : '',
'data-[highlighted]:bg-surface-hover'
'data-[highlighted]:bg-surface-hover',
items[idx].index == selected ? 'bg-surface-selected' : ''
)}
onClick={() => {
onSelectedIteration({
@@ -164,31 +164,34 @@
connectingData =
flowPropPickerConfig && pickableIds && Object.keys(pickableIds).includes(id)
? pickableIds[id]
: (flowStateStore?.[id]?.previewResult ?? {})
: (flowStateStore?.val?.[id]?.previewResult ?? {})
}
$effect(() => {
const args = [id, pickableIds, $flowPropPickerConfig, $flowStateStore] as const
const args = [id, pickableIds, $flowPropPickerConfig, flowStateStore] as const
untrack(() => updateConnectingData(...args))
})
function updateLastJob(flowStateStore: any | undefined) {
if (!flowStateStore || !id || flowStateStore[id]?.previewResult === 'never tested this far') {
if (
!flowStateStore ||
!id ||
flowStateStore.val[id]?.previewResult === 'never tested this far'
) {
return
}
lastJob = {
id: flowStateStore[id]?.previewJobId ?? '',
result: flowStateStore[id]?.previewResult,
id: flowStateStore.val[id]?.previewJobId ?? '',
result: flowStateStore.val[id]?.previewResult,
type: 'CompletedJob' as const,
workspace_id: flowStateStore[id]?.previewWorkspaceId ?? '',
success: flowStateStore[id]?.previewSuccess ?? undefined
success: flowStateStore.val[id]?.previewSuccess ?? undefined
}
}
$effect(() => {
if (testJob && testJob.type === 'CompletedJob') {
lastJob = $state.snapshot(testJob)
} else if (flowStateStore && $flowStateStore) {
untrack(() => updateLastJob($flowStateStore))
} else if (flowStateStore && flowStateStore) {
untrack(() => updateLastJob(flowStateStore))
}
})
@@ -268,7 +271,7 @@
{#if deletable && id && flowEditorContext?.flowStore && outputPickerVisible}
{@const flowStore = flowEditorContext?.flowStore.val}
{@const mod = flowStore?.value ? dfsPreviousResults(id, flowStore, false)[0] : undefined}
{#if mod && $flowStateStore?.[id]}
{#if mod && flowStateStore?.val?.[id]}
<ModuleTest bind:this={moduleTest} {mod} bind:testIsLoading bind:testJob />
{/if}
{/if}
@@ -36,9 +36,10 @@
import type { InlineScript, InsertKind } from '$lib/components/graph/graphBuilder.svelte'
import { refreshStateStore } from '$lib/svelte5Utils.svelte'
import type { GraphModuleState } from '$lib/components/graph'
import { writable, type Writable } from 'svelte/store'
import FlowStickyNode from './FlowStickyNode.svelte'
import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte'
import { ModulesTestStates } from '$lib/components/modulesTest.svelte'
import type { StateStore } from '$lib/utils'
interface Props {
sidebarSize?: number | undefined
@@ -51,7 +52,8 @@
workspace?: string | undefined
onTestUpTo?: ((id: string) => void) | undefined
onEditInput?: (moduleId: string, key: string) => void
localModuleStates?: Writable<Record<string, GraphModuleState>>
localModuleStates?: Record<string, GraphModuleState>
testModuleStates?: ModulesTestStates
aiChatOpen?: boolean
showFlowAiButton?: boolean
toggleAiChat?: () => void
@@ -64,7 +66,7 @@
individualStepTests?: boolean
flowJob?: Job | undefined
showJobStatus?: boolean
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
onDelete?: (id: string) => void
flowHasChanged?: boolean
}
@@ -80,7 +82,8 @@
workspace = $workspaceStore,
onTestUpTo,
onEditInput,
localModuleStates = writable({}),
localModuleStates = {},
testModuleStates = new ModulesTestStates(),
aiChatOpen,
showFlowAiButton,
toggleAiChat,
@@ -93,7 +96,7 @@
individualStepTests = false,
flowJob = undefined,
showJobStatus = false,
suspendStatus = writable({}),
suspendStatus = $bindable({ val: {} }),
onDelete,
flowHasChanged
}: Props = $props()
@@ -115,9 +118,9 @@
inlineScript?: InlineScript
): Promise<FlowModule[]> {
push(history, flowStore.val)
let module = emptyModule($flowStateStore, flowStore.val, kind == 'flow')
let module = emptyModule(flowStateStore.val, flowStore.val, kind == 'flow')
let state = emptyFlowModuleState()
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
if (wsFlow) {
;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id)
} else if (wsScript) {
@@ -139,14 +142,14 @@
} else if (inlineScript) {
const { language, kind, subkind, summary } = inlineScript
;[module, state] = await createInlineScriptModule(language, kind, subkind, module.id, summary)
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
if (kind == 'trigger') {
module.summary = 'Trigger'
} else if (kind == 'approval') {
module.summary = 'Approval'
}
}
$flowStateStore[module.id] = state
flowStateStore.val[module.id] = state
if (kind == 'approval') {
module.suspend = { required_events: 1, timeout: 1800 }
@@ -303,18 +306,16 @@
id: previousJobId[0].id
})
if ('result' in getJobResult) {
$flowStateStore[moduleId] = {
...($flowStateStore[moduleId] ?? {}),
flowStateStore.val[moduleId] = {
...(flowStateStore.val[moduleId] ?? {}),
previewResult: getJobResult.result,
previewJobId: previousJobId[0].id,
previewWorkspaceId: previousJobId[0].workspace_id,
previewSuccess: getJobResult.success
}
if (stepHistoryLoader) {
stepHistoryLoader.stepStates[moduleId].loadingJobs = false
}
}
$flowStateStore = $flowStateStore
}
}
$effect(() => {
@@ -388,12 +389,13 @@
editMode
{onTestUpTo}
{onEditInput}
flowModuleStates={$localModuleStates}
flowModuleStates={localModuleStates}
{testModuleStates}
{isOwner}
{individualStepTests}
{flowJob}
{showJobStatus}
{suspendStatus}
suspendStatus={suspendStatus.val}
{flowHasChanged}
onDelete={(id) => {
dependents = getDependentComponents(id, flowStore.val)
@@ -408,7 +410,7 @@
}
refreshStateStore(flowStore)
onDelete?.(id)
delete $flowStateStore[id]
delete flowStateStore.val[id]
}
if (Object.keys(dependents).length > 0) {
@@ -520,7 +522,6 @@
if (['branchone', 'branchall'].includes(detail.kind)) {
await addBranch(targetModules[detail.index ?? 0].id)
}
$flowStateStore = $flowStateStore
refreshStateStore(flowStore)
dispatch('change')
}
@@ -570,9 +571,8 @@
mod.id = newId
}
})
$flowStateStore[newId] = $flowStateStore[id]
delete $flowStateStore[id]
$flowStateStore = $flowStateStore
flowStateStore.val[newId] = flowStateStore.val[id]
delete flowStateStore.val[id]
refreshStateStore(flowStore)
$selectedId = newId
}}
@@ -70,7 +70,6 @@ function getFlowInput(
const topFlowInput = schemaToObject(schema, args)
const parentState = parentModule ? flowState[parentModule.id] : undefined
if (parentState && parentModule) {
if (
parentState.previewArgs &&
@@ -276,8 +275,9 @@ declare const results = ${JSON.stringify(results)};
*/
declare const previous_result: ${previousId ? JSON.stringify(results[previousId]) : 'any'};
${resume
? `
${
resume
? `
/**
* resume payload
*/
@@ -288,8 +288,8 @@ declare const resume: any
*/
declare const approvers: string
`
: ''
}
: ''
}
`
}
@@ -17,7 +17,7 @@
getContext<FlowEditorContext | undefined>('FlowEditorContext') || {}
onMount(() => {
testSteps?.updateStepArgs(id, $flowStateStore, flowStore?.val, previewArgs?.val)
testSteps?.updateStepArgs(id, flowStateStore, flowStore?.val, previewArgs?.val)
})
const input = $derived(testSteps?.getStepArgs(id)?.value)
@@ -44,7 +44,7 @@
{#if testSteps?.isArgManuallySet(id, key)}
<button
onclick={() => {
testSteps?.evalArg(id, key, $flowStateStore, flowStore?.val, previewArgs?.val)
testSteps?.evalArg(id, key, flowStateStore, flowStore?.val, previewArgs?.val)
}}
title="Re-evaluate input"
class="-my-1 ml-0.5 hover:text-primary dark:hover:text-primary dark:text-gray-500 text-gray-300"
+20 -20
View File
@@ -37,31 +37,31 @@ export type ExtendedOpenFlow = OpenFlow & {
export type FlowInputEditorState = {
selectedTab:
| 'inputEditor'
| 'history'
| 'savedInputs'
| 'json'
| 'captures'
| 'firstStepInputs'
| undefined
| 'inputEditor'
| 'history'
| 'savedInputs'
| 'json'
| 'captures'
| 'firstStepInputs'
| undefined
editPanelSize: number | undefined
payloadData: Record<string, any> | undefined
}
export type CurrentEditor =
| ((
| {
type: 'script'
editor: Editor
showDiffMode: () => void
hideDiffMode: () => void
diffMode: boolean
lastDeployedCode: string | undefined
}
| { type: 'iterator'; editor: SimpleEditor }
) & {
stepId: string
})
| {
type: 'script'
editor: Editor
showDiffMode: () => void
hideDiffMode: () => void
diffMode: boolean
lastDeployedCode: string | undefined
}
| { type: 'iterator'; editor: SimpleEditor }
) & {
stepId: string
})
| undefined
export type FlowEditorContext = {
@@ -74,7 +74,7 @@ export type FlowEditorContext = {
pathStore: Writable<string>
flowStore: StateStore<ExtendedOpenFlow>
flowInputEditorState: Writable<FlowInputEditorState>
flowStateStore: Writable<FlowState>
flowStateStore: StateStore<FlowState>
testSteps: TestSteps
saveDraft: () => void
initialPathStore: Writable<string>
+5 -6
View File
@@ -9,7 +9,7 @@ import {
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { cleanExpr, emptySchema } from '$lib/utils'
import { get, type Writable } from 'svelte/store'
import { get } from 'svelte/store'
import type { FlowModuleState } from './flowState'
import { type PickableProperties, dfs } from './previousResults'
import { NEVER_TESTED_THIS_FAR } from './models'
@@ -206,7 +206,7 @@ export function checkIfParentLoop(
export function updateDerivedModuleStatesFromTestJobs(
moduleId: string | undefined,
moduleTestStates: ModulesTestStates | undefined,
moduleStates: Writable<Record<string, GraphModuleState>> | undefined
moduleStates: Record<string, GraphModuleState> | undefined
) {
if (!moduleId || !moduleTestStates || !moduleStates) {
return
@@ -238,9 +238,8 @@ export function updateDerivedModuleStatesFromTestJobs(
}
}
// Update the store with test job states
moduleStates.update((currentStates) => ({
...currentStates,
return {
...moduleStates,
...newStates
}))
}
}
@@ -54,6 +54,8 @@
import AssetsOverflowedNode from './renderers/nodes/AssetsOverflowedNode.svelte'
import type { FlowGraphAssetContext } from '../flows/types'
import { ChangeTracker } from '$lib/svelte5Utils.svelte'
import type { ModulesTestStates } from '../modulesTest.svelte'
import { deepEqual } from 'fast-equals'
let useDataflow: Writable<boolean | undefined> = writable<boolean | undefined>(false)
@@ -73,6 +75,7 @@
maxHeight?: number | undefined
notSelectable?: boolean
flowModuleStates?: Record<string, GraphModuleState> | undefined
testModuleStates?: ModulesTestStates
selectedId?: Writable<string | undefined>
path?: string | undefined
newFlow?: boolean
@@ -95,7 +98,7 @@
individualStepTests?: boolean
flowJob?: Job | undefined
showJobStatus?: boolean
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus?: Record<string, { job: Job; nb: number }>
onDelete?: (id: string) => void
onInsert?: (detail: {
sourceId?: string
@@ -144,6 +147,7 @@
maxHeight = undefined,
notSelectable = false,
flowModuleStates = undefined,
testModuleStates = undefined,
selectedId = writable<string | undefined>(undefined),
path = undefined,
newFlow = false,
@@ -171,7 +175,7 @@
individualStepTests = false,
flowJob = undefined,
showJobStatus = false,
suspendStatus = writable({}),
suspendStatus = {},
flowHasChanged = false
}: Props = $props()
@@ -207,12 +211,16 @@
)
}
let lastNodes: [NodeLayout[], (Node & NodeLayout)[]] | undefined = undefined
function layoutNodes(nodes: NodeLayout[]): (Node & NodeLayout)[] {
type NodeDep = { id: string; parentIds?: string[]; offset?: number }
type NodePos = { position: { x: number; y: number } }
let lastNodes: [NodeDep[], (NodeDep & NodePos)[]] | undefined = undefined
function layoutNodes(nodes: NodeDep[]): (NodeDep & NodePos)[] {
let lastResult = lastNodes?.[1]
if (lastResult && nodes === lastNodes?.[0]) {
if (lastResult && deepEqual(nodes, lastNodes?.[0])) {
console.debug('layoutNodes', 'same nodes')
return lastResult
}
console.debug('layoutNodes', nodes.length)
let seenId: string[] = []
for (const n of nodes) {
if (seenId.includes(n.id)) {
@@ -222,7 +230,7 @@
}
let nodeWidths: Record<string, number> = {}
const nodes2 = nodes.map((n) => {
const nodes2: (NodeDep & NodePos)[] = nodes.map((n) => {
return { ...n, position: { x: 0, y: 0 } }
})
for (const n of topologicalSort(nodes)) {
@@ -238,7 +246,7 @@
}
}
const dag = dagStratify().id(({ id }: Node) => id)(nodes2)
const dag = dagStratify().id(({ id }: NodeDep & NodePos) => id)(nodes2)
let boxSize: any
try {
@@ -262,12 +270,11 @@
const yOffset = insertable ? 100 : 0
const newNodes = dag.descendants().map((des) => ({
...des.data,
id: des.data.id,
position: {
x: des.x
? // @ts-ignore
(des.data.data.offset ?? 0) +
(des.data.offset ?? 0) +
// @ts-ignore
des.x +
(fullSize ? fullWidth : width) / 2 -
@@ -349,7 +356,9 @@
}
}
let moduleTracker = new ChangeTracker($state.snapshot(modules))
let moduleTracker = new ChangeTracker(
$state.snapshot([modules, failureModule, preprocessorModule])
)
let nodes = $state.raw<Node[]>([])
let edges = $state.raw<Edge[]>([])
@@ -372,10 +381,23 @@
if (graph.error) {
return
}
let newGraph = graph
newGraph.nodes.sort((a, b) => b.id.localeCompare(a.id))
// console.log('compute')
;[nodes, edges] = computeAssetNodes(layoutNodes(newGraph.nodes), newGraph.edges)
let layoutedNodes = layoutNodes(
Object.values(graph.nodes).map((n) => ({
id: n.id,
parentIds: n.parentIds,
offset: n.data.offset ?? 0
}))
)
let newNodes: (Node & NodeLayout)[] = layoutedNodes.map((n) => {
return {
...n,
...graph.nodes[n.id]
}
})
;[nodes, edges] = computeAssetNodes(newNodes, graph.edges)
console.log('nodes', nodes)
await tick()
height = Math.max(...nodes.map((n) => n.position.y + NODE.height + 100), minHeight)
}
@@ -420,8 +442,11 @@
})
$effect(() => {
readFieldsRecursively(modules)
untrack(() => moduleTracker.track($state.snapshot(modules)))
untrack(() =>
moduleTracker.track($state.snapshot([modules, failureModule, preprocessorModule]))
)
})
let graph = $derived.by(() => {
moduleTracker.counter
return graphBuilder(
@@ -429,8 +454,9 @@
{
disableAi,
insertable,
flowModuleStates,
selectedId: $selectedId,
flowModuleStates: untrack(() => flowModuleStates),
testModuleStates: untrack(() => testModuleStates),
selectedId: untrack(() => $selectedId),
path,
newFlow,
cache,
@@ -445,18 +471,19 @@
flowHasChanged,
additionalAssetsMap: flowGraphAssetsCtx?.val.additionalAssetsMap
},
failureModule,
preprocessorModule,
untrack(() => failureModule),
untrack(() => preprocessorModule),
eventHandler,
success,
$useDataflow,
$selectedId,
untrack(() => $selectedId),
moving,
simplifiableFlow,
triggerNode ? path : undefined,
expandedSubflows
)
})
$effect(() => {
;[graph, allowSimplifiedPoll]
untrack(() => updateStores())
@@ -5,8 +5,8 @@ import { dfsByModule } from '../flows/previousResults'
import { defaultIfEmptyString } from '$lib/utils'
import type { GraphModuleState } from './model'
import { getFlowModuleAssets, type AssetWithAltAccessType } from '../assets/lib'
import type { Writable } from 'svelte/store'
import { assetDisplaysAsOutputInFlowGraph } from './renderers/nodes/AssetNode.svelte'
import type { ModulesTestStates, ModuleTestState } from '../modulesTest.svelte'
export type InsertKind =
| 'script'
@@ -80,6 +80,9 @@ export function buildPrefix(prefix: string | undefined, id: string): string {
export type NodeLayout = {
id: string
parentIds?: string[]
data: {
offset?: number
}
} & FlowNode
export type FlowNode =
@@ -129,7 +132,8 @@ export type ModuleN = {
parentIds: string[]
eventHandlers: GraphEventHandlers
moving: string | undefined
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
testModuleState: ModuleTestState | undefined
insertable: boolean
editMode: boolean
flowJob: Job | undefined
@@ -146,7 +150,7 @@ export type BranchAllStartN = {
id: string
branchIndex: number
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
insertable: boolean
branchOne: boolean
}
@@ -158,7 +162,7 @@ export type BranchAllEndN = {
offset: number
id: string
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
}
}
@@ -169,7 +173,7 @@ export type ForLoopEndN = {
id: string
eventHandlers: GraphEventHandlers
simplifiedTriggerView: boolean
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
}
}
@@ -179,7 +183,7 @@ export type ForLoopStartN = {
offset: number
id: string
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
selectedId: string | undefined
editMode: boolean
simplifiedTriggerView: boolean
@@ -219,7 +223,7 @@ export type BranchOneStartN = {
offset: number
id: string
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
selected: boolean
insertable: boolean
label: string
@@ -235,7 +239,7 @@ export type BranchOneEndN = {
offset: number
id: string
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
}
}
@@ -258,7 +262,7 @@ export type NoBranchN = {
offset: number
id: string
eventHandlers: GraphEventHandlers
flowModuleStates: Record<string, GraphModuleState> | undefined
flowModuleState: GraphModuleState | undefined
branchOne: boolean
label: string
branchIndex: number
@@ -291,22 +295,22 @@ export type AssetsOverflowedN = {
}
}
export function topologicalSort(nodes: NodeLayout[]): NodeLayout[] {
const nodeMap = new Map(nodes.map(n => [n.id, n]));
const result: NodeLayout[] = [];
const visited = new Set<string>();
export function topologicalSort(nodes: { id: string; parentIds?: string[] }[]): { id: string; parentIds?: string[] }[] {
const nodeMap = new Map(nodes.map((n) => [n.id, n]))
const result: { id: string; parentIds?: string[] }[] = []
const visited = new Set<string>()
function visit(id: string): void {
if (visited.has(id)) return;
visited.add(id);
if (visited.has(id)) return
visited.add(id)
const node = nodeMap.get(id)!;
node.parentIds?.forEach(visit);
result.push(node);
const node = nodeMap.get(id)!
node.parentIds?.forEach(visit)
result.push(node)
}
nodes.forEach(n => visit(n.id));
return result.reverse();
nodes.forEach((n) => visit(n.id))
return result.reverse()
}
// input2: InputNode,
@@ -330,6 +334,7 @@ export function graphBuilder(
disableAi: boolean
insertable: boolean
flowModuleStates: Record<string, GraphModuleState> | undefined
testModuleStates: ModulesTestStates | undefined
selectedId: string | undefined
path: string | undefined
newFlow: boolean
@@ -341,7 +346,7 @@ export function graphBuilder(
individualStepTests: boolean
flowJob: Job | undefined
showJobStatus: boolean
suspendStatus: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus: Record<string, { job: Job; nb: number }>
flowHasChanged: boolean
additionalAssetsMap?: Record<string, AssetWithAltAccessType[]>
},
@@ -360,18 +365,20 @@ export function graphBuilder(
// flowIsSimplifiable?: boolean
// }
): {
nodes: NodeLayout[]
nodes: { [key: string]: NodeLayout }
edges: Edge[]
error?: string | undefined
} {
console.debug('Building graph')
const nodes: NodeLayout[] = []
const edges: Edge[] = []
try {
if (!modules) {
return { nodes, edges }
return { nodes: {}, edges: [] }
}
const nodes: NodeLayout[] = []
const edges: Edge[] = []
function addNode(module: FlowModule, offset: number) {
const duplicated = nodes.find((n) => n.id === module.id)
if (duplicated) {
@@ -392,7 +399,8 @@ export function graphBuilder(
parentIds: [],
eventHandlers: eventHandlers,
moving: moving,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
testModuleState: extra.testModuleStates?.states?.[module.id],
insertable: extra.insertable,
editMode: extra.editMode,
isOwner: extra.isOwner,
@@ -612,7 +620,7 @@ export function graphBuilder(
offset: currentOffset,
id: module.id,
eventHandlers: eventHandlers,
flowModuleStates: extra.flowModuleStates
flowModuleState: extra.flowModuleStates?.[module.id]
},
type: 'branchAllEnd'
}
@@ -628,7 +636,7 @@ export function graphBuilder(
id: module.id,
branchIndex: -1,
eventHandlers: eventHandlers,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
branchOne: false,
label: 'No branches'
},
@@ -655,7 +663,7 @@ export function graphBuilder(
id: module.id,
branchIndex: branchIndex,
eventHandlers: eventHandlers,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
insertable: extra.insertable,
branchOne: false
},
@@ -703,7 +711,7 @@ export function graphBuilder(
simplifiedTriggerView,
eventHandlers: eventHandlers,
editMode: extra.editMode,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
selectedId: extra.selectedId
},
type: 'forLoopStart'
@@ -726,7 +734,7 @@ export function graphBuilder(
id: module.id,
eventHandlers: eventHandlers,
simplifiedTriggerView,
flowModuleStates: extra.flowModuleStates
flowModuleState: extra.flowModuleStates?.[module.id]
},
type: 'forLoopEnd'
}
@@ -800,7 +808,7 @@ export function graphBuilder(
data: {
offset: currentOffset,
eventHandlers: eventHandlers,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
id: module.id
},
type: 'branchOneEnd'
@@ -832,7 +840,7 @@ export function graphBuilder(
eventHandlers: eventHandlers,
insertable: extra.insertable,
preLabel: undefined,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
selected: false,
modules: module.value.default
},
@@ -870,7 +878,7 @@ export function graphBuilder(
branchIndex: branchIndex,
eventHandlers: eventHandlers,
insertable: extra.insertable,
flowModuleStates: extra.flowModuleStates,
flowModuleState: extra.flowModuleStates?.[module.id],
selected: false,
modules: branch.modules
},
@@ -1059,10 +1067,10 @@ export function graphBuilder(
}
}
return { nodes, edges }
return { nodes: Object.fromEntries(nodes.map((n) => [n.id, n])), edges }
} catch (e) {
return {
nodes: [],
nodes: {},
edges: [],
error: e
}
+9 -6
View File
@@ -1,5 +1,5 @@
import type { FlowStatusModule, Job } from '$lib/gen'
import type { Writable } from 'svelte/store'
import type { StateStore } from '$lib/utils'
import type { FlowState } from '../flows/flowState'
export type ModuleHost = 'workspace' | 'inline' | 'hub'
@@ -24,15 +24,18 @@ export type GraphModuleStates = {
}
export type DurationStatus = {
iteration_from?: number
iteration_total?: number
byJob: Record<string, { created_at?: number; started_at?: number; duration_ms?: number }>
}
export type GlobalIterationBounds = {
iteration_from?: number
iteration_total?: number
}
export type FlowStatusViewerContext = {
flowStateStore?: Writable<FlowState>
retryStatus: Writable<Record<string, number | undefined>>
suspendStatus: Writable<Record<string, { nb: number; job: Job }>>
flowStateStore?: FlowState
retryStatus: StateStore<Record<string, number | undefined>>
suspendStatus: StateStore<Record<string, { nb: number; job: Job }>>
hideDownloadInGraph?: boolean
hideTimeline?: boolean
hideNodeDefinition?: boolean
@@ -43,7 +43,7 @@
flowModuleStates: Record<string, GraphModuleState> | undefined
isOwner: boolean
flowJob: Job | undefined
suspendStatus?: Writable<Record<string, { job: Job; nb: number }>>
suspendStatus?: Record<string, { job: Job; nb: number }>
shouldOffsetInsertBtnDueToAssetNode?: boolean
}
} = $props()
@@ -73,7 +73,7 @@
data?.flowModuleStates?.[`${data.sourceId}-v`]?.type === 'WaitingForEvents'
)
const suspendStatus: Writable<Record<string, { job: Job; nb: number }>> | undefined = $derived(
let suspendStatus: Record<string, { job: Job; nb: number }> | undefined = $derived(
data?.suspendStatus
)
</script>
@@ -176,9 +176,9 @@
isOwner={data.isOwner}
light
/>
{:else if $suspendStatus && Object.keys($suspendStatus).length > 0}
{:else if suspendStatus && Object.keys(suspendStatus).length > 0}
<div class="flex gap-2 flex-col">
{#each Object.values($suspendStatus) as suspendCount (suspendCount.job.id)}
{#each Object.values(suspendStatus) as suspendCount (suspendCount.job.id)}
<FlowStatusWaitingForEvents
job={suspendCount.job}
workspaceId={$workspaceStore!}
@@ -20,7 +20,7 @@
selected={false}
bgColor={getStateColor(undefined, darkMode)}
bgHoverColor={getStateHoverColor(undefined, darkMode)}
borderColor={getStateColor(data?.flowModuleStates?.[data?.id]?.type, darkMode)}
borderColor={getStateColor(data?.flowModuleState?.type, darkMode)}
on:select={(e) => {
data?.eventHandlers?.select(e.detail)
}}
@@ -15,7 +15,7 @@
let { data }: Props = $props()
let borderStatus = $derived(
computeBorderStatus(data.branchIndex, 'branchall', data.flowModuleStates?.[data.id])
computeBorderStatus(data.branchIndex, 'branchall', data.flowModuleState)
)
</script>
@@ -15,7 +15,7 @@
let { data }: Props = $props()
let borderStatus = $derived(
computeBorderStatus(data.branchIndex + 1, 'branchone', data.flowModuleStates?.[data.id])
computeBorderStatus(data.branchIndex + 1, 'branchone', data.flowModuleState)
)
</script>
@@ -21,7 +21,7 @@
id={data.id}
hideId
bgColor={getStateColor(undefined, darkMode)}
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
borderColor={getStateColor(data.flowModuleState?.type, darkMode)}
on:select={(e) => {
setTimeout(() => data?.eventHandlers?.select(e.detail))
}}
@@ -33,7 +33,7 @@
selected={false}
id={data.id}
bgColor={getStateColor(undefined, darkMode)}
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
borderColor={getStateColor(data.flowModuleState?.type, darkMode)}
on:select={(e) => {
setTimeout(() => data?.eventHandlers?.select(e.detail))
}}
@@ -75,7 +75,7 @@
hideId
bgColor={getStateColor(undefined, darkMode)}
bgHoverColor={getStateHoverColor(undefined, darkMode)}
borderColor={getStateColor(computeStatus(data.flowModuleStates?.[data.id]), darkMode)}
borderColor={getStateColor(computeStatus(data.flowModuleState), darkMode)}
on:select={(e) => {
setTimeout(() => data?.eventHandlers?.select(e.detail))
}}
@@ -6,6 +6,7 @@
import NodeWrapper from './NodeWrapper.svelte'
import { getStateColor, getStateHoverColor } from '../../util'
import type { ModuleN } from '../../graphBuilder.svelte'
import { jobToGraphModuleState } from '$lib/components/modulesTest.svelte'
interface Props {
data: ModuleN['data']
@@ -13,20 +14,25 @@
let { data }: Props = $props()
let moduleState = $derived(data.flowModuleStates?.[data.id])
let state = $derived.by(() => {
return data.testModuleState
? (jobToGraphModuleState(data.testModuleState) ?? data.flowModuleState)
: data.flowModuleState
})
let flowJobs = $derived(
moduleState?.flow_jobs
state?.flow_jobs
? {
flowJobs: moduleState?.flow_jobs,
selected: moduleState?.selectedForloopIndex ?? 0,
selectedManually: moduleState?.selectedForLoopSetManually,
flowJobsSuccess: moduleState?.flow_jobs_success
flowJobs: state?.flow_jobs,
selected: state?.selectedForloopIndex ?? 0,
selectedManually: state?.selectedForLoopSetManually,
flowJobsSuccess: state?.flow_jobs_success
}
: (undefined as any)
)
let type = $derived.by(() => {
let typ = data.flowModuleStates?.[data.id]?.type
let typ = state?.type
if (!typ && flowJobs) {
return 'InProgress'
}
@@ -59,27 +65,22 @@
annotation={flowJobs &&
(data.module.value.type === 'forloopflow' || data.module.value.type === 'whileloopflow')
? 'Iteration: ' +
((moduleState?.selectedForloopIndex ?? 0) >= 0
? (moduleState?.selectedForloopIndex ?? 0) + 1
: moduleState?.flow_jobs?.length) +
((state?.selectedForloopIndex ?? 0) >= 0
? (state?.selectedForloopIndex ?? 0) + 1
: state?.flow_jobs?.length) +
'/' +
(moduleState?.iteration_total ?? '?')
(state?.iteration_total ?? '?')
: ''}
bgColor={getStateColor(
data.editMode ? undefined : type,
darkMode,
true,
moduleState?.skipped
)}
bgColor={getStateColor(data.editMode ? undefined : type, darkMode, true, state?.skipped)}
bgHoverColor={getStateHoverColor(
data.editMode ? undefined : type,
darkMode,
true,
moduleState?.skipped
state?.skipped
)}
moving={data.moving}
duration_ms={moduleState?.duration_ms}
retries={moduleState?.retries}
duration_ms={state?.duration_ms}
retries={state?.retries}
{flowJobs}
on:delete={(e) => {
data.eventHandlers.delete(e.detail, '')
@@ -108,7 +109,7 @@
isOwner={data.isOwner}
{type}
{darkMode}
skipped={moduleState?.skipped}
skipped={state?.skipped}
/>
<div class="absolute -bottom-10 left-1/2 transform -translate-x-1/2 z-10">
@@ -20,7 +20,7 @@
selected={false}
bgColor={getStateColor(undefined, darkMode)}
bgHoverColor={getStateHoverColor(undefined, darkMode)}
borderColor={getStateColor(data?.flowModuleStates?.[data?.id]?.type, darkMode)}
borderColor={getStateColor(data?.flowModuleState?.type, darkMode)}
on:select={(e) => {
setTimeout(() => data?.eventHandlers?.select(e.detail))
}}
@@ -1,17 +1,50 @@
import type { Job } from '$lib/gen'
import type { GraphModuleState } from './graph'
type moduleTestState = {
export type ModuleTestState = {
loading: boolean
cancel?: () => Promise<void>
testJob?: Job
hiddenInGraph?: boolean
}
export class ModulesTestStates {
states: Record<string, moduleTestState> = $state({})
states: Record<string, ModuleTestState> = $state({})
runTestCb?: (moduleId: string) => void
hideJobsInGraph() {
for (const state of Object.values(this.states)) {
state.hiddenInGraph = true
}
}
constructor(runTestCb?: (moduleId: string) => void) {
this.states = {}
this.runTestCb = runTestCb
}
}
export function jobToGraphModuleState(testState: ModuleTestState): GraphModuleState | undefined {
if (testState.hiddenInGraph) {
return undefined
} else if (testState.loading) {
return {
type: 'InProgress',
args: {}
}
} else if (testState.testJob) {
return {
args: testState.testJob.args,
type:
testState.testJob.type === 'QueuedJob'
? 'InProgress'
: testState.testJob['success']
? 'Success'
: 'Failure',
job_id: testState.testJob.id,
tag: testState.testJob.tag,
duration_ms: testState.testJob['duration_ms'],
started_at: testState.testJob.started_at
? new Date(testState.testJob.started_at).getTime()
: undefined
}
}
}
@@ -1,4 +1,3 @@
import { type Writable, get } from 'svelte/store'
import type { FlowState } from './flows/flowState'
import { NEVER_TESTED_THIS_FAR } from './flows/models'
import { JobService, type Flow, type FlowModule } from '$lib/gen'
@@ -76,7 +75,7 @@ export class StepHistoryLoader {
async loadIndividualStepsStates(
flow: Flow,
flowStateStore: Writable<FlowState>,
flowStateStore: FlowState,
workspaceId: string,
initialPath: string,
path: string
@@ -84,7 +83,7 @@ export class StepHistoryLoader {
// Collect all modules that need loading
const modulesToLoad: FlowModule[] = []
dfs(flow.value.modules, (module) => {
const prev = get(flowStateStore)[module.id]?.previewResult
const prev = flowStateStore.val[module.id]?.previewResult
if (!prev || prev === NEVER_TESTED_THIS_FAR) {
modulesToLoad.push(module)
// Initialize step state if it doesn't exist
@@ -128,16 +127,12 @@ export class StepHistoryLoader {
})
if ('result' in getJobResult) {
flowStateStore.update((state) => ({
...state,
[module.id]: {
...(state[module.id] ?? {}),
previewResult: getJobResult.result,
previewJobId: previousJobId[0].id,
previewWorkspaceId: previousJobId[0].workspace_id,
previewSuccess: getJobResult.success
}
}))
flowStateStore.val[module.id] = {
...(flowStateStore.val[module.id] ?? {}),
previewResult: getJobResult.result,
previewJobId: previousJobId[0].id,
previewSuccess: getJobResult.success
}
this.#stepStates[module.id].initial =
this.#stepStates[module.id].initial !== undefined
? this.#stepStates[module.id].initial
@@ -23,7 +23,7 @@
on:error
on:skipAll
getSteps={(driver, options) => {
const id = nextId($flowStateStore, flowStore.val)
const id = nextId(flowStateStore, flowStore.val)
const index = options?.indexToInsertAt ?? flowStore.val.value.modules.length
const steps = [
@@ -26,7 +26,7 @@
on:error
on:skipAll
getSteps={(driver, options) => {
const id = nextId($flowStateStore, flowStore.val)
const id = nextId(flowStateStore, flowStore.val)
const index = options?.indexToInsertAt ?? flowStore.val.value.modules.length
let tempId = ''
@@ -109,7 +109,7 @@
'We can refer to the result of previous steps using the results object: results.a or use static values like [1,2,3] in this case.',
onNextClick: () => {
updateFlowModuleById(flowStore.val, id, (module) => {
const newId = nextId($flowStateStore, flowStore.val)
const newId = nextId(flowStateStore, flowStore.val)
tempId = newId
if (module.value.type === 'forloopflow') {
@@ -134,8 +134,8 @@
]
}
$flowStateStore[newId] = emptyFlowModuleState()
let schema = $flowStateStore[newId].schema ?? { properties: {} }
flowStateStore.val[newId] = emptyFlowModuleState()
let schema = flowStateStore.val[newId].schema ?? { properties: {} }
schema.properties = {
x: {
type: 'string',
@@ -5,14 +5,12 @@
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import type { FlowState } from '$lib/components/flows/flowState'
import { importFlowStore, initFlow } from '$lib/components/flows/flowStore'
import { FlowService, type Flow } from '$lib/gen'
import { initialArgsStore, userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { decodeState, emptySchema, type StateStore } from '$lib/utils'
import { tick } from 'svelte'
import { writable } from 'svelte/store'
import { replaceScriptPlaceholderWithItsValues } from '$lib/hub'
import type { Trigger } from '$lib/components/triggers/utils'
@@ -56,7 +54,7 @@
schema: emptySchema()
}
})
const flowStateStore = writable<FlowState>({})
const flowStateStore = $state({ val: {} })
let draftTriggersFromUrl: Trigger[] | undefined = $state(undefined)
let selectedTriggerIndexFromUrl: number | undefined = $state(undefined)
@@ -13,8 +13,7 @@
import { initFlow } from '$lib/components/flows/flowStore'
import { goto } from '$lib/navigation'
import { afterNavigate, replaceState } from '$app/navigation'
import { writable } from 'svelte/store'
import type { FlowState } from '$lib/components/flows/flowState'
import { sendUserToast } from '$lib/toast'
import DiffDrawer from '$lib/components/DiffDrawer.svelte'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
@@ -60,7 +59,7 @@
schema: emptySchema()
}
})
const flowStateStore = writable<FlowState>({})
const flowStateStore = $state({ val: {} })
let loading = $state(false)
@@ -1006,11 +1006,11 @@
{#if job?.id}
<FlowStatusViewer
jobId={job?.id ?? ''}
on:jobsLoaded={({ detail }) => {
job = detail
onJobsLoaded={({ job: newJob }) => {
job = newJob
}}
on:done={(e) => {
job = e.detail
onDone={({ job: newJob }) => {
job = newJob
}}
initialJob={job}
workspaceId={$workspaceStore}
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { emptySchema, sendUserToast } from '$lib/utils'
import { emptySchema, sendUserToast, type StateStore } from '$lib/utils'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { onDestroy, onMount, setContext, untrack } from 'svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
@@ -68,7 +68,7 @@
})
let initialCode = JSON.stringify(flowStore, null, 4)
const flowStateStore = writable({} as FlowState)
const flowStateStore = $state({ val: {} }) as StateStore<FlowState>
const previewArgsStore = $state({ val: {} })
const scriptEditorDrawer = writable(undefined)
@@ -3,7 +3,6 @@
import FlowWrapper from '$lib/components/FlowWrapper.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getUserExt } from '$lib/user'
import { writable } from 'svelte/store'
loadUser()
@@ -31,10 +30,10 @@
}
})
let flowStateStore = writable({})
let flowStateStore = $state({ val: {} })
let customUi: FlowBuilderWhitelabelCustomUi = {
tagLabel: 'agent',
tagLabel: 'agent'
// disableAi: true
}
</script>