feat: violet ai branding + flow summary (#3171)

* feat: violet ai branding + flow summary

* fix: npm run check
This commit is contained in:
HugoCasa
2024-02-07 14:33:33 +01:00
committed by GitHub
parent 6edc4c4fc8
commit 91743c3cfb
12 changed files with 449 additions and 396 deletions
@@ -35,7 +35,6 @@
Settings,
X
} from 'lucide-svelte'
import autosize from 'svelte-autosize'
import { SCRIPT_SHOW_BASH, SCRIPT_SHOW_GO } from '$lib/consts'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { sendUserToast } from '$lib/toast'
@@ -369,8 +368,6 @@
let dirtyPath = false
let selectedTab: 'metadata' | 'runtime' | 'ui' = 'metadata'
let descriptionTextArea: HTMLTextAreaElement | undefined
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -411,38 +408,31 @@
</div>
</svelte:fragment>
<div class="flex flex-col gap-4">
<MetadataGen
label="Summary"
bind:content={script.summary}
lang={script.language}
code={script.content}
configName="summary"
let:updateFocus
let:active
let:classNames
focused
on:change={() => {
if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
path?.setName(
script.summary
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
}
}}
>
<input
type="text"
autofocus
class={classNames}
on:focus={() => updateFocus(true)}
on:blur={() => updateFocus(false)}
bind:value={script.summary}
placeholder={!active ? 'Short summary to be displayed when listed' : ''}
<Label label="Summary">
<MetadataGen
label="Summary"
bind:content={script.summary}
lang={script.language}
code={script.content}
promptConfigName="summary"
generateOnAppear
on:change={() => {
if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
path?.setName(
script.summary
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
}
}}
elementProps={{
type: 'text',
placeholder: 'Short summary to be displayed when listed'
}}
/>
</MetadataGen>
</Label>
<Label label="Path">
<Path
bind:this={path}
@@ -455,27 +445,18 @@
kind="script"
/>
</Label>
<MetadataGen
bind:content={script.description}
lang={script.language}
code={script.content}
configName="description"
el={descriptionTextArea}
label="Description"
let:updateFocus
let:active
let:classNames
>
<textarea
on:focus={() => updateFocus(true)}
on:blur={() => updateFocus(false)}
class={classNames}
use:autosize
bind:this={descriptionTextArea}
bind:value={script.description}
placeholder={!active ? 'Description displayed in the details page' : ''}
<Label label="Description">
<MetadataGen
bind:content={script.description}
lang={script.language}
code={script.content}
promptConfigName="description"
elementType="textarea"
elementProps={{
placeholder: 'Description displayed'
}}
/>
</MetadataGen>
</Label>
</div>
</Section>
@@ -33,15 +33,12 @@
: {
icon: $codeCompletionSessionEnabled ? SparklesIcon : SparklesOffIcon,
classes: $codeCompletionSessionEnabled
? 'text-sky-900 dark:text-sky-200'
: 'text-sky-900/50 dark:text-sky-200/50'
? 'text-violet-800 dark:text-violet-400'
: 'text-violet-800/50 dark:text-violet-400/50'
}}
on:click={() => {
storeSetting()
}}
btnClasses={$codeCompletionSessionEnabled
? 'bg-sky-50 dark:bg-gray-700 dark:hover:bg-surface-hover'
: ''}
/>
</Popover>
{:else}
@@ -54,7 +51,7 @@
startIcon={{
icon: SparklesOffIcon
}}
disabled
btnClasses="text-violet-800/50 dark:text-violet-400/50"
/>
</Popover>
{/if}
@@ -56,13 +56,13 @@
>
<svelte:fragment slot="button">
<Button
color={genLoading ? 'red' : 'dark'}
color={genLoading ? 'red' : 'light'}
size="xs"
nonCaptureEvent={!genLoading}
startIcon={{ icon: Wand2 }}
iconOnly
title="AI Assistant"
btnClasses="min-h-[30px] text-sky-900 dark:text-sky-200 bg-sky-100 dark:bg-gray-700 hover:bg-surface-hover dark:hover:bg-surface-hover"
btnClasses="min-h-[30px] text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
loading={genLoading}
clickableWhileLoading
on:click={genLoading ? () => abortController?.abort() : undefined}
@@ -84,10 +84,10 @@
/>
<Button
size="xs"
color="dark"
color="light"
variant="contained"
buttonType="button"
btnClasses="!p-1 !w-[38px] !ml-2 text-sky-900 dark:text-sky-200 bg-sky-100 dark:bg-gray-700 hover:bg-surface-hover dark:hover:bg-surface-hover"
btnClasses="!p-1 !w-[38px] !ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate CRON schedule from prompt"
aria-label="Generate"
iconOnly
@@ -38,7 +38,7 @@
$currentStepStore !== undefined ? 'z-[901]' : '',
copilotLoading || ($currentStepStore !== undefined && $currentStepStore !== 'Input')
? ''
: 'bg-sky-100 dark:bg-gray-700 text-sky-900 dark:text-sky-200 dark:hover:bg-surface-hover'
: '!text-violet-800 !dark:text-violet-400'
)}
on:click={() => {
if (copilotLoading || ($currentStepStore !== undefined && $currentStepStore !== 'Input')) {
@@ -72,7 +72,7 @@
? 'Stop'
: $currentStepStore !== undefined && $currentStepStore !== 'Input'
? 'Exit'
: 'AI Flow Builder'}
: 'AI Builder'}
</Button>
<div slot="content" class="text-sm flex flex-row items-center z-[901]">
<span class="font-semibold">
@@ -125,12 +125,12 @@
startIcon={{
icon: Wand2
}}
btnClasses="bg-sky-100 dark:bg-gray-700 text-sky-900 dark:text-sky-200 dark:hover:bg-surface-hover"
btnClasses="text-violet-800 dark:text-violet-400"
color={'light'}
variant={'border'}
nonCaptureEvent
>
AI
AI Builder
</Button>
</svelte:fragment>
<div class="block text-primary">
@@ -1,23 +1,27 @@
<script lang="ts">
import { getCompletion } from './lib'
import type { NewScript } from '$lib/gen'
import { isInitialCode } from '$lib/script_helpers'
import { Loader2 } from 'lucide-svelte'
import { Check, Loader2, Wand2 } from 'lucide-svelte'
import type { ChatCompletionMessageParam } from 'openai/resources'
import { copilotInfo, metadataCompletionEnabled } from '$lib/stores'
import Label from '../Label.svelte'
import { createEventDispatcher, onDestroy } from 'svelte'
import { sendUserToast } from '$lib/toast'
import { twMerge } from 'tailwind-merge'
import autosize from 'svelte-autosize'
import type { FlowValue } from '$lib/gen'
import YAML from 'yaml'
type Config = {
type PromptConfig = {
system: string
user: string
mode: 'automatic' | 'manual'
placeholderName: 'code' | 'flow'
}
const configs: {
summary: Config
description: Config
const promptConfigs: {
summary: PromptConfig
description: PromptConfig
flowSummary: PromptConfig
flowDescription: PromptConfig
} = {
summary: {
system: `
@@ -26,56 +30,94 @@ The summaries need to be as short as possible (maximum 8 words) and only give a
Examples: List the commits of a GitHub repository, Divide a number by 16, etc..
`,
user: `
Generate a very short summary for the script below:
\'\'\'{lang}
Generate a very short summary for the script below:
\'\'\'code
{code}
\`\`\`
`,
mode: 'automatic'
placeholderName: 'code'
},
description: {
system: `
You are a helpful AI assistant. You generate descriptions from scripts.
These descriptions are used to explain to other users what the script does, on particular on the input and what it returns.
Descriptions should contain a maximum of 4-5 sentences.
The description should focus on what it does and should not contain what concepts it uses (e.g. function named main, export an async function, etc...)
These descriptions are used to explain to other users what the script does and how to use it.
Be as short as possible to give a global idea, maximum 3-4 sentences.
All scripts export an asynchronous function called main, do not include it in the description.
Do not describe how to call it either.
`,
user: `
Generate a description for the script below:
\'\'\'{lang}
Generate a description for the script below:
\'\'\'code
{code}
\`\`\`
`,
mode: 'manual'
placeholderName: 'code'
},
flowSummary: {
system: `
You are a helpful AI assistant. You generate very brief summaries from scripts.
The summaries need to be as short as possible (maximum 8 words) and only give a global idea. Do not use any punctation. Avoid using prepositions and articles.
`,
user: `
Summarize the flow below in one very short sentence without punctation:
{flow}`,
placeholderName: 'flow'
},
flowDescription: {
system: `
You are a helpful AI assistant. You generate descriptions from flow.
These descriptions are used to explain to other users what the flow does and how to use it.
Be as short as possible to give a global idea, maximum 3-4 sentences.
Do not include line breaks.
`,
user: `
Generate a description for the flow below:
{flow}`,
placeholderName: 'flow'
}
}
function removeLocks(obj: any) {
if (obj && typeof obj === 'object') {
delete obj.lock
for (const key in obj) {
removeLocks(obj[key])
}
}
return obj
}
export let content: string | undefined
export let code: string
export let lang: NewScript.language
export let configName: keyof typeof configs
export let el: HTMLElement | undefined = undefined
export let label: string
export let code: string | undefined = undefined
export let flow: FlowValue | undefined = undefined
export let promptConfigName: keyof typeof promptConfigs
export let generateOnAppear: boolean = false
export let elementType: 'input' | 'textarea' = 'input'
export let elementProps: Record<string, any> = {}
let el: HTMLElement | undefined
let generatedContent = ''
let genEl: HTMLElement | undefined = undefined
let active = false
let loading = false
let abortController = new AbortController()
let manualDisabled = false
let width = 0
let genHeight = 0
export let focused = false
const updateFocus = (val) => {
focused = val
}
let config: Config = configs[configName]
let focused = false
let config: PromptConfig = promptConfigs[promptConfigName]
async function generateContent(automatic = false) {
abortController = new AbortController()
loading = true
try {
const placeholderContent = flow ? YAML.stringify(removeLocks(flow)) : code
if (!placeholderContent) {
sendUserToast('Could not generate summary: no content to generate from', true)
return
}
// return
const messages: ChatCompletionMessageParam[] = [
{
role: 'system',
@@ -83,7 +125,7 @@ The description should focus on what it does and should not contain what concept
},
{
role: 'user',
content: config.user.replace('{lang}', lang).replace('{code}', code)
content: config.user.replace(`{${config.placeholderName}}`, placeholderContent)
}
]
const response = await getCompletion(messages, abortController)
@@ -91,9 +133,6 @@ The description should focus on what it does and should not contain what concept
for await (const chunk of response) {
const toks = chunk.choices[0]?.delta?.content || ''
generatedContent += toks
if (el !== undefined && genEl !== undefined) {
el.style.height = Math.max(genEl.scrollHeight + 34, 58) + 'px'
}
}
} catch (err) {
if (!abortController.signal.aborted) {
@@ -111,11 +150,14 @@ The description should focus on what it does and should not contain what concept
if (
$copilotInfo.exists_openai_resource_path &&
$metadataCompletionEnabled &&
config.mode === 'automatic' &&
code &&
generateOnAppear &&
!content &&
code &&
!isInitialCode(code)
) {
setTimeout(() => {
el?.focus()
}, 0)
generateContent(true)
}
@@ -130,81 +172,120 @@ The description should focus on what it does and should not contain what concept
$metadataCompletionEnabled &&
!content &&
(loading || focused || !!generatedContent) &&
!manualDisabled
!manualDisabled &&
((config.placeholderName === 'code' && !!code) ||
(config.placeholderName === 'flow' &&
!!flow &&
Array.isArray(flow.modules) &&
flow.modules.length > 0))
$: focused && (manualDisabled = false)
$: if (content) {
abortController.abort()
generatedContent = ''
} else {
manualDisabled = false
}
$: if (elementType === 'textarea' && el !== undefined && !content) {
el.style.height = Math.max(genHeight + 34, 58) + 'px'
}
onDestroy(() => {
abortController.abort()
})
</script>
<div class="relative">
<div class="flex flex-row" />
<!-- svelte-ignore a11y-no-static-element-interactions -->
<Label {label}>
<div
class="relative"
on:keydown={(event) => {
if (!$copilotInfo.exists_openai_resource_path || !$metadataCompletionEnabled) {
return
}
if (event.key === 'Tab') {
if (!loading && generatedContent) {
event.preventDefault()
content = generatedContent
generatedContent = ''
} else if (!loading && !content) {
event.preventDefault()
generateContent()
}
} else if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
if (loading) {
abortController.abort()
} else {
manualDisabled = true
}
}
}}
>
<div
class={'absolute left-0.5 flex flex-row pl-1 gap-2 items-start top-[0.3rem] pointer-events-none'}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={twMerge('relative', $$props.class)}
bind:clientWidth={width}
on:keydown={(event) => {
if (!$copilotInfo.exists_openai_resource_path || !$metadataCompletionEnabled) {
return
}
if (event.key === 'Tab') {
if (manualDisabled) {
event.preventDefault()
manualDisabled = false
} else if (!loading && generatedContent) {
event.preventDefault()
content = generatedContent
generatedContent = ''
} else if (!loading && !content) {
event.preventDefault()
generateContent()
}
} else if (event.key === 'Escape' && !manualDisabled) {
event.preventDefault()
event.stopPropagation()
if (loading) {
abortController.abort()
} else {
manualDisabled = true
generatedContent = ''
}
} else if (event.key === 'Backspace' && !loading && !content) {
manualDisabled = true
generatedContent = ''
}
}}
>
<div
class={'absolute left-[0.5rem] top-[0.3rem] flex flex-row gap-2 items-start pointer-events-none'}
>
{#if active}
<span
class="absolute text-xs bg-violet-100 text-violet-800 dark:bg-gray-700 dark:text-violet-400 px-1 py-0.5 rounded-md flex flex-row items-center justify-center gap-2 transition-all shrink-0"
>
{#if active}
<span
class="absolute text-xs text-sky-900 bg-sky-100 dark:text-sky-200 dark:bg-gray-700 p-1 rounded-md flex flex-row items-center justify-center gap-2 w-32 shrink-0"
>
{#if loading}
<Loader2 class="animate-spin text-gray-500 dark:text-gray-400" size={16} />
{/if}
<div>
<span class="px-1 py-0.5 rounded-md text-2xs text-bold bg-white dark:bg-surface">
{#if loading}
ESC
{:else}
TAB
{/if}
</span>
{#if loading}
to cancel
{:else if generatedContent}
to accept
{:else}
to generate
{/if}
</div>
</span>
<span
class="text-sm leading-6 indent-[8.5rem] text-gray-500 dark:text-gray-400 pr-1"
bind:this={genEl}
>
{generatedContent}
</span>
{/if}
<span class="px-0.5 py-0.5 rounded-md text-2xs text-bold flex flex-row items-center gap-1">
{#if loading}
ESC
{:else}
TAB
{/if}
{#if loading}
<Loader2 class="animate-spin" size={12} />
{:else if generatedContent}
<Check size={12} />
{:else}
<Wand2 size={12} />
{/if}
</span>
</span>
<div
bind:clientHeight={genHeight}
class={twMerge(
'text-sm leading-6 indent-[3.5rem] text-gray-500 dark:text-gray-400 pr-1',
elementType === 'input' ? 'text-ellipsis overflow-hidden whitespace-nowrap' : ''
)}
style={elementType === 'input' ? `max-width: calc(${width}px - 0.5rem)` : ''}
>
{generatedContent}
</div>
<slot {updateFocus} {active} classNames={active && !generatedContent ? '!pl-[8.7rem]' : ''} />
</div>
</Label>
{/if}
</div>
{#if elementType === 'textarea'}
<textarea
bind:this={el}
bind:value={content}
use:autosize
{...elementProps}
placeholder={!active ? elementProps.placeholder : ''}
class={active ? '!indent-[3.5rem]' : ''}
on:focus={() => (focused = true)}
on:blur={() => (focused = false)}
/>
{:else}
<input
bind:this={el}
bind:value={content}
placeholder={!active ? elementProps.placeholder : ''}
class={active ? '!indent-[3.5rem]' : ''}
on:focus={() => (focused = true)}
on:blur={() => (focused = false)}
/>
{/if}
<!-- <slot {updateFocus} {active} {generatedContent} classNames={active ? '!indent-[8.8rem]' : ''} /> -->
</div>
@@ -1,5 +1,5 @@
<script lang="ts">
import { ExternalLink, Sparkles, ZapOffIcon } from 'lucide-svelte'
import { ExternalLink, Sparkles } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { copilotInfo, metadataCompletionEnabled } from '$lib/stores'
import Popover from '../Popover.svelte'
@@ -22,7 +22,7 @@
{#if $copilotInfo.exists_openai_resource_path}
<Popover>
<svelte:fragment slot="text"
>Metadata completion: {$metadataCompletionEnabled ? 'disabled' : 'enabled'} (applies only to you)</svelte:fragment
>Metadata completion: {$metadataCompletionEnabled ? 'enabled' : 'disabled'} (applies only to you)</svelte:fragment
>
<Button
size="xs"
@@ -30,13 +30,13 @@
startIcon={{
icon: $metadataCompletionEnabled ? Sparkles : SparklesOffIcon,
classes: $metadataCompletionEnabled
? 'text-sky-900 dark:text-sky-200'
: 'text-sky-900/50 dark:text-sky-200/50'
? 'text-violet-800 dark:text-violet-400'
: 'text-violet-800/50 dark:text-violet-400/50'
}}
on:click={() => {
storeSetting()
}}
btnClasses={$metadataCompletionEnabled ? 'bg-sky-100 dark:bg-gray-700' : ''}
btnClasses="p-1.5"
/>
</Popover>
{:else}
@@ -52,10 +52,12 @@
</svelte:fragment>
<Button
color="light"
size="xs"
class="text-violet-800/50 dark:text-violet-400/50"
btnClasses="p-1.5"
startIcon={{
icon: ZapOffIcon
icon: SparklesOffIcon
}}
disabled
/>
</Popover>
{/if}
@@ -162,14 +162,14 @@
<Button
title="Fix code"
size="xs"
color={genLoading ? 'red' : 'blue'}
color={genLoading ? 'red' : 'light'}
spacingSize="xs2"
startIcon={genLoading ? undefined : { icon: Wand2 }}
nonCaptureEvent={!genLoading}
on:click={genLoading ? () => abortController?.abort() : undefined}
btnClasses={genLoading
? ''
: 'text-sky-900 bg-sky-100 dark:text-sky-200 dark:bg-gray-700 hover:bg-surface-hover dark:hover:bg-surface-hover'}
: 'text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700'}
>{#if genLoading}
<WindmillIcon
white
@@ -270,9 +270,7 @@
<Button
size="xs"
color={genLoading ? 'red' : 'light'}
btnClasses={genLoading
? '!px-3 z-[5000]'
: '!px-2 bg-sky-50 dark:bg-gray-700 dark:hover:bg-surface-hover'}
btnClasses={genLoading ? '!px-3 z-[5000]' : '!px-2'}
nonCaptureEvent={!genLoading}
on:click={genLoading ? () => abortController?.abort() : undefined}
bind:element={button}
@@ -280,23 +278,19 @@
title="Generate code from Prompt"
startIcon={genLoading
? { icon: Ban }
: { icon: Wand2, classes: 'text-sky-900 dark:text-sky-200 ' }}
: { icon: Wand2, classes: 'text-violet-800 dark:text-violet-400' }}
/>
{:else}
<Button
title="Generate code from prompt"
btnClasses={twMerge(
'!font-medium',
genLoading
? 'z-[5000]'
: 'text-sky-900 dark:text-sky-200 bg-sky-50 dark:bg-gray-700 dark:hover:bg-surface-hover'
genLoading ? 'z-[5000]' : 'text-violet-800 dark:text-violet-400'
)}
size="xs"
color={genLoading ? 'red' : 'light'}
spacingSize="md"
startIcon={genLoading
? undefined
: { icon: Wand2, classes: 'text-sky-900 dark:text-sky-200' }}
startIcon={genLoading ? undefined : { icon: Wand2 }}
propagateEvent
on:click={genLoading
? () => abortController?.abort()
@@ -366,9 +360,9 @@
/>
<Button
size="xs"
color="blue"
color="light"
buttonType="button"
btnClasses="!p-1 !w-[38px] !ml-2 text-sky-900 dark:text-sky-200 bg-sky-100 dark:bg-gray-700 hover:bg-surface-hover dark:hover:bg-surface-hover"
btnClasses="!p-1 !w-[38px] !ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate code from prompt"
aria-label="Generate"
on:click={() => {
@@ -2,6 +2,7 @@
import Badge from '$lib/components/common/badge/Badge.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
import MetadataGen from '$lib/components/copilot/MetadataGen.svelte'
import IconedPath from '$lib/components/IconedPath.svelte'
import { ScriptService, type FlowModule, type PathScript } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
@@ -41,7 +42,15 @@
<div class="mx-0.5">
<LanguageIcon lang={flowModule.value.language} width={20} height={20} />
</div>
<input bind:value={flowModule.summary} placeholder={'Summary'} class="w-full grow" />
<MetadataGen
bind:content={flowModule.summary}
promptConfigName="summary"
code={flowModule.value.content}
class="w-full"
elementProps={{
placeholder: 'Summary'
}}
/>
{:else if flowModule?.value.type === 'script' && 'path' in flowModule.value && flowModule.value.path}
<IconedPath path={flowModule.value.path} hash={flowModule.value.hash} class="grow" />
@@ -11,7 +11,6 @@
import { Alert, Button, SecondsInput } from '$lib/components/common'
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import autosize from 'svelte-autosize'
import Slider from '$lib/components/Slider.svelte'
import { enterpriseLicense, workerTags, workspaceStore } from '$lib/stores'
import { isCloudHosted } from '$lib/cloud'
@@ -25,6 +24,8 @@
import Label from '$lib/components/Label.svelte'
import ErrorHandlerToggleButton from '$lib/components/details/ErrorHandlerToggleButton.svelte'
import WorkerTagPicker from '$lib/components/WorkerTagPicker.svelte'
import MetadataGen from '$lib/components/copilot/MetadataGen.svelte'
import MetadataGenToggle from '$lib/components/copilot/MetadataGenToggle.svelte'
export let noEditor: boolean
@@ -69,192 +70,202 @@
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4 h-full overflow-auto">
<div class="h-full gap-8 flex flex-col">
<Label label="Summary">
<input
type="text"
autofocus
bind:value={$flowStore.summary}
placeholder="Short summary to be displayed when listed"
id="flow-summary"
on:keyup={() => {
if (initialPath == '' && $flowStore.summary?.length > 0 && !dirtyPath) {
path?.setName(
$flowStore.summary
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
}
}}
/>
</Label>
{#if !noEditor}
<Label label="Path">
<Path
autofocus={false}
bind:this={path}
bind:dirty={dirtyPath}
bind:path={$pathStore}
{initialPath}
namePlaceholder="flow"
kind="flow"
/>
</Label>
{/if}
<Label label="Description">
<textarea
use:autosize
type="text"
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="What this flow does and how to use it."
rows="3"
/>
</Label>
<!-- TODO: Add EE-only badge when we have it -->
<Toggle
disabled={!$enterpriseLicense || isCloudHosted()}
checked={$flowStore.value.priority !== undefined && $flowStore.value.priority > 0}
on:change={() => {
if ($flowStore.value.priority) {
$flowStore.value.priority = undefined
} else {
$flowStore.value.priority = 100
}
}}
options={{
right: `Label as high priority`,
rightTooltip: `All jobs scheduled by flows labeled as high priority take precedence over the other jobs in the jobs queue. ${
!$enterpriseLicense
? 'This is a feature only available on enterprise edition.'
: ''
}`
}}
>
<svelte:fragment slot="right">
<input
type="number"
class="!w-16 ml-4"
disabled={$flowStore.value.priority === undefined}
bind:value={$flowStore.value.priority}
on:focus
<Section label="Metadata">
<svelte:fragment slot="header">
<MetadataGenToggle />
</svelte:fragment>
<div class="h-full gap-8 flex flex-col">
<Label label="Summary">
<MetadataGen
bind:content={$flowStore.summary}
promptConfigName="flowSummary"
flow={$flowStore.value}
on:change={() => {
if ($flowStore.value.priority && $flowStore.value.priority > 100) {
$flowStore.value.priority = 100
} else if ($flowStore.value.priority && $flowStore.value.priority < 0) {
$flowStore.value.priority = 0
if (initialPath == '' && $flowStore.summary?.length > 0 && !dirtyPath) {
path?.setName(
$flowStore.summary
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
}
}}
elementProps={{
type: 'text',
id: 'flow-summary',
placeholder: 'Short summary to be displayed when listed'
}}
/>
</svelte:fragment>
</Toggle>
</Label>
<div class="flex flex-row items-center gap-1">
<ErrorHandlerToggleButton
kind="flow"
scriptOrFlowPath={$pathStore}
bind:errorHandlerMuted={$flowStore.ws_error_handler_muted}
iconOnly={false}
/>
</div>
{#if !noEditor}
<Label label="Path">
<Path
autofocus={false}
bind:this={path}
bind:dirty={dirtyPath}
bind:path={$pathStore}
{initialPath}
namePlaceholder="flow"
kind="flow"
/>
</Label>
{/if}
<Slider text="How to trigger flows?">
<div class="text-sm text-tertiary border p-4 mb-20">
On-demand:
<ul class="pt-4">
<li>
1. <a
href="https://www.windmill.dev/docs/core_concepts/auto_generated_uis"
target="_blank">Auto-generated UIs</a
>
</li>
<li>
2. <a href="/apps/add?nodraft=true" target="_blank"> App Editor</a> for customized-UIs
</li>
<li>
3. <a href="/schedules" target="_blank">Scheduling</a>
</li>
<li>
4. <a href="https://www.windmill.dev/docs/advanced/cli" target="_blank"
>Windmill CLI</a
>
</li>
<br />
<li class="mt-2">
<div class="flex flex-col gap-2">
<p> From external events: </p>
</div>
</li>
<li class="mt-2">
5. Send a <a
href="https://www.windmill.dev/docs/core_concepts/webhooks"
target="_blank">webhook</a
>
after each event:
<ul class="list-disc pl-4"
><li
>Async <Tooltip
>Return an uuid instantly that you can use to fetch status and result</Tooltip
>:
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-secondary ml-2">
<Clipboard />
</span>
</a>
</li>
<li
>Sync <Tooltip>Wait for result within a timeout of 20s</Tooltip>:
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(syncedUrl)
}}
href={syncedUrl}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{syncedUrl}
<span class="text-secondary ml-2">
<Clipboard />
</span>
</a>
</li>
</ul></li
>
<br />
<li>
6. Use a <a
href="https://www.windmill.dev/docs/flows/flow_trigger"
target="_blank">trigger script</a
>
and schedule this flow to run as frequently as needed and compare a state persisted
in Windmill to the state of the external system. If a difference is detected, then
the rest of the flow is triggered. Oftentimes, the second step of a flow is a for-loop
that will iterate over every elements. When using a trigger, a default schedule
will be created.
<img
class="shadow-lg border rounded"
alt="static button"
src="/trigger_button.png"
/>
</li></ul
>
<Label label="Description">
<MetadataGen
bind:content={$flowStore.description}
promptConfigName="flowDescription"
flow={$flowStore.value}
class="w-full"
elementType="textarea"
elementProps={{
id: 'inp',
placeholder: 'What this flow does and how to use it.'
}}
/>
</Label>
<!-- TODO: Add EE-only badge when we have it -->
<Toggle
disabled={!$enterpriseLicense || isCloudHosted()}
checked={$flowStore.value.priority !== undefined && $flowStore.value.priority > 0}
on:change={() => {
if ($flowStore.value.priority) {
$flowStore.value.priority = undefined
} else {
$flowStore.value.priority = 100
}
}}
options={{
right: `Label as high priority`,
rightTooltip: `All jobs scheduled by flows labeled as high priority take precedence over the other jobs in the jobs queue. ${
!$enterpriseLicense
? 'This is a feature only available on enterprise edition.'
: ''
}`
}}
>
<svelte:fragment slot="right">
<input
type="number"
class="!w-16 ml-4"
disabled={$flowStore.value.priority === undefined}
bind:value={$flowStore.value.priority}
on:focus
on:change={() => {
if ($flowStore.value.priority && $flowStore.value.priority > 100) {
$flowStore.value.priority = 100
} else if ($flowStore.value.priority && $flowStore.value.priority < 0) {
$flowStore.value.priority = 0
}
}}
/>
</svelte:fragment>
</Toggle>
<div class="flex flex-row items-center gap-1">
<ErrorHandlerToggleButton
kind="flow"
scriptOrFlowPath={$pathStore}
bind:errorHandlerMuted={$flowStore.ws_error_handler_muted}
iconOnly={false}
/>
</div>
</Slider>
</div>
<Slider text="How to trigger flows?">
<div class="text-sm text-tertiary border p-4 mb-20">
On-demand:
<ul class="pt-4">
<li>
1. <a
href="https://www.windmill.dev/docs/core_concepts/auto_generated_uis"
target="_blank">Auto-generated UIs</a
>
</li>
<li>
2. <a href="/apps/add?nodraft=true" target="_blank"> App Editor</a> for customized-UIs
</li>
<li>
3. <a href="/schedules" target="_blank">Scheduling</a>
</li>
<li>
4. <a href="https://www.windmill.dev/docs/advanced/cli" target="_blank"
>Windmill CLI</a
>
</li>
<br />
<li class="mt-2">
<div class="flex flex-col gap-2">
<p> From external events: </p>
</div>
</li>
<li class="mt-2">
5. Send a <a
href="https://www.windmill.dev/docs/core_concepts/webhooks"
target="_blank">webhook</a
>
after each event:
<ul class="list-disc pl-4"
><li
>Async <Tooltip
>Return an uuid instantly that you can use to fetch status and result</Tooltip
>:
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-secondary ml-2">
<Clipboard />
</span>
</a>
</li>
<li
>Sync <Tooltip>Wait for result within a timeout of 20s</Tooltip>:
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(syncedUrl)
}}
href={syncedUrl}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{syncedUrl}
<span class="text-secondary ml-2">
<Clipboard />
</span>
</a>
</li>
</ul></li
>
<br />
<li>
6. Use a <a
href="https://www.windmill.dev/docs/flows/flow_trigger"
target="_blank">trigger script</a
>
and schedule this flow to run as frequently as needed and compare a state persisted
in Windmill to the state of the external system. If a difference is detected,
then the rest of the flow is triggered. Oftentimes, the second step of a flow
is a for-loop that will iterate over every elements. When using a trigger, a
default schedule will be created.
<img
class="shadow-lg border rounded"
alt="static button"
src="/trigger_button.png"
/>
</li></ul
>
</div>
</Slider>
</div>
</Section>
</TabContent>
<TabContent value="settings-schedule" class="p-4 h-full overflow-scroll">
<Alert
@@ -170,7 +170,7 @@
: undefined}
slot="trigger"
type="button"
class=" bg-sky-100 dark:bg-gray-700 text-sky-900 dark:text-sky-200 dark:hover:bg-surface-hover border mx-0.5 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-8 h-8 flex items-center justify-center"
class=" bg-surface text-violet-800 dark:text-violet-400 border mx-0.5 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-8 h-8 flex items-center justify-center"
>
<Wand2 size={16} />
</button>
-22
View File
@@ -188,7 +188,6 @@ components:
- $ref: "#/components/schemas/BranchOne"
- $ref: "#/components/schemas/BranchAll"
- $ref: "#/components/schemas/Identity"
- $ref: "#/components/schemas/Graphql"
discriminator:
propertyName: type
mapping:
@@ -199,7 +198,6 @@ components:
branchone: "#/components/schemas/BranchOne"
branchall: "#/components/schemas/BranchAll"
identity: "#/components/schemas/Identity"
graphql: "#/components/schemas/Graphql"
RawScript:
type: object
@@ -385,26 +383,6 @@ components:
required:
- type
Http:
type: object
properties:
type:
type: string
enum:
- http
required:
- type
Graphql:
type: object
properties:
type:
type: string
enum:
- graphql
required:
- type
FlowStatus:
type: object
properties: