mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 16:03:21 +00:00
show preview chip on write tools, not open_preview (#10261)
* fix(ai-sessions): show preview chip on write tools, not open_preview Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-sessions): render preview chip label in UI font, not mono Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-sessions): cast RowIcon to IconType for Button startIcon RowIcon has a required `kind` prop, so it is not assignable to Button's `IconType` (Component<{ size?: number }>). The `props` field carries the runtime prop, so cast the icon to satisfy the type check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
co-authored by
Claude Opus 4.8
Ruben Fiszel
parent
1da664fc89
commit
c23f1880d2
@@ -97,7 +97,7 @@
|
||||
</button>
|
||||
{/snippet}
|
||||
|
||||
<!-- Discrete preview chip for an item a tool created/updated/opened, pinned to
|
||||
<!-- Discrete preview chip for an item a tool created/updated, pinned to
|
||||
the right of the header row. Rendered inline (not gated on expand) so it
|
||||
stays visible after the tool collapses. -->
|
||||
{#if showPreviewChip && message.previewCard}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { PanelRight } from 'lucide-svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
|
||||
import type { IconType } from '$lib/utils'
|
||||
import { runToolDisplayAction } from './createdResourceActions.svelte'
|
||||
import { openItemPreviewAction, type PreviewCardKind } from './shared'
|
||||
|
||||
@@ -10,12 +12,7 @@
|
||||
|
||||
let { card }: Props = $props()
|
||||
|
||||
// RowIcon has no 'pipeline' kind — a pipeline is a folder graph, shown with the
|
||||
// data-pipeline icon.
|
||||
const iconKind = $derived(card.kind === 'pipeline' ? 'data_pipeline' : card.kind)
|
||||
const kindLabel = $derived(
|
||||
card.kind === 'raw_app' ? 'app' : card.kind === 'pipeline' ? 'pipeline' : card.kind
|
||||
)
|
||||
const kindLabel = $derived(card.kind === 'raw_app' ? 'app' : card.kind)
|
||||
|
||||
let opening = $state(false)
|
||||
async function open() {
|
||||
@@ -29,17 +26,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={open}
|
||||
<Button
|
||||
variant="default"
|
||||
unifiedSize="2xs"
|
||||
disabled={opening}
|
||||
title="Open {kindLabel} preview: {card.path}"
|
||||
class="group shrink-0 inline-flex items-center gap-1.5 rounded-md border border-light bg-surface pl-1.5 pr-2 py-1 transition-colors hover:bg-surface-hover disabled:opacity-60"
|
||||
onClick={open}
|
||||
startIcon={{ icon: RowIcon as unknown as IconType, props: { kind: card.kind, size: 12 } }}
|
||||
endIcon={{ icon: PanelRight }}
|
||||
wrapperClasses="shrink-0"
|
||||
>
|
||||
<span class="inline-flex shrink-0">
|
||||
<RowIcon kind={iconKind} size={12} />
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 text-2xs text-tertiary group-hover:text-secondary">
|
||||
Preview <PanelRight size={11} />
|
||||
</span>
|
||||
</button>
|
||||
<!-- The chip renders inside the tool row's font-mono scope; the label is UI text.
|
||||
(font-main, not font-sans — this Tailwind config only defines main/mono.) -->
|
||||
<span class="font-main">Preview</span>
|
||||
</Button>
|
||||
|
||||
@@ -3266,18 +3266,7 @@ export const globalTools: Tool<{}>[] = [
|
||||
),
|
||||
fn: async (ctx) => {
|
||||
const parsed = openPreviewSchema.parse(ctx.args)
|
||||
const sessionId = sessionIdFromCtx(ctx)
|
||||
const { opened, message } = openSessionPreview(parsed, sessionId)
|
||||
// Surface the same discrete card the write tools show, so the user can
|
||||
// re-open/focus the preview later from the tool call. Only when the tool
|
||||
// actually opened the preview (not for a refused open, e.g. a gated
|
||||
// pipeline), and only in a session.
|
||||
if (sessionId && opened) {
|
||||
ctx.toolCallbacks.setToolStatus(ctx.toolId, {
|
||||
previewCard: { kind: parsed.kind, path: parsed.path }
|
||||
})
|
||||
}
|
||||
return message
|
||||
return openSessionPreview(parsed, sessionIdFromCtx(ctx))
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -3491,6 +3480,9 @@ type WriteDraftCtx = {
|
||||
// reloads the preview of the session that issued the deploy — not the
|
||||
// UI-active one. Undefined for the global side-panel chat.
|
||||
sessionId?: string
|
||||
// Present when the ctx is the raw tool `fn` context — session chats carry
|
||||
// their id here (see SessionToolHelpers / sessionIdFromCtx).
|
||||
helpers?: unknown
|
||||
}
|
||||
|
||||
// Sessions are the only context where `open_preview` makes sense — the global
|
||||
@@ -3566,25 +3558,18 @@ export function setOpenPreviewHandler(handler: OpenPreviewHandler | undefined):
|
||||
openPreviewHandler = handler
|
||||
}
|
||||
|
||||
// `opened` distinguishes a real open (the caller may then offer a preview card)
|
||||
// from a refusal that is returned as a message rather than thrown — so the card is
|
||||
// never shown for an item the tool declined to preview (e.g. a gated pipeline).
|
||||
function openSessionPreview(
|
||||
args: { kind: 'script' | 'flow' | 'raw_app' | 'pipeline'; path: string },
|
||||
sessionId: string | undefined
|
||||
): { opened: boolean; message: string } {
|
||||
): string {
|
||||
if (!openPreviewHandler) {
|
||||
return {
|
||||
opened: false,
|
||||
message:
|
||||
'Error: open_preview is only available inside an AI session. Tell the user to switch to a session to view the preview, or describe the item textually.'
|
||||
}
|
||||
return 'Error: open_preview is only available inside an AI session. Tell the user to switch to a session to view the preview, or describe the item textually.'
|
||||
}
|
||||
// open_preview only exists in sessions, so no sessionId check is needed here.
|
||||
if (args.kind === 'pipeline' && !isSessionPipelinesEnabled()) {
|
||||
return { opened: false, message: SESSION_PIPELINES_GATED_MESSAGE }
|
||||
return SESSION_PIPELINES_GATED_MESSAGE
|
||||
}
|
||||
return { opened: true, message: openPreviewHandler({ ...args, sessionId }) }
|
||||
return openPreviewHandler({ ...args, sessionId })
|
||||
}
|
||||
|
||||
// Opens a workspace *page* (Runs, Schedules, …) as a page tab in the session's
|
||||
@@ -3949,15 +3934,17 @@ const PREVIEW_CARD_KIND_BY_ITEM_KIND: Partial<
|
||||
}
|
||||
|
||||
// Offer a preview card for a write that landed a previewable item. Session chats
|
||||
// only (`ctx.sessionId`): the card opens the item in the side panel, which the
|
||||
// global side-panel chat has no equivalent of. `path` is the item's display path
|
||||
// (what `open_preview` takes), not its synthetic draft storage key.
|
||||
// only: the card opens the item in the side panel, which the global side-panel
|
||||
// chat has no equivalent of. `path` is the item's display path (what
|
||||
// `open_preview` takes), not its synthetic draft storage key.
|
||||
function maybeAttachPreviewCard(
|
||||
ctx: WriteDraftCtx,
|
||||
itemKind: DraftPersistResult['itemKind'],
|
||||
path: string
|
||||
): void {
|
||||
if (!ctx.sessionId) return
|
||||
// Write tools pass the raw tool ctx, whose session id lives in `helpers` —
|
||||
// `ctx.sessionId` is only set by callers that thread it explicitly.
|
||||
if (!ctx.sessionId && !sessionIdFromCtx(ctx)) return
|
||||
const kind = PREVIEW_CARD_KIND_BY_ITEM_KIND[itemKind]
|
||||
if (!kind) return
|
||||
ctx.toolCallbacks.setToolStatus(ctx.toolId, { previewCard: { kind, path } })
|
||||
|
||||
@@ -515,14 +515,14 @@ export type NavigateAction = {
|
||||
page: string
|
||||
}
|
||||
|
||||
/** Kinds of item a session preview can host — the subset the `open_preview` tool
|
||||
* accepts. `pipeline` targets a folder graph, the rest a workspace item path. */
|
||||
export type PreviewCardKind = 'script' | 'flow' | 'raw_app' | 'pipeline'
|
||||
/** Kinds of previewable item a write tool can land — the subset of draft item
|
||||
* kinds a session preview can host. */
|
||||
export type PreviewCardKind = 'script' | 'flow' | 'raw_app'
|
||||
|
||||
// A discrete card shown under a tool call that created/updated/opened the preview of
|
||||
// a workspace item. Clicking it opens the item's live preview in the session side
|
||||
// panel — or focuses the tab if it is already open. The handler is registered by the
|
||||
// sessions page (the only surface with a preview panel).
|
||||
// A discrete card shown on a tool call that created or updated a workspace item.
|
||||
// Clicking it opens the item's live preview in the session side panel — or focuses
|
||||
// the tab if it is already open. The handler is registered by the sessions page
|
||||
// (the only surface with a preview panel).
|
||||
export type OpenItemPreviewAction = {
|
||||
id: string
|
||||
type: 'open_item_preview'
|
||||
@@ -586,8 +586,8 @@ export type ToolDisplayMessage = {
|
||||
webSearchSources?: WebSearchSource[]
|
||||
/** Data URL of an image the tool produced (e.g. take_screenshot), shown on the card. */
|
||||
imageUrl?: string
|
||||
/** Workspace item this tool created/updated or opened a preview of. Rendered as a
|
||||
* discrete, always-visible card that opens (or focuses) the item's preview in the
|
||||
/** Workspace item this tool created or updated. Rendered as a discrete,
|
||||
* always-visible card that opens (or focuses) the item's preview in the
|
||||
* session side panel. Set only for session chats — the side panel is their surface. */
|
||||
previewCard?: { kind: PreviewCardKind; path: string }
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Preview cards under create/update/open-preview tool calls dispatch here. Open
|
||||
// Preview cards on create/update tool calls dispatch here. Open
|
||||
// (or focus, if already shown) the item's preview in the active session's panel —
|
||||
// the visible chat is always the active session, so `owner` is its panel. Read
|
||||
// `owner` lazily inside the handler (not in the effect body) so this registers
|
||||
|
||||
Reference in New Issue
Block a user