optimize script/flow lazy loading

This commit is contained in:
Ruben Fiszel
2025-07-20 00:25:50 +00:00
parent 0c0f648827
commit cda763a45c
2 changed files with 531 additions and 537 deletions
@@ -15,7 +15,7 @@
import DetailPageLayout from '$lib/components/details/DetailPageLayout.svelte'
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import { Alert, Badge as HeaderBadge, Skeleton } from '$lib/components/common'
import { Alert, Badge as HeaderBadge } from '$lib/components/common'
import MoveDrawer from '$lib/components/MoveDrawer.svelte'
import RunForm from '$lib/components/RunForm.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
@@ -409,11 +409,6 @@
let mainButtons = $derived(getMainButtons(flow, args))
</script>
<Skeleton
class="!max-w-7xl !px-4 sm:!px-6 md:!px-8"
loading={!flow}
layout={[0.75, [2, 0, 2], 2.25, [{ h: 1.5, w: 40 }], 0.2, [{ h: 1, w: 30 }]]}
/>
<svelte:window onkeydown={onKeyDown} />
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<ShareModal bind:this={shareModal} />
@@ -428,234 +423,235 @@
<FlowHistory bind:this={flowHistory} path={flow.path} onHistoryRestore={loadFlow} />
{/if}
{#if flow}
<DetailPageLayout
bind:selected={rightPaneSelected}
isOperator={$userStore?.operator}
flow_json={{
value: flow.value,
summary: flow.summary,
description: flow.description,
schema: flow.schema
}}
>
{#snippet header()}
{#if flow}
<DetailPageHeader
on:seeTriggers={() => {
<DetailPageLayout
bind:selected={rightPaneSelected}
isOperator={$userStore?.operator}
flow_json={{
value: flow?.value,
summary: flow?.summary,
description: flow?.description,
schema: flow?.schema
}}
>
{#snippet header()}
<DetailPageHeader
on:seeTriggers={() => {
rightPaneSelected = 'triggers'
}}
{mainButtons}
menuItems={getMenuItems(flow, deployUiSettings)}
title={defaultIfEmptyString(flow?.summary, flow?.path ?? '')}
bind:errorHandlerMuted={
() => flow?.ws_error_handler_muted ?? false,
(v) => {
if (flow?.ws_error_handler_muted) {
flow.ws_error_handler_muted = v
}
}
}
scriptOrFlowPath={flow?.path ?? ''}
errorHandlerKind="flow"
tag={flow?.tag ?? ''}
>
<!-- @migration-task: migrate this slot by hand, `trigger-badges` is an invalid identifier -->
{#snippet trigger_badges()}
<TriggersBadge
showOnlyWithCount={true}
showDraft={false}
{path}
newItem={false}
isFlow
selected={rightPaneSelected == 'triggers'}
onSelect={async (triggerIndex: number) => {
rightPaneSelected = 'triggers'
await tick()
triggersState.selectedTriggerIndex = triggerIndex
}}
{mainButtons}
menuItems={getMenuItems(flow, deployUiSettings)}
title={defaultIfEmptyString(flow.summary, flow.path)}
bind:errorHandlerMuted={flow.ws_error_handler_muted}
scriptOrFlowPath={flow.path}
errorHandlerKind="flow"
tag={flow.tag}
>
<!-- @migration-task: migrate this slot by hand, `trigger-badges` is an invalid identifier -->
{#snippet trigger_badges()}
<TriggersBadge
showOnlyWithCount={true}
showDraft={false}
{path}
newItem={false}
isFlow
selected={rightPaneSelected == 'triggers'}
onSelect={async (triggerIndex: number) => {
rightPaneSelected = 'triggers'
await tick()
triggersState.selectedTriggerIndex = triggerIndex
}}
small={false}
/>
{/snippet}
{#if $workspaceStore}
<Star
kind="flow"
path={flow.path}
{starred}
workspace_id={$workspaceStore}
on:starred={() => {
starred = !starred
}}
/>
{/if}
{#if flow?.value?.priority != undefined}
<div class="hidden md:block">
<HeaderBadge color="blue" variant="outlined" size="xs">
{`Priority: ${flow?.value?.priority}`}
</HeaderBadge>
</div>
{/if}
{#if flow?.value?.concurrent_limit != undefined && flow?.value?.concurrency_time_window_s != undefined}
<div class="hidden md:block">
<HeaderBadge color="gray" variant="outlined" size="xs">
{`Concurrency limit: ${flow?.value?.concurrent_limit} runs every ${flow?.value?.concurrency_time_window_s}s`}
</HeaderBadge>
</div>
{/if}
</DetailPageHeader>
small={false}
/>
{/snippet}
{#if $workspaceStore && flow}
<Star
kind="flow"
path={flow.path}
{starred}
workspace_id={$workspaceStore}
on:starred={() => {
starred = !starred
}}
/>
{/if}
{/snippet}
{#snippet form()}
{#if flow}
<div class="flex-col flex h-full justify-between">
<div class="p-8 w-full max-w-3xl mx-auto gap-2 bg-surface">
<div class="mb-1">
{#if !emptyString(flow?.description)}
<GfmMarkdown md={defaultIfEmptyString(flow?.description, 'No description')} />
{/if}
{#if flow?.value?.priority != undefined}
<div class="hidden md:block">
<HeaderBadge color="blue" variant="outlined" size="xs">
{`Priority: ${flow?.value?.priority}`}
</HeaderBadge>
</div>
{/if}
{#if flow?.value?.concurrent_limit != undefined && flow?.value?.concurrency_time_window_s != undefined}
<div class="hidden md:block">
<HeaderBadge color="gray" variant="outlined" size="xs">
{`Concurrency limit: ${flow?.value?.concurrent_limit} runs every ${flow?.value?.concurrency_time_window_s}s`}
</HeaderBadge>
</div>
{/if}
</DetailPageHeader>
{/snippet}
{#snippet form()}
{#if flow}
<div class="flex-col flex h-full justify-between">
<div class="p-8 w-full max-w-3xl mx-auto gap-2 bg-surface">
<div class="mb-1">
{#if !emptyString(flow?.description)}
<GfmMarkdown md={defaultIfEmptyString(flow?.description, 'No description')} />
{/if}
</div>
{#if deploymentInProgress}
<HeaderBadge color="yellow">
<Loader2 size={12} class="inline animate-spin mr-1" />
Deployment in progress
</HeaderBadge>
{/if}
{#if flow.lock_error_logs && flow.lock_error_logs != ''}
<div class="bg-red-100 dark:bg-red-700 border-l-4 border-red-500 p-4" role="alert">
<p class="font-bold">Error deploying this flow</p>
<p> This flow has not been deployed successfully because of the following errors: </p>
<LogViewer content={flow.lock_error_logs} isLoading={false} tag={undefined} />
</div>
{/if}
{#if deploymentInProgress}
<HeaderBadge color="yellow">
<Loader2 size={12} class="inline animate-spin mr-1" />
Deployment in progress
</HeaderBadge>
{/if}
{#if flow.lock_error_logs && flow.lock_error_logs != ''}
<div class="bg-red-100 dark:bg-red-700 border-l-4 border-red-500 p-4" role="alert">
<p class="font-bold">Error deploying this flow</p>
<p>
This flow has not been deployed successfully because of the following errors:
</p>
<LogViewer content={flow.lock_error_logs} isLoading={false} tag={undefined} />
</div>
{/if}
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge
onReject={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON',
rightTooltip: 'Fill args from JSON'
}}
lightMode
on:change={(e) => {
runForm?.setCode(JSON.stringify(args ?? {}, null, '\t'))
}}
/>
</div>
{#if flow.schema?.prompt_for_ai !== undefined}
<AIFormAssistant
instructions={flow.schema?.prompt_for_ai as string}
onEditInstructions={() => {
goto(`/flows/edit/${flow?.path}`)
}}
runnableType="flow"
/>
{/if}
<RunForm
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
viewKeybinding
{loading}
autofocus
detailed={false}
bind:isValid
runnable={flow}
runAction={runFlow}
bind:args
bind:this={runForm}
{jsonView}
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge
onReject={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON',
rightTooltip: 'Fill args from JSON'
}}
lightMode
on:change={(e) => {
runForm?.setCode(JSON.stringify(args ?? {}, null, '\t'))
}}
/>
</div>
<div class="py-10"></div>
{#if !emptyString(flow.summary)}
<div class="mb-2">
<span class="!text-tertiary">{flow.path}</span>
</div>
{#if flow.schema?.prompt_for_ai !== undefined}
<AIFormAssistant
instructions={flow.schema?.prompt_for_ai as string}
onEditInstructions={() => {
goto(`/flows/edit/${flow?.path}`)
}}
runnableType="flow"
/>
{/if}
<div class="flex flex-row gap-x-2 flex-wrap items-center">
<span class="text-sm text-tertiary">
Edited <TimeAgo date={flow.edited_at ?? ''} /> by {flow.edited_by}
</span>
{#if flow.archived}
<div class=""></div>
<Alert type="error" title="Archived">This flow was archived</Alert>
{/if}
</div>
</div>
<div class="mt-8">
<FlowGraphViewer
triggerNode={true}
download
{flow}
overflowAuto
noSide={true}
on:select={(e) => {
if (e.detail) {
stepDetail = e.detail
rightPaneSelected = 'flow_step'
} else {
stepDetail = undefined
rightPaneSelected = 'saved_inputs'
}
}}
on:triggerDetail={(e) => {
rightPaneSelected = 'triggers'
}}
<RunForm
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
viewKeybinding
{loading}
autofocus
detailed={false}
bind:isValid
runnable={flow}
runAction={runFlow}
bind:args
bind:this={runForm}
{jsonView}
/>
</div>
</div>
{/if}
{/snippet}
{#snippet save_inputs()}
<SavedInputsV2
bind:this={savedInputsV2}
schema={flow?.schema}
{jsonView}
flowPath={flow?.path}
{isValid}
args={args ?? {}}
bind:inputSelected
on:selected_args={(e) => {
const nargs = JSON.parse(JSON.stringify(e.detail))
args = nargs
}}
/>
{/snippet}
{#snippet flow_step()}
{#if flow}
{#if stepDetail}
<FlowGraphViewerStep schema={flow.schema} {stepDetail} />
{/if}
<div class="py-10"></div>
{#if !emptyString(flow.summary)}
<div class="mb-2">
<span class="!text-tertiary">{flow.path}</span>
</div>
{/if}
<div class="flex flex-row gap-x-2 flex-wrap items-center">
<span class="text-sm text-tertiary">
Edited <TimeAgo date={flow.edited_at ?? ''} /> by {flow.edited_by}
</span>
{#if flow.archived}
<div class=""></div>
<Alert type="error" title="Archived">This flow was archived</Alert>
{/if}
</div>
</div>
<div class="mt-8">
<FlowGraphViewer
triggerNode={true}
download
{flow}
overflowAuto
noSide={true}
on:select={(e) => {
if (e.detail) {
stepDetail = e.detail
rightPaneSelected = 'flow_step'
} else {
stepDetail = undefined
rightPaneSelected = 'saved_inputs'
}
}}
on:triggerDetail={(e) => {
rightPaneSelected = 'triggers'
}}
/>
</div>
</div>
{/if}
{/snippet}
{#snippet save_inputs()}
<SavedInputsV2
bind:this={savedInputsV2}
schema={flow?.schema}
{jsonView}
flowPath={flow?.path}
{isValid}
args={args ?? {}}
bind:inputSelected
on:selected_args={(e) => {
const nargs = JSON.parse(JSON.stringify(e.detail))
args = nargs
}}
/>
{/snippet}
{#snippet flow_step()}
{#if flow}
{#if stepDetail}
<FlowGraphViewerStep schema={flow.schema} {stepDetail} />
{/if}
{/snippet}
{#snippet triggers()}
{#if flow}
<TriggersEditor
initialPath={flow.path}
currentPath={flow.path}
noEditor={true}
newItem={false}
isFlow={true}
schema={flow.schema}
isDeployed={true}
noCapture={true}
isEditor={false}
/>
{/if}
{/snippet}
</DetailPageLayout>
{/if}
{/if}
{/snippet}
{#snippet triggers()}
{#if flow}
<TriggersEditor
initialPath={flow.path}
currentPath={flow.path}
noEditor={true}
newItem={false}
isFlow={true}
schema={flow.schema}
isDeployed={true}
noCapture={true}
isEditor={false}
/>
{/if}
{/snippet}
</DetailPageLayout>
<FlowAssetsHandler
modules={flow?.value.modules ?? []}
@@ -539,9 +539,9 @@
<PersistentScriptDrawer bind:this={persistentScriptDrawer} />
<ShareModal bind:this={shareModal} />
{#if script}
<Drawer bind:open={versionsDrawerOpen} size="1200px">
<DrawerContent title="Versions History" on:close={() => (versionsDrawerOpen = false)} noPadding>
<Drawer bind:open={versionsDrawerOpen} size="1200px">
<DrawerContent title="Versions History" on:close={() => (versionsDrawerOpen = false)} noPadding>
{#if script}
<ScriptVersionHistory
scriptPath={script.path}
openDetails
@@ -552,325 +552,323 @@
versionsDrawerOpen = false
}}
/>
</DrawerContent>
</Drawer>
{#key script.hash}
<DetailPageLayout bind:selected={rightPaneSelected} isOperator={$userStore?.operator}>
{#snippet header()}
{#if script}
<DetailPageHeader
{mainButtons}
menuItems={getMenuItems(script, deployUiSettings)}
title={defaultIfEmptyString(script.summary, script.path)}
bind:errorHandlerMuted={script.ws_error_handler_muted}
errorHandlerKind="script"
scriptOrFlowPath={script.path}
tag={script.tag}
on:seeTriggers={() => {
rightPaneSelected = 'triggers'
}}
>
{#snippet trigger_badges()}
<TriggersBadge
showOnlyWithCount={true}
showDraft={false}
path={script?.path ?? ''}
newItem={false}
isFlow={false}
selected={rightPaneSelected === 'triggers'}
onSelect={async (triggerIndex: number) => {
if (rightPaneSelected !== 'triggers') {
rightPaneSelected = 'triggers'
}
await tick()
triggersState.selectedTriggerIndex = triggerIndex
}}
/>
{/snippet}
{#if $workspaceStore}
<Star
kind="script"
path={script.path}
{starred}
workspace_id={$workspaceStore}
on:starred={() => {
starred = !starred
}}
/>
{/if}
{#if script.codebase}
<Badge
>bundle<Tooltip
>This script is deployed as a bundle and can only be deployed from the CLI for now</Tooltip
></Badge
>
{/if}
{#if script?.priority != undefined}
<div class="hidden md:block">
<Badge color="blue" variant="outlined" size="xs">
{`Priority: ${script.priority}`}
</Badge>
</div>
{/if}
{#if script?.restart_unless_cancelled ?? false}
<button onclick={() => persistentScriptDrawer?.open?.(script)}>
<div class="hidden md:block">
<Badge color="red" variant="outlined" size="xs">Persistent</Badge>
</div>
</button>
{/if}
{#if script?.concurrent_limit != undefined && script.concurrency_time_window_s != undefined}
<div class="hidden md:block">
<Badge color="gray" variant="outlined" size="xs">
{`Concurrency limit: ${script.concurrent_limit} runs every ${script.concurrency_time_window_s}s`}
</Badge>
</div>
{/if}
</DetailPageHeader>
{/if}
{/snippet}
{#snippet form()}
{#if script}
<div class="p-8 w-full max-w-3xl mx-auto">
<div class="flex flex-col gap-0.5 mb-1">
{#if script.lock_error_logs || topHash || script.archived || script.deleted}
<div class="flex flex-col gap-2 my-2">
{#if script.lock_error_logs}
<div
class="bg-red-100 dark:bg-red-700 border-l-4 border-red-500 p-4"
role="alert"
>
<p class="font-bold">Error deploying this script</p>
<p>
This script has not been deployed successfully because of the following
errors:
</p>
<LogViewer
content={script.lock_error_logs}
isLoading={false}
tag={undefined}
/>
</div>
{/if}
{#if topHash}
<div class="mt-2"></div>
<Alert type="warning" title="Not HEAD">
This hash is not HEAD (latest non-archived version at this path) :
<a href="{base}/scripts/get/{topHash}?workspace={$workspaceStore}"
>Go to the HEAD of this path</a
>
</Alert>
{/if}
{#if script.archived && !topHash}
<Alert type="error" title="Archived">This path was archived</Alert>
{/if}
{#if script.deleted}
<div
class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4"
role="alert"
>
<p class="font-bold">Deleted</p>
<p>The content of this script was deleted (by an admin, no less)</p>
</div>
{/if}
</div>
{/if}
{#if !emptyString(script.description)}
<GfmMarkdown md={defaultIfEmptyString(script?.description, 'No description')} />
{/if}
</div>
{#if deploymentInProgress}
<Badge color="yellow">
<Loader2 size={12} class="inline animate-spin mr-1" />
Deployment in progress
</Badge>
{/if}
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge
onReject={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON',
rightTooltip: 'Fill args from JSON'
}}
lightMode
on:change={(e) => {
runForm?.setCode(JSON.stringify(args ?? {}, null, '\t'))
}}
/>
</div>
{#if script?.schema?.prompt_for_ai !== undefined}
<AIFormAssistant
instructions={script.schema?.prompt_for_ai as string}
onEditInstructions={() => {
goto(`/scripts/edit/${script?.path}?metadata_open=true`)
}}
runnableType="script"
/>
{/if}
<RunForm
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
viewKeybinding
loading={runLoading}
autofocus
detailed={false}
bind:isValid
runnable={script}
runAction={runScript}
bind:args
schedulable={true}
bind:this={runForm}
{jsonView}
/>
</div>
<div class="py-10"></div>
{#if !emptyString(script.summary)}
<div class="mb-2">
<span class="!text-tertiary">{script.path}</span>
</div>
{/if}
<div class="flex flex-row gap-x-2 flex-wrap items-center">
<span class="text-sm text-tertiary">
Edited <TimeAgo date={script.created_at || ''} /> by {script.created_by ||
'unknown'}
</span>
<Badge small color="gray">
{truncateHash(script?.hash ?? '')}
</Badge>
{#if script?.is_template}
<Badge color="blue">Template</Badge>
{/if}
{#if script && script.kind !== 'script'}
<Badge color="blue">
{script?.kind}
</Badge>
{/if}
<SharedBadge canWrite={can_write} extraPerms={script?.extra_perms ?? {}} />
</div>
</div>
{/if}
{/snippet}
{#snippet save_inputs()}
{#if args}
<SavedInputsV2
schema={script?.schema}
bind:this={savedInputsV2}
scriptPath={script?.path}
scriptHash={topHash}
{isValid}
{jsonView}
{args}
bind:inputSelected
on:selected_args={(e) => {
const nargs = JSON.parse(JSON.stringify(e.detail))
args = nargs
}}
/>
{/if}
{/snippet}
{#snippet triggers()}
{#if script}
<TriggersEditor
initialPath={script.path}
currentPath={script.path}
noEditor={true}
{/if}
</DrawerContent>
</Drawer>
{#key script?.hash}
<DetailPageLayout bind:selected={rightPaneSelected} isOperator={$userStore?.operator}>
{#snippet header()}
<DetailPageHeader
{mainButtons}
menuItems={getMenuItems(script, deployUiSettings)}
title={defaultIfEmptyString(script?.summary, script?.path ?? '')}
bind:errorHandlerMuted={
() => script?.ws_error_handler_muted ?? false,
(v) => {
if (script?.ws_error_handler_muted) {
script.ws_error_handler_muted = v
}
}
}
errorHandlerKind="script"
scriptOrFlowPath={script?.path ?? ''}
tag={script?.tag ?? ''}
on:seeTriggers={() => {
rightPaneSelected = 'triggers'
}}
>
{#snippet trigger_badges()}
<TriggersBadge
showOnlyWithCount={true}
showDraft={false}
path={script?.path ?? ''}
newItem={false}
isFlow={false}
schema={script.schema}
isDeployed={true}
noCapture={true}
isEditor={false}
selected={rightPaneSelected === 'triggers'}
onSelect={async (triggerIndex: number) => {
if (rightPaneSelected !== 'triggers') {
rightPaneSelected = 'triggers'
}
await tick()
triggersState.selectedTriggerIndex = triggerIndex
}}
/>
{/snippet}
{#if $workspaceStore && script}
<Star
kind="script"
path={script.path}
{starred}
workspace_id={$workspaceStore}
on:starred={() => {
starred = !starred
}}
/>
{/if}
{/snippet}
{#snippet scriptRender()}
{#if script}
<div class="h-full">
<Skeleton {loading} layout={[[20]]} />
<Tabs selected="code">
<Tab value="code" size="xs">Code</Tab>
<Tab value="dependencies" size="xs">Lockfile</Tab>
<Tab value="schema" size="xs">Schema</Tab>
{#snippet content()}
{#if script}
<TabContent value="code">
<div class="p-2 w-full">
<HighlightCode
language={script.language}
code={script.content}
class="whitespace-pre-wrap"
/>
</div>
</TabContent>
<TabContent value="dependencies">
<div>
{#if script?.lock}
<div class="relative overflow-x-auto w-full">
<Button
wrapperClasses="absolute top-2 right-2 z-20"
on:click={() => copyToClipboard(script?.lock)}
color="light"
size="xs2"
startIcon={{
icon: ClipboardCopy
}}
iconOnly
/>
<pre class="bg-surface-secondary text-sm p-2 h-full overflow-auto w-full"
>{script.lock}</pre
>
</div>
{:else}
<p class="bg-surface-secondary text-sm p-2">
There is no lock file for this script
</p>
{/if}
</div>
</TabContent>
<TabContent value="schema">
<div class="p-1 relative h-full">
<button
onclick={() => copyToClipboard(JSON.stringify(script?.schema, null, 4))}
class="absolute top-2 right-2"
>
<ClipboardCopy size={14} />
</button>
<Highlight language={json} code={JSON.stringify(script?.schema, null, 4)} />
</div>
</TabContent>
{#if script?.codebase}
<Badge
>bundle<Tooltip
>This script is deployed as a bundle and can only be deployed from the CLI for now</Tooltip
></Badge
>
{/if}
{#if script?.priority != undefined}
<div class="hidden md:block">
<Badge color="blue" variant="outlined" size="xs">
{`Priority: ${script.priority}`}
</Badge>
</div>
{/if}
{#if script?.restart_unless_cancelled ?? false}
<button onclick={() => persistentScriptDrawer?.open?.(script)}>
<div class="hidden md:block">
<Badge color="red" variant="outlined" size="xs">Persistent</Badge>
</div>
</button>
{/if}
{#if script?.concurrent_limit != undefined && script.concurrency_time_window_s != undefined}
<div class="hidden md:block">
<Badge color="gray" variant="outlined" size="xs">
{`Concurrency limit: ${script.concurrent_limit} runs every ${script.concurrency_time_window_s}s`}
</Badge>
</div>
{/if}
</DetailPageHeader>
{/snippet}
{#snippet form()}
{#if script}
<div class="p-8 w-full max-w-3xl mx-auto">
<div class="flex flex-col gap-0.5 mb-1">
{#if script.lock_error_logs || topHash || script.archived || script.deleted}
<div class="flex flex-col gap-2 my-2">
{#if script.lock_error_logs}
<div
class="bg-red-100 dark:bg-red-700 border-l-4 border-red-500 p-4"
role="alert"
>
<p class="font-bold">Error deploying this script</p>
<p>
This script has not been deployed successfully because of the following
errors:
</p>
<LogViewer content={script.lock_error_logs} isLoading={false} tag={undefined} />
</div>
{/if}
{/snippet}
</Tabs>
{#if topHash}
<div class="mt-2"></div>
<Alert type="warning" title="Not HEAD">
This hash is not HEAD (latest non-archived version at this path) :
<a href="{base}/scripts/get/{topHash}?workspace={$workspaceStore}"
>Go to the HEAD of this path</a
>
</Alert>
{/if}
{#if script.archived && !topHash}
<Alert type="error" title="Archived">This path was archived</Alert>
{/if}
{#if script.deleted}
<div
class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4"
role="alert"
>
<p class="font-bold">Deleted</p>
<p>The content of this script was deleted (by an admin, no less)</p>
</div>
{/if}
</div>
{/if}
{#if !emptyString(script.description)}
<GfmMarkdown md={defaultIfEmptyString(script?.description, 'No description')} />
{/if}
</div>
{#if script.envs && script.envs.length > 0}
<h3>Static Env Variables</h3>
<ul>
{#each script.envs as e}
<li>{e}</li>
{/each}
</ul>
{#if deploymentInProgress}
<Badge color="yellow">
<Loader2 size={12} class="inline animate-spin mr-1" />
Deployment in progress
</Badge>
{/if}
{/if}
{/snippet}
</DetailPageLayout>
{/key}
{/if}
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge
onReject={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON',
rightTooltip: 'Fill args from JSON'
}}
lightMode
on:change={(e) => {
runForm?.setCode(JSON.stringify(args ?? {}, null, '\t'))
}}
/>
</div>
{#if script?.schema?.prompt_for_ai !== undefined}
<AIFormAssistant
instructions={script.schema?.prompt_for_ai as string}
onEditInstructions={() => {
goto(`/scripts/edit/${script?.path}?metadata_open=true`)
}}
runnableType="script"
/>
{/if}
<RunForm
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
viewKeybinding
loading={runLoading}
autofocus
detailed={false}
bind:isValid
runnable={script}
runAction={runScript}
bind:args
schedulable={true}
bind:this={runForm}
{jsonView}
/>
</div>
<div class="py-10"></div>
{#if !emptyString(script.summary)}
<div class="mb-2">
<span class="!text-tertiary">{script.path}</span>
</div>
{/if}
<div class="flex flex-row gap-x-2 flex-wrap items-center">
<span class="text-sm text-tertiary">
Edited <TimeAgo date={script.created_at || ''} /> by {script.created_by || 'unknown'}
</span>
<Badge small color="gray">
{truncateHash(script?.hash ?? '')}
</Badge>
{#if script?.is_template}
<Badge color="blue">Template</Badge>
{/if}
{#if script && script.kind !== 'script'}
<Badge color="blue">
{script?.kind}
</Badge>
{/if}
<SharedBadge canWrite={can_write} extraPerms={script?.extra_perms ?? {}} />
</div>
</div>
{/if}
{/snippet}
{#snippet save_inputs()}
{#if args}
<SavedInputsV2
schema={script?.schema}
bind:this={savedInputsV2}
scriptPath={script?.path}
scriptHash={topHash}
{isValid}
{jsonView}
{args}
bind:inputSelected
on:selected_args={(e) => {
const nargs = JSON.parse(JSON.stringify(e.detail))
args = nargs
}}
/>
{/if}
{/snippet}
{#snippet triggers()}
{#if script}
<TriggersEditor
initialPath={script.path}
currentPath={script.path}
noEditor={true}
newItem={false}
isFlow={false}
schema={script.schema}
isDeployed={true}
noCapture={true}
isEditor={false}
/>
{/if}
{/snippet}
{#snippet scriptRender()}
<div class="h-full">
<Skeleton {loading} layout={[[20]]} />
<Tabs selected="code">
<Tab value="code" size="xs">Code</Tab>
<Tab value="dependencies" size="xs">Lockfile</Tab>
<Tab value="schema" size="xs">Schema</Tab>
{#snippet content()}
{#if script}
<TabContent value="code">
<div class="p-2 w-full">
<HighlightCode
language={script.language}
code={script.content}
class="whitespace-pre-wrap"
/>
</div>
</TabContent>
<TabContent value="dependencies">
<div>
{#if script?.lock}
<div class="relative overflow-x-auto w-full">
<Button
wrapperClasses="absolute top-2 right-2 z-20"
on:click={() => copyToClipboard(script?.lock)}
color="light"
size="xs2"
startIcon={{
icon: ClipboardCopy
}}
iconOnly
/>
<pre class="bg-surface-secondary text-sm p-2 h-full overflow-auto w-full"
>{script.lock}</pre
>
</div>
{:else}
<p class="bg-surface-secondary text-sm p-2">
There is no lock file for this script
</p>
{/if}
</div>
</TabContent>
<TabContent value="schema">
<div class="p-1 relative h-full">
<button
onclick={() => copyToClipboard(JSON.stringify(script?.schema, null, 4))}
class="absolute top-2 right-2"
>
<ClipboardCopy size={14} />
</button>
<Highlight language={json} code={JSON.stringify(script?.schema, null, 4)} />
</div>
</TabContent>
{/if}
{/snippet}
</Tabs>
</div>
{#if script?.envs && script.envs.length > 0}
<h3>Static Env Variables</h3>
<ul>
{#each script?.envs as e}
<li>{e}</li>
{/each}
</ul>
{/if}
{/snippet}
</DetailPageLayout>
{/key}