mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 08:02:19 +00:00
8e6b519a0d
* draft
* Phase 1: Remove deprecated granular flow AI tools
Simplify AI chat flow mode to use only YAML-based editing:
- Remove all commented-out granular tools (add_step, remove_step, set_code, etc.)
- Clean up FlowAIChatHelpers interface to only essential methods
- Update system prompts to focus on YAML-only workflow
- Remove unused imports and type definitions
This is part of a larger refactoring to simplify the flow editing
experience to a single YAML editing tool with automatic diff visualization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* use minified json
* use openflow in system prompt
* handle inputs
* cleaning
* cleaning
* diffmode in flowgraph
* remove acceptrejectmodule
* use new diff mode
* cleaning
* better props
* better logic
* cleaning
* accept reject logic
* use get set
* draft manager
* use diff manager
* draft
* Refactor flowDiffManager to be instance-based with auto-computation
- Remove singleton export, making it instantiable per FlowGraphV2
- Add afterFlow state tracking for auto-diff computation
- Add beforeInputSchema/afterInputSchema for schema change tracking
- Add $effect for reactive auto-computation when beforeFlow/afterFlow changes
- Add setAfterFlow() and setInputSchemas() methods
- Simplify accept/reject methods to just mark pending=false
- Add validation to throw error when accepting/rejecting without beforeFlow
- Update setSnapshot to accept undefined for clearing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor FlowGraphV2 to own diffManager instance
- Import and create diffManager instance per FlowGraphV2
- Remove onAcceptModule and onRejectModule props
- Add validation $effect to error if both diffBeforeFlow and moduleActions provided
- Add $effect to sync props (diffBeforeFlow or moduleActions) to diffManager
- Add $effect to watch current flow changes and update afterFlow
- Replace computedDiff with diffManager.moduleActions
- Use raw modules instead of merged flow (diffManager handles merging)
- Expose getDiffManager() and setBeforeFlow() methods
- Pass diffManager to graph context instead of callbacks
- Remove $inspect for removed props
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update FlowModuleSchemaMap to use FlowGraphV2's diffManager
- Remove import of flowDiffManager singleton
- Update setBeforeFlow to call graph.setBeforeFlow()
- Update setModuleActions and getModuleActions to use graph.getDiffManager()
- Add getDiffManager() proxy method
- Simplify handleAcceptModule and handleRejectModule to use new API
- Handle editor state separately from diff operations
- Remove diffBeforeFlow, moduleActions, onAcceptModule, onRejectModule props passed to FlowGraphV2
- Remove onAcceptModule and onRejectModule from Props interface and destructured props
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update FlowAIChat to use flowModuleSchemaMap's diffManager
- Remove import of flowDiffManager singleton
- Update revertToSnapshot to use flowModuleSchemaMap.getDiffManager()
- Add null check for diffManager before using
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Verify FlowGraphDiffViewer compatibility with refactored architecture
FlowGraphDiffViewer already uses the correct prop patterns:
- Before graph: moduleActions prop (display-only mode)
- After graph: diffBeforeFlow prop (full diff mode with auto-computation)
Each FlowGraphV2 instance creates its own diffManager, making the side-by-side
view work correctly with independent diff state per graph.
No code changes required.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update graph components to use diffManager instead of callbacks
- Update graphBuilder.svelte.ts to pass diffManager instead of onAcceptModule/onRejectModule
- Update InputNode and ModuleN type definitions with diffManager
- Update ModuleNode.svelte to pass diffManager to MapItem
- Update MapItem.svelte to pass diffManager to FlowModuleSchemaItem
- Update FlowModuleSchemaItem.svelte to use diffManager directly for accept/reject
- Replace callback-based accept/reject with direct diffManager calls
- Only show accept/reject buttons when beforeFlow exists and action is pending
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix removed modules not showing in diff viewer
Problem: After refactoring, removed modules were no longer appearing in the
diff viewer because we changed effectiveModules from using the merged flow
(which includes removed modules) to using raw modules.
Solution:
- Add mergedFlow state to flowDiffManager to store timeline's merged flow
- Add markRemovedAsShadowed parameter support for side-by-side view
- Store timeline.mergedFlow in auto-computation $effect
- Add getter for mergedFlow and setMarkRemovedAsShadowed method
- Clear mergedFlow in clearSnapshot()
- Update FlowGraphV2 to set markRemovedAsShadowed in diffManager
- Update effectiveModules/FailureModule/PreprocessorModule to use mergedFlow
The merged flow contains all modules including removed ones, enabling:
- Unified view: Removed modules appear in red with "removed" badge
- Side-by-side view: Removed modules show as shadowed in After graph
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify accept/reject logic by removing actions instead of toggling pending state
Previously, accepting or rejecting a module action would set pending to false but keep the action in the moduleActions map. This caused a bug where the $effect would overwrite moduleActions with fresh actions having pending: true, making accept/reject buttons reappear on previously handled modules.
Now, when a user accepts or rejects a module action, we remove it entirely from the moduleActions map. This is simpler and fixes the button reappearing issue.
Changes:
- acceptModule: Remove action from moduleActions instead of setting pending: false
- rejectModule: Remove action from moduleActions instead of setting pending: false
- checkAndClearSnapshot: Check if moduleActions is empty instead of checking pending states
- Fix typo: getModuleFromFrom → getModuleFromFlow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* cleaning
* fix logic
* make diff drawer part of manager
* accept submodules
* fixes
* Phase 4: Add checkAndApplyChanges() helper to flowDiffManager
- Added new checkAndApplyChanges() function to apply mergedFlow to flowStore when all changes are decided
- This replaces the old checkAndClearSnapshot() behavior and ensures flowStore is updated atomically
- Handles both flow structure and input schema updates
* Phase 2: Simplify acceptModule() - only modify mergedFlow
- Remove flowStore mutations from acceptModule()
- For removed modules: just delete the shadowed (__prefix) version from mergedFlow
- For added/modified: no action needed (already correct in mergedFlow)
- Call checkAndApplyChanges() to apply changes when all decided
* Phase 3: Simplify rejectModule() - only modify mergedFlow
- Remove all flowStore mutations from rejectModule()
- For added modules: delete from mergedFlow
- For removed modules: replace shadowed (__) module with original from beforeFlow
- For modified modules: restore old version in mergedFlow
- For Input schema: revert afterInputSchema
- Call checkAndApplyChanges() to apply changes when all decided
* Phase 5: Verify acceptAll/rejectAll work with new architecture
- acceptAll() and rejectAll() already pass options correctly to acceptModule/rejectModule
- They will automatically benefit from checkAndApplyChanges()
- No changes needed for this phase
* Phase 6: Remove FlowGraphV2 reactive effect that updates afterFlow
- Removed the (lines 252-266) that continuously updated afterFlow
- This effect created reactive loops when flowStore changed
- afterFlow should only be set once when AI generates changes via setFlowYaml()
- The initial sync effect (lines 226-250) is kept for prop-driven diff mode
* Phase 7: Update FlowAIChat setFlowYaml to use diffManager
- Changed setFlowYaml() to use diffManager.setAfterFlow() instead of modifying flowStore
- flowStore remains unchanged during AI review phase
- Changes are staged in mergedFlow for user review
- Only applied to flowStore when all changes are accepted/rejected
- Added error handling for missing diffManager
* Fix linter warnings
- Remove unused FlowTimeline type import
- Fix ChangeTracker initialization with proper type parameter
- Keep deleteModuleFromFlow and checkAndClearSnapshot for potential future use
* Update plan document with implementation status
- Mark all phases as complete
- Add commit references
- Update file checklist
- Add implementation summary at top of document
* Add comprehensive implementation summary document
- Detailed overview of architecture changes
- Before/after comparisons for each file
- Complete testing scenarios checklist
- Troubleshooting guide
- Migration notes and backwards compatibility info
* Show pending modules in editor panel
- Pass diffManager from FlowModuleSchemaMap to FlowEditorPanel
- Add effectiveModules derived value that uses mergedFlow when in diff mode
- Update module iteration to use effectiveModules instead of flowStore
- Allows users to view added/modified modules during AI review
- Fixes issue where clicking on pending modules showed nothing
* Add implementation summary for show pending modules feature
* fix
* shorter system prompt
* Fix Input schema diff mode issues
- Add Accept/Reject buttons to Input node (previously only showed Diff button)
- Pass diffManager to FlowInput component
- Add effectiveSchema derived value that uses afterInputSchema when in diff mode
- Add effectiveDisabled to prevent editing Input when reviewing AI changes
- Update FlowInputViewer to show pending schema changes
- Fixes issue where Input schema changes couldn't be accepted/rejected
- Fixes issue where pending Input schema wasn't visible in the panel
* Disable delete and move buttons when in pending mode
- Add effectiveDeletable derived value that checks diffManager.hasPendingChanges
- Replace all instances of deletable with effectiveDeletable in template
- Prevents delete/move operations when AI changes are being reviewed
- Delete and move buttons are hidden when there are pending changes
- Buttons reappear once all changes are accepted or rejected
- Prevents conflicting operations during review phase
* no move or delte when reviewing
* use context
* inline script reduction
* use json
* rollback to direct modif
* fix merge
* cleaning
* fix reject removed
* add set step code tool
* better prompt
* add back relevant tools
* add back accept reject
* use edit mode for pending
* fix input
* remove unneeded effect
* cleaner + bug fix
* fix failure and preprocessor
* fix show diff for failure module
* fix accept reject on failre module
* no auto add module to context
* cleaning
* add back effect
* cleaning
* fix multiple setflowjson
* track effectivemoduleactions for graph rendering
* nit prompt
* styling
* rm md files
* rm flake copy
* cleaning
* fix z index
* fix revert
* only change before after
* use add remove modify tools
* input + failure + preproc tools
* parsing issues
* nit
* use raw schema for tools
* resolve ref for gemini
* fix schema
* show test on graph
* much cleaner logic
* ignore empty assets
* Remove debug console.log statements from production code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove debug $inspect calls from FlowGraphV2
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add error logging to setFlowJson before re-throwing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Standardize null/undefined handling to prefer null
- Use .nullable().optional() instead of .nullish() in Zod schemas
- Simplify addModuleToFlow signature to use string | null
- Coerce undefined to null when extracting parsed args
- Simplify null checks to only check !== null
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove debug console.log from AI tool functions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Extract special module IDs to constants
Add SPECIAL_MODULE_IDS constant with INPUT, PREPROCESSOR, and FAILURE
to avoid magic strings throughout the flow AI chat code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add cleanup for diffDrawer reference on unmount
Prevents potential memory leaks by clearing the diffDrawer reference
when the FlowGraphV2 component is destroyed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Use structuredClone instead of JSON.parse(JSON.stringify())
structuredClone is more efficient and type-safe for deep cloning objects.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Cache module lookups in reconstructMergedFlow
Move getAllModulesMap and getAllModuleIds calls outside the loop to avoid
redundant recomputation. Track merged IDs incrementally as modules are added.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Revert "Use structuredClone instead of JSON.parse(JSON.stringify())"
This reverts commit a62ba5b980.
* cleaning
* allow delete
* better openflow for ai agents + truncate system prompt
* handle ai agent tools
* fix set code for tool
* fix wrong cancel request called
* mark tool calls as canceled
* get lang instructions
* use streamiing args
* give db url to claude
* fix revert
* save and clear when leaving editor
* keep whitespace in user message
* uniformize colors
* fix diff button
* remove db from backend claude
* remove move module tool
* no failure and preprocessor
* fix error given to llm
* fix z index
* fix ts errors
* cleaning
* fix add module logic
* fix(copilot): add 'tools' to branchPath description for aiagent containers
The branchPath parameter description was missing 'tools' option for aiagent
containers and didn't mention branchall support.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(copilot): correct AI agent tool IDs and summaries documentation
Tool summaries CAN contain spaces (they're human-readable descriptions).
Only tool IDs must avoid spaces.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(copilot): remove reference to non-existent set_flow_json tool
The set_module_code tool description referenced set_flow_json which
doesn't exist as an exposed tool (it's an internal helper).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(copilot): clarify inspect_inline_script is read-only
The tool description incorrectly suggested it could modify code.
This tool only inspects - use set_module_code to modify.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(copilot): clarify afterId behavior for AI agent tools
Updated wording to clarify that afterId can be used but is optional
for AI agent tools since tool order doesn't affect execution.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(copilot): remove unused id param from get_instructions_for_code_generation
The id parameter was only used to check for preprocessor, which is no
longer needed. Simplified the tool to only require the language param.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(copilot): add result format to search_scripts tool description
Helps AI understand what data format to expect from the tool.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(copilot): add result format to resource_type tool description
Helps AI understand what data format to expect from the tool and
provides example resource type names.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* nit
* Add support for adding branches to branchall/branchone via add_module
Previously, add_module could only add modules inside existing branches.
Now, using insideId with branchPath=null will add a NEW branch to a
branchall or branchone container.
API:
- add_module({ insideId: "my_branchall", branchPath: null, value: { summary: "New Branch", skip_failure: false, modules: [] } })
- add_module({ insideId: "my_branchone", branchPath: null, value: { summary: "Condition", expr: "...", modules: [] } })
Changes:
- Extended addModuleToFlow to handle branchPath=null case
- Updated validation to allow branchPath=null when adding branches
- Updated tool descriptions and system prompt documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* nit
* add remove branch tool
* check all ids for duplicates
* no dup
* nit
* cleaning
* fix dup ids
* split core.ts
* only mount diff drawer if useful
* remove wrong logic
* update exprs
* fix
* chore(flow): Add unit tests to flow diff manager (#7291)
* setup
* add basic tests for flowdiff
* add complex tests
* fix branch issue
* more complex tests
* add flow diff manager tests
* add utils
* better handling of moved case
* more tests for move case
* add buggy test case
* rm
* rework config
* cleaning
* fix config
* rm
* fix for reverting type change module
* all good
* rm
* add missing testmode
---------
Co-authored-by: Claude <noreply@anthropic.com>
646 lines
18 KiB
Svelte
646 lines
18 KiB
Svelte
<script lang="ts">
|
|
import { preventDefault, stopPropagation } from 'svelte/legacy'
|
|
|
|
import Popover from '$lib/components/Popover.svelte'
|
|
import { classNames, type StateStore } from '$lib/utils'
|
|
import {
|
|
Bed,
|
|
Database,
|
|
Gauge,
|
|
Move,
|
|
PhoneIncoming,
|
|
Repeat,
|
|
Square,
|
|
SkipForward,
|
|
Pin,
|
|
X,
|
|
Play,
|
|
Loader2,
|
|
TriangleAlert,
|
|
Timer,
|
|
Maximize2
|
|
} from 'lucide-svelte'
|
|
import { createEventDispatcher, getContext } from 'svelte'
|
|
import { fade } from 'svelte/transition'
|
|
import type { FlowEditorContext } from '../types'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import IdEditorInput from '$lib/components/IdEditorInput.svelte'
|
|
import { dfs } from '../dfs'
|
|
import { dfs as dfsPreviousResults } from '../previousResults'
|
|
import { Drawer } from '$lib/components/common'
|
|
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
|
import { getDependeeAndDependentComponents } from '../flowExplorer'
|
|
import { replaceId } from '../flowStore.svelte'
|
|
import FlowModuleSchemaItemViewer from './FlowModuleSchemaItemViewer.svelte'
|
|
import type { PropPickerContext } from '$lib/components/prop_picker'
|
|
import OutputPicker from '$lib/components/flows/propPicker/OutputPicker.svelte'
|
|
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
|
|
import type { FlowState } from '$lib/components/flows/flowState'
|
|
import { Button } from '$lib/components/common'
|
|
import ModuleTest from '$lib/components/ModuleTest.svelte'
|
|
import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte'
|
|
import type { Job } from '$lib/gen'
|
|
import {
|
|
getNodeColorClasses,
|
|
aiActionToNodeState,
|
|
type FlowNodeState
|
|
} from '$lib/components/graph'
|
|
import type { ModuleActionInfo } from '$lib/components/flows/flowDiff'
|
|
import DiffActionBar from './DiffActionBar.svelte'
|
|
import { getGraphContext } from '$lib/components/graph/graphContext'
|
|
|
|
interface Props {
|
|
selected?: boolean
|
|
deletable?: boolean
|
|
moduleAction: ModuleActionInfo | undefined
|
|
retry?: boolean
|
|
cache?: boolean
|
|
earlyStop?: boolean
|
|
skip?: boolean
|
|
suspend?: boolean
|
|
sleep?: boolean
|
|
mock?:
|
|
| {
|
|
enabled?: boolean
|
|
return_value?: unknown
|
|
}
|
|
| undefined
|
|
bold?: boolean
|
|
id?: string | undefined
|
|
label: string
|
|
path?: string
|
|
modType?: string | undefined
|
|
nodeState?: FlowNodeState
|
|
concurrency?: boolean
|
|
// TODO: Implement for this one. See how concurrency is implemented.
|
|
debouncing?: boolean
|
|
retries?: number | undefined
|
|
warningMessage?: string | undefined
|
|
isTrigger?: boolean
|
|
editMode?: boolean
|
|
alwaysShowOutputPicker?: boolean
|
|
loopStatus?: { type: 'inside' | 'self'; flow: 'forloopflow' | 'whileloopflow' } | undefined
|
|
icon?: import('svelte').Snippet
|
|
onTestUpTo?: ((id: string) => void) | undefined
|
|
inputTransform?: Record<string, any> | undefined
|
|
onUpdateMock?: (mock: { enabled: boolean; return_value?: unknown }) => void
|
|
onEditInput?: (moduleId: string, key: string) => void
|
|
flowJob?: Job | undefined
|
|
isOwner?: boolean
|
|
enableTestRun?: boolean
|
|
maximizeSubflow?: () => void
|
|
}
|
|
|
|
let {
|
|
selected = false,
|
|
deletable = false,
|
|
moduleAction = undefined,
|
|
retry = false,
|
|
cache = false,
|
|
earlyStop = false,
|
|
skip = false,
|
|
suspend = false,
|
|
sleep = false,
|
|
mock = { enabled: false },
|
|
bold = false,
|
|
id = undefined,
|
|
label,
|
|
path = '',
|
|
modType = undefined,
|
|
nodeState,
|
|
concurrency = false,
|
|
debouncing = false,
|
|
retries = undefined,
|
|
warningMessage = undefined,
|
|
isTrigger = false,
|
|
editMode = false,
|
|
alwaysShowOutputPicker = false,
|
|
loopStatus = undefined,
|
|
icon,
|
|
onTestUpTo,
|
|
inputTransform,
|
|
onUpdateMock,
|
|
onEditInput,
|
|
flowJob,
|
|
enableTestRun = false,
|
|
maximizeSubflow = undefined
|
|
}: Props = $props()
|
|
|
|
// Execution state takes priority over AI action colors
|
|
let effectiveState = $derived(nodeState ?? aiActionToNodeState(moduleAction?.action))
|
|
let colorClasses = $derived(getNodeColorClasses(effectiveState, selected))
|
|
|
|
const flowEditorContext = getContext<FlowEditorContext | undefined>('FlowEditorContext')
|
|
const flowInputsStore = flowEditorContext?.flowInputsStore
|
|
const flowStore = flowEditorContext?.flowStore
|
|
|
|
const flowGraphContext = getGraphContext()
|
|
const diffManager = flowGraphContext?.diffManager
|
|
|
|
let pickableIds: Record<string, any> | undefined = $state(undefined)
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
const propPickerContext = getContext<PropPickerContext>('PropPickerContext')
|
|
const flowPropPickerConfig = propPickerContext?.flowPropPickerConfig
|
|
const pickablePropertiesFiltered = propPickerContext?.pickablePropertiesFiltered
|
|
|
|
$effect(() => {
|
|
pickableIds = $pickablePropertiesFiltered?.priorIds
|
|
})
|
|
|
|
let editId = $state(false)
|
|
|
|
let newId: string = $state(id ?? '')
|
|
|
|
let moduleTest: ModuleTest | undefined = $state(undefined)
|
|
let testIsLoading = $state(false)
|
|
let hover = $state(false)
|
|
let connectingData: any | undefined = $state(undefined)
|
|
let outputPicker: OutputPicker | undefined = $state(undefined)
|
|
let testJob: any | undefined = $state(undefined)
|
|
let outputPickerBarOpen = $state(false)
|
|
|
|
let flowStateStore = $derived(flowEditorContext?.flowStateStore)
|
|
|
|
let stepHistoryLoader = getStepHistoryLoaderContext()
|
|
|
|
function updateConnectingData(
|
|
id: string | undefined,
|
|
pickableIds: Record<string, any> | undefined,
|
|
flowPropPickerConfig: any | undefined,
|
|
flowStateStore: StateStore<FlowState> | undefined
|
|
) {
|
|
if (!id) return
|
|
connectingData =
|
|
flowPropPickerConfig && pickableIds && Object.keys(pickableIds).includes(id)
|
|
? pickableIds[id]
|
|
: (flowStateStore?.val?.[id]?.previewResult ?? {})
|
|
}
|
|
$effect(() => {
|
|
updateConnectingData(id, pickableIds, $flowPropPickerConfig, flowStateStore)
|
|
})
|
|
|
|
let isConnectingCandidate = $derived(
|
|
!!id && !!$flowPropPickerConfig && !!pickableIds && Object.keys(pickableIds).includes(id)
|
|
)
|
|
|
|
const outputPickerVisible = $derived(
|
|
editMode && (isConnectingCandidate || alwaysShowOutputPicker) && !!id
|
|
)
|
|
|
|
const icon_render = $derived(icon)
|
|
|
|
let testRunDropdownOpen = $state(false)
|
|
|
|
let outputPickerInner: OutputPickerInner | undefined = $state(undefined)
|
|
let historyOpen = $derived.by(() => outputPickerInner?.getHistoryOpen?.() ?? false)
|
|
</script>
|
|
|
|
{#if deletable && id && editId}
|
|
{@const flowStore = flowEditorContext?.flowStore ?? undefined}
|
|
{@const getDeps = getDependeeAndDependentComponents(
|
|
id,
|
|
flowStore?.val?.value.modules ?? [],
|
|
flowStore?.val?.value.failure_module
|
|
)}
|
|
<Drawer bind:open={editId}>
|
|
<DrawerContent title="Edit Step Id {id}" on:close={() => (editId = false)}>
|
|
<div>
|
|
<IdEditorInput
|
|
buttonText="Edit Id "
|
|
btnClasses="!ml-1"
|
|
label=""
|
|
initialId={id}
|
|
acceptUnderScores
|
|
reservedIds={dfs(flowStore?.val?.value.modules ?? [], (x) => x.id)}
|
|
bind:value={newId}
|
|
onSave={({ oldId, newId }) => {
|
|
dispatch('changeId', { id: oldId, newId, deps: getDeps?.dependents ?? {} })
|
|
editId = false
|
|
}}
|
|
onClose={() => {
|
|
editId = false
|
|
}}
|
|
/>
|
|
<div class="mt-8">
|
|
<h3>Step Inputs Replacements</h3>
|
|
<div class="text-2xs text-primary pt-0.5">
|
|
Replace all occurrences of `results.<span class="font-bold">{id}</span>` with{' '}
|
|
results.<span class="font-bold">{newId}</span> in the step inputs of all steps that depend
|
|
on it.
|
|
</div>
|
|
<div class="pt-8 flex flex-col gap-y-4">
|
|
{#if Object.keys(getDeps?.dependents ?? {})?.length > 0}
|
|
{#each Object.entries(getDeps?.dependents ?? {}) as dependents}
|
|
<div>
|
|
<h4>{dependents[0]}</h4>
|
|
<div>
|
|
{#each dependents?.[1] as d}
|
|
<div>
|
|
<span class="font-mono text-sm">{d}</span> →
|
|
<span class="font-mono text-sm">{replaceId(d, id, newId)}</span>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{:else}
|
|
<div class="text-2xs text-primary"> No dependents </div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DrawerContent>
|
|
</Drawer>
|
|
{/if}
|
|
|
|
{#if deletable && id && flowStore && outputPickerVisible}
|
|
{@const flowStoreVal = flowStore.val}
|
|
{@const mod = flowStoreVal?.value ? dfsPreviousResults(id, flowStoreVal, false)[0] : undefined}
|
|
{#if mod && flowStateStore?.val?.[id]}
|
|
<ModuleTest
|
|
bind:this={moduleTest}
|
|
{mod}
|
|
bind:testIsLoading
|
|
bind:testJob
|
|
onJobDone={() => {
|
|
outputPickerInner?.setJobPreview?.()
|
|
}}
|
|
/>
|
|
{/if}
|
|
{/if}
|
|
|
|
<div class="relative">
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
class={classNames(
|
|
'w-full module flex rounded-md cursor-pointer max-w-full drop-shadow-base',
|
|
colorClasses.bg
|
|
)}
|
|
style="width: 275px; height: 34px;"
|
|
onmouseenter={() => (hover = true)}
|
|
onmouseleave={() => (hover = false)}
|
|
onpointerdown={stopPropagation(preventDefault((e) => dispatch('pointerdown', e)))}
|
|
>
|
|
{#if id}
|
|
<DiffActionBar moduleId={id} {moduleAction} {diffManager} {flowStore} />
|
|
{/if}
|
|
<div
|
|
class={classNames('absolute z-0 rounded-md outline-offset-0', colorClasses.outline)}
|
|
style={`width: 275px; height: 34px;`}
|
|
></div>
|
|
<div
|
|
class="absolute text-sm right-2 flex flex-row gap-1 z-10 transition-all duration-100"
|
|
style={`bottom: ${outputPickerBarOpen ? '-38px' : '-12px'}`}
|
|
>
|
|
{#if retry}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
{#if retries}<span class="text-red-400 mr-2">{retries}</span>{/if}
|
|
<Repeat size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Retries
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
|
|
{#if concurrency}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<Gauge size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Concurrency Limits
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if debouncing}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<Timer size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Debouncing
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if cache}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<Database size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Cached
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if earlyStop}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<Square size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
{isTrigger ? 'Stop early if there are no new events' : 'Early stop/break'}
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if skip}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<SkipForward size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Skip
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if suspend}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<PhoneIncoming size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Suspend
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if sleep}
|
|
<Popover notClickable>
|
|
<div
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
|
>
|
|
<Bed size={12} />
|
|
</div>
|
|
{#snippet text()}
|
|
Sleep
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
{#if mock?.enabled}
|
|
<Popover notClickable>
|
|
<button
|
|
transition:fade|local={{ duration: 200 }}
|
|
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
|
onclick={() => {
|
|
outputPicker?.toggleOpen()
|
|
}}
|
|
data-popover
|
|
>
|
|
<Pin size={12} />
|
|
</button>
|
|
{#snippet text()}
|
|
Pinned
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="flex flex-col w-full">
|
|
<FlowModuleSchemaItemViewer
|
|
{label}
|
|
{path}
|
|
{id}
|
|
{deletable}
|
|
{bold}
|
|
bind:editId
|
|
{hover}
|
|
{colorClasses}
|
|
>
|
|
{#snippet icon()}
|
|
{@render icon_render?.()}
|
|
{/snippet}
|
|
</FlowModuleSchemaItemViewer>
|
|
|
|
{#if outputPickerVisible}
|
|
<OutputPicker
|
|
bind:this={outputPicker}
|
|
{selected}
|
|
{hover}
|
|
{isConnectingCandidate}
|
|
{historyOpen}
|
|
{inputTransform}
|
|
id={id ?? ''}
|
|
bind:bottomBarOpen={outputPickerBarOpen}
|
|
{loopStatus}
|
|
{onEditInput}
|
|
>
|
|
{#snippet children({ allowCopy, isConnecting, selectConnection })}
|
|
<OutputPickerInner
|
|
{allowCopy}
|
|
prefix={'results'}
|
|
connectingData={isConnecting ? connectingData : undefined}
|
|
{mock}
|
|
{testJob}
|
|
moduleId={id}
|
|
onSelect={selectConnection}
|
|
{onUpdateMock}
|
|
{path}
|
|
{loopStatus}
|
|
rightMargin
|
|
historyOffset={{ mainAxis: 12, crossAxis: -9 }}
|
|
clazz="p-1"
|
|
isLoading={testIsLoading ||
|
|
(id ? stepHistoryLoader?.stepStates[id]?.loadingJobs : false)}
|
|
initial={id ? stepHistoryLoader?.stepStates[id]?.initial : undefined}
|
|
bind:this={outputPickerInner}
|
|
/>
|
|
{/snippet}
|
|
</OutputPicker>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if deletable}
|
|
{#if maximizeSubflow !== undefined}
|
|
{@render buttonMaximizeSubflow?.()}
|
|
{/if}
|
|
|
|
{#if id !== 'preprocessor'}
|
|
<!-- The `style="will-change: transform;"` fixes a bug in Safari where the close and move
|
|
and delete buttons would get clipped (unless an animation is running) -->
|
|
<div
|
|
class={twMerge('absolute -translate-y-[100%] top-2 right-4 h-7 p-1 min-w-7')}
|
|
style="will-change: transform;"
|
|
>
|
|
<button
|
|
class={twMerge(
|
|
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary',
|
|
hover || selected ? 'block' : '!hidden',
|
|
'shadow-md rounded-md',
|
|
'group-hover:block'
|
|
)}
|
|
onclick={stopPropagation(preventDefault((event) => dispatch('move')))}
|
|
title="Move"
|
|
>
|
|
<Move size={12} />
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
|
|
<div
|
|
class="absolute -translate-y-[100%] top-2 -right-2 h-7 p-1 min-w-7"
|
|
style="will-change: transform;"
|
|
>
|
|
<button
|
|
class={twMerge(
|
|
'trash center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-red-400 hover:text-white p-1',
|
|
selected || hover ? 'block' : '!hidden',
|
|
'group-hover:block',
|
|
'shadow-md rounded-md'
|
|
)}
|
|
title="Delete"
|
|
onclick={stopPropagation(
|
|
preventDefault((event) => dispatch('delete', { id, type: modType }))
|
|
)}
|
|
onpointerdown={stopPropagation(preventDefault(() => {}))}
|
|
>
|
|
<X size={12} />
|
|
</button>
|
|
</div>
|
|
|
|
{#if (id && Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}).length > 0) || Boolean(warningMessage)}
|
|
<Popover
|
|
style="will-change: transform;"
|
|
class={twMerge(
|
|
'absolute -translate-y-[100%] top-1 -left-1',
|
|
'flex items-center justify-center rounded-b-none rounded-md p-1 shadow-md duration-0 ',
|
|
id &&
|
|
Object.values($flowInputsStore?.[id]?.flowStepWarnings || {})?.some(
|
|
(x) => x.type === 'error'
|
|
)
|
|
? 'border-red-600 text-red-600 bg-red-100 hover:bg-red-300'
|
|
: ' text-yellow-600 bg-yellow-100 hover:bg-yellow-300'
|
|
)}
|
|
>
|
|
{#snippet text()}
|
|
<ul class="list-disc px-2">
|
|
{#if id}
|
|
{#each Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}) as m}
|
|
<li>
|
|
{m.message}
|
|
</li>
|
|
{/each}
|
|
{/if}
|
|
</ul>
|
|
{/snippet}
|
|
|
|
<TriangleAlert size={12} strokeWidth={2} />
|
|
</Popover>
|
|
{/if}
|
|
{:else if maximizeSubflow !== undefined}
|
|
{@render buttonMaximizeSubflow?.()}
|
|
{/if}
|
|
</div>
|
|
|
|
{#if editMode && enableTestRun && flowJob?.type !== 'QueuedJob'}
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
class="absolute top-1/2 -translate-y-1/2 -translate-x-[100%] -left-[0] flex items-center w-fit px-1 h-9 min-w-9"
|
|
onmouseenter={() => (hover = true)}
|
|
onmouseleave={() => (hover = false)}
|
|
>
|
|
{#if (hover || selected || testRunDropdownOpen) && outputPickerVisible}
|
|
<div transition:fade={{ duration: 100 }}>
|
|
{#if !testIsLoading}
|
|
<Button
|
|
size="xs"
|
|
title="Run"
|
|
variant="default"
|
|
btnClasses="px-1 py-1.5 bg-surface"
|
|
on:click={() => {
|
|
outputPicker?.toggleOpen(true)
|
|
moduleTest?.loadArgsAndRunTest()
|
|
}}
|
|
dropdownItems={[
|
|
{
|
|
label: 'Test up to here',
|
|
onClick: () => {
|
|
if (id) {
|
|
onTestUpTo?.(id)
|
|
}
|
|
}
|
|
}
|
|
]}
|
|
dropdownBtnClasses="!w-3 px-0.5"
|
|
bind:dropdownOpen={testRunDropdownOpen}
|
|
>
|
|
{#if testIsLoading}
|
|
<Loader2 size={12} class="animate-spin" />
|
|
{:else}
|
|
<Play size={12} />
|
|
{/if}
|
|
</Button>
|
|
{:else}
|
|
<Button
|
|
size="xs"
|
|
color="red"
|
|
variant="contained"
|
|
btnClasses="!h-[25.5px] !w-[36px] !p-1.5 gap-0.5"
|
|
on:click={async () => {
|
|
moduleTest?.cancelJob()
|
|
}}
|
|
>
|
|
<Loader2 size={10} class="animate-spin mr-0.5" />
|
|
<X size={14} />
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
{#snippet buttonMaximizeSubflow()}
|
|
<div class="absolute -translate-y-[100%] top-2 right-10 h-7 p-1">
|
|
<button
|
|
title="Expand subflow"
|
|
class={twMerge(
|
|
'center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary p-1',
|
|
'shadow-md rounded-md',
|
|
hover || selected ? 'opacity-100' : 'opacity-50'
|
|
)}
|
|
onclick={(e) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
maximizeSubflow?.()
|
|
}}
|
|
onpointerdown={(e) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
}}
|
|
>
|
|
<Maximize2 size={12} />
|
|
</button>
|
|
</div>
|
|
{/snippet}
|
|
|
|
<style>
|
|
.module:hover .trash {
|
|
display: flex !important;
|
|
}
|
|
</style>
|