* (frontend) add quick access menu in flow editor * (frontend) add quick access menu in flow editor * improve UI * make design prettier * add scroll effects * improve loading preview * change no items found * prevent scroll using menu * change user folder button * set default integration icon * reduce column width * ajust font * add defaults script button * add shadow divider * fix scroll * add chevron * Change toogle bar * Add preprocessor menu * add handler * simplify scroll * fix display * fix minor issues * delete useless log * revert node tree changes * merge main * fix z-index issues * iterate * fix: improve allowed domains setting for sso * chore(main): release 1.402.3 (#4458) * chore(main): release 1.402.3 * Apply automatic changes --------- Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com> * improve allowed domains change handling * send stats when renewing key if last >24h (#4430) * feat: send stats when renewing key if last >24h * nits * fix: sqlx * nit * renewal reason * stats reason * update ee ref * Update ee-repo-ref.txt --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev> * fix: skip one migration to avoid using md5 for azure support * all * all * Apply automatic changes * all * done? * nit * nit * nit * nit * nit noAi if prefilter is not all * fix shadow * fix error handler * fix error handler * Polishing default script settings * all * all * full * all * add deno_core as features * all * remove warnings * all * npm check * npm check * new script script * nits * nits item 0 * nits item 0 --------- Co-authored-by: Guilhem Le Mouel <guilhem.le-mouel.ext@altran.com> Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: HugoCasa <hugo@casademont.ch> Co-authored-by: Guilhem <guilhem@mbp-de-windmill.home>
247 lines
7.0 KiB
Svelte
247 lines
7.0 KiB
Svelte
<script lang="ts">
|
|
import { copilotInfo, workspaceStore } from '$lib/stores'
|
|
import { createEventDispatcher, getContext } from 'svelte'
|
|
import type { FlowEditorContext } from '../flows/types'
|
|
import type { FlowCopilotContext, FlowCopilotModule } from './flow'
|
|
import { ScriptService, type FlowModule, type Script } from '$lib/gen'
|
|
import { APP_TO_ICON_COMPONENT } from '../icons'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import { nextId } from '../flows/flowModuleNextId'
|
|
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
|
|
|
|
// state
|
|
let input: HTMLInputElement | undefined
|
|
let hubCompletions: FlowCopilotModule['hubCompletions'] = []
|
|
let selectedCompletion: FlowCopilotModule['selectedCompletion'] = undefined
|
|
let lang: FlowCopilotModule['lang'] = undefined
|
|
|
|
const { flowStore, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
|
const { modulesStore: copilotModulesStore, genFlow } =
|
|
getContext<FlowCopilotContext>('FlowCopilotContext')
|
|
|
|
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 as FlowCopilotModule['hubCompletions']
|
|
} catch (err) {
|
|
if (err.name !== 'CancelError') throw err
|
|
}
|
|
}
|
|
|
|
async function onGenerate() {
|
|
if (!selectedCompletion && !$copilotInfo.exists_openai_resource_path) {
|
|
sendUserToast(
|
|
'Windmill AI is not enabled, you can activate it in the workspace settings',
|
|
true
|
|
)
|
|
return
|
|
}
|
|
$copilotModulesStore = [
|
|
{
|
|
id: nextId($flowStateStore, $flowStore),
|
|
type: trigger ? 'trigger' : 'script',
|
|
description: funcDesc,
|
|
code: '',
|
|
source: selectedCompletion ? 'hub' : 'custom',
|
|
hubCompletions,
|
|
selectedCompletion,
|
|
editor: undefined,
|
|
lang
|
|
}
|
|
]
|
|
genFlow?.(index, modules, true)
|
|
}
|
|
|
|
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>
|