feat: preprocessor and error handler support
This commit is contained in:
@@ -61,7 +61,6 @@
|
||||
}
|
||||
|
||||
let flowCopilotContext: FlowCopilotContext = {
|
||||
currentStepStore: writable<string | undefined>(undefined),
|
||||
shouldUpdatePropertyType: writable<{
|
||||
[key: string]: 'static' | 'javascript' | undefined
|
||||
}>({}),
|
||||
|
||||
@@ -662,7 +662,6 @@
|
||||
}
|
||||
|
||||
let flowCopilotContext: FlowCopilotContext = {
|
||||
currentStepStore: writable<string | undefined>(undefined),
|
||||
shouldUpdatePropertyType: writable<{
|
||||
[key: string]: 'static' | 'javascript' | undefined
|
||||
}>({}),
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -272,6 +272,7 @@
|
||||
editor,
|
||||
stepId: flowModule.id,
|
||||
showDiffMode,
|
||||
hideDiffMode,
|
||||
diffMode,
|
||||
lastDeployedCode
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
>
|
||||
|
||||
@@ -49,6 +49,7 @@ export type FlowEditorContext = {
|
||||
type: 'script'
|
||||
editor: Editor
|
||||
showDiffMode: () => void
|
||||
hideDiffMode: () => void
|
||||
diffMode: boolean
|
||||
lastDeployedCode: string | undefined
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
}}
|
||||
selected={$selectedId == 'triggers'}
|
||||
newItem={data.newFlow}
|
||||
modules={data.modules}
|
||||
/>
|
||||
{:else}
|
||||
<VirtualItemWrapper
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user