feat: preprocessor and error handler support

This commit is contained in:
HugoCasa
2025-05-30 17:26:15 +02:00
parent 50b9dd0bd7
commit 700c484bd3
24 changed files with 380 additions and 453 deletions

View File

@@ -61,7 +61,6 @@
}
let flowCopilotContext: FlowCopilotContext = {
currentStepStore: writable<string | undefined>(undefined),
shouldUpdatePropertyType: writable<{
[key: string]: 'static' | 'javascript' | undefined
}>({}),

View File

@@ -662,7 +662,6 @@
}
let flowCopilotContext: FlowCopilotContext = {
currentStepStore: writable<string | undefined>(undefined),
shouldUpdatePropertyType: writable<{
[key: string]: 'static' | 'javascript' | undefined
}>({}),

View File

@@ -600,13 +600,13 @@
path,
lastSavedCode,
lastDeployedCode,
diffMode,
applyCode: (code) => {
hideDiffMode()
editor?.reviewAndApplyCode(code)
},
showDiffMode
diffMode
}}
applyCode={() => {
hideDiffMode()
editor?.reviewAndApplyCode(code)
}}
{showDiffMode}
headerLeft={aiChatHeaderLeft}
headerRight={aiChatHeaderRight}
/>

View File

@@ -1,233 +0,0 @@
<script lang="ts">
import { copilotInfo, workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import { ScriptService, type FlowModule, type Script, type ScriptLang } from '$lib/gen'
import { APP_TO_ICON_COMPONENT } from '../icons'
import { sendUserToast } from '$lib/toast'
import { Wand2 } from 'lucide-svelte'
import SearchItems from '../SearchItems.svelte'
import { defaultIfEmptyString, emptyString } from '$lib/utils'
export let index: number
export let open: boolean | undefined
export let close: () => void
export let funcDesc: string
export let modules: FlowModule[]
export let trigger = false
export let disableAi = false
type Completion = {
path: string
summary: string
app: string
}
// state
let input: HTMLInputElement | undefined
let hubCompletions: Completion[] = []
let selectedCompletion: Completion | undefined = undefined
let lang: ScriptLang | undefined = undefined
console.log(lang)
let scripts: Script[] | undefined = undefined
let filteredItems: (Script & { marked?: string })[] = []
$: prefilteredItems = scripts ?? []
async function loadScripts(): Promise<void> {
const loadedScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
perPage: 300,
kinds: trigger ? 'trigger' : 'script'
})
scripts = loadedScripts
}
$: scripts == undefined && funcDesc?.length > 1 && loadScripts()
let doneTs = 0
async function getHubCompletions(text: string) {
try {
// make sure we display the results of the last request last
const ts = Date.now()
const scripts = (
await ScriptService.queryHubScripts({
text: `${text}`,
limit: 3,
kind: trigger ? 'trigger' : 'script'
})
).map((s) => ({
...s,
path: `hub/${s.version_id}/${s.app}/${s.summary.toLowerCase().replaceAll(/\s+/g, '_')}`
}))
if (ts < doneTs) return
doneTs = ts
hubCompletions = scripts
} catch (err) {
if (err.name !== 'CancelError') throw err
}
}
async function onGenerate() {
if (!selectedCompletion && !$copilotInfo.enabled) {
sendUserToast(
'Windmill AI is not enabled, you can activate it in the workspace settings',
true
)
return
}
//TODO gen
}
const dispatch = createEventDispatcher()
$: {
if (open) {
setTimeout(() => {
input?.focus()
}, 0)
}
}
</script>
<SearchItems
filter={funcDesc}
items={prefilteredItems}
bind:filteredItems
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="text-primary transition-all {funcDesc?.length > 0 ? 'w-96' : 'w-60'}">
<div>
<div class="flex p-2 relative">
<input
type="text"
bind:this={input}
bind:value={funcDesc}
on:input={() => {
if (funcDesc?.length > 2) {
getHubCompletions(funcDesc)
} else {
hubCompletions = []
}
}}
placeholder="Search {trigger ? 'triggers' : 'scripts'} or AI gen"
/>
{#if funcDesc?.length === 0}
<Wand2
size={14}
class="absolute right-4 top-1/2 -translate-y-1/2 fill-current opacity-70 text-violet-800 dark:text-violet-400"
/>
{/if}
</div>
{#if !disableAi && funcDesc?.length > 0}
<ul class="transition-all divide-y">
<li>
<button
class="py-2 gap-4 flex flex-row hover:bg-surface-hover transition-all items-center justify-between w-full"
on:click={() => {
lang = 'bun'
onGenerate()
close()
}}
>
<div class="flex items-center gap-2.5 px-2">
<div
class="rounded-md p-1 flex justify-center items-center bg-surface border w-6 h-6"
>
<Wand2 size={14} class="text-violet-800 dark:text-violet-400" />
</div>
<div class="text-left text-xs text-secondary">
Generate "{funcDesc}" in TypeScript
</div>
</div>
</button>
</li>
<li>
<button
class="py-2 gap-4 flex flex-row hover:bg-surface-hover transition-all items-center justify-between w-full"
on:click={() => {
lang = 'python3'
onGenerate()
close()
}}
>
<div class="flex items-center gap-2.5 px-2">
<div
class="rounded-md p-1 flex justify-center items-center bg-surface border w-6 h-6"
>
<Wand2 size={14} class="text-violet-800 dark:text-violet-400" />
</div>
<div class="text-left text-xs text-secondary">
Generate "{funcDesc}" in Python
</div>
</div>
</button>
</li>
</ul>
{/if}
{#if funcDesc?.length > 0 && filteredItems?.length > 0}
<div class="text-left mt-2">
<p class="text-xs text-secondary ml-2">Workspace {trigger ? 'Triggers' : 'Scripts'}</p>
<ul class="transition-all divide-y">
{#each filteredItems?.slice(0, 3) ?? [] as item (item.path)}
<li>
<button
class="py-2 gap-4 flex flex-row hover:bg-surface-hover transition-all items-center justify-between w-full"
on:click={() => {
dispatch('insert', { path: item.path, summary: item.summary })
close()
}}
>
<div class="flex items-center gap-2.5 px-2">
<div
class="rounded-md p-1 flex justify-center items-center bg-surface border w-6 h-6"
>
<svelte:component this={APP_TO_ICON_COMPONENT[item['app']]} />
</div>
<div class="text-left text-xs text-secondary">
{defaultIfEmptyString(item.summary, item.path)}
</div>
</div>
</button>
</li>
{/each}
</ul>
</div>
{/if}
{#if hubCompletions.length > 0}
<div class="text-left mt-2">
<p class="text-xs text-secondary ml-2">Hub {trigger ? 'Triggers' : 'Scripts'}</p>
<ul class="transition-all divide-y">
{#each hubCompletions as item (item.path)}
<li>
<button
class="py-2 gap-4 flex flex-row hover:bg-surface-hover transition-all items-center justify-between w-full"
on:click={() => {
selectedCompletion = item
close()
onGenerate()
}}
>
<div class="flex items-center gap-2.5 px-2">
<div
class="rounded-md p-1 flex justify-center items-center bg-surface border w-6 h-6"
>
<svelte:component this={APP_TO_ICON_COMPONENT[item['app']]} />
</div>
<div class="text-left text-xs text-secondary">
{item.summary ?? ''} ({item.app})
</div>
</div>
</button>
</li>
{/each}
</ul>
</div>
{/if}
</div>
</div>

View File

@@ -40,17 +40,18 @@
lastSavedCode?: string | undefined
lastDeployedCode?: string | undefined
diffMode: boolean
applyCode: (code: string) => void
showDiffMode: () => void
}
flowHelpers?: FlowAIChatHelpers & {
getFlow: () => OpenFlow
}
showDiffMode: () => void
applyCode: (code: string) => void
headerLeft?: Snippet
headerRight?: Snippet
}
let { scriptOptions, flowHelpers, headerLeft, headerRight }: Props = $props()
let { scriptOptions, flowHelpers, applyCode, showDiffMode, headerLeft, headerRight }: Props =
$props()
let instructions = $state('')
let loading = writable(false)
@@ -59,7 +60,6 @@
script: scriptOptions !== undefined,
flow: flowHelpers !== undefined
})
$inspect(allowedModes)
let mode: 'script' | 'flow' = $state(flowHelpers ? 'flow' : 'script')
async function updateMode(currentMode: 'script' | 'flow') {
@@ -78,10 +78,25 @@
loading,
currentReply,
canApplyCode: () => allowedModes.script,
applyCode: scriptOptions?.applyCode ?? (() => {})
applyCode
})
async function sendRequest(options: { removeDiff?: boolean; addBackCode?: boolean } = {}) {
export async function sendRequest(
options: {
removeDiff?: boolean
addBackCode?: boolean
instructions?: string
mode?: 'script' | 'flow'
lang?: ScriptLang | 'bunnative'
isPreprocessor?: boolean
} = {}
) {
if (options.mode) {
mode = options.mode
}
if (options.instructions) {
instructions = options.instructions
}
if (!instructions.trim()) {
return
}
@@ -112,14 +127,19 @@
throw new Error('No flow helpers passed')
}
if (mode === 'script' && !scriptOptions) {
if (mode === 'script' && !scriptOptions && !options.lang) {
throw new Error('No script options passed')
}
const lang = scriptOptions?.lang ?? options.lang ?? 'bun'
const isPreprocessor = scriptOptions?.path === 'preprocessor' || options.isPreprocessor
const userMessage =
mode === 'flow'
? prepareFlowUserMessage(oldInstructions, flowHelpers!.getFlow())
: await prepareScriptUserMessage(oldInstructions, scriptOptions!.lang, oldSelectedContext)
: await prepareScriptUserMessage(oldInstructions, lang, oldSelectedContext, {
isPreprocessor
})
messages.push({ role: 'user', content: userMessage })
await historyManager.saveChat(displayMessages, messages)
@@ -184,9 +204,7 @@
})
} else {
const tools: Tool<ScriptChatHelpers>[] = []
if (
['python3', 'php', 'bun', 'deno', 'nativets', 'bunnative'].includes(scriptOptions!.lang)
) {
if (['python3', 'php', 'bun', 'deno', 'nativets', 'bunnative'].includes(lang)) {
tools.push(resourceTypeTool)
}
if (oldSelectedContext.filter((c) => c.type === 'db').length > 0) {
@@ -196,7 +214,7 @@
...params,
tools,
helpers: {
getLang: () => scriptOptions!.lang
getLang: () => lang
}
})
}
@@ -260,7 +278,7 @@
addBackCode: options.withCode === false
})
if (options.withDiff) {
scriptOptions.showDiffMode()
showDiffMode()
}
}

View File

@@ -8,6 +8,7 @@
copilotSessionModel
} from '$lib/stores'
import { storeLocalSetting } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
$: providerModel = $copilotSessionModel ??
$copilotInfo.defaultModel ??
@@ -33,9 +34,12 @@
</svelte:fragment>
<svelte:fragment slot="content" let:close>
<div class="flex flex-col gap-1 p-1 min-w-24">
{#each $copilotInfo.aiModels.filter((m) => m.model !== providerModel.model) as providerModel}
{#each $copilotInfo.aiModels as providerModel}
<button
class="text-left text-xs hover:bg-surface-hover rounded-md p-1 font-normal"
class={twMerge(
'text-left text-xs hover:bg-surface-hover rounded-md p-1 font-normal',
providerModel.model === $copilotSessionModel?.model && 'bg-surface-hover'
)}
on:click={() => {
$copilotSessionModel = providerModel
storeLocalSetting(COPILOT_SESSION_MODEL_SETTING_NAME, providerModel.model)

View File

@@ -6,10 +6,15 @@
import { dfs } from '$lib/components/flows/previousResults'
import { getSubModules } from '$lib/components/flows/flowExplorer'
import AIChat from '../AIChat.svelte'
import type { FlowModule } from '$lib/gen'
import type { FlowModule, OpenFlow, ScriptLang } from '$lib/gen'
import { getIndexInNestedModules, getNestedModules } from './utils'
import type { FlowModuleState } from '$lib/components/flows/flowState'
import { getStringError } from '../utils'
import type { FlowAIChatHelpers } from './core'
import {
insertNewFailureModule,
insertNewPreprocessorModule
} from '$lib/components/flows/flowStateUtils'
let {
flowModuleSchemaMap,
@@ -24,8 +29,18 @@
const { exprsToSet } = getContext<FlowCopilotContext | undefined>('FlowCopilotContext') ?? {}
function getScriptOptions() {
const module = dfs($selectedId, $flowStore, false)[0]
function getModule(id: string) {
if (id === 'preprocessor') {
return $flowStore.value.preprocessor_module
} else if (id === 'failure') {
return $flowStore.value.failure_module
} else {
return dfs(id, $flowStore, false)[0]
}
}
function getScriptOptions(id: string) {
const module = getModule(id)
if (
module &&
@@ -47,38 +62,18 @@
path: module.id,
diffMode: $currentEditor.diffMode,
lastDeployedCode: $currentEditor.lastDeployedCode,
lastSavedCode: undefined,
showDiffMode: () => {
if (
$currentEditor &&
$currentEditor.type === 'script' &&
$currentEditor.stepId === module.id
) {
$currentEditor.showDiffMode()
}
},
applyCode: (code: string) => {
if (
$currentEditor &&
$currentEditor.type === 'script' &&
$currentEditor.stepId === module.id
) {
$currentEditor.editor.reviewAndApplyCode(code)
}
}
lastSavedCode: undefined
}
}
return undefined
}
let scriptOptions = $derived.by(getScriptOptions)
</script>
let scriptOptions = $derived.by(() => getScriptOptions($selectedId))
<AIChat
{headerLeft}
{scriptOptions}
flowHelpers={{
const flowHelpers: FlowAIChatHelpers & {
getFlow: () => OpenFlow
} = {
getFlow: () => $flowStore,
setCode: (code) => {
if (
@@ -92,7 +87,6 @@
}
},
insertStep: async (location, step) => {
console.log('insertStep', location, step)
const { index, modules } =
location.type === 'start'
? {
@@ -109,43 +103,65 @@
index: -1,
modules: getNestedModules($flowStore, location.inside, location.branchIndex)
}
: getIndexInNestedModules($flowStore, location.afterId)
: location.type === 'after'
? getIndexInNestedModules($flowStore, location.afterId)
: {
index: -1,
modules: $flowStore.value.modules
}
const indexToInsertAt = index + 1
let newModules: FlowModule[] | undefined
let newModules: FlowModule[] | undefined = undefined
switch (step.type) {
case 'rawscript': {
newModules = await flowModuleSchemaMap?.insertNewModuleAtIndex(
modules,
indexToInsertAt,
'script',
undefined,
undefined,
{
language: step.language,
kind: 'script',
subkind: 'flow'
}
)
const inlineScript = {
language: step.language,
kind: 'script' as const,
subkind: 'flow' as const
}
if (location.type === 'preprocessor') {
await insertNewPreprocessorModule(flowStore, flowStateStore, inlineScript)
} else if (location.type === 'failure') {
await insertNewFailureModule(flowStore, flowStateStore, inlineScript)
} else {
newModules = await flowModuleSchemaMap?.insertNewModuleAtIndex(
modules,
indexToInsertAt,
'script',
undefined,
undefined,
inlineScript
)
}
break
}
case 'script': {
newModules = await flowModuleSchemaMap?.insertNewModuleAtIndex(
modules,
indexToInsertAt,
'script',
{
path: step.path,
summary: '',
hash: undefined
}
)
const wsScript = {
path: step.path,
summary: '',
hash: undefined
}
if (location.type === 'preprocessor') {
await insertNewPreprocessorModule(flowStore, flowStateStore, undefined, wsScript)
} else if (location.type === 'failure') {
await insertNewFailureModule(flowStore, flowStateStore, undefined, wsScript)
} else {
newModules = await flowModuleSchemaMap?.insertNewModuleAtIndex(
modules,
indexToInsertAt,
'script',
wsScript
)
}
break
}
case 'forloop':
case 'branchall':
case 'branchone': {
if (location.type === 'preprocessor' || location.type === 'failure') {
throw new Error('Cannot insert a non-script module for preprocessing or error handling')
}
newModules = await flowModuleSchemaMap?.insertNewModuleAtIndex(
modules,
indexToInsertAt,
@@ -158,25 +174,37 @@
}
}
const newModule = newModules?.[indexToInsertAt]
if (location.type === 'preprocessor' || location.type === 'failure') {
$flowStateStore = $flowStateStore
$flowStore = $flowStore
return location.type
} else {
const newModule = newModules?.[indexToInsertAt]
if (!newModule) {
throw new Error('Failed to insert module')
if (!newModule) {
throw new Error('Failed to insert module')
}
if (['branchone', 'branchall'].includes(step.type)) {
await flowModuleSchemaMap?.addBranch(newModule)
}
$flowStateStore = $flowStateStore
$flowStore = $flowStore
return newModule.id
}
if (['branchone', 'branchall'].includes(step.type)) {
await flowModuleSchemaMap?.addBranch(newModule)
}
$flowStateStore = $flowStateStore
$flowStore = $flowStore
return newModule.id
},
removeStep: async (id) => {
console.log('removeStep', id)
const { modules } = getIndexInNestedModules($flowStore, id)
flowModuleSchemaMap?.selectNextId(id)
flowModuleSchemaMap?.removeAtId(modules, id)
if (id === 'preprocessor') {
$flowStore.value.preprocessor_module = undefined
} else if (id === 'failure') {
$flowStore.value.failure_module = undefined
} else {
const { modules } = getIndexInNestedModules($flowStore, id)
flowModuleSchemaMap?.removeAtId(modules, id)
}
if ($flowInputsStore) {
delete $flowInputsStore[id]
@@ -187,20 +215,22 @@
flowModuleSchemaMap?.updateFlowInputsStore()
},
getStepInputs: async (id) => {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
const inputs =
module.value.type === 'script' || module.value.type === 'rawscript'
? module.value.input_transforms
: {}
console.log('getStepInputs inputs', id, inputs)
return inputs
},
setStepInputs: async (id, inputs) => {
if (id === 'preprocessor') {
throw new Error('Cannot set inputs for preprocessor')
}
const regex = /\[\[(.+?)\]\]\s*\n([\s\S]*?)(?=\n\[\[|$)/g
const parsedInputs = Array.from(inputs.matchAll(regex)).map((match) => ({
@@ -219,7 +249,7 @@
}
exprsToSet?.set(argsToUpdate)
} else {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -247,7 +277,7 @@
$selectedId = id
},
getStepCode: (id) => {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -259,7 +289,7 @@
},
getModules: (id?: string) => {
if (id) {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
@@ -270,7 +300,7 @@
return $flowStore.value.modules
},
setBranchPredicate: async (id, branchIndex, expression) => {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -285,7 +315,7 @@
$flowStore = $flowStore
},
addBranch: async (id) => {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -296,7 +326,7 @@
$flowStore = $flowStore
},
removeBranch: async (id, branchIndex) => {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -315,7 +345,7 @@
if ($currentEditor && $currentEditor.type === 'iterator' && $currentEditor.stepId === id) {
$currentEditor.editor.setCode(expression)
} else {
const module = dfs(id, $flowStore, false)[0]
const module = getModule(id)
if (!module) {
throw new Error('Module not found')
}
@@ -326,5 +356,35 @@
$flowStore = $flowStore
}
}
}
let aiChat: AIChat | undefined = undefined
export async function generateStep(moduleId: string, lang: ScriptLang, instructions: string) {
flowHelpers.selectStep(moduleId)
aiChat?.sendRequest({
instructions: instructions,
mode: 'script',
lang: lang,
isPreprocessor: moduleId === 'preprocessor'
})
}
</script>
<AIChat
bind:this={aiChat}
{headerLeft}
{scriptOptions}
{flowHelpers}
showDiffMode={() => {
if ($currentEditor && $currentEditor.type === 'script') {
$currentEditor.showDiffMode()
}
}}
applyCode={(code: string) => {
if ($currentEditor && $currentEditor.type === 'script') {
$currentEditor.hideDiffMode()
$currentEditor.editor.reviewAndApplyCode(code)
}
}}
/>

View File

@@ -125,7 +125,17 @@ const insertLocationSchema = z.union([
})
.describe(
'Add a step at the start of a given branch of the given step (branchone or branchall only)'
)
),
z
.object({
type: z.literal('preprocessor')
})
.describe('Insert a preprocessor step (runs before the first step when triggered externally)'),
z
.object({
type: z.literal('failure')
})
.describe('Insert a failure step (only executed when the flow fails)')
])
type InsertLocation = z.infer<typeof insertLocationSchema>
@@ -352,7 +362,13 @@ export const flowTools: Tool<FlowAIChatHelpers>[] = [
? 'Adding a step at the start'
: parsedArgs.location.type === 'start_inside_forloop'
? `Adding a step at the start of the forloop ${parsedArgs.location.inside}`
: `Adding a step at the start of the branch ${parsedArgs.location.inside} ${parsedArgs.location.branchIndex}`
: parsedArgs.location.type === 'start_inside_branch'
? `Adding a step at the start of the branch ${parsedArgs.location.inside} ${parsedArgs.location.branchIndex}`
: parsedArgs.location.type === 'preprocessor'
? 'Adding a preprocessor step'
: parsedArgs.location.type === 'failure'
? 'Adding a failure step'
: 'Adding a step'
)
const id = await helpers.insertStep(parsedArgs.location, parsedArgs.step)
helpers.selectStep(id)
@@ -361,7 +377,8 @@ export const flowTools: Tool<FlowAIChatHelpers>[] = [
if (parsedArgs.step.type === 'rawscript') {
const langContext = getLangContext(parsedArgs.step.language, {
allowResourcesFetch: true
allowResourcesFetch: true,
isPreprocessor: parsedArgs.location.type === 'preprocessor'
})
return `Step ${id} added, here is some additional instructions to help you write the code:\n${langContext}`
} else {
@@ -555,6 +572,12 @@ Here are the variables you can use:
- Flow inputs are accessible as flow_input.property_name. The flow input doesn't have to exist already but make sure to add it to the schema if it doesn't.
- If you want to use static values, set them like in javascript (e.g. "hello", true, 3, etc...).
### Special modules
- Preprocessor: Runs before the first step when triggered externally. You cannot link its inputs. It's id is 'preprocessor'
- Error handler: Runs when the flow fails. When linking it's input, you can only refer to flow_input and error (error: { message, name, stack, step_id }). It's id is 'failure'.
Both modules only support a script or rawscript step. You cannot nest modules using foorloop/branchone/branchall.
## Resource types
On Windmill, credentials and configuration are stored in resources. Resource types define the format of the resource.
@@ -576,6 +599,12 @@ ${JSON.stringify(flow.schema ?? emptySchema())}
flow modules:
${YAML.stringify(flow.value.modules)}
preprocessor module:
${YAML.stringify(flow.value.preprocessor_module)}
failure module:
${YAML.stringify(flow.value.failure_module)}
## INSTRUCTIONS:
${instructions}`
}

View File

@@ -9,6 +9,7 @@ import { scriptLangToEditorLang } from '$lib/scripts'
import { getDbSchemas } from '$lib/components/apps/components/display/dbtable/utils'
import type { CodePieceElement, ContextElement } from '../context'
import type { Tool } from '../shared'
import { PYTHON_PREPROCESSOR_MODULE_CODE, TS_PREPROCESSOR_MODULE_CODE } from '$lib/script_helpers'
export function formatResourceTypes(
allResourceTypes: ResourceType[],
@@ -52,7 +53,7 @@ async function getResourceTypes(prompt: string, workspace: string) {
const TS_RESOURCE_TYPE_SYSTEM = `On Windmill, credentials and configuration are stored in resources and passed as parameters to main.
If you need credentials, you should add a parameter to \`main\` with the corresponding resource type inside the \`RT\` namespace: for instance \`RT.Stripe\`.
You should only use them if you need them to satisfy the user's instructions. Always use the RT namespace.`
You should only use them if you need them to satisfy the user's instructions. Always use the RT namespace.\n`
const TS_INLINE_TYPE_INSTRUCTION = `You must always inline the objects types instead of defining them separately. If INSTRUCTIONS ask you to use an already defined type **apart from the RT namespace**, you MUST inline it instead of using the type name. Explain to the user that you are inlining the type for better arguments inference.`
@@ -69,6 +70,24 @@ You need to **redefine** the type of the resources that are needed before the ma
Before defining each type, check if the class already exists using class_exists.
The resource type name has to be exactly as specified.`
const PREPROCESSOR_INSTRUCTION_BASE = `The current script is a preprocessor. It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, Postgres, or email) before passing it to the flow. This separates the trigger logic from the flow logic and keeps the auto-generated UI clean.
The returned object determines the parameter values passed to the flow.
e.g., \`{ b: 1, a: 2 }\` → Calls the flow with \`a = 2\` and \`b = 1\`, assuming the flow has two inputs called \`a\` and \`b\`.
The preprocessor receives a single parameter called event.
Here's a sample script which includes the event object definition:\n`
const TS_PREPROCESSOR_INSTRUCTION =
PREPROCESSOR_INSTRUCTION_BASE +
`\`\`\`typescript
${TS_PREPROCESSOR_MODULE_CODE}
\`\`\`\n`
const PYTHON_PREPROCESSOR_INSTRUCTION =
PREPROCESSOR_INSTRUCTION_BASE +
`\`\`\`python
${PYTHON_PREPROCESSOR_MODULE_CODE}
\`\`\``
export const SUPPORTED_CHAT_SCRIPT_LANGUAGES = [
'bunnative',
'nativets',
@@ -90,38 +109,45 @@ export const SUPPORTED_CHAT_SCRIPT_LANGUAGES = [
export function getLangContext(
lang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json',
{ allowResourcesFetch = false }: { allowResourcesFetch?: boolean } = {}
{
allowResourcesFetch = false,
isPreprocessor = false
}: { allowResourcesFetch?: boolean; isPreprocessor?: boolean; isFailure?: boolean } = {}
) {
const tsContext =
TS_RESOURCE_TYPE_SYSTEM +
(allowResourcesFetch
? `\nTo query the RT namespace, you can use the \`search_resource_types\` function.\n`
: '') +
TS_INLINE_TYPE_INSTRUCTION
(isPreprocessor
? TS_PREPROCESSOR_INSTRUCTION
: TS_RESOURCE_TYPE_SYSTEM +
(allowResourcesFetch
? `To query the RT namespace, you can use the \`search_resource_types\` function.\n`
: '')) + TS_INLINE_TYPE_INSTRUCTION
const mainFunctionName = isPreprocessor ? 'preprocessor' : 'main'
switch (lang) {
case 'bunnative':
case 'nativets':
return (
'The user is coding in TypeScript. On Windmill, it is expected that the script exports a single **async** function called `main`. You should use fetch (available globally, no need to import) and are not allowed to import any libraries.\n' +
`The user is coding in TypeScript. On Windmill, it is expected that the script exports a single **async** function called \`${mainFunctionName}\`. You should use fetch (available globally, no need to import) and are not allowed to import any libraries.\n` +
tsContext
)
case 'bun':
return (
'The user is coding in TypeScript (bun runtime). On Windmill, it is expected that the script exports a single **async** function called `main`. Do not call the main function. Libraries are installed automatically, do not show how to install them.\n' +
`The user is coding in TypeScript (bun runtime). On Windmill, it is expected that the script exports a single **async** function called \`${mainFunctionName}\`. Do not call the ${mainFunctionName} function. Libraries are installed automatically, do not show how to install them.\n` +
tsContext
)
case 'deno':
return (
'The user is coding in TypeScript (deno runtime). On Windmill, it is expected that the script exports a single **async** function called `main`. Do not call the main function. Libraries are installed automatically, do not show how to install them.\n' +
`The user is coding in TypeScript (deno runtime). On Windmill, it is expected that the script exports a single **async** function called \`${mainFunctionName}\`. Do not call the ${mainFunctionName} function. Libraries are installed automatically, do not show how to install them.\n` +
tsContext +
'\nYou can import deno libraries or you can import npm libraries like that: `import ... from "npm:{package}";`.'
)
case 'python3':
return (
'The user is coding in Python. On Windmill, it is expected the script contains at least one function called `main`. Do not call the main function. Libraries are installed automatically, do not show how to install them.' +
PYTHON_RESOURCE_TYPE_SYSTEM +
`${allowResourcesFetch ? `\nTo query the available resource types, you can use the \`search_resource_types\` function.` : ''}`
)
return `The user is coding in Python. On Windmill, it is expected the script contains at least one function called \`${mainFunctionName}\`. Do not call the ${mainFunctionName} function. Libraries are installed automatically, do not show how to install them.` +
isPreprocessor
? PYTHON_PREPROCESSOR_INSTRUCTION
: PYTHON_RESOURCE_TYPE_SYSTEM +
`${allowResourcesFetch ? `\nTo query the available resource types, you can use the \`search_resource_types\` function.` : ''}`
case 'php':
return (
'The user is coding in PHP. On Windmill, it is expected the script contains at least one function called `main`. The script must start with <?php.' +
@@ -276,7 +302,10 @@ const applyCodePieceToCodeContext = (codePieces: CodePieceElement[], codeContext
export async function prepareScriptUserMessage(
instructions: string,
language: ScriptLang | 'bunnative',
selectedContext: ContextElement[]
selectedContext: ContextElement[],
options: {
isPreprocessor?: boolean
} = {}
) {
let codeContext = 'CODE:\n'
let errorContext = 'ERROR:\n'
@@ -319,7 +348,7 @@ export async function prepareScriptUserMessage(
let userMessage = CHAT_USER_PROMPT.replace('{instructions}', instructions).replace(
'{lang_context}',
getLangContext(language, { allowResourcesFetch: true })
getLangContext(language, { allowResourcesFetch: true, ...options })
)
if (hasCode) {
userMessage += codeContext

View File

@@ -2,7 +2,6 @@ import { type InputTransform } from '$lib/gen'
import type { Writable } from 'svelte/store'
export type FlowCopilotContext = {
currentStepStore: Writable<string | undefined>
shouldUpdatePropertyType: Writable<{
[key: string]: 'static' | 'javascript' | undefined
}>

View File

@@ -68,6 +68,8 @@
export function getIsAiPanelClosed() {
return aiPanelSize === 0
}
let flowAIChat: FlowAIChat | undefined = undefined
</script>
<svelte:window
@@ -103,6 +105,12 @@
{newFlow}
bind:modules={$flowStore.value.modules}
on:reload
on:generateStep={({ detail }) => {
if (getIsAiPanelClosed()) {
toggleAiPanel()
}
flowAIChat?.generateStep(detail.moduleId, detail.lang, detail.instructions)
}}
/>
{/if}
</div>
@@ -140,7 +148,7 @@
/>
{/snippet}
<Pane bind:size={aiPanelSize} minSize={20}>
<FlowAIChat {flowModuleSchemaMap} headerLeft={aiChatHeaderLeft} />
<FlowAIChat bind:this={flowAIChat} {flowModuleSchemaMap} headerLeft={aiChatHeaderLeft} />
</Pane>
{/if}
</Splitpanes>

View File

@@ -9,7 +9,7 @@
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import PickHubScriptQuick from '../pickers/PickHubScriptQuick.svelte'
import { type Script, type FlowModule, type ScriptLang, type HubScriptKind } from '$lib/gen'
import { type Script, type ScriptLang, type HubScriptKind } from '$lib/gen'
import ListFiltersQuick from '$lib/components/home/ListFiltersQuick.svelte'
import { Folder, User } from 'lucide-svelte'
import type { FlowEditorContext } from '../../flows/types'
@@ -31,8 +31,6 @@
export let disableAi = false
export let preFilter: 'all' | 'workspace' | 'hub' = 'hub'
export let funcDesc: string
export let index: number
export let modules: FlowModule[]
export let owners: string[] = []
export let loading = false
export let small = false
@@ -52,8 +50,6 @@
}
let lang: ScriptLang | undefined = undefined
console.log(lang)
let selectedCompletion: HubCompletion | undefined = undefined
let filteredWorkspaceItems: (Script & { marked?: string })[] = []
@@ -93,15 +89,24 @@
}
async function onGenerate() {
if (!selectedCompletion && !$copilotInfo.enabled) {
if (!$copilotInfo.enabled) {
sendUserToast(
'Windmill AI is not enabled, you can activate it in the workspace settings',
true
)
return
}
//TODO gen
dispatch('close')
console.log('ongenerate', selectedKind, lang, funcDesc)
dispatch('new', {
kind: selectedKind,
inlineScript: {
language: lang,
kind: selectedKind,
subkind: 'flow',
summary,
instructions: funcDesc
}
})
}
let openScriptSettings = false

View File

@@ -272,6 +272,7 @@
editor,
stepId: flowModule.id,
showDiffMode,
hideDiffMode,
diffMode,
lastDeployedCode
})

View File

@@ -19,6 +19,7 @@ import { loadSchemaFromModule } from './flowInfers'
import { nextId } from './flowModuleNextId'
import { findNextAvailablePath } from '$lib/path'
import type { ExtendedOpenFlow } from './types'
import { emptySchema } from '$lib/utils'
export async function loadFlowModuleState(flowModule: FlowModule): Promise<FlowModuleState> {
try {
@@ -303,11 +304,11 @@ export async function insertNewPreprocessorModule(
},
wsScript?: { path: string; summary: string; hash: string | undefined }
) {
var module: FlowModule = {
let module: FlowModule = {
id: 'preprocessor',
value: { type: 'identity' }
}
var state = emptyFlowModuleState()
let state = emptyFlowModuleState()
if (inlineScript) {
;[module, state] = await createInlineScriptModule(
@@ -330,3 +331,44 @@ export async function insertNewPreprocessorModule(
return fss
})
}
export async function insertNewFailureModule(
flowStore: Writable<ExtendedOpenFlow>,
flowStateStore: Writable<FlowState>,
inlineScript?: {
language: RawScript['language']
subkind: 'pgsql' | 'flow'
instructions?: string
},
wsScript?: { path: string; summary: string; hash: string | undefined }
) {
let module: FlowModule = {
id: 'failure',
value: { type: 'identity' }
}
let state: FlowModuleState = {
schema: emptySchema(),
previewResult: NEVER_TESTED_THIS_FAR
}
if (inlineScript) {
;[module, state] = await createInlineScriptModule(
inlineScript.language,
'failure',
inlineScript.subkind,
'failure'
)
} else if (wsScript) {
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
}
flowStore.update((fs) => {
fs.value.failure_module = module
return fs
})
flowStateStore.update((fss) => {
fss[module.id] = state
return fss
})
}

View File

@@ -1,58 +1,43 @@
<script lang="ts">
import type { FlowEditorContext } from '../types'
import { getContext } from 'svelte'
import { classNames, emptySchema } from '$lib/utils'
import type { FlowModuleState } from '../flowState'
import { NEVER_TESTED_THIS_FAR } from '../models'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import { fade } from 'svelte/transition'
import { createEventDispatcher, getContext } from 'svelte'
import { classNames } from '$lib/utils'
import { Bug, X } from 'lucide-svelte'
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
import { createInlineScriptModule, pickScript } from '$lib/components/flows/flowStateUtils'
import type { FlowModule, RawScript } from '$lib/gen'
import { insertNewFailureModule } from '$lib/components/flows/flowStateUtils'
import type { RawScript, ScriptLang } from '$lib/gen'
import { twMerge } from 'tailwind-merge'
export let small: boolean
const dispatch = createEventDispatcher<{
generateStep: { moduleId: string; instructions: string; lang: ScriptLang }
}>()
const { selectedId, flowStateStore, flowStore } =
getContext<FlowEditorContext>('FlowEditorContext')
async function insertNewFailureModule(
async function insertFailureModule(
inlineScript?: {
language: RawScript['language']
subkind: 'pgsql' | 'flow'
instructions?: string
},
wsScript?: { path: string; summary: string; hash: string | undefined }
) {
var module: FlowModule = {
id: 'failure',
value: { type: 'identity' }
}
var state: FlowModuleState = {
schema: emptySchema(),
previewResult: NEVER_TESTED_THIS_FAR
}
await insertNewFailureModule(flowStore, flowStateStore, inlineScript, wsScript)
if (inlineScript) {
;[module, state] = await createInlineScriptModule(
inlineScript.language,
'failure',
inlineScript.subkind,
'failure'
)
} else if (wsScript) {
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
if (inlineScript?.instructions) {
dispatch('generateStep', {
moduleId: 'failure',
instructions: inlineScript.instructions,
lang: inlineScript.language
})
}
$flowStore.value.failure_module = module
$flowStateStore[module.id] = state
$selectedId = 'failure'
$flowStore = $flowStore
}
const { currentStepStore: copilotCurrentStepStore } =
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -62,23 +47,18 @@
id="flow-editor-error-handler"
class={classNames(
'z-10',
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : 'cursor-pointer',
'border transition-colors duration-[400ms] ease-linear rounded-sm px-2 py-1 gap-2 bg-surface text-sm flex items-center flex-row',
'cursor-pointer border transition-colors duration-[400ms] ease-linear rounded-sm px-2 py-1 gap-2 bg-surface text-sm flex items-center flex-row',
$selectedId?.includes('failure')
? 'outline outline-offset-1 outline-2 outline-slate-900 dark:outline-slate-900/0 dark:bg-surface-secondary dark:border-gray-400'
: ''
)}
style="min-width: {small ? '200px' : '230px'}; max-width: 275px;"
on:click={() => {
if ($copilotCurrentStepStore !== undefined) return
if ($flowStore?.value?.failure_module) {
$selectedId = 'failure'
}
}}
>
{#if $copilotCurrentStepStore !== undefined}
<div transition:fade class="absolute inset-0 bg-gray-500 bg-opacity-75 z-[900]"></div>
{/if}
<div class="flex items-center grow-0 min-w-0 gap-2">
<Bug size={16} color={$flowStore?.value?.failure_module ? '#3b82f6' : '#9CA3AF'} />
</div>
@@ -100,10 +80,10 @@
index={0}
placement={'top-center'}
on:new={(e) => {
insertNewFailureModule(e.detail.inlineScript)
insertFailureModule(e.detail.inlineScript)
}}
on:pickScript={(e) => {
insertNewFailureModule(undefined, e.detail)
insertFailureModule(undefined, e.detail)
}}
kind="failure"
/>

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import Popover from '$lib/components/Popover.svelte'
import { classNames } from '$lib/utils'
import {
@@ -74,9 +73,6 @@
const dispatch = createEventDispatcher()
const { currentStepStore: copilotCurrentStepStore } =
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
const propPickerContext = getContext<PropPickerContext>('PropPickerContext')
const flowPropPickerConfig = propPickerContext?.flowPropPickerConfig
const pickablePropertiesFiltered = propPickerContext?.pickablePropertiesFiltered
@@ -198,8 +194,7 @@
class={classNames(
'w-full module flex rounded-sm cursor-pointer max-w-full outline-offset-0 outline-slate-500 dark:outline-gray-400',
selected ? 'outline outline-2' : 'active:outline active:outline-2',
'flex relative',
$copilotCurrentStepStore === id ? 'z-[901]' : ''
'flex relative'
)}
style="width: 275px; height: 38px; background-color: {hover && bgHoverColor
? bgHoverColor

View File

@@ -13,7 +13,7 @@
pickFlow,
insertNewPreprocessorModule
} from '$lib/components/flows/flowStateUtils'
import type { FlowModule, RawScript, Script } from '$lib/gen'
import type { FlowModule, RawScript, Script, ScriptLang } from '$lib/gen'
import { emptyFlowModuleState, initFlowStepWarnings } from '../utils'
import FlowSettingsItem from './FlowSettingsItem.svelte'
import FlowConstantsItem from './FlowConstantsItem.svelte'
@@ -25,8 +25,6 @@
import Portal from '$lib/components/Portal.svelte'
import { getDependentComponents } from '../flowExplorer'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import { fade } from 'svelte/transition'
import { copilotInfo, tutorialsToDo, workspaceStore } from '$lib/stores'
import FlowTutorials from '$lib/components/FlowTutorials.svelte'
@@ -216,9 +214,6 @@
let deleteCallback: (() => void) | undefined = undefined
let dependents: Record<string, string[]> = {}
const { currentStepStore: copilotCurrentStepStore } =
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
function shouldRunTutorial(tutorialName: string, name: string, index: number) {
return (
$tutorialsToDo.includes(index) &&
@@ -228,7 +223,10 @@
)
}
const dispatch = createEventDispatcher()
const dispatch = createEventDispatcher<{
generateStep: { moduleId: string; instructions: string; lang: ScriptLang }
change: void
}>()
export async function updateFlowInputsStore() {
const keys = Object.keys(dependents ?? {})
@@ -323,13 +321,8 @@
</Portal>
<div class="flex flex-col h-full relative -pt-1">
<div
class={`z-10 sticky inline-flex flex-col gap-2 top-0 bg-surface-secondary flex-initial p-2 items-center transition-colors duration-[400ms] ease-linear border-b ${
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : ''
}`}
class={`z-10 sticky inline-flex flex-col gap-2 top-0 bg-surface-secondary flex-initial p-2 items-center transition-colors duration-[400ms] ease-linear border-b`}
>
{#if $copilotCurrentStepStore !== undefined}
<div transition:fade class="absolute inset-0 bg-gray-500 bg-opacity-75 z-[900] !m-0"></div>
{/if}
{#if !disableSettings}
<FlowSettingsItem />
{/if}
@@ -403,13 +396,21 @@
$moving = undefined
} else {
if (detail.detail === 'preprocessor') {
insertNewPreprocessorModule(
await insertNewPreprocessorModule(
flowStore,
flowStateStore,
detail.inlineScript,
detail.script
)
$selectedId = 'preprocessor'
if (detail.inlineScript?.instructions) {
dispatch('generateStep', {
moduleId: 'preprocessor',
lang: detail.inlineScript?.language,
instructions: detail.inlineScript?.instructions
})
}
} else {
const index = detail.index ?? 0
await insertNewModuleAtIndex(
@@ -420,9 +421,16 @@
detail.flow,
detail.inlineScript
)
const id = detail.modules[detail.index ?? 0].id
const id = detail.modules[index].id
$selectedId = id
if (detail.inlineScript?.instructions) {
dispatch('generateStep', {
moduleId: id,
lang: detail.inlineScript?.language,
instructions: detail.inlineScript?.instructions
})
}
if (detail.kind == 'trigger') {
await insertNewModuleAtIndex(
detail.modules,
@@ -525,7 +533,7 @@
? 'flex-row-reverse'
: 'justify-center'} border-b"
>
<FlowErrorHandlerItem small={smallErrorHandler} />
<FlowErrorHandlerItem small={smallErrorHandler} on:generateStep />
</div>
</div>

View File

@@ -2,7 +2,6 @@
import { createEventDispatcher, getContext } from 'svelte'
import StepGenQuick from '$lib/components/copilot/StepGenQuick.svelte'
import FlowInputsQuick from '../content/FlowInputsQuick.svelte'
import type { FlowModule } from '$lib/gen'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import ToggleHubWorkspaceQuick from '$lib/components/ToggleHubWorkspaceQuick.svelte'
import TopLevelNode from '../pickers/TopLevelNode.svelte'
@@ -11,9 +10,7 @@
const dispatch = createEventDispatcher()
export let stop = false
export let index: number = 0
export let funcDesc = ''
export let modules: FlowModule[] = []
export let disableAi = false
export let kind: 'script' | 'trigger' | 'preprocessor' | 'failure' = 'script'
export let allowTrigger = true
@@ -148,8 +145,6 @@ shouldUsePortal={true} -->
{selectedKind}
bind:loading
filter={funcDesc}
{modules}
{index}
{disableAi}
{funcDesc}
{kind}

View File

@@ -1,8 +1,6 @@
<script lang="ts">
import { Badge } from '$lib/components/common'
import { getContext } from 'svelte'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import VirtualItemWrapper from './VirtualItemWrapper.svelte'
import OutputPicker from '$lib/components/flows/propPicker/OutputPicker.svelte'
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
@@ -28,9 +26,6 @@
export let earlyStop: boolean = false
export let editMode: boolean = false
const { currentStepStore: copilotCurrentStepStore } =
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
const { viewport } = useSvelteFlow()
</script>
@@ -41,7 +36,6 @@
{selected}
{selectable}
{id}
onTop={label === 'Input' && $copilotCurrentStepStore === 'Input'}
on:select
let:hover
>

View File

@@ -49,6 +49,7 @@ export type FlowEditorContext = {
type: 'script'
editor: Editor
showDiffMode: () => void
hideDiffMode: () => void
diffMode: boolean
lastDeployedCode: string | undefined
}

View File

@@ -117,7 +117,7 @@
function onModulesChange(modules: FlowModule[]) {
computeSimplifiableFlow(
modules,
triggerContext?.simplifiedPoll ? get(triggerContext.simplifiedPoll) ?? false : false
triggerContext?.simplifiedPoll ? (get(triggerContext.simplifiedPoll) ?? false) : false
)
}
@@ -160,13 +160,13 @@
position: {
x: des.x
? // @ts-ignore
(des.data.data.offset ?? 0) +
// @ts-ignore
des.x +
(fullSize ? fullWidth : width) / 2 -
boxSize.width / 2 -
NODE.width / 2 -
(width - fullWidth) / 2
(des.data.data.offset ?? 0) +
// @ts-ignore
des.x +
(fullSize ? fullWidth : width) / 2 -
boxSize.width / 2 -
NODE.width / 2 -
(width - fullWidth) / 2
: 0,
y: des.y || 0
}

View File

@@ -166,7 +166,7 @@ export function graphBuilder(
disableMoveIds: options?.disableMoveIds,
enableTrigger: sourceId === 'Input',
// If the index is -1, it means that the target module is not in the modules array, so we set it to the length of the array
index: index >= 0 ? index : mods?.length ?? 0,
index: index >= 0 ? index : (mods?.length ?? 0),
...extra,
insertable: extra.insertable && !options?.disableInsert && prefix == undefined
}

View File

@@ -93,7 +93,6 @@
}}
selected={$selectedId == 'triggers'}
newItem={data.newFlow}
modules={data.modules}
/>
{:else}
<VirtualItemWrapper

View File

@@ -3,7 +3,6 @@
import { createEventDispatcher } from 'svelte'
import type { TriggerType } from '$lib/components/triggers/utils'
import TriggersBadge from './TriggersBadge.svelte'
import type { FlowModule } from '$lib/gen'
import { Plus } from 'lucide-svelte'
import InsertModuleInner from '$lib/components/flows/map/InsertModuleInner.svelte'
import AddTriggersButton from '$lib/components/triggers/AddTriggersButton.svelte'
@@ -15,7 +14,6 @@
selected: boolean
isEditor?: boolean
disableAi?: boolean
modules?: FlowModule[]
bgColor: string
bgHoverColor?: string
showDraft?: boolean
@@ -29,7 +27,6 @@
selected,
isEditor = false,
disableAi = false,
modules = [],
bgColor,
bgHoverColor = '',
showDraft,
@@ -117,8 +114,6 @@
addTriggersButton?.close()
}}
kind="trigger"
index={0}
{modules}
/>
</div>
{/snippet}