feat: test openai key + improve AI UI (#2465)
This commit is contained in:
@@ -150,21 +150,24 @@
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<div class="flex justify-start">
|
||||
<ToggleButtonGroup bind:selected={searchKind} class="h-10">
|
||||
<ToggleButton small value="all" label={'All' + counts.all} />
|
||||
<ToggleButton small light value="all" label={'All' + counts.all} />
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="scripts"
|
||||
icon={Code2}
|
||||
label={'Scripts' + counts.scripts}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="resources"
|
||||
icon={Boxes}
|
||||
label={'Resources' + counts.resources}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="flows"
|
||||
label={'Flows' + counts.flows}
|
||||
icon={FlowIcon}
|
||||
@@ -172,6 +175,7 @@
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="apps"
|
||||
label={'Apps' + counts.apps}
|
||||
icon={LayoutDashboard}
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
label={'${}'}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButton small label="Static" value="static" />
|
||||
<ToggleButton small light label="Static" value="static" />
|
||||
{/if}
|
||||
|
||||
<ToggleButton
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ml-3 flex-1 w-0">
|
||||
<p class="text-sm text-secondary">{message}</p>
|
||||
<p class="text-sm text-secondary break-words">{message}</p>
|
||||
{#if errorMessage}
|
||||
<p
|
||||
class="text-sm text-secondary border bg-surface-secondary p-2 w-full overflow-auto mt-2"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
export let element: ButtonType.Element | undefined = undefined
|
||||
export let id: string = ''
|
||||
export let nonCaptureEvent: boolean = false
|
||||
export let propagateEvent: boolean = false
|
||||
export let loading = false
|
||||
export let title: string | undefined = undefined
|
||||
export let style: string = ''
|
||||
@@ -99,7 +100,10 @@
|
||||
async function onClick(event: MouseEvent) {
|
||||
if (!nonCaptureEvent) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (!propagateEvent) {
|
||||
// by default events are not propagated, added this prop so that we can
|
||||
event.stopPropagation()
|
||||
}
|
||||
dispatch('click', event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
export let containerClasses: string = 'rounded-lg shadow-md border p-4 bg-surface'
|
||||
|
||||
const [floatingRef, floatingContent] = createFloatingActions(floatingConfig)
|
||||
|
||||
export let blockOpen = false
|
||||
</script>
|
||||
|
||||
<Popover on:close>
|
||||
@@ -22,6 +24,7 @@
|
||||
<Portal>
|
||||
<div use:floatingContent class="z5000">
|
||||
<Transition
|
||||
show={blockOpen || undefined}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
@@ -29,7 +32,7 @@
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<PopoverPanel let:close>
|
||||
<PopoverPanel let:close static={blockOpen}>
|
||||
<div class={containerClasses}>
|
||||
<slot {close} />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
export let icon: any | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let selectedColor: string = '#3b82f6'
|
||||
export let small: boolean = false
|
||||
export let small = false
|
||||
export let light = false
|
||||
export let iconProps: Record<string, any> = {}
|
||||
export let showTooltipIcon: boolean = false
|
||||
export let documentationLink: string | undefined = undefined
|
||||
@@ -35,7 +36,8 @@
|
||||
{disabled}
|
||||
class={twMerge(
|
||||
' rounded-md transition-all text-xs flex gap-1 flex-row items-center',
|
||||
small ? 'px-1 py-0.5' : 'px-2 py-1',
|
||||
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
|
||||
light ? 'font-medium' : '',
|
||||
$selected === value
|
||||
? 'bg-surface shadow-md'
|
||||
: 'bg-surface-secondary hover:bg-surface-hover',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let copilotLoading: boolean
|
||||
export let copilotStatus: string
|
||||
@@ -34,7 +35,7 @@
|
||||
<ManualPopover bind:this={copilotPopover}>
|
||||
<Button
|
||||
size="xs"
|
||||
btnClasses={'mr-2' + ($currentStepStore !== undefined ? 'z-[901]' : '')}
|
||||
btnClasses={twMerge('mr-2', $currentStepStore !== undefined ? 'z-[901]' : '')}
|
||||
on:click={() => {
|
||||
if (copilotLoading || ($currentStepStore !== undefined && $currentStepStore !== 'Input')) {
|
||||
abortController?.abort()
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
import { dbSchemas, copilotInfo, type DBSchema } from '$lib/stores'
|
||||
import type DiffEditor from '../DiffEditor.svelte'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
import type { Selection } from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
import type SimpleEditor from '../SimpleEditor.svelte'
|
||||
import Tooltip from '../Tooltip.svelte'
|
||||
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
@@ -28,6 +27,8 @@
|
||||
import { sleep } from '$lib/utils'
|
||||
import { autoPlacement } from '@floating-ui/core'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { isInitialCode } from '$lib/script_helpers'
|
||||
|
||||
// props
|
||||
export let iconOnly: boolean = false
|
||||
@@ -42,10 +43,10 @@
|
||||
let genLoading: boolean = false
|
||||
let input: HTMLInputElement | undefined
|
||||
let generatedCode = writable<string>('')
|
||||
let selection: Selection | undefined
|
||||
let isEdit = false
|
||||
let dbSchema: DBSchema | undefined = undefined
|
||||
let abortController: AbortController | undefined = undefined
|
||||
let blockPopupOpen = false
|
||||
let mode: 'gen' | 'edit' = 'gen'
|
||||
|
||||
let button: HTMLButtonElement | undefined
|
||||
|
||||
@@ -55,23 +56,20 @@
|
||||
}
|
||||
try {
|
||||
genLoading = true
|
||||
blockPopupOpen = true
|
||||
abortController = new AbortController()
|
||||
if (isEdit && selection) {
|
||||
const selectedCode = editor?.getSelectedLines() || ''
|
||||
const originalCode = editor?.getCode() || ''
|
||||
if (mode === 'edit') {
|
||||
await copilot(
|
||||
{
|
||||
language: lang,
|
||||
description: funcDesc,
|
||||
code: selectedCode,
|
||||
code: editor?.getCode() || '',
|
||||
dbSchema: dbSchema,
|
||||
type: 'edit'
|
||||
},
|
||||
generatedCode,
|
||||
abortController
|
||||
)
|
||||
setupDiff()
|
||||
diffEditor?.setModified(originalCode.replace(selectedCode, $generatedCode + '\n'))
|
||||
} else {
|
||||
await copilot(
|
||||
{
|
||||
@@ -83,9 +81,10 @@
|
||||
generatedCode,
|
||||
abortController
|
||||
)
|
||||
setupDiff()
|
||||
diffEditor?.setModified($generatedCode)
|
||||
}
|
||||
setupDiff()
|
||||
diffEditor?.setModified($generatedCode)
|
||||
blockPopupOpen = false
|
||||
await sleep(500)
|
||||
closePopup()
|
||||
await sleep(300)
|
||||
@@ -128,12 +127,6 @@
|
||||
diffEditor?.hide()
|
||||
}
|
||||
|
||||
function setSelectionHandler() {
|
||||
editor?.onDidChangeCursorSelection((e) => {
|
||||
selection = e.selection
|
||||
})
|
||||
}
|
||||
|
||||
$: input?.focus()
|
||||
|
||||
function clear() {
|
||||
@@ -143,18 +136,28 @@
|
||||
$: lang && clear()
|
||||
|
||||
$: !$generatedCode && hideDiff()
|
||||
$: editor && setSelectionHandler()
|
||||
$: selection && (isEdit = !selection.isEmpty())
|
||||
|
||||
function updateSchema(lang, args) {
|
||||
function updateSchema(lang, args, dbSchemas) {
|
||||
const schemaRes = lang === 'graphql' ? args.api : args.database
|
||||
if (typeof schemaRes === 'string') {
|
||||
dbSchema = $dbSchemas[schemaRes.replace('$res:', '')]
|
||||
const schemaPath = schemaRes.replace('$res:', '')
|
||||
if (schemaPath in dbSchemas && dbSchemas[schemaPath].lang === lang) {
|
||||
dbSchema = dbSchemas[schemaPath]
|
||||
} else {
|
||||
dbSchema = undefined
|
||||
}
|
||||
} else {
|
||||
dbSchema = undefined
|
||||
}
|
||||
}
|
||||
$: updateSchema(lang, args)
|
||||
|
||||
$: updateSchema(lang, args, $dbSchemas)
|
||||
</script>
|
||||
|
||||
{#if genLoading}
|
||||
<div transition:fade class="fixed z-[4999] inset-0 bg-gray-500/75" />
|
||||
{/if}
|
||||
|
||||
{#if $generatedCode.length > 0 && !genLoading}
|
||||
{#if inlineScript}
|
||||
<div class="flex gap-1">
|
||||
@@ -214,6 +217,7 @@
|
||||
]
|
||||
}}
|
||||
let:close
|
||||
blockOpen={blockPopupOpen}
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
{#if inlineScript}
|
||||
@@ -234,13 +238,23 @@
|
||||
{:else}
|
||||
<Button
|
||||
title="Generate code from prompt"
|
||||
btnClasses="!font-medium"
|
||||
btnClasses={'!font-medium ' + (genLoading ? 'z-[5000]' : '')}
|
||||
size="xs"
|
||||
color={genLoading ? 'red' : 'light'}
|
||||
spacingSize="md"
|
||||
startIcon={genLoading ? undefined : { icon: faMagicWandSparkles }}
|
||||
nonCaptureEvent={!genLoading}
|
||||
on:click={genLoading ? () => abortController?.abort() : undefined}
|
||||
propagateEvent
|
||||
on:click={genLoading
|
||||
? () => abortController?.abort()
|
||||
: () => {
|
||||
if (editor) {
|
||||
if (isInitialCode(editor.getCode())) {
|
||||
mode = 'gen'
|
||||
} else {
|
||||
mode = 'edit'
|
||||
}
|
||||
}
|
||||
}}
|
||||
bind:element={button}
|
||||
>
|
||||
{#if genLoading}
|
||||
@@ -253,7 +267,7 @@
|
||||
/>
|
||||
Stop
|
||||
{:else}
|
||||
{isEdit ? 'AI Edit' : 'AI Gen'}
|
||||
AI
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
@@ -270,50 +284,59 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if $copilotInfo.exists_openai_resource_path}
|
||||
<div class="flex w-96">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={input}
|
||||
bind:value={funcDesc}
|
||||
on:keypress={({ key }) => {
|
||||
if (key === 'Enter' && funcDesc.length > 0) {
|
||||
<div class="flex flex-col gap-4">
|
||||
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={mode}>
|
||||
<ToggleButton value={'gen'} label="Generate from scratch" small light />
|
||||
<ToggleButton value={'edit'} label="Edit existing code" small light />
|
||||
</ToggleButtonGroup>
|
||||
<div class="flex w-96">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={input}
|
||||
bind:value={funcDesc}
|
||||
on:keypress={({ key }) => {
|
||||
if (key === 'Enter' && funcDesc.length > 0) {
|
||||
onGenerate(() => close(input || null))
|
||||
}
|
||||
}}
|
||||
placeholder={mode === 'edit'
|
||||
? 'Describe the changes you want'
|
||||
: 'Describe what the script should do'}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[38px] !ml-2"
|
||||
aria-label="Generate"
|
||||
on:click={() => {
|
||||
onGenerate(() => close(input || null))
|
||||
}
|
||||
}}
|
||||
placeholder={isEdit
|
||||
? 'Describe the changes you want'
|
||||
: 'Describe what the script should do'}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[38px] !ml-2"
|
||||
aria-label="Generate"
|
||||
on:click={() => {
|
||||
onGenerate(() => close(input || null))
|
||||
}}
|
||||
disabled={funcDesc.length <= 0}
|
||||
>
|
||||
<Icon data={faMagicWandSparkles} />
|
||||
</Button>
|
||||
</div>
|
||||
{#if ['postgresql', 'mysql', 'snowflake', 'bigquery', 'graphql'].includes(lang) && dbSchema?.lang === lang}
|
||||
<div class="flex flex-row items-center justify-between w-96 mt-2">
|
||||
<p class="text-sm">
|
||||
Will take into account the DB schema
|
||||
<Tooltip>
|
||||
In order to better generate the script, we pass the selected DB schema to GPT-4.
|
||||
</Tooltip>
|
||||
</p>
|
||||
{#if dbSchema.lang !== 'graphql' && (dbSchema.schema?.public || dbSchema.schema?.PUBLIC)}
|
||||
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={dbSchema.publicOnly}>
|
||||
<ToggleButton value={true} label="Public schema" />
|
||||
<ToggleButton value={false} label="All schemas" />
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
}}
|
||||
disabled={funcDesc.length <= 0}
|
||||
>
|
||||
<Icon data={faMagicWandSparkles} />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if ['postgresql', 'mysql', 'snowflake', 'bigquery', 'graphql'].includes(lang) && dbSchema?.lang === lang}
|
||||
<div class="flex flex-row items-center justify-between gap-2 w-96">
|
||||
<div class="flex flex-row items-center">
|
||||
<p class="text-xs text-secondary">
|
||||
Context: {lang === 'graphql' ? 'GraphQL' : 'DB'} schema
|
||||
</p>
|
||||
<Tooltip>
|
||||
In order to better generate the script, we pass the selected schema to GPT-4.
|
||||
</Tooltip>
|
||||
</div>
|
||||
{#if dbSchema.lang !== 'graphql' && (dbSchema.schema?.public || dbSchema.schema?.PUBLIC)}
|
||||
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={dbSchema.publicOnly}>
|
||||
<ToggleButton value={true} label="Public schema" small light />
|
||||
<ToggleButton value={false} label="All schemas" small light />
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}</div
|
||||
>
|
||||
{:else}
|
||||
<p class="text-sm"
|
||||
>Enable Windmill AI in the <a
|
||||
|
||||
44
frontend/src/lib/components/copilot/TestOpenaiKey.svelte
Normal file
44
frontend/src/lib/components/copilot/TestOpenaiKey.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { testKey } from './lib'
|
||||
export let disabled = false
|
||||
export let apiKey: string | undefined = undefined
|
||||
let loading = false
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="contained"
|
||||
color="dark"
|
||||
{disabled}
|
||||
{loading}
|
||||
on:click={async () => {
|
||||
loading = true
|
||||
try {
|
||||
const abortController = new AbortController()
|
||||
setTimeout(() => {
|
||||
abortController.abort()
|
||||
}, 10000)
|
||||
await testKey({
|
||||
apiKey,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: "this is a test, simply reply with 'ok'"
|
||||
}
|
||||
],
|
||||
abortController
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
} catch (err) {
|
||||
if (err.message === 'Request was aborted.') {
|
||||
sendUserToast('Could not validate key within 10s', true)
|
||||
} else {
|
||||
sendUserToast(`Invalid key: ${err}`, true)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}}>Test key</Button
|
||||
>
|
||||
@@ -26,6 +26,35 @@ const openaiConfig: CompletionCreateParamsStreaming = {
|
||||
let workspace: string | undefined = undefined
|
||||
let openai: OpenAI | undefined = undefined
|
||||
|
||||
export async function testKey({
|
||||
apiKey,
|
||||
abortController,
|
||||
messages
|
||||
}: {
|
||||
apiKey?: string
|
||||
messages: CreateChatCompletionRequestMessage[]
|
||||
abortController: AbortController
|
||||
}) {
|
||||
if (apiKey) {
|
||||
const openai = new OpenAI({
|
||||
apiKey,
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
await openai.chat.completions.create(
|
||||
{
|
||||
...openaiConfig,
|
||||
messages,
|
||||
stream: false
|
||||
},
|
||||
{
|
||||
signal: abortController.signal
|
||||
}
|
||||
)
|
||||
} else {
|
||||
await getNonStreamingCompletion(messages, abortController)
|
||||
}
|
||||
}
|
||||
|
||||
workspaceStore.subscribe(async (value) => {
|
||||
workspace = value
|
||||
const baseURL = `${location.origin}${OpenAPI.BASE}/w/${workspace}/openai/proxy`
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import TestOpenaiKey from '$lib/components/copilot/TestOpenaiKey.svelte'
|
||||
|
||||
const rd = $page.url.searchParams.get('rd')
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
let errorId = ''
|
||||
let errorUser = ''
|
||||
let openAiKey = ''
|
||||
let codeCompletionEnabled = true
|
||||
let checking = false
|
||||
|
||||
$: id = name.toLowerCase().replace(/\s/gi, '-')
|
||||
@@ -69,7 +71,7 @@
|
||||
})
|
||||
await WorkspaceService.editCopilotConfig({
|
||||
workspace: id,
|
||||
requestBody: { openai_resource_path: path, code_completion_enabled: false }
|
||||
requestBody: { openai_resource_path: path, code_completion_enabled: codeCompletionEnabled }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,12 +152,29 @@
|
||||
{/if}
|
||||
</label>
|
||||
<label class="block pb-4">
|
||||
<span class="text-secondary text-sm"
|
||||
>OpenAI key for codegen<span class="text-2xs text-tertiary ml-2"
|
||||
>(optional but recommended)</span
|
||||
></span
|
||||
>
|
||||
<input type="password" bind:value={openAiKey} on:keyup={handleKeyUp} />
|
||||
<span class="text-secondary text-sm">
|
||||
OpenAI key for Windmill AI
|
||||
<Tooltip>
|
||||
Find out how it can help you <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/ai_generation"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">in the docs</a
|
||||
>
|
||||
</Tooltip>
|
||||
<span class="text-2xs text-tertiary ml-2">(optional but recommended)</span>
|
||||
</span>
|
||||
<div class="flex flex-row gap-1">
|
||||
<input type="password" bind:value={openAiKey} on:keyup={handleKeyUp} />
|
||||
<TestOpenaiKey apiKey={openAiKey} disabled={!openAiKey} />
|
||||
</div>
|
||||
{#if openAiKey}
|
||||
<Toggle
|
||||
disabled={!openAiKey}
|
||||
size="xs"
|
||||
bind:checked={codeCompletionEnabled}
|
||||
options={{ right: 'Enable code completion' }}
|
||||
/>
|
||||
{/if}
|
||||
</label>
|
||||
<Toggle
|
||||
disabled={!isDomainAllowed}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
import PremiumInfo from '$lib/components/settings/PremiumInfo.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import TestOpenaiKey from '$lib/components/copilot/TestOpenaiKey.svelte'
|
||||
|
||||
const slackErrorHandler = 'hub/2431/slack/schedule-error-handler-slack'
|
||||
|
||||
@@ -168,7 +169,10 @@
|
||||
if (emptyString($enterpriseLicense)) {
|
||||
errorHandlerSelected = 'custom'
|
||||
} else {
|
||||
errorHandlerSelected = emptyString(errorHandlerScriptPath) || errorHandlerScriptPath === slackErrorHandler ? 'slack' : 'custom'
|
||||
errorHandlerSelected =
|
||||
emptyString(errorHandlerScriptPath) || errorHandlerScriptPath === slackErrorHandler
|
||||
? 'slack'
|
||||
: 'custom'
|
||||
}
|
||||
errorHandlerExtraArgs = settings.error_handler_extra_args ?? {}
|
||||
codeCompletionEnabled = settings.code_completion_enabled
|
||||
@@ -184,18 +188,18 @@
|
||||
if (errorHandlerScriptPath) {
|
||||
await WorkspaceService.editErrorHandler({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
requestBody: {
|
||||
error_handler: `${errorHandlerItemKind}/${errorHandlerScriptPath}`,
|
||||
error_handler_extra_args: errorHandlerExtraArgs,
|
||||
error_handler_extra_args: errorHandlerExtraArgs
|
||||
}
|
||||
})
|
||||
sendUserToast(`workspace error handler set to ${errorHandlerScriptPath}`)
|
||||
} else {
|
||||
await WorkspaceService.editErrorHandler({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
requestBody: {
|
||||
error_handler: undefined,
|
||||
error_handler_extra_args: undefined,
|
||||
error_handler_extra_args: undefined
|
||||
}
|
||||
})
|
||||
sendUserToast(`workspace error handler removed`)
|
||||
@@ -438,7 +442,7 @@
|
||||
</div>
|
||||
{:else if tab == 'error_handler'}
|
||||
<PageHeader title="Script to run as error handler" primary={false} />
|
||||
|
||||
|
||||
<ErrorOrRecoveryHandler
|
||||
isEditable={true}
|
||||
handlersOnlyForEe={['slack']}
|
||||
@@ -458,15 +462,17 @@
|
||||
The following args will be passed to the error handler:
|
||||
<ul class="mt-1 ml-2">
|
||||
<li><b>path</b>: The path of the script or flow that errored.</li>
|
||||
<li><b>email</b>: The email of the user who ran the script or flow that errored.</li>
|
||||
<li>
|
||||
<b>email</b>: The email of the user who ran the script or flow that errored.
|
||||
</li>
|
||||
<li><b>error</b>: The error details.</li>
|
||||
<li><b>job_id</b>: The job id.</li>
|
||||
<li><b>is_flow</b>: Whether the error comes from a flow.</li>
|
||||
<li><b>workspace_id</b>: The workspace id of the failed script or flow.</li>
|
||||
</ul>
|
||||
<br />
|
||||
The error handler will be executed by the automatically created group g/error_handler. If
|
||||
your error handler requires variables or resources, you need to add them to the group.
|
||||
The error handler will be executed by the automatically created group g/error_handler.
|
||||
If your error handler requires variables or resources, you need to add them to the group.
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -475,14 +481,15 @@
|
||||
|
||||
<div class="flex mt-5 justify-start">
|
||||
<Button
|
||||
disabled={(errorHandlerSelected === 'slack' && !emptyString(errorHandlerScriptPath) && emptyString(errorHandlerExtraArgs['channel']))}
|
||||
disabled={errorHandlerSelected === 'slack' &&
|
||||
!emptyString(errorHandlerScriptPath) &&
|
||||
emptyString(errorHandlerExtraArgs['channel'])}
|
||||
size="sm"
|
||||
on:click={editErrorHandler}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{:else if tab == 'openai'}
|
||||
<PageHeader title="Windmill AI" primary={false} />
|
||||
<div class="mt-2">
|
||||
@@ -491,7 +498,7 @@
|
||||
features.
|
||||
</Alert>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="mt-5 flex gap-1">
|
||||
{#key openaiResourceInitialPath}
|
||||
<ResourcePicker
|
||||
resourceType="openai"
|
||||
@@ -501,6 +508,7 @@
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
<TestOpenaiKey disabled={!openaiResourceInitialPath} />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<Toggle
|
||||
|
||||
Reference in New Issue
Block a user