mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-28 00:02:42 +00:00
* fix: disable a dynamic input when its schema field is disabled Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * refactor: extract the run form's argument hygiene into job_args Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: give Tabs an opt-in sliding selection indicator Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * refactor: share the chat's scroll-fade measurement Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: give the chat a run-form contract and incremental job output Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: run and test a script from the chat through an argument form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: carry a chat run's card and job across saves and reloads Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: render a chat run as a tool call row with its form, logs and result Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: open a pending run form in the sessions preview pane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * test: benchmark running a deployed script from the chat Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: offer a test run's dynamic options from the draft it previews Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: say that a test run's dynselect helper executes on form display * fix: send a schema default the model omitted when yolo skips the form * fix: infer a test run's schema when the stored one declares no properties * docs: tighten the note on the form's mount-time helper job * fix: apply a nested schema default the bypass posture counts as answered * fix: apply a declared default to a null value and an optional nested field * fix: check required fields inside a supplied optional object before bypassing * fix: read required args as own properties before bypassing the form * fix: stop the turn from the run form's action row in the preview panel * refactor: drop the run-form prediction and share its secret minting * refactor: prefill a proposed secret instead of emptying the field * docs: correct the comments the run-form prediction left behind * fix: keep a proposed secret out of the chat's stored messages * docs: say what a literal secret argument now does * test: restore the copilotInfo export the aiStore mock omits * docs: cut the run form's helper-script note to its constraints * refactor: settle a run form from one entry and fetch a job's logs once * fix: separate colliding secret paths, gate plan mode, keep polled logs * fix: mint before the form opens, skip empty fields, show what ran * revert: mint a run form's secrets at submit, not before it opens * fix: settle a cancelled run card on the form's arguments, not the proposal * fix: settle a stopped run form like a cancelled one, and keep an empty secret empty * fix: snapshot a run's arguments before minting its secrets --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
259 lines
7.8 KiB
Svelte
259 lines
7.8 KiB
Svelte
<script lang="ts">
|
|
import { ChevronRightSquare, ClipboardCopy, Download, Expand } from 'lucide-svelte'
|
|
import ArgInfo from './ArgInfo.svelte'
|
|
import { Badge, Button, Drawer, DrawerContent, Skeleton } from './common'
|
|
|
|
import { Highlight } from 'svelte-highlight'
|
|
import { copyToClipboard } from '$lib/utils'
|
|
import { json } from 'svelte-highlight/languages'
|
|
import DataTable from './table/DataTable.svelte'
|
|
import Cell from './table/Cell.svelte'
|
|
import Head from './table/Head.svelte'
|
|
import Row from './table/Row.svelte'
|
|
import HighlightTheme from './HighlightTheme.svelte'
|
|
import { deepEqual } from 'fast-equals'
|
|
import { isWindmillTooBigObject } from './job_args'
|
|
import { downloadViaClient, shouldDownloadViaClient } from '$lib/utils/downloadFile'
|
|
import { appendViewToken } from '$lib/viewToken'
|
|
|
|
interface Props {
|
|
id?: string | undefined
|
|
args: any
|
|
argLabel?: string | undefined
|
|
workspace?: string | undefined
|
|
/** Drop the header's expand-into-a-drawer button, for a caller that already offers a
|
|
* way to open the run in full. */
|
|
disableExpand?: boolean
|
|
}
|
|
|
|
let {
|
|
id = undefined,
|
|
args,
|
|
argLabel = undefined,
|
|
workspace = undefined,
|
|
disableExpand = false
|
|
}: Props = $props()
|
|
|
|
// Internal flag injected by "test this step" runs to suppress the asset
|
|
// dispatcher. Not a real input: shown as a badge instead of a table row,
|
|
// but kept in the expanded JSON drawer and download.
|
|
const SKIP_ASSET_DISPATCH_ARG = '_wmill_skip_asset_dispatch'
|
|
// Internal map (path -> content digest) injected by local-dev previews so
|
|
// relative imports resolve from not-yet-deployed local content. Same
|
|
// treatment: badge, not a noisy table row.
|
|
const TEMP_SCRIPT_REFS_ARG = '_TEMP_SCRIPT_REFS'
|
|
const INTERNAL_ARGS = [SKIP_ASSET_DISPATCH_ARG, TEMP_SCRIPT_REFS_ARG]
|
|
|
|
let skippedAssetDispatch = $derived(
|
|
args != undefined && typeof args === 'object' && args[SKIP_ASSET_DISPATCH_ARG] === true
|
|
)
|
|
let hasTempScriptRefs = $derived(
|
|
args != undefined && typeof args === 'object' && args[TEMP_SCRIPT_REFS_ARG] != undefined
|
|
)
|
|
let displayArgs = $derived(
|
|
args != undefined && typeof args === 'object'
|
|
? Object.fromEntries(Object.entries(args).filter(([k]) => !INTERNAL_ARGS.includes(k)))
|
|
: args
|
|
)
|
|
|
|
let jsonViewer: Drawer | undefined = $state()
|
|
let runLocally: Drawer | undefined = $state()
|
|
let jsonStr = $state('')
|
|
|
|
const argsDownloadName = 'windmill-args.json'
|
|
let argsApiPath = $derived(
|
|
id && workspace ? appendViewToken(`/w/${workspace}/jobs_u/get_args/${id}`) : undefined
|
|
)
|
|
let argsDataHref = $derived(`data:text/json;charset=utf-8,${encodeURIComponent(jsonStr)}`)
|
|
|
|
function pythonCode() {
|
|
return `
|
|
if __name__ == "__main__":
|
|
${Object.entries(displayArgs)
|
|
.map(([arg, value]) => {
|
|
return ` ${arg} = ${JSON.stringify(value)}`
|
|
})
|
|
.join('\n')}
|
|
|
|
main(${Object.keys(displayArgs)
|
|
.map((x) => `${x} = ${x}`)
|
|
.join(', ')})
|
|
`
|
|
}
|
|
|
|
function typescriptCode() {
|
|
return `
|
|
if (import.meta.main) {
|
|
${Object.entries(displayArgs)
|
|
.map(([arg, value]) => {
|
|
return ` let ${arg} = ${JSON.stringify(value)}`
|
|
})
|
|
.join('\n')}
|
|
|
|
await main(...)
|
|
}
|
|
`
|
|
}
|
|
</script>
|
|
|
|
{#if args && typeof args === 'object' && deepEqual( Object.keys(args ?? {}), ['reason'] ) && args['reason'] == 'PREPROCESSOR_ARGS_ARE_DISCARDED'}
|
|
Preprocessor args are discarded
|
|
{:else if id && workspace && args && typeof args === 'object' && deepEqual( Object.keys(args ?? {}), ['reason'] ) && args['reason'] == 'WINDMILL_TOO_BIG'}
|
|
The args are too big in size to be able to fetch alongside job. Please {#if shouldDownloadViaClient()}<button
|
|
class="text-blue-500 hover:underline"
|
|
onclick={() => downloadViaClient(argsApiPath!, argsDownloadName)}
|
|
>download the JSON file to view them</button
|
|
>{:else}<a href="/api{argsApiPath}" target="_blank">download the JSON file to view them</a
|
|
>{/if}.
|
|
{:else}
|
|
<div class="relative">
|
|
<DataTable size="sm" containerClass="bg-surface-tertiary">
|
|
<Head>
|
|
<tr class="w-full">
|
|
<Cell head first>
|
|
{argLabel ?? 'Input'}
|
|
{#if skippedAssetDispatch}
|
|
<Badge
|
|
color="gray"
|
|
verySmall
|
|
title="This run was started with {SKIP_ASSET_DISPATCH_ARG} and did not auto-trigger downstream asset consumers"
|
|
>
|
|
asset dispatch skipped
|
|
</Badge>
|
|
{/if}
|
|
{#if hasTempScriptRefs}
|
|
<Badge
|
|
color="gray"
|
|
verySmall
|
|
title="This preview carried {TEMP_SCRIPT_REFS_ARG} so relative imports resolve from local (not-yet-deployed) script content"
|
|
>
|
|
local import refs
|
|
</Badge>
|
|
{/if}
|
|
</Cell>
|
|
<Cell head last>Value</Cell>
|
|
</tr>
|
|
{#snippet headerAction()}
|
|
{#if !disableExpand}
|
|
<div class="center-center -m-1">
|
|
<Button
|
|
unifiedSize="md"
|
|
variant="subtle"
|
|
onClick={() => {
|
|
jsonStr = JSON.stringify(args, null, 4)
|
|
jsonViewer?.openDrawer()
|
|
}}
|
|
iconOnly
|
|
startIcon={{ icon: Expand }}
|
|
></Button>
|
|
</div>
|
|
{/if}
|
|
{/snippet}
|
|
</Head>
|
|
|
|
<tbody class="divide-y w-full">
|
|
{#if displayArgs && typeof displayArgs === 'object' && Object.keys(displayArgs ?? {}).length > 0}
|
|
{#each Object.entries(displayArgs ?? {}).sort( (a, b) => a?.[0]?.localeCompare(b?.[0]) ) as [arg, value]}
|
|
<Row>
|
|
<Cell first>{arg}</Cell>
|
|
<Cell><ArgInfo {value} /></Cell>
|
|
</Row>
|
|
{/each}
|
|
{:else if displayArgs && typeof displayArgs !== 'object'}
|
|
<Row><Cell>Argument is not an object (type: {typeof displayArgs})</Cell></Row>
|
|
{:else if args}
|
|
<Row><Cell>No arguments</Cell></Row>
|
|
{:else}
|
|
<Row>
|
|
<Cell first>
|
|
<Skeleton layout={[[1], 0.5, [1]]} />
|
|
</Cell>
|
|
<Cell last>
|
|
<Skeleton layout={[[1], 0.5, [1]]} />
|
|
</Cell>
|
|
</Row>
|
|
{/if}
|
|
</tbody>
|
|
</DataTable>
|
|
</div>
|
|
{/if}
|
|
|
|
<HighlightTheme />
|
|
|
|
<Drawer bind:this={jsonViewer} size="900px">
|
|
<DrawerContent title="Expanded Args" on:close={jsonViewer.closeDrawer}>
|
|
{#snippet actions()}
|
|
{#if argsApiPath && shouldDownloadViaClient()}
|
|
<Button
|
|
on:click={() => downloadViaClient(argsApiPath!, argsDownloadName)}
|
|
startIcon={{ icon: Download }}
|
|
size="xs"
|
|
color="light"
|
|
>
|
|
Download
|
|
</Button>
|
|
{:else}
|
|
<Button
|
|
download={argsDownloadName}
|
|
href={argsApiPath ? `/api${argsApiPath}` : argsDataHref}
|
|
startIcon={{ icon: Download }}
|
|
size="xs"
|
|
color="light"
|
|
>
|
|
Download
|
|
</Button>
|
|
{/if}
|
|
<Button
|
|
on:click={() => runLocally?.openDrawer()}
|
|
color="light"
|
|
size="xs"
|
|
startIcon={{ icon: ChevronRightSquare }}
|
|
>
|
|
Use in a local script
|
|
</Button>
|
|
<Button
|
|
on:click={() => copyToClipboard(jsonStr)}
|
|
color="light"
|
|
size="xs"
|
|
startIcon={{ icon: ClipboardCopy }}
|
|
>
|
|
Copy to clipboard
|
|
</Button>
|
|
{/snippet}
|
|
{#if jsonStr.length > 100000 || (id && workspace && args && isWindmillTooBigObject(args))}
|
|
<div class="text-sm mb-2 text-primary">
|
|
{#if argsApiPath && shouldDownloadViaClient()}
|
|
<button
|
|
class="underline"
|
|
onclick={() => downloadViaClient(argsApiPath!, argsDownloadName)}
|
|
>
|
|
JSON is too large to be displayed in full.
|
|
</button>
|
|
{:else}
|
|
<a download={argsDownloadName} href={argsApiPath ? `/api${argsApiPath}` : argsDataHref}>
|
|
JSON is too large to be displayed in full.
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<Highlight language={json} code={jsonStr.replace(/\\n/g, '\n')} />
|
|
{/if}
|
|
</DrawerContent>
|
|
</Drawer>
|
|
<Drawer bind:this={runLocally} size="900px">
|
|
<DrawerContent title="Run locally" on:close={runLocally.closeDrawer}>
|
|
<h3 class="mb-2">Envs</h3>
|
|
If using the wmill client in your code, set the following env variables:
|
|
<pre
|
|
><code
|
|
>BASE_URL="{window.location.origin}"
|
|
WM_TOKEN="{'<TOKEN>'}"</code
|
|
></pre
|
|
>
|
|
<h3 class="mt-8">TypeScript</h3>
|
|
<pre><code>{typescriptCode()}</code></pre>
|
|
<h3 class="mt-8">Python</h3>
|
|
<pre><code>{pythonCode()}</code></pre>
|
|
</DrawerContent>
|
|
</Drawer>
|