mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 16:03:27 +00:00
* feat: responsive modal step panel for the flow editor in sessions On narrow layouts the flow editor's step-details pane opens as a modal (double-click a graph node) instead of a split pane, with a dock/float toggle. Scoped to sessions via allowModalPanel; the full-page editor is unchanged. - FlowEditor: modal/docked modes gated by mount width + allowModalPanel, small header (step-id Badge + subtle dock/close), standing double-click hint, and a per-step hint in the name tooltip - selectionManager: onSelectIntent hook so flow-level panels (settings, input, triggers…) open the modal on single click - PropPickerWrapper: collapse the prop picker until connect and animate it in via AnimatedPane (runs-page pattern), no blue connect ring in modal mode - StepInputGen: drop the TAB/Wand autocompletion button + spinner (feature still works via focus + Tab) - InputTransformForm: decouple the Help dropdown from the AI suggestion - FlowModuleHeader: move 'Save to workspace' into an ellipsis dropdown Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: loop editor rendering and nested splitpanes splitters in the sessions modal - Loop iterator/parallelism: keep the picker split pane (forceExpanded) so the editor fills its box and the picker shows; the collapse-until-connect mode stays for the step inputs - Remove the intrusive AI TAB/Wand autocompletion button from IteratorGen (generation still runs headless via focus + Tab) - Size the iterator connect plug and restyle the loop header/labels/toggles - Scope the global `.splitter-hidden` splitter-hiding rule to direct children so it no longer leaks into nested Splitpanes under the sessions preview Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: redesign flow step advanced settings as a single toggle-first column Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: taller step test pane by default and restyle advanced section titles Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: show flow run-settings params disabled when a setting is toggled off Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column for-loop panel reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column while-loop panel reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column branch panels reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: auto-open modal panel when creating an AI agent tool Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: redesign branch panels with card layout and shared predicate editor Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: remove per-setting status badges from flow map nodes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync package-lock after windmill-utils-internal bump Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: polish prop-picker plug button and branch panel layouts * fix: persist skip-if-stopped toggles in early stop settings * fix: open the step panel modal on demand and cap its width * fix: restore graph step setting badges, strip panel header chips instead * feat: docked panel header with detach action and open-details step menu * feat: width-based panel mode on every surface with inline detach action * refactor: single source for flow step settings and their defaults * docs: pin flow editor vocabulary in CONTEXT.md * fix: open the trigger panel on double click or a specific trigger * fix: keep module pickers inside their pane and dismissable * fix: drop the misleading chevron on the MCP tool entry * fix: resolve flow approvals against the job's workspace, not the nav one * refactor: derive the approval workspace from the job, not from callers * fix: restore S3 snippets and gate params while their setting is off * fix: restore branch mock controls and address review findings * chore: drop stray debug log from the flow map item * feat: pinned output section for loop and branch panels * fix: open the panel for deliberate navigation from the flow header * perf: mount branch predicate editors on demand * fix: skip predicate picker previews the previous step's result * fix: flow-level graph nodes open their panel on a single click * fix: open the step panel for AI chat selections, not for undo * chore: drop dead console.log and duplicated modalPanel doc * fix: re-sync expression editors and scope error-handler settings * fix: match the failure module exactly and ignore unselectable nodes * fix: keep concurrency editable, honour module cache_ttl, tighten panel ids * fix: open panel from indirect selections, use presence for value-driven toggles * fix: don't open settings on error-handler delete, flush editors on unmount * fix: guard editor destroy flush, keep retry kind reachable * refactor: name the run settings panel after the domain vocabulary * fix: only write editor flushes to the step they belong to * fix: bind step panels by id so a delete can't retarget editor writes * fix: don't let the trigger picker's escape close the drawer beneath it * docs: condense two comments to the constraint they record * fix: arbitrate escape through the overlay stack instead of deferring to it * fix: key nested step blocks by identity so anchored bindings can't go stale * fix: untrack the overlay-stack push and drop the frozen branch binding * chore: state the escape rationale once, key branch lists, format * fix: let the topmost overlay own escape instead of the graph * fix: keep the dynamic-input help box out of static template fields * fix: restore the graph connect on the for-loop iterator * fix: end connect mode with the modal and keep it to docked panels * fix: never enter graph connect mode from the modal panel * fix: reveal inserted steps, restore editor pane size, unleak the drawer stack * fix: keep the enable-AI popover reachable in session panes * feat: add the connect policy and its single armed slot * refactor: one picker for every expression input * refactor: route every connect through one armed slot * fix: give every connect button the same footprint * fix: keep the connect ring from showing through the button * fix: keep flow card actions right-aligned beside the detach button * fix: give the connect ring an opaque ground to mask against * feat: dock the panel back without reopening it * feat: dock the panel from the graph control bar * style: round the graph control bar and size its glyphs * style: customize the graph controls through their supported api * style: build the graph control bar from lucide icons * fix: use the graph's tooltip component in the zoom controls * style: pad the graph controls and enlarge their glyphs * style: pad the graph controls and put dock at the bar's end * refactor: give settings rows the same popover picker as other expressions * fix: pass the wrapper's pickable properties to nested inputs * refactor: stack step settings and render every expression through the step input form * feat: split loop panels into tabs and rework the approval form * feat: anchor drawers to their host pane and give them a size floor * fix: mark the loop iterator expression as required * refactor: badge ee-only toggles instead of a warning line * fix: flag an empty loop iterator expression as an error * refactor: pick the early-stop flow status from one toggle group * fix: keep parallel loops uncapped unless a limit is opted into * fix: scope the overlay stack to its host and disarm connect on dismissal * fix: anchor the trigger picker to its host pane * feat: move diff into the menu when the top bar is narrow * fix: gate the result logs toggle to the graph popover * feat: raise the modal-panel breakpoint to 1280 * fix: anchor flow editor popovers and fullscreen to their host pane * fix: anchor overlays to their host pane and mute them when hidden * fix: portal hosted modals and menus into the pane they anchor to * fix: keep non-listening dialogs off the overlay stack * fix: drop the topmost gate from confirmation dialogs * fix: silence overlays in a collapsed preview panel * feat: rework the branch panels with tabs, reordering and add/delete * refactor: fold the detached-panel chrome into the card header * fix: give every flow panel a titled card header * fix: stop the step panel oscillating on an auto-height editor * feat: consolidate script panel actions and restore branch predicate AI * fix: restore the logs toggle on the flow result popover * fix: collapse the idle property picker in modal step panels * fix: stop the docked pane scrolling alongside its panel * fix: space the last settings row off the panel bottom * revert: always show the property picker pane in step panels * chore: keep the inline script AI button identical to main * fix: ask for AI input suggestions on click, not on hover * fix: keep graph connects armed and remount the parallelism input * style: reveal the predicate AI button on row hover * style: give branch cards a handle and delete column * refactor: arbitrate flow overlay escape through Disposable * fix: give the popover picker its results and re-narrow the EE badge * docs: correct loopSubset and guard the modal width measurement * fix: insert picked properties at the cursor in expression inputs * fix: give the expanded-subflow panel the shared header chrome * style: rename the suspend setting to Suspend until approval/resume * feat: open a step's modal when clicking the step already selected * feat: add an auto/attached/detached toggle for the step panel * refactor: pick the step panel's placement from one named menu * refactor: keep the panel-mode module's exports to what is consumed * feat: show each configured setting's value on its badge * fix: carry the suspend rename into the step settings registry * docs: name both gestures in the step explore hint * test: pin where the step panel goes for a given width and preference --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
241 lines
7.9 KiB
Svelte
241 lines
7.9 KiB
Svelte
<script lang="ts">
|
|
import { getNonStreamingMetadataCompletion } from './lib'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import type { Flow } from '$lib/gen'
|
|
import { createEventDispatcher, getContext } from 'svelte'
|
|
import type { FlowEditorContext } from '../flows/types'
|
|
import type { PickableProperties } from '../flows/previousResults'
|
|
import YAML from 'yaml'
|
|
import { sliceModules } from '../flows/flowStateUtils.svelte'
|
|
import { dfs } from '../flows/dfs'
|
|
import { yamlStringifyExceptKeys } from './utils'
|
|
import type { FlowCopilotContext } from './flow'
|
|
import { stepInputCompletionEnabled } from '$lib/stores'
|
|
import type { SchemaProperty } from '$lib/common'
|
|
import FlowCopilotInputsModal from './FlowCopilotInputsModal.svelte'
|
|
import { copilotInfo } from '$lib/aiStore'
|
|
import Button from '../common/button/Button.svelte'
|
|
import { AIBtnClasses } from './chat/AIButtonStyle'
|
|
import { Check, Wand2 } from 'lucide-svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
let generatedContent = $state('')
|
|
let loading = $state(false)
|
|
interface Props {
|
|
/** Whether the input this belongs to has focus — a suggestion is only worth keeping
|
|
* while the user is still on that input. */
|
|
focused?: boolean
|
|
schemaProperty: SchemaProperty
|
|
pickableProperties?: PickableProperties | undefined
|
|
argName: string
|
|
}
|
|
|
|
let { focused = false, schemaProperty, pickableProperties = undefined, argName }: Props = $props()
|
|
|
|
/** The button takes focus when clicked, which blurs the input — without tracking that,
|
|
* asking for a suggestion would immediately look like leaving the field. */
|
|
let btnFocused = $state(false)
|
|
|
|
let abortController = new AbortController()
|
|
let newFlowInput = $state('')
|
|
|
|
const { flowStore, selectionManager } = getContext<FlowEditorContext>('FlowEditorContext')
|
|
const { generatedExprs } = getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
|
|
|
function createFlowInput() {
|
|
if (!newFlowInput) {
|
|
return
|
|
}
|
|
const properties = {
|
|
...(flowStore.val.schema?.properties as Record<string, SchemaProperty> | undefined),
|
|
[newFlowInput]: schemaProperty
|
|
}
|
|
const required = [
|
|
...((flowStore.val.schema?.required as string[] | undefined) ?? []),
|
|
newFlowInput
|
|
]
|
|
flowStore.val.schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
properties,
|
|
required,
|
|
type: 'object'
|
|
}
|
|
}
|
|
|
|
async function generateStepInput() {
|
|
if (generatedContent.length > 0 || loading) {
|
|
return
|
|
}
|
|
abortController = new AbortController()
|
|
loading = true
|
|
const flow: Flow = JSON.parse(JSON.stringify(flowStore.val))
|
|
const idOrders = dfs(flow.value.modules, (x) => x.id)
|
|
const upToIndex = idOrders.indexOf(selectionManager.getSelectedId())
|
|
if (upToIndex === -1) {
|
|
throw new Error('Could not find the selected id in the flow')
|
|
}
|
|
|
|
const flowDetails =
|
|
'Take into account the following information for never tested results:\n<flowDetails>\n' +
|
|
yamlStringifyExceptKeys(sliceModules(flow.value.modules, upToIndex, idOrders), ['lock']) +
|
|
'</flowDetails>'
|
|
try {
|
|
const availableData = {
|
|
results: pickableProperties?.priorIds,
|
|
flow_input: pickableProperties?.flow_input
|
|
}
|
|
const isInsideLoop = availableData.flow_input && 'iter' in availableData.flow_input
|
|
const user = `I'm building a workflow which is a DAG of script steps.
|
|
The current step is ${selectionManager.getSelectedId()}, you can find the details for the step and previous ones below:
|
|
${flowDetails}
|
|
Determine for the input "${argName}", what to pass either from the previous results or the flow inputs.
|
|
All possibles inputs either start with results. or flow_input. and are followed by the key of the input.
|
|
${
|
|
isInsideLoop
|
|
? 'As the step is in a loop, the iterator value is accessible as flow_input.iter.value.'
|
|
: 'As the step is not in a loop, flow_input.iter.value is not available.'
|
|
}
|
|
Here's a summary of the available data:
|
|
<available>
|
|
${YAML.stringify(availableData)}</available>
|
|
${
|
|
isInsideLoop
|
|
? 'Favor results and flow_input.iter.value over flow inputs.'
|
|
: 'Favor results over flow inputs'
|
|
}
|
|
If none of the available results are appropriate, are already used or are more appropriate for other inputs, you can also imagine new flow_input properties which we will create programmatically based on what you provide.
|
|
Reply with the most probable answer, do not explain or discuss.
|
|
Use javascript object dot notation to access the properties.
|
|
Only return the expression without any wrapper.`
|
|
generatedContent = await getNonStreamingMetadataCompletion(
|
|
[
|
|
{
|
|
role: 'user',
|
|
content: user
|
|
}
|
|
],
|
|
abortController
|
|
)
|
|
|
|
if (
|
|
pickableProperties &&
|
|
generatedContent.startsWith('flow_input.') &&
|
|
generatedContent.split('.')[1] &&
|
|
!(generatedContent.split('.')[1] in pickableProperties.flow_input)
|
|
) {
|
|
newFlowInput = generatedContent.split('.')[1]
|
|
} else {
|
|
newFlowInput = ''
|
|
}
|
|
} catch (err) {
|
|
if (!abortController.signal.aborted) {
|
|
sendUserToast('Could not generate step input: ' + err, true)
|
|
}
|
|
} finally {
|
|
loading = false
|
|
}
|
|
}
|
|
|
|
export function onKeyUp(event: KeyboardEvent) {
|
|
if (!$copilotInfo.enabled || !$stepInputCompletionEnabled) {
|
|
return
|
|
}
|
|
if (event.key === 'Tab') {
|
|
if (!loading && generatedContent) {
|
|
event.preventDefault()
|
|
dispatch('setExpr', generatedContent)
|
|
if (newFlowInput) {
|
|
openInputsModal = true
|
|
}
|
|
generatedContent = ''
|
|
}
|
|
} else {
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
function cancel() {
|
|
abortController.abort()
|
|
generatedContent = ''
|
|
}
|
|
|
|
// Drop a suggestion once the user has moved on, so the accept button can't sit there armed
|
|
// against an input nobody is editing. Deferred because focus moves through nothing on its
|
|
// way from the input to the button, and left alone while loading: the click that asked for
|
|
// the suggestion is itself what blurred the input.
|
|
$effect(() => {
|
|
if (focused || btnFocused) return
|
|
const timer = setTimeout(() => {
|
|
if (!focused && !btnFocused && !loading) {
|
|
cancel()
|
|
}
|
|
}, 150)
|
|
return () => clearTimeout(timer)
|
|
})
|
|
|
|
$effect(() => {
|
|
dispatch('showExpr', generatedContent)
|
|
})
|
|
|
|
$effect(() => {
|
|
dispatch('showExpr', $generatedExprs?.[argName] || '')
|
|
})
|
|
|
|
let openInputsModal = $state(false)
|
|
|
|
/** A suggestion is waiting to be accepted, rather than waiting to be asked for. */
|
|
let ready = $derived(!loading && generatedContent.length > 0)
|
|
|
|
function accept() {
|
|
dispatch('setExpr', generatedContent)
|
|
if (newFlowInput) {
|
|
openInputsModal = true
|
|
}
|
|
generatedContent = ''
|
|
}
|
|
|
|
// A suggestion costs a model call, so nothing generates on its own — this button is the
|
|
// only trigger, and the same control then accepts what it produced. Blur must not cancel:
|
|
// clicking here takes focus out of the input, which is the gesture that started the call.
|
|
function onClick() {
|
|
if (loading) cancel()
|
|
else if (ready) accept()
|
|
else generateStepInput()
|
|
}
|
|
</script>
|
|
|
|
{#if $copilotInfo.enabled && $stepInputCompletionEnabled}
|
|
<FlowCopilotInputsModal
|
|
on:confirmed={async () => {
|
|
createFlowInput()
|
|
}}
|
|
bind:open={openInputsModal}
|
|
inputs={[newFlowInput]}
|
|
/>
|
|
<!-- Sized to match FlowPlugConnect: it shares the control row with the connect plug. -->
|
|
<Button
|
|
variant="default"
|
|
size="xs3"
|
|
iconOnly
|
|
{loading}
|
|
clickableWhileLoading
|
|
title={loading ? 'Cancel' : ready ? 'Accept the suggestion' : 'Suggest an expression with AI'}
|
|
startIcon={{ icon: ready ? Check : Wand2 }}
|
|
btnClasses={twMerge(
|
|
AIBtnClasses(ready ? 'green' : 'default'),
|
|
'bg-surface overflow-clip flex p-0'
|
|
)}
|
|
wrapperClasses={twMerge(
|
|
'h-5 w-8 p-0 group-hover:opacity-100 transition-opacity',
|
|
// Same reveal-on-hover as the connect plug beside it, but a request in flight or a
|
|
// suggestion waiting to be accepted has to stay reachable once the pointer leaves.
|
|
loading || ready ? '' : 'opacity-0'
|
|
)}
|
|
on:click={onClick}
|
|
on:focus={() => (btnFocused = true)}
|
|
on:blur={() => (btnFocused = false)}
|
|
/>
|
|
{/if}
|