fix: optimize public apps rendering

This commit is contained in:
Ruben Fiszel
2025-06-29 22:46:27 +00:00
parent e2128b854e
commit 41c8ac1c92
46 changed files with 1014 additions and 692 deletions
@@ -1,4 +1,4 @@
import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte'
import { triggerablesByAi } from '$lib/components/copilot/chat/sharedChatState.svelte'
export interface TriggerableByAIOptions {
id?: string
@@ -60,15 +60,15 @@ export function triggerableByAI(element: HTMLElement, options: TriggerableByAIOp
// register the triggerable
const currentData = { description, onTrigger: handleTrigger }
aiChatManager.triggerablesByAI[id] = currentData
triggerablesByAi[id] = currentData
}
function unregister() {
if (isDisabled || !id) return
// unregister the triggerable
if (aiChatManager.triggerablesByAI[id]) {
delete aiChatManager.triggerablesByAI[id]
if (triggerablesByAi[id]) {
delete triggerablesByAi[id]
}
}
@@ -77,7 +77,7 @@ export function triggerableByAI(element: HTMLElement, options: TriggerableByAIOp
return {
update(newOptions: TriggerableByAIOptions) {
;({ id, description, callback, showAnimation = true } = newOptions)
; ({ id, description, callback, showAnimation = true } = newOptions)
register()
},
destroy() {
@@ -0,0 +1,8 @@
<script lang="ts">
import ComponentInner from './apps/editor/component/ComponentInner.svelte'
// import AppPreview from './apps/editor/AppPreview.svelte'
// AppPreview
ComponentInner
</script>
@@ -13,7 +13,8 @@
Table2,
Braces,
Highlighter,
ArrowDownFromLine
ArrowDownFromLine,
Loader2
} from 'lucide-svelte'
import Portal from '$lib/components/Portal.svelte'
import DisplayResultControlBar from './DisplayResultControlBar.svelte'
@@ -34,7 +35,6 @@
import { convertJsonToCsv } from './table/tableUtils'
import Tooltip from './Tooltip.svelte'
import HighlightTheme from './HighlightTheme.svelte'
import PdfViewer from './display/PdfViewer.svelte'
import type { DisplayResultUi } from './custom_ui'
import { getContext, hasContext, createEventDispatcher, onDestroy } from 'svelte'
import { toJsonStr } from '$lib/utils'
@@ -634,10 +634,14 @@
</div>
{:else if !forceJson && resultKind === 'pdf'}
<div class="h-96 mt-2 border">
<PdfViewer
allowFullscreen
source="data:application/pdf;base64,{contentOrRootString(result.pdf)}"
/>
{#await import('$lib/components/display/PdfViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
allowFullscreen
source="data:application/pdf;base64,{contentOrRootString(result.pdf)}"
/>
{/await}
</div>
{:else if !forceJson && resultKind === 'plain'}<div class="h-full text-2xs"
><pre class="whitespace-pre-wrap"
@@ -809,18 +813,22 @@
</div>
{:else if result?.s3?.endsWith('.pdf')}
<div class="h-96 mt-2 border">
<PdfViewer
allowFullscreen
source="{`/api/w/${workspaceId}/${
appPath
? 'apps_u/download_s3_file/' + appPath
: 'job_helpers/load_image_preview'
}?${appPath ? 's3' : 'file_key'}=${encodeURIComponent(result.s3)}` +
(result.storage ? `&storage=${result.storage}` : '')}{appPath &&
result.presigned
? `&${result.presigned}`
: ''}"
/>
{#await import('$lib/components/display/PdfViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
allowFullscreen
source="{`/api/w/${workspaceId}/${
appPath
? 'apps_u/download_s3_file/' + appPath
: 'job_helpers/load_image_preview'
}?${appPath ? 's3' : 'file_key'}=${encodeURIComponent(result.s3)}` +
(result.storage ? `&storage=${result.storage}` : '')}{appPath &&
result.presigned
? `&${result.presigned}`
: ''}"
/>
{/await}
</div>
{/if}
{/if}
@@ -893,12 +901,16 @@
{/if}
{:else if s3object?.s3?.endsWith('.pdf')}
<div class="h-96 mt-2 border" data-interactive>
<PdfViewer
allowFullscreen
source={`/api/w/${workspaceId}/job_helpers/load_image_preview?file_key=${encodeURIComponent(
s3object.s3
)}` + (s3object.storage ? `&storage=${s3object.storage}` : '')}
/>
{#await import('$lib/components/display/PdfViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
allowFullscreen
source={`/api/w/${workspaceId}/job_helpers/load_image_preview?file_key=${encodeURIComponent(
s3object.s3
)}` + (s3object.storage ? `&storage=${s3object.storage}` : '')}
/>
{/await}
</div>
{/if}
{/each}
@@ -16,7 +16,7 @@
import ResolveOpen from '$lib/components/common/menu/ResolveOpen.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { twMerge } from 'tailwind-merge'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import { untrack } from 'svelte'
interface Props {
@@ -1,5 +1,5 @@
<script lang="ts">
import { MenuItem } from '$lib/components/meltComponents'
import MenuItem from '$lib/components/meltComponents/MenuItem.svelte'
import { Loader2 } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { MenubarMenuElements } from '@melt-ui/svelte'
+3 -7
View File
@@ -105,13 +105,8 @@
vimMode
} from '$lib/stores'
import {
createHash as randomHash,
editorConfig,
langToExt,
updateOptions,
extToLang
} from '$lib/editorUtils'
import { editorConfig, updateOptions } from '$lib/editorUtils'
import { createHash as randomHash } from '$lib/editorLangUtils'
import { workspaceStore } from '$lib/stores'
import { type Preview, ResourceService, type ScriptLang, UserService } from '$lib/gen'
import type { Text } from 'yjs'
@@ -156,6 +151,7 @@
import { formatResourceTypes } from './copilot/chat/script/core'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
import { editorPositionMap } from '$lib/utils'
import { extToLang, langToExt } from '$lib/editorLangUtils'
// import EditorTheme from './EditorTheme.svelte'
let divEl: HTMLDivElement | null = null
@@ -11,10 +11,11 @@
import { base } from '$lib/base'
import FlowJobResult from './FlowJobResult.svelte'
import FlowPreviewStatus from './preview/FlowPreviewStatus.svelte'
import DisplayResult from './DisplayResult.svelte'
import { createEventDispatcher, getContext, tick } from 'svelte'
import { onDestroy } from 'svelte'
import { Badge, Button, Skeleton, Tab } from './common'
import DisplayResult from './DisplayResult.svelte'
import Tabs from './common/tabs/Tabs.svelte'
import { type DurationStatus, type FlowStatusViewerContext, type GraphModuleState } from './graph'
import ModuleStatus from './ModuleStatus.svelte'
+33 -21
View File
@@ -1,40 +1,52 @@
<script lang="ts">
import { debounce, displayDate, msToSec } from '$lib/utils'
import { onDestroy } from 'svelte'
import { onDestroy, untrack } from 'svelte'
import { getDbClockNow } from '$lib/forLater'
import { Loader2 } from 'lucide-svelte'
import TimelineBar from './TimelineBar.svelte'
import type { Writable } from 'svelte/store'
import WaitTimeWarning from './common/waitTimeWarning/WaitTimeWarning.svelte'
export let selfWaitTime: number | undefined = undefined
export let aggregateWaitTime: number | undefined = undefined
export let flowModules: string[]
export let durationStatuses: Writable<
Record<
string,
{
byJob: Record<string, { created_at?: number; started_at?: number; duration_ms?: number }>
iteration_from?: number
iteration_total?: number
}
interface Props {
selfWaitTime?: number | undefined
aggregateWaitTime?: number | undefined
flowModules: string[]
durationStatuses: Writable<
Record<
string,
{
byJob: Record<string, { created_at?: number; started_at?: number; duration_ms?: number }>
iteration_from?: number
iteration_total?: number
}
>
>
>
export let flowDone = false
flowDone?: boolean
}
let min: undefined | number = undefined
let max: undefined | number = undefined
let total: number | undefined = undefined
let {
selfWaitTime = undefined,
aggregateWaitTime = undefined,
flowModules,
durationStatuses,
flowDone = false
}: Props = $props()
let min: undefined | number = $state(undefined)
let max: undefined | number = $state(undefined)
let total: number | undefined = $state(undefined)
let items:
| Record<
string,
Array<{ created_at?: number; started_at?: number; duration_ms?: number; id: string }>
>
| undefined = undefined
| undefined = $state(undefined)
let { debounced, clearDebounce } = debounce(() => computeItems($durationStatuses), 30)
$: flowDone != undefined && $durationStatuses && debounced()
$effect(() => {
flowDone != undefined && $durationStatuses && untrack(() => debounced())
})
export function reset() {
min = undefined
@@ -104,7 +116,7 @@
}
}
let now = getDbClockNow().getTime()
let now = $state(getDbClockNow().getTime())
let interval = setInterval((x) => {
if (!max) {
@@ -165,7 +177,7 @@
<div class="w-full flex flex-row-reverse sticky top-0">
<button
class="!text-secondary underline mr-2 text-2xs text-right whitespace-nowrap"
on:click={() => {
onclick={() => {
let r = $durationStatuses[k]
if (r.iteration_from) {
r.iteration_from -= 20
+1 -1
View File
@@ -18,7 +18,7 @@
import { page } from '$app/stores'
import { replaceState } from '$app/navigation'
import JsonInputs from '$lib/components/JsonInputs.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
import { untrack } from 'svelte'
@@ -25,7 +25,6 @@
import TableSimple from './TableSimple.svelte'
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
import FileUploadModal from './common/fileUpload/FileUploadModal.svelte'
import PdfViewer from './display/PdfViewer.svelte'
import { twMerge } from 'tailwind-merge'
let deletionModalOpen = $state(false)
@@ -722,11 +721,15 @@
</div>
{:else if fileMetadata?.fileKey.endsWith('.pdf')}
<div class="w-full h-[950px] border">
<PdfViewer
source={`/api/w/${$workspaceStore}/job_helpers/load_image_preview?file_key=${encodeURIComponent(
fileMetadata.fileKey
)}` + (storage ? `&storage=${storage}` : '')}
/>
{#await import('$lib/components/display/PdfViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
source={`/api/w/${$workspaceStore}/job_helpers/load_image_preview?file_key=${encodeURIComponent(
fileMetadata.fileKey
)}` + (storage ? `&storage=${storage}` : '')}
/>
{/await}
</div>
{:else if filePreviewLoading}
<div class="flex h-6 items-center text-tertiary mb-4">
@@ -45,7 +45,7 @@
import { getStringError } from './copilot/chat/utils'
import type { ScriptOptions } from './copilot/chat/ContextManager.svelte'
import { aiChatManager, AIMode } from './copilot/chat/AIChatManager.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
interface Props {
// Exported
@@ -418,10 +418,13 @@
<svelte:window onkeydown={onKeyDown} />
<!-- Standalone triggerable registration for the script editor -->
<div style="display: none" use:triggerableByAI={{
id: "script-editor",
description: "Component to edit a script"
}}></div>
<div
style="display: none"
use:triggerableByAI={{
id: 'script-editor',
description: 'Component to edit a script'
}}
></div>
<Modal title="Invite others" bind:open={showCollabPopup}>
<div>Have others join by sharing the following url:</div>
@@ -57,7 +57,9 @@
<script lang="ts">
import { BROWSER } from 'esm-env'
import { createHash, editorConfig, langToExt, updateOptions } from '$lib/editorUtils'
import { editorConfig, updateOptions } from '$lib/editorUtils'
import { createHash } from '$lib/editorLangUtils'
// import {
// editor as meditor,
// KeyCode,
@@ -80,6 +82,7 @@
import { initVim } from './monaco_keybindings'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
import { editorPositionMap } from '$lib/utils'
import { langToExt } from '$lib/editorLangUtils'
// import { createConfiguredEditor } from 'vscode/monaco'
// import type { IStandaloneCodeEditor } from 'vscode/vscode/vs/editor/standalone/browser/standaloneCodeEditor'
+14 -10
View File
@@ -38,9 +38,9 @@
</script>
<script lang="ts">
import { CornerDownLeft } from 'lucide-svelte'
import { CornerDownLeft, Loader2 } from 'lucide-svelte'
import Button from './common/button/Button.svelte'
import Editor from './Editor.svelte'
import type Editor from './Editor.svelte'
import { runScriptAndPollResult } from './jobs/utils'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
@@ -146,14 +146,18 @@
<Splitpanes>
<Pane class="relative">
<Editor
bind:this={editor}
bind:code
lang="sql"
scriptLang="mysql"
class="w-full h-full"
cmdEnterAction={run}
/>
{#await import('$lib/components/Editor.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
bind:this={editor}
bind:code
lang="sql"
scriptLang="mysql"
class="w-full h-full"
cmdEnterAction={run}
/>
{/await}
<Button
wrapperClasses="absolute z-10 bottom-2 right-6"
color={isRunning ? 'red' : undefined}
@@ -21,11 +21,12 @@
import {
convertKind,
createDocumentationString,
createHash,
displayPartsToString,
editorConfig,
updateOptions
} from '$lib/editorUtils'
import { createHash } from '$lib/editorLangUtils'
import libStdContent from '$lib/es6.d.ts.txt?raw'
import { editor as meditor, Uri as mUri, languages, Range, KeyMod, KeyCode } from 'monaco-editor'
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
+1 -1
View File
@@ -4,7 +4,7 @@
import { twMerge } from 'tailwind-merge'
import Tooltip from './Tooltip.svelte'
import { AlertTriangle } from 'lucide-svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
export let options: {
left?: string
@@ -6,7 +6,7 @@
import InputValue from '../helpers/InputValue.svelte'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import PdfViewer from '$lib/components/display/PdfViewer.svelte'
import { Loader2 } from 'lucide-svelte'
interface Props {
id: string
@@ -46,17 +46,21 @@
{#if render}
<div class="relative w-full h-full bg-gray-100 component-wrapper">
<PdfViewer
{source}
{zoom}
class={css?.container?.class}
style={css?.container?.style}
on:loading={() => {
outputs.loading.set(true)
}}
on:loaded={() => {
outputs.loading.set(false)
}}
/>
{#await import('$lib/components/display/PdfViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
{source}
{zoom}
class={css?.container?.class}
style={css?.container?.style}
on:loading={() => {
outputs.loading.set(true)
}}
on:loaded={() => {
outputs.loading.set(false)
}}
/>
{/await}
</div>
{/if}
@@ -1,10 +1,24 @@
<script lang="ts">
export let applyPageWidth = false
export let stickToTop = false
interface Props {
applyPageWidth?: boolean
stickToTop?: boolean
class?: string | undefined
left?: import('svelte').Snippet
middle?: import('svelte').Snippet
right?: import('svelte').Snippet
}
$: wide = applyPageWidth ? 'max-w-7xl mx-auto px-4 sm:px-6 md:px-8' : ''
let {
applyPageWidth = false,
stickToTop = false,
class: clazz = '',
left,
middle,
right
}: Props = $props()
let wide = $derived(applyPageWidth ? 'max-w-7xl mx-auto px-4 sm:px-6 md:px-8' : '')
let scrollY = 0
let scrollY = $state(0)
</script>
<svelte:window bind:scrollY />
@@ -13,24 +27,24 @@
class={'bg-surface py-3 ' +
(stickToTop
? 'lg:sticky lg:top-0 z-[500] border-b border-gray-200 border-opacity-0 duration-300 ' +
(scrollY >= 30 ? 'border-opacity-100 ' : '')
(scrollY >= 30 ? 'border-opacity-100 ' : '')
: '') +
($$props.class || '')}
(clazz || '')}
>
<div class={'w-full flex flex-wrap justify-between items-center gap-4 ' + wide}>
<div class="flex flex-wrap items-center gap-2">
{#if $$slots.left}
<slot name="left" />
{#if left}
{@render left?.()}
{/if}
</div>
<div class="flex flex-wrap items-center gap-2">
{#if $$slots.middle}
<slot name="middle" />
{#if middle}
{@render middle?.()}
{/if}
</div>
<div class="flex flex-wrap items-center gap-2 lg:gap-4">
{#if $$slots.right}
<slot name="right" />
{#if right}
{@render right?.()}
{/if}
</div>
</div>
@@ -12,7 +12,7 @@
import type { Placement } from '@floating-ui/core'
import { conditionalMelt } from '$lib/utils'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
type MenuItem = {
label: string
@@ -12,7 +12,7 @@
} from '$lib/utils'
import { page } from '$app/stores'
import type { GetInitialAndModifiedValues } from './unsavedTypes'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
export let getInitialAndModifiedValues: GetInitialAndModifiedValues = undefined
export let diffDrawer: DiffDrawer | undefined = undefined
@@ -69,7 +69,13 @@
</script>
{#if open}
<div style="display: none" use:triggerableByAI={{id: "unsaved-changes-confirmation-modal", description: "Unsaved changes confirmation modal. Needs user confirmation to leave the page."}}></div>
<div
style="display: none"
use:triggerableByAI={{
id: 'unsaved-changes-confirmation-modal',
description: 'Unsaved changes confirmation modal. Needs user confirmation to leave the page.'
}}
></div>
{/if}
<ConfirmationModal
@@ -3,21 +3,41 @@
import { BROWSER } from 'esm-env'
import Disposable from './Disposable.svelte'
import ConditionalPortal from './ConditionalPortal.svelte'
import { aiChatManager } from '../../copilot/chat/AIChatManager.svelte'
import { chatState } from '$lib/components/copilot/chat/sharedChatState.svelte'
export let open = false
export let duration = 0.3
export let placement = 'right'
export let size = '600px'
export let alwaysOpen = false
export let shouldUsePortal: boolean = true
export let offset: number = 0
export let preventEscape = false
export let disableChatOffset: boolean = false
interface Props {
open?: boolean
duration?: number
placement?: string
size?: string
alwaysOpen?: boolean
shouldUsePortal?: boolean
offset?: number
preventEscape?: boolean
disableChatOffset?: boolean
class?: string | undefined
positionClass?: string | undefined
children?: import('svelte').Snippet<[any]>
}
let disposable: Disposable | undefined = undefined
let {
open = $bindable(false),
duration = 0.3,
placement = 'right',
size = '600px',
alwaysOpen = false,
shouldUsePortal = true,
offset = 0,
preventEscape = false,
disableChatOffset = false,
class: clazz = '',
positionClass = undefined,
children
}: Props = $props()
$: durationMs = duration * 1000
let disposable: Disposable | undefined = $state(undefined)
let durationMs = $derived(duration * 1000)
export function toggleDrawer() {
disposable?.toggleDrawer()
@@ -42,7 +62,7 @@
let mounted = false
const dispatch = createEventDispatcher()
$: style = `--duration: ${duration}s; --size: ${size};`
let style = $derived(`--duration: ${duration}s; --size: ${size};`)
function scrollLock(open: boolean) {
if (BROWSER) {
@@ -54,45 +74,55 @@
}
}
$: scrollLock(open)
$effect(() => {
scrollLock(open)
})
$: open ? openDrawer() : closeDrawer()
$effect(() => {
open ? openDrawer() : closeDrawer()
})
let timeout = true
$: !open ? setTimeout(() => (timeout = true), durationMs) : (timeout = false)
let timeout = $state(true)
$effect(() => {
!open ? setTimeout(() => (timeout = true), durationMs) : (timeout = false)
})
onMount(() => {
mounted = true
})
const children_render = $derived(children)
const aiChatOpen = $derived(chatState.size > 0)
</script>
<ConditionalPortal condition={shouldUsePortal}>
<Disposable
initialOffset={offset}
let:handleClickAway
let:zIndex
bind:open
bind:this={disposable}
on:open
on:close
{preventEscape}
>
<aside
class="drawer windmill-app windmill-drawer {$$props.class ?? ''} {$$props.positionClass ??
''} {aiChatManager.open ? 'respect-global-chat' : ''}"
class:open
class:close={!open && timeout}
class:global-chat-open={aiChatManager.open}
style={`${style}; --zIndex: ${zIndex}; --adjusted-offset: calc(${aiChatManager.open && placement === 'right' && !disableChatOffset ? aiChatManager.size : 0}% + 4px)`}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="overlay {$$props.positionClass ?? ''}" on:click={handleClickAway}></div>
<div class="panel {placement} {$$props.positionClass}" class:size>
{#if open || !timeout || alwaysOpen}
<slot {open} />
{/if}
</div>
</aside>
{#snippet children({ handleClickAway, zIndex })}
<aside
class="drawer windmill-app windmill-drawer {clazz ?? ''} {positionClass ?? ''} {aiChatOpen
? 'respect-global-chat'
: ''}"
class:open
class:close={!open && timeout}
class:global-chat-open={aiChatOpen}
style={`${style}; --zIndex: ${zIndex}; --adjusted-offset: calc(${aiChatOpen && placement === 'right' && !disableChatOffset ? chatState.size : 0}% + 4px)`}
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="overlay {positionClass ?? ''}" onclick={handleClickAway}></div>
<div class="panel {placement} {positionClass}" class:size>
{#if open || !timeout || alwaysOpen}
{@render children_render?.({ open })}
{/if}
</div>
</aside>
{/snippet}
</Disposable>
</ConditionalPortal>
@@ -2,7 +2,7 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import { classNames } from '$lib/utils'
import CloseButton from '../CloseButton.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import { createEventDispatcher } from 'svelte'
interface Props {
@@ -5,7 +5,7 @@
import { BellOff } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import { goto } from '$lib/navigation'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
const dispatch = createEventDispatcher()
@@ -2,7 +2,7 @@
import type { TabsContext } from '$lib/components/apps/editor/settingsPanel/inputEditor/tabs.svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
const dispatch = createEventDispatcher<{ onpointerdown: any }>()
@@ -11,7 +11,7 @@
import { yamlStringifyExceptKeys } from './utils'
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
type PromptConfig = {
system: string
@@ -234,72 +234,70 @@ Generate a description for the flow below:
}
}}
>
<div
class="absolute left-[0.5rem] {elementType === 'textarea'
? 'top-[1.3rem]'
: 'top-[0.3rem]'} flex flex-row gap-2 items-start pointer-events-none"
>
{#if active}
<span
class={twMerge(
'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',
!loading && generatedContent.length > 0
? 'bg-green-100 text-green-800 dark:text-green-400 dark:bg-green-700'
: ''
)}
>
<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>
<div
class="absolute left-[0.5rem] {elementType === 'textarea'
? 'top-[1.3rem]'
: 'top-[0.3rem]'} flex flex-row gap-2 items-start pointer-events-none"
>
{#if active}
<span
class={twMerge(
'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',
!loading && generatedContent.length > 0
? 'bg-green-100 text-green-800 dark:text-green-400 dark:bg-green-700'
: ''
)}
>
<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>
<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>
{/if}
</div>
{#if elementType === 'textarea'}
<div>
<div class="flex flex-row-reverse !text-3xs text-tertiary -mt-4">GH Markdown</div>
<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)}
></textarea>
</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>
{:else}
<input
{/if}
</div>
{#if elementType === 'textarea'}
<div>
<div class="flex flex-row-reverse !text-3xs text-tertiary -mt-4">GH Markdown</div>
<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)}
/>
{/if}
></textarea>
</div>
{: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>
@@ -30,14 +30,8 @@ import type { CurrentEditor, ExtendedOpenFlow } from '$lib/components/flows/type
import { untrack } from 'svelte'
import type { DBSchemas } from '$lib/stores'
import { askTools, prepareAskSystemMessage } from './ask/core'
import { chatState, DEFAULT_SIZE, triggerablesByAi } from './sharedChatState.svelte'
type TriggerablesMap = Record<
string,
{
description: string
onTrigger: ((value?: string) => void) | undefined
}
>
export enum AIMode {
SCRIPT = 'script',
@@ -47,7 +41,6 @@ export enum AIMode {
}
class AIChatManager {
DEFAULT_SIZE = 22
NAVIGATION_SYSTEM_PROMPT = `
CONSIDERATIONS:
- You are provided with a tool to switch to navigation mode, only use it when you are sure that the user is asking you to navigate the application, help them find something or fetch data from the API. Do not use it otherwise.
@@ -57,8 +50,7 @@ class AIChatManager {
abortController: AbortController | undefined = undefined
mode = $state<AIMode>(AIMode.NAVIGATOR)
size = $state<number>(localStorage.getItem('ai-chat-open') === 'true' ? this.DEFAULT_SIZE : 0)
readonly isOpen = $derived(this.size > 0)
readonly isOpen = $derived(chatState.size > 0)
savedSize = $state<number>(0)
instructions = $state<string>('')
pendingPrompt = $state<string>('')
@@ -74,7 +66,6 @@ class AIChatManager {
tools = $state<Tool<any>[]>([])
helpers = $state<any | undefined>(undefined)
triggerablesByAI = $state<TriggerablesMap>({})
scriptEditorOptions = $state<ScriptOptions | undefined>(undefined)
scriptEditorApplyCode = $state<((code: string) => void) | undefined>(undefined)
scriptEditorShowDiffMode = $state<(() => void) | undefined>(undefined)
@@ -89,7 +80,7 @@ class AIChatManager {
ask: true
})
open = $derived(this.size > 0)
open = $derived(chatState.size > 0)
async loadApiTools() {
if (this.apiTools.length === 0) {
@@ -129,7 +120,7 @@ class AIChatManager {
getLang: () => lang
}
if (options?.closeScriptSettings) {
const closeComponent = this.triggerablesByAI['close-script-builder-settings']
const closeComponent = triggerablesByAi['close-script-builder-settings']
if (closeComponent) {
closeComponent.onTrigger?.()
}
@@ -188,22 +179,22 @@ class AIChatManager {
}
openChat = () => {
this.size = this.savedSize > 0 ? this.savedSize : this.DEFAULT_SIZE
chatState.size = this.savedSize > 0 ? this.savedSize : DEFAULT_SIZE
localStorage.setItem('ai-chat-open', 'true')
}
closeChat = () => {
this.savedSize = this.size
this.size = 0
this.savedSize = chatState.size
chatState.size = 0
localStorage.setItem('ai-chat-open', 'false')
}
toggleOpen = () => {
if (this.size > 0) {
this.savedSize = this.size
if (chatState.size > 0) {
this.savedSize = chatState.size
}
this.size = this.size === 0 ? (this.savedSize > 0 ? this.savedSize : this.DEFAULT_SIZE) : 0
localStorage.setItem('ai-chat-open', this.size === 0 ? 'false' : 'true')
chatState.size = chatState.size === 0 ? (this.savedSize > 0 ? this.savedSize : DEFAULT_SIZE) : 0
localStorage.setItem('ai-chat-open', chatState.size === 0 ? 'false' : 'true')
}
askAi = (
@@ -432,8 +423,8 @@ class AIChatManager {
: this.mode === AIMode.NAVIGATOR
? prepareNavigatorUserMessage(oldInstructions)
: await prepareScriptUserMessage(oldInstructions, lang, oldSelectedContext, {
isPreprocessor
})
isPreprocessor
})
this.messages.push(userMessage)
await this.historyManager.saveChat(this.displayMessages, this.messages)
@@ -503,7 +494,7 @@ class AIChatManager {
restartLastGeneration = (displayMessageIndex: number) => {
const userMessage = this.displayMessages[displayMessageIndex]
if (!userMessage || userMessage.role !== 'user') {
throw new Error('No user message found at the specified index')
}
@@ -657,15 +648,15 @@ class AIChatManager {
const editorRelated =
currentEditor && currentEditor.type === 'script' && currentEditor.stepId === module.id
? {
diffMode: currentEditor.diffMode,
lastDeployedCode: currentEditor.lastDeployedCode,
lastSavedCode: undefined
}
diffMode: currentEditor.diffMode,
lastDeployedCode: currentEditor.lastDeployedCode,
lastSavedCode: undefined
}
: {
diffMode: false,
lastDeployedCode: undefined,
lastSavedCode: undefined
}
diffMode: false,
lastDeployedCode: undefined,
lastSavedCode: undefined
}
return {
args: moduleState?.previewArgs ?? {},
@@ -1,4 +1,3 @@
import { langToExt } from '$lib/editorUtils'
import { ResourceService, type ListResourceResponse, type ScriptLang } from '$lib/gen'
import { scriptLangToEditorLang } from '$lib/scripts'
import { SQLSchemaLanguages, type DBSchemas } from '$lib/stores'
@@ -6,6 +5,7 @@ import { diffLines } from 'diff'
import type { ContextElement } from './context'
import type { DisplayMessage } from './shared'
import { langToExt } from '$lib/editorLangUtils'
export interface ScriptOptions {
lang: ScriptLang | 'bunnative'
@@ -160,15 +160,15 @@ export default class ContextManager {
.map((c) =>
c.type === 'code'
? {
...c,
content: scriptOptions.code,
title: this.getContextCodePath(scriptOptions)
}
...c,
content: scriptOptions.code,
title: this.getContextCodePath(scriptOptions)
}
: c.type === 'db' && dbSchemas[c.title]
? {
...c,
schema: dbSchemas[c.title]
}
...c,
schema: dbSchemas[c.title]
}
: c
)
@@ -234,14 +234,14 @@ export default class ContextManager {
...(options.withCode === false ? [] : [codeContext]),
...(options.withDiff
? [
{
type: 'diff' as const,
title: 'diff_with_last_deployed_version',
content: this.scriptOptions.lastDeployedCode ?? '',
diff: diffLines(this.scriptOptions.lastDeployedCode ?? '', this.scriptOptions.code),
lang: this.scriptOptions.lang
}
]
{
type: 'diff' as const,
title: 'diff_with_last_deployed_version',
content: this.scriptOptions.lastDeployedCode ?? '',
diff: diffLines(this.scriptOptions.lastDeployedCode ?? '', this.scriptOptions.code),
lang: this.scriptOptions.lang
}
]
: [])
]
}
@@ -268,14 +268,14 @@ export default class ContextManager {
contextElements:
m.role !== 'tool' && m.contextElements
? m.contextElements.map((c) =>
c.type === 'db'
? {
type: 'db',
title: c.title,
schema: dbSchemas[c.title]
}
: c
)
c.type === 'db'
? {
type: 'db',
title: c.title,
schema: dbSchemas[c.title]
}
: c
)
: undefined
}))
}
@@ -1,6 +1,6 @@
import { openDB, type DBSchema as IDBSchema, type IDBPDatabase } from 'idb'
import type { DisplayMessage } from './shared'
import { createLongHash } from '$lib/editorUtils'
import { createLongHash } from '$lib/editorLangUtils'
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
interface ChatSchema extends IDBSchema {
chats: {
@@ -3,19 +3,6 @@
if (!id) return undefined
return aiChatManager.flowAiChatHelpers?.getModuleAction(id)
}
export function aiModuleActionToBgColor(action: AIModuleAction | undefined) {
switch (action) {
case 'modified':
return '!bg-orange-200 dark:!bg-orange-800'
case 'added':
return '!bg-green-200 dark:!bg-green-800'
case 'removed':
return '!bg-red-200/50 dark:!bg-red-800/50'
default:
return ''
}
}
</script>
<script lang="ts">
@@ -1,5 +1,6 @@
import { dfs } from '$lib/components/flows/previousResults'
import type { OpenFlow } from '$lib/gen'
import type { AIModuleAction } from './core'
export function getIndexInNestedModules(flow: OpenFlow, id: string) {
const accessingModules = dfs(id, flow, true).reverse()
@@ -80,3 +81,18 @@ export function getNestedModules(flow: OpenFlow, id: string, branchIndex?: numbe
throw new Error('Module is not a loop or branch')
}
}
export function aiModuleActionToBgColor(action: AIModuleAction | undefined) {
switch (action) {
case 'modified':
return '!bg-orange-200 dark:!bg-orange-800'
case 'added':
return '!bg-green-200 dark:!bg-green-800'
case 'removed':
return '!bg-red-200/50 dark:!bg-red-800/50'
default:
return ''
}
}
@@ -5,10 +5,10 @@ import type {
ChatCompletionUserMessageParam
} from 'openai/resources/index.mjs'
import type { Tool } from '../shared'
import { aiChatManager } from '../AIChatManager.svelte'
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { get } from 'svelte/store'
import { triggerablesByAi } from '../sharedChatState.svelte'
export const CHAT_SYSTEM_PROMPT = `
You are Windmill's intelligent assistant, designed to help users navigate the application and answer questions about its functionality. It is your only purpose to help the user in the context of the windmill application.
@@ -160,8 +160,8 @@ const GET_AVAILABLE_RESOURCES_TOOL: ChatCompletionTool = {
function getTriggerableComponents(): string {
try {
// Get components registered in the triggerablesByAI store
const registeredComponents = aiChatManager.triggerablesByAI
// Get components registered in the triggerablesByAi store
const registeredComponents = triggerablesByAi
let result = 'TRIGGERABLE_COMPONENTS:\n'
// If there are no components registered, return a message
@@ -218,7 +218,7 @@ function triggerComponent(args: { id: string; value: string }): string {
return 'Trigger command requires an id parameter'
}
const component = aiChatManager.triggerablesByAI[id]
const component = triggerablesByAi[id]
if (!component) {
return `No triggerable component found with id: ${id}`
@@ -0,0 +1,17 @@
export const triggerablesByAi = $state<TriggerablesMap>({})
export type TriggerablesMap = Record<
string,
{
description: string
onTrigger: ((value?: string) => void) | undefined
}
>
export const DEFAULT_SIZE = 22
type ChatState = {
size: number
}
export const chatState = $state<ChatState>({
size: localStorage.getItem('ai-chat-open') === 'true' ? DEFAULT_SIZE : 0,
})
+40 -41
View File
@@ -1,49 +1,49 @@
import { createLongHash } from '$lib/editorUtils'
import { createLongHash } from '$lib/editorLangUtils'
import { type editor as meditor } from 'monaco-editor'
export type VisualChange =
| {
type: 'added_inline'
position: {
line: number
column: number
}
value: string
options?: {
greenHighlight?: boolean
}
}
type: 'added_inline'
position: {
line: number
column: number
}
value: string
options?: {
greenHighlight?: boolean
}
}
| {
type: 'added_block'
position: {
afterLineNumber: number
type: 'added_block'
position: {
afterLineNumber: number
}
value: string
options?: {
greenHighlight?: boolean
review?: {
acceptFn: () => void
rejectFn: () => void
}
value: string
options?: {
greenHighlight?: boolean
review?: {
acceptFn: () => void
rejectFn: () => void
}
extraChanges?: VisualChange[]
}
}
extraChanges?: VisualChange[]
}
}
| {
type: 'deleted'
range: {
startLine: number
startColumn: number
endLine: number
endColumn: number
type: 'deleted'
range: {
startLine: number
startColumn: number
endLine: number
endColumn: number
}
options?: {
isWholeLine?: boolean
review?: {
acceptFn: () => void
rejectFn: () => void
}
options?: {
isWholeLine?: boolean
review?: {
acceptFn: () => void
rejectFn: () => void
}
}
}
}
}
function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean) {
const computedStyles = window.getComputedStyle(
@@ -81,9 +81,8 @@ function addInlineGhostText(change: Extract<VisualChange, { type: 'added_inline'
endColumn: change.position.column + change.value.length
},
options: {
beforeContentClassName: `editor-ghost-text editor-ghost-text-content-${cssId} ${
change.options?.greenHighlight ? 'editor-ghost-text-green' : ''
}`
beforeContentClassName: `editor-ghost-text editor-ghost-text-content-${cssId} ${change.options?.greenHighlight ? 'editor-ghost-text-green' : ''
}`
}
}
@@ -14,7 +14,7 @@
import type { Trigger } from '$lib/components/triggers/utils'
import FlowAIChat from '../copilot/chat/flow/FlowAIChat.svelte'
import { aiChatManager, AIMode } from '../copilot/chat/AIChatManager.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
interface Props {
@@ -9,9 +9,9 @@
import { twMerge } from 'tailwind-merge'
import { refreshStateStore } from '$lib/svelte5Utils.svelte'
import ModuleAcceptReject, {
aiModuleActionToBgColor,
getAiModuleAction
} from '$lib/components/copilot/chat/flow/ModuleAcceptReject.svelte'
import { aiModuleActionToBgColor } from '$lib/components/copilot/chat/flow/utils'
let {
small,
clazz
@@ -35,12 +35,12 @@
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
import type { FlowState } from '$lib/components/flows/flowState'
import ModuleAcceptReject, {
aiModuleActionToBgColor,
getAiModuleAction
} from '$lib/components/copilot/chat/flow/ModuleAcceptReject.svelte'
import { Button } from '$lib/components/common'
import ModuleTest from '$lib/components/ModuleTest.svelte'
import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte'
import { aiModuleActionToBgColor } from '$lib/components/copilot/chat/flow/utils'
interface Props {
selected?: boolean
@@ -8,9 +8,9 @@
import { fade } from 'svelte/transition'
import { Database, Square } from 'lucide-svelte'
import ModuleAcceptReject, {
aiModuleActionToBgColor,
getAiModuleAction
} from '$lib/components/copilot/chat/flow/ModuleAcceptReject.svelte'
import { aiModuleActionToBgColor } from '$lib/components/copilot/chat/flow/utils'
interface Props {
label?: string | undefined
+211 -205
View File
@@ -41,7 +41,7 @@
import TreeViewRoot from './TreeViewRoot.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { getContext, untrack } from 'svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
interface Props {
filter?: string
subtab?: 'flow' | 'script' | 'app'
@@ -351,217 +351,223 @@
</Drawer>
<CenteredPage>
<div class="flex flex-wrap gap-2 items-center justify-between w-full mt-2" use:triggerableByAI={{id: "home-items-list", description: "Lists of scripts, flows, and apps"}}>
<div class="flex justify-start">
<ToggleButtonGroup
bind:selected={itemKind}
on:selected={() => {
if (itemKind != 'all') {
subtab = itemKind
}
setQuery($page.url, 'kind', itemKind)
}}
class="h-10"
>
{#snippet children({ item })}
<ToggleButton value="all" label="All" class="text-sm px-4 py-2" {item} />
<div
class="flex flex-wrap gap-2 items-center justify-between w-full mt-2"
use:triggerableByAI={{
id: 'home-items-list',
description: 'Lists of scripts, flows, and apps'
}}
>
<div class="flex justify-start">
<ToggleButtonGroup
bind:selected={itemKind}
on:selected={() => {
if (itemKind != 'all') {
subtab = itemKind
}
setQuery($page.url, 'kind', itemKind)
}}
class="h-10"
>
{#snippet children({ item })}
<ToggleButton value="all" label="All" class="text-sm px-4 py-2" {item} />
<ToggleButton
value="script"
icon={Code2}
label="Scripts"
class="text-sm px-4 py-2"
{item}
/>
{#if HOME_SEARCH_SHOW_FLOW}
<ToggleButton
value="script"
icon={Code2}
label="Scripts"
value="flow"
label="Flows"
icon={FlowIcon}
class="text-sm px-4 py-2"
selectedColor="#14b8a6"
{item}
/>
{#if HOME_SEARCH_SHOW_FLOW}
<ToggleButton
value="flow"
label="Flows"
icon={FlowIcon}
class="text-sm px-4 py-2"
selectedColor="#14b8a6"
{item}
/>
{/if}
<ToggleButton
value="app"
label="Apps"
icon={LayoutDashboard}
class="text-sm px-4 py-2"
selectedColor="#fb923c"
{item}
{/if}
<ToggleButton
value="app"
label="Apps"
icon={LayoutDashboard}
class="text-sm px-4 py-2"
selectedColor="#fb923c"
{item}
/>
{/snippet}
</ToggleButtonGroup>
</div>
<div class="relative text-tertiary grow min-w-[100px]">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus
placeholder={HOME_SEARCH_PLACEHOLDER}
bind:value={filter}
class="bg-surface !h-10 !px-4 !pr-10 !rounded-lg text-sm focus:outline-none"
/>
<button aria-label="Search" type="submit" class="absolute right-0 top-0 mt-3 mr-4">
<svg
class="h-4 w-4 fill-current"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 56.966 56.966"
style="enable-background:new 0 0 56.966 56.966;"
xml:space="preserve"
width="512px"
height="512px"
>
<path
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
/>
</svg>
</button>
</div>
<Button
on:click={() => openSearchWithPrefilledText('#')}
variant="border"
size="sm"
spacingSize="lg"
wrapperClasses="h-10"
color="light"
endIcon={{
icon: SearchCode
}}
>
Content
</Button>
</div>
<div class="relative">
<ListFilters
syncQuery
bind:selectedFilter={ownerFilter}
filters={owners}
bottomMargin={false}
/>
{#if filteredItems?.length == 0}
<div class="mt-10"></div>
{/if}
{#if !loading}
<div class="flex w-full flex-row-reverse gap-2 mt-4 mb-1 items-center h-6">
<Popover floatingConfig={{ placement: 'bottom-end' }}>
{#snippet trigger()}
<Button
startIcon={{
icon: SlidersHorizontal
}}
nonCaptureEvent
iconOnly
size="xs"
color="light"
variant="border"
spacingSize="xs2"
/>
{/snippet}
</ToggleButtonGroup>
</div>
<div class="relative text-tertiary grow min-w-[100px]">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus
placeholder={HOME_SEARCH_PLACEHOLDER}
bind:value={filter}
class="bg-surface !h-10 !px-4 !pr-10 !rounded-lg text-sm focus:outline-none"
/>
<button aria-label="Search" type="submit" class="absolute right-0 top-0 mt-3 mr-4">
<svg
class="h-4 w-4 fill-current"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 56.966 56.966"
style="enable-background:new 0 0 56.966 56.966;"
xml:space="preserve"
width="512px"
height="512px"
>
<path
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
/>
</svg>
</button>
</div>
<Button
on:click={() => openSearchWithPrefilledText('#')}
variant="border"
size="sm"
spacingSize="lg"
wrapperClasses="h-10"
color="light"
endIcon={{
icon: SearchCode
}}
>
Content
</Button>
</div>
<div class="relative">
<ListFilters
syncQuery
bind:selectedFilter={ownerFilter}
filters={owners}
bottomMargin={false}
/>
{#if filteredItems?.length == 0}
<div class="mt-10"></div>
{/if}
{#if !loading}
<div class="flex w-full flex-row-reverse gap-2 mt-4 mb-1 items-center h-6">
<Popover floatingConfig={{ placement: 'bottom-end' }}>
{#snippet trigger()}
<Button
startIcon={{
icon: SlidersHorizontal
}}
nonCaptureEvent
iconOnly
size="xs"
color="light"
variant="border"
spacingSize="xs2"
/>
{/snippet}
{#snippet content()}
<div class="p-4">
<span class="text-sm font-semibold">Filters</span>
<div class="flex flex-col gap-2 mt-2">
<Toggle size="xs" bind:checked={archived} options={{ right: 'Only archived' }} />
{#if $userStore && !$userStore.operator}
<Toggle
size="xs"
bind:checked={includeWithoutMain}
options={{ right: 'Include without main function' }}
/>
{/if}
</div>
{#snippet content()}
<div class="p-4">
<span class="text-sm font-semibold">Filters</span>
<div class="flex flex-col gap-2 mt-2">
<Toggle size="xs" bind:checked={archived} options={{ right: 'Only archived' }} />
{#if $userStore && !$userStore.operator}
<Toggle
size="xs"
bind:checked={includeWithoutMain}
options={{ right: 'Include without main function' }}
/>
{/if}
</div>
{/snippet}
</Popover>
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
<Toggle
size="xs"
bind:checked={filterUserFolders}
options={{ right: `Only u/${$userStore.username} and f/*` }}
/>
{/if}
<Toggle size="xs" bind:checked={treeView} options={{ right: 'Tree view' }} />
{#if treeView}
<Button
btnClasses="py-0 h-6"
size="xs"
variant="border"
color="light"
on:click={() => (collapseAll = !collapseAll)}
startIcon={{
icon: collapseAll ? UnfoldVertical : FoldVertical
}}
>
{#if collapseAll}
Expand all
{:else}
Collapse all
{/if}
</Button>
{/if}
</div>
{/if}
</div>
<div>
{#if filteredItems == undefined}
<div class="mt-4"></div>
<Skeleton layout={[[2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.5]} />
{/each}
{:else if filteredItems.length === 0}
<NoItemFound />
{:else if treeView}
<TreeViewRoot
{items}
{nbDisplayed}
{collapseAll}
isSearching={filter !== ''}
on:scriptChanged={() => loadScripts(includeWithoutMain)}
on:flowChanged={loadFlows}
on:appChanged={loadApps}
on:rawAppChanged={loadRawApps}
on:reload={() => {
loadScripts(includeWithoutMain)
loadFlows()
loadApps()
loadRawApps()
}}
{showCode}
/>
{:else}
<div class="border rounded-md">
{#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)}
<Item
{item}
on:scriptChanged={() => loadScripts(includeWithoutMain)}
on:flowChanged={loadFlows}
on:appChanged={loadApps}
on:rawAppChanged={loadRawApps}
on:reload={() => {
loadScripts(includeWithoutMain)
loadFlows()
loadApps()
loadRawApps()
}}
{showCode}
/>
{/each}
</div>
{#if items && items?.length > 15 && nbDisplayed < items.length}
<span class="text-xs"
>{nbDisplayed} items out of {items.length}
<button class="ml-4" onclick={() => (nbDisplayed += 30)}>load 30 more</button></span
>
</div>
{/snippet}
</Popover>
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
<Toggle
size="xs"
bind:checked={filterUserFolders}
options={{ right: `Only u/${$userStore.username} and f/*` }}
/>
{/if}
<Toggle size="xs" bind:checked={treeView} options={{ right: 'Tree view' }} />
{#if treeView}
<Button
btnClasses="py-0 h-6"
size="xs"
variant="border"
color="light"
on:click={() => (collapseAll = !collapseAll)}
startIcon={{
icon: collapseAll ? UnfoldVertical : FoldVertical
}}
>
{#if collapseAll}
Expand all
{:else}
Collapse all
{/if}
</Button>
{/if}
</div>
{/if}
</div>
<div>
{#if filteredItems == undefined}
<div class="mt-4"></div>
<Skeleton layout={[[2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.5]} />
{/each}
{:else if filteredItems.length === 0}
<NoItemFound />
{:else if treeView}
<TreeViewRoot
{items}
{nbDisplayed}
{collapseAll}
isSearching={filter !== ''}
on:scriptChanged={() => loadScripts(includeWithoutMain)}
on:flowChanged={loadFlows}
on:appChanged={loadApps}
on:rawAppChanged={loadRawApps}
on:reload={() => {
loadScripts(includeWithoutMain)
loadFlows()
loadApps()
loadRawApps()
}}
{showCode}
/>
{:else}
<div class="border rounded-md">
{#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)}
<Item
{item}
on:scriptChanged={() => loadScripts(includeWithoutMain)}
on:flowChanged={loadFlows}
on:appChanged={loadApps}
on:rawAppChanged={loadRawApps}
on:reload={() => {
loadScripts(includeWithoutMain)
loadFlows()
loadApps()
loadRawApps()
}}
{showCode}
/>
{/each}
</div>
{#if items && items?.length > 15 && nbDisplayed < items.length}
<span class="text-xs"
>{nbDisplayed} items out of {items.length}
<button class="ml-4" onclick={() => (nbDisplayed += 30)}>load 30 more</button></span
>
{/if}
</div>
{/if}
</div>
</CenteredPage>
@@ -1,7 +1,7 @@
<script lang="ts">
import { type AnyMeltElement } from '@melt-ui/svelte'
import { conditionalMelt } from '$lib/utils'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
export let aiId: string | undefined = undefined
export let aiDescription: string | undefined = undefined
@@ -1,7 +1,7 @@
<script lang="ts">
import { melt } from '@melt-ui/svelte'
import type { MenubarMenuElements } from '@melt-ui/svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
interface Props {
aiId?: string | undefined
@@ -3,7 +3,7 @@
import { Button } from '../common'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import Toggle from '../Toggle.svelte'
import { UserService, type NewToken } from '$lib/gen'
import { copyToClipboard } from '$lib/utils'
@@ -135,10 +135,13 @@
<h3 class="pb-3 font-semibold">Add a new token</h3>
{#if showMcpMode}
<div class="mb-4 flex flex-row flex-shrink-0" use:triggerableByAI={{
id: 'account-settings-create-mcp-token',
description: 'Create a new MCP token to authenticate to the Windmill API'
}}>
<div
class="mb-4 flex flex-row flex-shrink-0"
use:triggerableByAI={{
id: 'account-settings-create-mcp-token',
description: 'Create a new MCP token to authenticate to the Windmill API'
}}
>
<Toggle
on:change={(e) => {
mcpCreationMode = e.detail
@@ -6,7 +6,7 @@
import { goto } from '$app/navigation'
import { conditionalMelt } from '$lib/utils'
import type { MenubarMenuElements } from '@melt-ui/svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
interface Props {
aiId?: string | undefined
@@ -4,7 +4,7 @@
import { navigating, page } from '$app/stores'
import Popover from '../Popover.svelte'
import { base } from '$app/paths'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import { goto } from '$app/navigation'
import { twMerge } from 'tailwind-merge'
@@ -7,7 +7,7 @@
import AddTriggersButton from './AddTriggersButton.svelte'
import TriggerLabel from './TriggerLabel.svelte'
import DeleteTriggerButton from './DeleteTriggerButton.svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
interface Props {
// Props
+112
View File
@@ -0,0 +1,112 @@
export function langToExt(lang: string): string {
switch (lang) {
case 'tsx':
return 'tsx'
case 'javascript':
return 'js'
case 'jsx':
return 'js'
case 'bunnative':
return 'ts'
case 'json':
return 'json'
case 'sql':
return 'sql'
case 'yaml':
return 'yaml'
case 'typescript':
return 'ts'
case 'python':
return 'py'
case 'go':
return 'go'
case 'bash':
return 'sh'
case 'powershell':
return 'ps1'
case 'php':
return 'php'
case 'rust':
return 'rs'
case 'deno':
return 'ts'
case 'nativets':
return 'ts'
case 'graphql':
return 'gql'
case 'css':
return 'css'
case 'ansible':
return 'yml'
case 'csharp':
return 'cs'
case 'nu':
return 'nu'
case 'java':
return 'java'
case 'svelte':
return 'svelte'
case 'vue':
return 'vue'
default:
return 'unknown'
}
}
export function extToLang(ext: string) {
switch (ext) {
case 'tsx':
return 'typescript'
case 'ts':
return 'typescript'
case 'js':
return 'javascript'
case 'jsx':
return 'javascript'
case 'json':
return 'json'
case 'sql':
return 'sql'
case 'yaml':
return 'yaml'
case 'py':
return 'python'
case 'go':
return 'go'
case 'sh':
return 'bash'
case 'ps1':
return 'powershell'
case 'php':
return 'php'
case 'rs':
return 'rust'
case 'gql':
return 'graphql'
case 'css':
return 'css'
case 'yml':
return 'ansible'
case 'cs':
return 'csharp'
case 'svelte':
return 'svelte'
case 'vue':
return 'vue'
case 'nu':
return 'nu'
case 'java':
return 'java'
// for related places search: ADD_NEW_LANG
default:
return 'unknown'
}
}
export function createHash() {
return (Math.random() + 1).toString(36).substring(2)
}
export function createLongHash() {
return 'h' + Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)
}
-111
View File
@@ -41,118 +41,7 @@ export function editorConfig(
}
}
export function createHash() {
return (Math.random() + 1).toString(36).substring(2)
}
export function createLongHash() {
return 'h' + Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)
}
export function langToExt(lang: string): string {
switch (lang) {
case 'tsx':
return 'tsx'
case 'javascript':
return 'js'
case 'jsx':
return 'js'
case 'bunnative':
return 'ts'
case 'json':
return 'json'
case 'sql':
return 'sql'
case 'yaml':
return 'yaml'
case 'typescript':
return 'ts'
case 'python':
return 'py'
case 'go':
return 'go'
case 'bash':
return 'sh'
case 'powershell':
return 'ps1'
case 'php':
return 'php'
case 'rust':
return 'rs'
case 'deno':
return 'ts'
case 'nativets':
return 'ts'
case 'graphql':
return 'gql'
case 'css':
return 'css'
case 'ansible':
return 'yml'
case 'csharp':
return 'cs'
case 'nu':
return 'nu'
case 'java':
return 'java'
case 'svelte':
return 'svelte'
case 'vue':
return 'vue'
default:
return 'unknown'
}
}
export function extToLang(ext: string) {
switch (ext) {
case 'tsx':
return 'typescript'
case 'ts':
return 'typescript'
case 'js':
return 'javascript'
case 'jsx':
return 'javascript'
case 'json':
return 'json'
case 'sql':
return 'sql'
case 'yaml':
return 'yaml'
case 'py':
return 'python'
case 'go':
return 'go'
case 'sh':
return 'bash'
case 'ps1':
return 'powershell'
case 'php':
return 'php'
case 'rs':
return 'rust'
case 'gql':
return 'graphql'
case 'css':
return 'css'
case 'yml':
return 'ansible'
case 'cs':
return 'csharp'
case 'svelte':
return 'svelte'
case 'vue':
return 'vue'
case 'nu':
return 'nu'
case 'java':
return 'java'
// for related places search: ADD_NEW_LANG
default:
return 'unknown'
}
}
export const updateOptions = { tabSize: 2, insertSpaces: true }
+243 -36
View File
@@ -1,46 +1,253 @@
<script lang="ts">
// import { BROWSER } from 'esm-env'
// import { page } from '$app/stores'
// import { base } from '$lib/base'
// import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '$lib/components/apps/types'
// import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte'
// aiChatManager
// import { WindmillIcon } from '$lib/components/icons'
// import { AppService, OpenAPI, type AppWithLastVersion } from '$lib/gen'
// import { enterpriseLicense, userStore } from '$lib/stores'
// import { twMerge } from 'tailwind-merge'
// import type { AIProviderModel, ScriptLang } from '$lib/gen/types.gen'
// import type { ScriptOptions } from './ContextManager.svelte'
// import {
// flowTools,
// prepareFlowSystemMessage,
// prepareFlowUserMessage,
// type FlowAIChatHelpers
// } from './flow/core'
// import ContextManager from './ContextManager.svelte'
// import HistoryManager from './HistoryManager.svelte'
// import { processToolCall, type DisplayMessage, type Tool, type ToolCallbacks } from './shared'
// HistoryManager
// ContextManager
// import type {
// ChatCompletionChunk,
// ChatCompletionMessageParam,
// ChatCompletionMessageToolCall,
// ChatCompletionSystemMessageParam,
// ChatCompletionUserMessageParam
// } from 'openai/resources/chat/completions.mjs'
// import { setContext } from 'svelte'
// import { writable } from 'svelte/store'
// import { setLicense } from '$lib/enterpriseUtils'
// import { isCloudHosted } from '$lib/cloud'
// import { getUserExt } from '$lib/user'
// import { User, UserRoundX } from 'lucide-svelte'
// import { goto } from '$app/navigation'
// import { sendUserToast } from '$lib/toast'
// import Alert from '$lib/components/common/alert/Alert.svelte'
// import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
// import Login from '$lib/components/Login.svelte'
// import { dfs } from '$lib/components/flows/previousResults'
// import type { FlowModuleState, FlowState } from '$lib/components/flows/flowState'
// import type { CurrentEditor, ExtendedOpenFlow } from '$lib/components/flows/types'
// import { untrack } from 'svelte'
// import type { DBSchemas } from '$lib/stores'
// import { getCompletion } from '$lib/components/copilot/lib'
// import { getStringError } from '$lib/components/copilot/chat/utils'
// import {
// navigatorTools,
// prepareNavigatorSystemMessage,
// prepareNavigatorUserMessage
// } from '$lib/components/copilot/chat/navigator/core'
// import {
// prepareScriptSystemMessage,
// prepareScriptTools,
// prepareScriptUserMessage
// } from '$lib/components/copilot/chat/script/core'
// import { loadApiTools } from '$lib/components/copilot/chat/navigator/apiTools'
// import { langToExt } from '$lib/editorUtils'
// import { langToExt } from '$lib/editorLangUtils'
// import { ResourceService, type ListResourceResponse, type ScriptLang } from '$lib/gen'
// import { scriptLangToEditorLang } from '$lib/scripts'
// import HistoryManager from '$lib/components/copilot/chat/HistoryManager.svelte'
// import ContextManager from '$lib/components/copilot/chat/ContextManager.svelte'
// loadApiTools
// import { askTools, prepareAskSystemMessage } from './ask/core'
// import { chatState, DEFAULT_SIZE, triggerablesByAi } from './sharedChatState.svelte'
// import FlowStatusViewerInner from '$lib/components/FlowStatusViewerInner.svelte'
// import ArgTest from '$lib/components/ArgTest.svelte'
// import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
// import { goto } from '$lib/navigation'
// import Github from '$lib/components/icons/brands/Github.svelte'
// import Gitlab from '$lib/components/icons/brands/Gitlab.svelte'
// import Google from '$lib/components/icons/brands/Google.svelte'
// import Microsoft from '$lib/components/icons/brands/Microsoft.svelte'
// import Okta from '$lib/components/icons/brands/Okta.svelte'
// import Auth0 from '$lib/components/icons/brands/Auth0.svelte'
// AppPrevi
// import ArgInput from '$lib/components/ArgInput.svelte'
// import { OauthService, UserService, WorkspaceService } from '$lib/gen'
// import { usersWorkspaceStore, workspaceStore, userStore } from '$lib/stores'
// import { classNames, emptyString, parseQueryParams } from '$lib/utils'
// import { base } from '$lib/base'
// import { getUserExt } from '$lib/user'
// import { sendUserToast } from '$lib/toast'
// import { isCloudHosted } from '$lib/cloud'
// import { refreshSuperadmin } from '$lib/refreshUser'
// import { createEventDispatcher, onDestroy, onMount } from 'svelte'
// import SchemaForm from '$lib/components/SchemaForm.svelte'
// import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
// import Button from '$lib/components/common/button/Button.svelte'
// import RunnableComponent from '$lib/components/apps/components/helpers/RunnableComponent.svelte'
// import ComponentInner from '$lib/components/apps/editor/component/ComponentInner.svelte'
// import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
// import { getContext, onDestroy, setContext, untrack } from 'svelte'
// import { get, writable, type Writable } from 'svelte/store'
// import { buildWorld } from '../rx'
// import type {
// App,
// AppViewerContext,
// ConnectingInput,
// EditorBreakpoint,
// EditorMode
// } from '../types'
// import type { Policy } from '$lib/gen'
// import { Unlock } from 'lucide-svelte'
// import GridViewer from '$lib/components/apps/editor/GridViewer.svelte'
// import { twMerge } from 'tailwind-merge'
// import { ButtonType } from '$lib/components/common'
// import { dfs, maxHeight } from './appUtils'
// import { BG_PREFIX, migrateApp } from '../utils'
// import { workspaceStore, enterpriseLicense } from '$lib/stores'
// import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
// import { getTheme } from './componentsPanel/themeUtils'
// import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
// import RecomputeAllComponents from './RecomputeAllComponents.svelte'
// HiddenComponent,
// RecomputeAllComponents
// import HiddenComponent from '$lib/components/apps/components/helpers/HiddenComponent.svelte'
// RunnableComponent
// import { buildWorld } from '../rx'
// import type {
// App,
// AppViewerContext,
// ConnectingInput,
// EditorBreakpoint,
// EditorMode
// } from '../types'
// import type { Policy } from '$lib/gen'
// import Button from '../../common/button/Button.svelte'
// import { Unlock } from 'lucide-svelte'
// import GridViewer from './GridViewer.svelte'
// import Component from './component/Component.svelte'
// import { twMerge } from 'tailwind-merge'
// import { deepEqual } from 'fast-equals'
// import { dfs, maxHeight } from './appUtils'
// import { BG_PREFIX, migrateApp } from '../utils'
// import { workspaceStore, enterpriseLicense } from '$lib/stores'
// import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
// import { getTheme } from './componentsPanel/themeUtils'
// import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
// import RecomputeAllComponents from './RecomputeAllComponents.svelte'
// ComponentInner
// import { Button } from '$lib/components/common'
// SchemaForm
// ArgInput
// import {
// setInputCat as computeInputCat,
// debounce,
// emptyString,
// getSchemaFromProperties
// } from '$lib/utils'
// import { DollarSign, Pipette, Plus, X, Check, Loader2 } from 'lucide-svelte'
// import { createEventDispatcher, onDestroy, onMount, tick, untrack } from 'svelte'
// import { fade } from 'svelte/transition'
// import { Button, SecondsInput } from './common'
// import FieldHeader from './FieldHeader.svelte'
// import type ItemPicker from './ItemPicker.svelte'
// import ObjectResourceInput from './ObjectResourceInput.svelte'
// import Range from './Range.svelte'
// import ResourcePicker from './ResourcePicker.svelte'
// import type SimpleEditor from './SimpleEditor.svelte'
// import Toggle from './Toggle.svelte'
// import type VariableEditor from './VariableEditor.svelte'
// import { twMerge } from 'tailwind-merge'
// import ArgEnum from './ArgEnum.svelte'
// import DateTimeInput from './DateTimeInput.svelte'
// import DateInput from './DateInput.svelte'
// import S3FilePicker from './S3FilePicker.svelte'
// import CurrencyInput from './apps/components/inputs/currency/CurrencyInput.svelte'
// import FileUpload from './common/fileUpload/FileUpload.svelte'
// import autosize from '$lib/autosize'
// import PasswordArgInput from './PasswordArgInput.svelte'
// import Password from './Password.svelte'
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
// import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
// import SchemaFormDnd from '$lib/components/schema/SchemaFormDND.svelte'
// import SchemaForm from './SchemaForm.svelte'
// import { deepEqual } from 'fast-equals'
// import DynSelect from './DynSelect.svelte'
// import type { Script } from '$lib/gen'
// import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
// import type { ComponentCustomCSS } from './apps/types'
// import MultiSelect from './select/MultiSelect.svelte'
// import { safeSelectItems } from './select/utils.svelte'
// SchemaFormDnd
// import FlowJobResult from './FlowJobResult.svelte'
// import FlowPreviewStatus from './preview/FlowPreviewStatus.svelte'
// import DisplayResult from './DisplayResult.svelte'
// FlowJobResult
// import FlowJobResult from '$lib/components/FlowJobResult.svelte'
// import FlowPreviewStatus from '$lib/components/preview/FlowPreviewStatus.svelte'
// import DisplayResult from '$lib/components/DisplayResult.svelte'
// FlowJobResult
// FlowPreviewStatus
// DisplayResult
// FlowStatusViewerInner
// import JobArgs from './JobArgs.svelte'
// import { ChevronDown, Hourglass, Loader2 } from 'lucide-svelte'
// import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte'
// import { deepEqual } from 'fast-equals'
// import FlowTimeline from './FlowTimeline.svelte'
// import { dfs } from './flows/dfs'
// import { get, writable, type Unsubscriber, type Writable } from 'svelte/store'
// import Alert from './common/alert/Alert.svelte'
// import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
// import FlowGraphV2 from './graph/FlowGraphV2.svelte'
// import { buildPrefix } from './graph/graphBuilder.svelte'
// import JobArgs from '$lib/components/JobArgs.svelte'
// import FlowStatusWaitingForEvents from '$lib/components/FlowStatusWaitingForEvents.svelte'
// import DisplayResult from '$lib/components/DisplayResult.svelte'
// import FlowTimeline from '$lib/components/FlowTimeline.svelte'
// import { dfs } from '$lib/components/flows/dfs'
// import { get, writable, type Unsubscriber, type Writable } from 'svelte/store'
// TimelineBar
// import Alert from '$lib/components/common/alert/Alert.svelte'
// import FlowGraphViewerStep from '$lib/components/FlowGraphViewerStep.svelte'
// import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte'
// import { buildPrefix } from '$lib/components/graph/graphBuilder.svelte'
// import Popover from '$lib/components/Popover.svelte'
// import TimelineBar from '$lib/components/TimelineBar.svelte'
// Popover
// import ModuleNode from './renderers/nodes/ModuleNode.svelte'
// import InputNode from './renderers/nodes/InputNode.svelte'
// import BranchAllStart from './renderers/nodes/BranchAllStart.svelte'
// import BranchAllEndNode from './renderers/nodes/BranchAllEndNode.svelte'
// import ForLoopEndNode from './renderers/nodes/ForLoopEndNode.svelte'
// import ForLoopStartNode from './renderers/nodes/ForLoopStartNode.svelte'
// import ResultNode from './renderers/nodes/ResultNode.svelte'
// import BaseEdge from './renderers/edges/BaseEdge.svelte'
// import EmptyEdge from './renderers/edges/EmptyEdge.svelte'
// import { sugiyama, dagStratify, coordCenter, decrossTwoLayer, decrossOpt } from 'd3-dag'
// import { Expand } from 'lucide-svelte'
// import Toggle from '../Toggle.svelte'
// import DataflowEdge from './renderers/edges/DataflowEdge.svelte'
// import { encodeState } from '$lib/utils'
// import BranchOneStart from './renderers/nodes/BranchOneStart.svelte'
// import NoBranchNode from './renderers/nodes/NoBranchNode.svelte'
// import HiddenBaseEdge from './renderers/edges/HiddenBaseEdge.svelte'
// import TriggersNode from './renderers/nodes/TriggersNode.svelte'
// import { Alert, Drawer } from '../common'
// import Button from '../common/button/Button.svelte'
// import FlowYamlEditor from '../flows/header/FlowYamlEditor.svelte'
// import BranchOneEndNode from './renderers/nodes/branchOneEndNode.svelte'
// import type { TriggerContext } from '../triggers'
// import { workspaceStore } from '$lib/stores'
// import SubflowBound from './renderers/nodes/SubflowBound.svelte'
// import { deepEqual } from 'fast-equals'
// import ViewportResizer from './ViewportResizer.svelte'
// import ModuleNode from '$lib/components/graph/renderers/nodes/ModuleNode.svelte'
// import InputNode from '$lib/components/graph/renderers/nodes/InputNode.svelte'
// import BranchAllStart from '$lib/components/graph/renderers/nodes/BranchAllStart.svelte'
// import BranchAllEndNode from '$lib/components/graph/renderers/nodes/BranchAllEndNode.svelte'
// import ForLoopEndNode from '$lib/components/graph/renderers/nodes/ForLoopEndNode.svelte'
// import ForLoopStartNode from '$lib/components/graph/renderers/nodes/ForLoopStartNode.svelte'
// import ResultNode from '$lib/components/graph/renderers/nodes/ResultNode.svelte'
// import BaseEdge from '$lib/components/graph/renderers/edges/BaseEdge.svelte'
// import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte'
</script>