mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
* Remove $$props.field usage * Rename slots to ensure no hyphen * _props * _trigger * OnSelectedIteration type correct capitalization * rename _content * Remove afterUpdate * Migrate everything to svelte 5 * array bind * Fix popover * type never * nit fixes * Fixed many trivial errors * onClick * Fix errors * use let: * nit typing * fix: wrap state_referenced_locally vars with untrack() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add untrack import * Fix all syntax errors due to untrack migration * Fix undefined errors * Fix more undefined errors * untrack(() => initialOpen) * svelte-ignore * Fix state_descriptors_fixed error in Chart.svelte Use $state.snapshot() to pass plain copies of data/options to Chart.js instead of $state proxies. Chart.js's listenArrayEvents tries to define property descriptors on data arrays, which Svelte 5 proxies reject. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit typing * Merge issue * Fix "path is not set" error in resource picker / editor * Fix InputTransformForm error when rerunning some flows * fix npm run check --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
6.1 KiB
Svelte
200 lines
6.1 KiB
Svelte
<script lang="ts" module>
|
|
let cachedValues: Record<
|
|
string,
|
|
{
|
|
latestHash: string | undefined
|
|
}
|
|
> = {}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import Badge from '$lib/components/common/badge/Badge.svelte'
|
|
import Button from '$lib/components/common/button/Button.svelte'
|
|
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
|
|
import MetadataGen from '$lib/components/copilot/MetadataGen.svelte'
|
|
import IconedPath from '$lib/components/IconedPath.svelte'
|
|
import { ScriptService, type FlowModuleValue, type PathScript } from '$lib/gen'
|
|
import { hubBaseUrlStore, workspaceStore } from '$lib/stores'
|
|
import { Flag, Lock, RefreshCw, Unlock } from 'lucide-svelte'
|
|
import { createEventDispatcher, untrack } from 'svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import { validateToolName } from '$lib/components/graph/renderers/nodes/AIToolNode.svelte'
|
|
import { DEFAULT_HUB_BASE_URL, PRIVATE_HUB_MIN_VERSION } from '$lib/hub'
|
|
|
|
interface Props {
|
|
flowModuleValue?: FlowModuleValue | undefined
|
|
title?: string | undefined
|
|
summary?: string | undefined
|
|
children?: import('svelte').Snippet
|
|
action?: import('svelte').Snippet
|
|
isAgentTool?: boolean
|
|
}
|
|
|
|
let {
|
|
flowModuleValue = undefined,
|
|
title = undefined,
|
|
summary = $bindable(undefined),
|
|
children,
|
|
action,
|
|
isAgentTool = false
|
|
}: Props = $props()
|
|
|
|
let latestHash: string | undefined = $state(undefined)
|
|
|
|
// Extract version_id from hub path (format: hub/{version_id}/{app}/{summary})
|
|
let hubVersionId = $derived(
|
|
flowModuleValue?.type === 'script' && flowModuleValue.path?.startsWith('hub/')
|
|
? flowModuleValue.path.split('/')[1]
|
|
: undefined
|
|
)
|
|
|
|
function getCachedKey(path: string) {
|
|
return `${$workspaceStore}-${path}`
|
|
}
|
|
function getCachedValues(path: string) {
|
|
const key = getCachedKey(path)
|
|
latestHash = cachedValues[key]?.latestHash
|
|
}
|
|
const untrackedFlowModuleValue = untrack(() => flowModuleValue)
|
|
if (untrackedFlowModuleValue?.type === 'script' && untrackedFlowModuleValue.path) {
|
|
getCachedValues(untrackedFlowModuleValue.path)
|
|
}
|
|
|
|
async function loadLatestHash(value: PathScript) {
|
|
let script = await ScriptService.getScriptByPath({
|
|
workspace: $workspaceStore!,
|
|
path: value.path
|
|
})
|
|
const key = getCachedKey(value.path)
|
|
cachedValues[key] = {
|
|
latestHash: script.hash
|
|
}
|
|
latestHash = script.hash
|
|
}
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
$effect.pre(() => {
|
|
$workspaceStore &&
|
|
flowModuleValue?.type === 'script' &&
|
|
flowModuleValue.path &&
|
|
!flowModuleValue.path.startsWith('hub/') &&
|
|
untrack(() => loadLatestHash(flowModuleValue))
|
|
})
|
|
</script>
|
|
|
|
<div
|
|
class="overflow-x-auto scrollbar-hidden flex items-center justify-between px-4 py-2 flex-nowrap"
|
|
>
|
|
{#if flowModuleValue}
|
|
<span class="text-sm w-full mr-4">
|
|
<div class="flex items-center space-x-2">
|
|
{#if flowModuleValue.type === 'identity'}
|
|
<span class="font-bold text-xs">Identity (input copied to output)</span>
|
|
{:else if flowModuleValue.type === 'rawscript'}
|
|
<div class="mx-0.5">
|
|
<LanguageIcon lang={flowModuleValue.language} width={20} height={20} />
|
|
</div>
|
|
<MetadataGen
|
|
bind:content={summary}
|
|
promptConfigName={isAgentTool ? 'agentToolFunctionName' : 'summary'}
|
|
code={flowModuleValue.content}
|
|
class="w-full"
|
|
elementProps={{
|
|
placeholder: isAgentTool ? 'Tool name' : 'Summary'
|
|
}}
|
|
/>
|
|
{:else if flowModuleValue.type === 'script' && 'path' in flowModuleValue && flowModuleValue.path}
|
|
<IconedPath path={flowModuleValue.path} hash={flowModuleValue.hash} class="grow" />
|
|
|
|
{#if hubVersionId}
|
|
<Button
|
|
title="Report an issue with this hub script"
|
|
unifiedSize="sm"
|
|
variant="subtle"
|
|
on:click={() => {
|
|
const targetHubBaseUrl =
|
|
Number(hubVersionId) < PRIVATE_HUB_MIN_VERSION
|
|
? DEFAULT_HUB_BASE_URL
|
|
: $hubBaseUrlStore
|
|
window.open(
|
|
`${targetHubBaseUrl}/from_version/${hubVersionId}?report_issue=${hubVersionId}`,
|
|
'_blank'
|
|
)
|
|
}}
|
|
>
|
|
<Flag size={12} />Report issue
|
|
</Button>
|
|
{/if}
|
|
|
|
{#if flowModuleValue.hash}
|
|
{#if latestHash != flowModuleValue.hash}
|
|
<Button
|
|
size="xs"
|
|
variant="default"
|
|
on:click={() => {
|
|
if (flowModuleValue.type == 'script') {
|
|
dispatch('setHash', latestHash)
|
|
}
|
|
dispatch('reload')
|
|
}}>Update to latest hash</Button
|
|
>
|
|
{/if}
|
|
<Button
|
|
title="Unlock hash to always use latest deployed version at that path"
|
|
size="xs"
|
|
btnClasses="text-primary inline-flex gap-1 items-center"
|
|
color="light"
|
|
on:click={() => {
|
|
if (flowModuleValue.type == 'script') {
|
|
dispatch('setHash', undefined)
|
|
}
|
|
}}><Unlock size={12} />hash</Button
|
|
>
|
|
{:else if latestHash}
|
|
<div class="flex gap-2">
|
|
<Button
|
|
title="Lock hash to always use this specific version"
|
|
unifiedSize="sm"
|
|
variant="default"
|
|
on:click={() => {
|
|
if (flowModuleValue.type == 'script') {
|
|
dispatch('setHash', latestHash)
|
|
}
|
|
}}><Lock size={12} />hash</Button
|
|
>
|
|
<Button
|
|
title="Reload latest hash"
|
|
unifiedSize="sm"
|
|
variant="default"
|
|
on:click={() => dispatch('reload')}
|
|
startIcon={{ icon: RefreshCw }}
|
|
iconOnly
|
|
/>
|
|
</div>
|
|
{/if}
|
|
<input
|
|
bind:value={summary}
|
|
placeholder={isAgentTool ? 'Tool name' : 'Summary'}
|
|
class={twMerge(
|
|
'w-full grow',
|
|
isAgentTool && !validateToolName(summary ?? '') && '!border-red-400'
|
|
)}
|
|
/>
|
|
{:else if flowModuleValue.type === 'flow'}
|
|
<Badge color="indigo" capitalize>flow</Badge>
|
|
<input bind:value={summary} placeholder="Summary" class="w-full grow" />
|
|
{:else if flowModuleValue.type === 'aiagent'}
|
|
<Badge color="indigo">AI Agent</Badge>
|
|
<input bind:value={summary} placeholder="Summary" class="w-full grow" />
|
|
{/if}
|
|
</div>
|
|
</span>
|
|
{/if}
|
|
{#if title}
|
|
<div class="text-sm font-bold text-primary pr-2">{title}</div>
|
|
{/if}
|
|
{@render children?.()}
|
|
{@render action?.()}
|
|
</div>
|