diff --git a/frontend/src/lib/components/RunForm.svelte b/frontend/src/lib/components/RunForm.svelte index 2ae75f3c47..7be09785b1 100644 --- a/frontend/src/lib/components/RunForm.svelte +++ b/frontend/src/lib/components/RunForm.svelte @@ -152,8 +152,8 @@ * form is embedded in a page that is not the runnable's own — an AI session preview * tab — since there the fragment would land on an unrelated URL. */ syncArgsToUrl?: boolean - /** Controls beside the Run button of a form that cannot schedule, in the row a - * schedulable one gives its Advanced options. */ + /** Controls beside the Run button: left of Advanced on a schedulable form, in + * Advanced's place on one that cannot schedule. */ actions?: import('svelte').Snippet } @@ -405,6 +405,7 @@ {/snippet} + {@render actions?.()} {@render unparseableError()} {#if overrideTag} diff --git a/frontend/src/lib/components/copilot/AIFormAssistant.svelte b/frontend/src/lib/components/copilot/AIFormAssistant.svelte index 72ab57cab6..b4526f1153 100644 --- a/frontend/src/lib/components/copilot/AIFormAssistant.svelte +++ b/frontend/src/lib/components/copilot/AIFormAssistant.svelte @@ -8,10 +8,13 @@ import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte' import { copilotInfo } from '$lib/aiStore' import { logFeatureUsage } from '$lib/utils/featureUsage' + import { pageHref } from '$lib/components/sessions/previewPaths' interface Props { /** Unset for a viewer who cannot edit the item: the card then offers no way to the editor. */ onEditInstructions: (() => void) | undefined + /** Empty renders the AI button alone, sized for the run form's action row, which + * is where the host places it; a prompt makes this a card of its own. */ instructions: string runnableType: 'script' | 'flow' path: string | undefined @@ -37,72 +40,52 @@ aiChatManager.askAi(`Analyze the ${runnableType} form on this page and fill the inputs for me`) } - // A session cannot reach this page's form (the preview is a separate editor, - // and form filling drives the DOM through NAVIGATOR mode), so the hand-off - // asks it to run the item instead. Naming the DEPLOYED version matters: the - // test_run_* tools prefer drafts, which is not what this page runs. + // The session's preview opens on this same deployed page (`/get/`), not the item's + // editor: someone on a run page came to run the item, not to change it. const sessionSource = $derived( path ? { - target: { kind: runnableType, path } as const, + page: () => pageHref(`/${runnableType}s/get/${path}`), workspaceId: $operatingWorkspace ?? undefined, - beforeOpen: logAsked, - seedPrompt: - `Run the deployed ${runnableType} \`${path}\` for me. Pick sensible inputs, ` + - `tell me what you chose, then run it.` + - (instructions ? `\n\nHow to choose the inputs:\n${instructions}` : '') + beforeOpen: logAsked } : undefined ) -{#if !$copilotInfo.workspaceDisabled} +{#snippet aiButton(unifiedSize: 'sm' | 'md')} + + + {#snippet fallback()} + + {/snippet} + +{/snippet} + +{#if $copilotInfo.workspaceDisabled} + +{:else if instructions}
- {#if instructions} - - {:else if onEditInstructions} - - {:else} - - {/if} - - (expanded = !expanded)} > - {#snippet fallback()} - - {/snippet} - + AI prompt + + {@render aiButton('sm')}
- {#if instructions && expanded} + {#if expanded}
{/if}
+{:else} + {@render aiButton('md')} {/if} diff --git a/frontend/src/lib/components/copilot/AIFormSettings.svelte b/frontend/src/lib/components/copilot/AIFormSettings.svelte index 2094dda9f5..4bae2a825a 100644 --- a/frontend/src/lib/components/copilot/AIFormSettings.svelte +++ b/frontend/src/lib/components/copilot/AIFormSettings.svelte @@ -13,7 +13,7 @@ {#if !$copilotInfo.workspaceDisabled}
diff --git a/frontend/src/lib/components/details/ScriptDetail.svelte b/frontend/src/lib/components/details/ScriptDetail.svelte index 89708ea0ca..078ab20a47 100644 --- a/frontend/src/lib/components/details/ScriptDetail.svelte +++ b/frontend/src/lib/components/details/ScriptDetail.svelte @@ -166,6 +166,7 @@ // asynchronously, and a value read after `getScriptByHash` resolves can land before that // lookup does. Assigned once, that leaves the page permanently read-only — `canWrite` // refuses an unknown user — with nothing to recompute it. A hub script is never writable. + const promptForAi = $derived((script?.schema?.prompt_for_ai as string | undefined) ?? '') const can_write = $derived( !isHubScript && !!script && @@ -1073,15 +1074,20 @@ {/if} - {#if Object.keys(script?.schema?.properties ?? {}).length > 0} - onNavigate(`/scripts/edit/${script?.path}?metadata_open=true`) - : undefined} - runnableType="script" - path={script?.path} - /> + {#snippet aiAssistant()} + {#if Object.keys(script?.schema?.properties ?? {}).length > 0} + onNavigate(`/scripts/edit/${script?.path}?metadata_open=true`) + : undefined} + runnableType="script" + path={script?.path} + /> + {/if} + {/snippet} + {#if promptForAi} + {@render aiAssistant()} {/if} diff --git a/frontend/src/lib/components/flows/content/FlowSettings.svelte b/frontend/src/lib/components/flows/content/FlowSettings.svelte index 079647112c..0ae94b208e 100644 --- a/frontend/src/lib/components/flows/content/FlowSettings.svelte +++ b/frontend/src/lib/components/flows/content/FlowSettings.svelte @@ -93,7 +93,7 @@ let activeAdvancedOptions = $derived([ { - name: 'Additional prompt for AI', + name: 'AI prompt', active: !!flowStore.val.schema?.prompt_for_ai }, { diff --git a/frontend/src/lib/components/sessions/ItemViewerView.svelte b/frontend/src/lib/components/sessions/ItemViewerView.svelte index b805f1b91a..f02b215350 100644 --- a/frontend/src/lib/components/sessions/ItemViewerView.svelte +++ b/frontend/src/lib/components/sessions/ItemViewerView.svelte @@ -7,7 +7,7 @@ * is caught here and turned into a move inside the preview panel, so nothing takes * the browser out of the session. */ - import { untrack } from 'svelte' + import { setContext, untrack } from 'svelte' import { base } from '$lib/base' import ScriptDetail from '$lib/components/details/ScriptDetail.svelte' import FlowDetail from '$lib/components/details/FlowDetail.svelte' @@ -48,6 +48,13 @@ active?: boolean } = $props() + // Mounted in the preview panel, outside the chat's own subtree, as PageItemEditorView is: + // the detail pages' AI entry points would otherwise offer a session from inside one. + setContext( + 'aiChatManager', + untrack(() => runtime.manager) + ) + // This reads the deployed version over the API rather than from the editor cell the // chat mutates, so it is the one preview kind that does not self-sync (the invariant // `previewReload.ts` states for editors). A deploy from the editor topbar beside it @@ -179,50 +186,50 @@ - {#key loadKey} - {@const mountedKey = loadKey} - {@const setLoadState = (state: 'loaded' | 'not_found') => onLoadState(mountedKey, state)} - {#if notDeployed} - -
-
Not deployed yet
-
- Nothing is deployed at {path}, so there is no deployed - version to view. Deploy it from the editor to see it here. -
- +
+
Not deployed yet
+
+ Nothing is deployed at {path}, so there is no deployed + version to view. Deploy it from the editor to see it here.
- {:else if kind === 'script'} - - {:else if kind === 'flow'} - - {:else} - onNavigate(`/apps_raw/edit/${path}`)} - onLoadState={setLoadState} - syncHashToUrl={false} - /> - {/if} - {/key} + +
+ {:else if kind === 'script'} + + {:else if kind === 'flow'} + + {:else} + onNavigate(`/apps_raw/edit/${path}`)} + onLoadState={setLoadState} + syncHashToUrl={false} + /> + {/if} +{/key}