mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
feat: name the previewed item in an empty AI session's composer (#12021)
* feat: name the previewed item in an empty session's composer instead of greeting * feat: name the item's kind in the empty session's composer placeholder * fix: name the previewed item as its tab does, so view tabs get the summary
This commit is contained in:
@@ -79,17 +79,23 @@
|
||||
import { goto } from '$lib/navigation'
|
||||
import { splitterPointerCapture } from '$lib/utils/splitterPointerCapture'
|
||||
import { StarterPrompts } from '$lib/components/copilot/chat/starter/starterPrompts.svelte'
|
||||
import { resolvePreviewTab } from './previewRouter'
|
||||
import { whereIs } from './sessionPreviewTabs.svelte'
|
||||
import type { SessionPreviewTab } from './sessionState.svelte'
|
||||
|
||||
let {
|
||||
sessionId,
|
||||
headerInPage = false,
|
||||
covered = false
|
||||
covered = false,
|
||||
previewTabName
|
||||
}: {
|
||||
sessionId: string
|
||||
/** True for the session the page is showing: its header rides the page header bar. */
|
||||
headerInPage?: boolean
|
||||
/** True while the page holds this wrapper inert under an overlay, where nothing takes focus. */
|
||||
covered?: boolean
|
||||
/** The name the preview's tab strip shows for a tab, without its side marker. */
|
||||
previewTabName: (tab: SessionPreviewTab) => string
|
||||
} = $props()
|
||||
|
||||
// Parent keys by sessionId; this wrapper only mounts when the session exists.
|
||||
@@ -117,8 +123,25 @@
|
||||
// Whether the first message goes out in Use. Not `permissions.readOnly`: before that send a
|
||||
// staged fork's session reads the workspace it forks, while the message builds in the fork.
|
||||
const heldInUse = $derived(!!workModeLock.current || runtime?.manager.workMode === AIWorkMode.USE)
|
||||
// The item an unsent session opened on ("Open in AI session", or a tab opened before the
|
||||
// first send): the chat reads the displayed tab with each message, so the empty chat asks
|
||||
// about that item instead of greeting. What is on screen only: a collapsed panel hides it.
|
||||
// Named as its tab is, so a deployed item in View mode gets its summary from the listing.
|
||||
const previewedItem = $derived.by(() => {
|
||||
if (!opensSession) return undefined
|
||||
const tab = runtime?.previewTabs.displayedTab
|
||||
if (!tab) return undefined
|
||||
const slot = resolvePreviewTab(whereIs(tab))
|
||||
if (slot.kind !== 'editor' && slot.kind !== 'viewer') return undefined
|
||||
const kind = slot.kind === 'editor' ? slot.editorKind : slot.viewerKind
|
||||
return {
|
||||
kind: kind === 'raw_app' ? 'app' : kind,
|
||||
name: previewTabName(tab)
|
||||
}
|
||||
})
|
||||
const greets = $derived(opensSession && !previewedItem)
|
||||
const starterPrompts = new StarterPrompts(
|
||||
() => (opensSession ? (actingWorkspaceId ?? $workspaceStore ?? undefined) : undefined),
|
||||
() => (greets ? (actingWorkspaceId ?? $workspaceStore ?? undefined) : undefined),
|
||||
() => !heldInUse
|
||||
)
|
||||
|
||||
@@ -402,6 +425,13 @@
|
||||
// utilities on one element resolve by stylesheet order, not attribute order.
|
||||
const bannerClass =
|
||||
'flex flex-row items-center justify-between gap-2 py-2 px-3 text-xs border rounded-md'
|
||||
|
||||
const previewedItemPlaceholder = $derived(
|
||||
previewedItem &&
|
||||
(heldInUse
|
||||
? `Ask about the ${previewedItem.kind} ${previewedItem.name}, or run it…`
|
||||
: `Ask about the ${previewedItem.kind} ${previewedItem.name}, or describe a change…`)
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if !session || !runtime}
|
||||
@@ -409,7 +439,12 @@
|
||||
{:else}
|
||||
<!-- A sent session whose transcript is still loading (or came back empty) shows nothing
|
||||
rather than the docked copilot's keyboard-shortcut hint, which means nothing here. -->
|
||||
{#snippet sessionEmptyHint()}{/snippet}
|
||||
{#snippet sessionEmptyHint()}
|
||||
{#if previewedItem}
|
||||
<!-- Holds the composer at the bottom, as in a session under way. -->
|
||||
<div class="flex-1 min-h-0"></div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet inputPreface()}
|
||||
{#if showRecoveryNotice}
|
||||
@@ -583,14 +618,14 @@
|
||||
onDraftChange={(text) => setSessionDraftPrompt(sessionId, text)}
|
||||
initialAttachments={armedAtInit ? undefined : getSessionDraftAttachments(sessionId)}
|
||||
onAttachmentsChange={(a) => setSessionDraftAttachments(sessionId, a)}
|
||||
placeholder={composerPlaceholder}
|
||||
placeholder={previewedItemPlaceholder ?? composerPlaceholder}
|
||||
forceDisabled={isUnavailable || !!session.archived}
|
||||
forceDisabledMessage={isUnavailable
|
||||
? 'This session is linked to a workspace that no longer exists. Move it or discard it from the banner above to keep working.'
|
||||
: session.archived
|
||||
? 'This session is archived. Unarchive it from the banner above to keep working.'
|
||||
: ''}
|
||||
starter={opensSession
|
||||
starter={greets
|
||||
? { where: 'session', suggestions: starterPrompts.suggestions }
|
||||
: undefined}
|
||||
viewTransitionNames={headerInPage && (opensSession || arrivingFromHomeSend())}
|
||||
@@ -604,37 +639,37 @@
|
||||
its transcript area, its column, and its footer row's height; for a session
|
||||
that has never sent, the greeting above the centred composer. -->
|
||||
<div
|
||||
class="flex flex-col h-full {opensSession ? 'justify-center' : ''}"
|
||||
class="flex flex-col h-full {greets ? 'justify-center' : ''}"
|
||||
{@attach () => () =>
|
||||
runtime.manager.setComposerHasDraft(PLACEHOLDER_DRAFT_KEY, false)}
|
||||
>
|
||||
{#if opensSession}
|
||||
{#if greets}
|
||||
<div class="flex-1 min-h-0 flex flex-col justify-end">
|
||||
<div class="w-full max-w-3xl mx-auto px-7">
|
||||
<SessionGreeting />
|
||||
</div>
|
||||
</div>
|
||||
{:else if runtime.manager.displayMessages.length > 0}
|
||||
{:else if previewedItem || runtime.manager.displayMessages.length > 0}
|
||||
<div class="flex-1 min-h-0"></div>
|
||||
{/if}
|
||||
<div class="w-full max-w-3xl mx-auto px-7 pb-2">
|
||||
{@render inputPreface()}
|
||||
<ComposerPlaceholder
|
||||
bind:value={() => draftText, setPlaceholderText}
|
||||
toolbar={opensSession ? placeholderToolbar : undefined}
|
||||
placeholder={opensSession
|
||||
toolbar={greets ? placeholderToolbar : undefined}
|
||||
placeholder={greets
|
||||
? heldInUse
|
||||
? USE_STARTER_PLACEHOLDER
|
||||
: STARTER_PLACEHOLDER
|
||||
: runtime.manager.displayMessages.length > 0
|
||||
? 'Ask followup'
|
||||
: composerPlaceholder}
|
||||
: (previewedItemPlaceholder ?? composerPlaceholder)}
|
||||
disabled={isUnavailable || !!session.archived}
|
||||
autofocus={sessionState.currentSessionId === sessionId && !covered}
|
||||
onSelectionChange={(selection) => (handoff = selection ?? null)}
|
||||
onCompositionChange={(composing) => (placeholderComposing = composing)}
|
||||
/>
|
||||
{#if !opensSession}
|
||||
{#if !greets}
|
||||
<div class="mt-1 h-5"></div>
|
||||
{:else if !isUnavailable && !session.archived && starterPrompts.suggestions.length > 0}
|
||||
<div class="pt-2">
|
||||
@@ -646,7 +681,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if opensSession}
|
||||
{#if greets}
|
||||
<div class="flex-1 min-h-0"></div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -852,13 +852,16 @@
|
||||
// cell reactively, so the editor mirrors the name onto the tab model). Before
|
||||
// that, the workspace listing names it. Falls back to the plain location label
|
||||
// for summary-less items and non-item pages.
|
||||
function tabLabelFor(tab: SessionPreviewTab, workspace: string): string {
|
||||
function tabNameFor(tab: SessionPreviewTab, workspace: string): string {
|
||||
const listed = listedItemFor(tab, workspace)
|
||||
const name =
|
||||
return (
|
||||
tab.friendlyLabel ??
|
||||
(listed && itemDisplayName(listed.path, listed.draftPath, listed.summary)) ??
|
||||
previewLocationLabel(tab.loc)
|
||||
return name + sideSuffix(tab.url)
|
||||
)
|
||||
}
|
||||
function tabLabelFor(tab: SessionPreviewTab, workspace: string): string {
|
||||
return tabNameFor(tab, workspace) + sideSuffix(tab.url)
|
||||
}
|
||||
|
||||
// Hover title for a tab. A summary label is free text the strip truncates, and
|
||||
@@ -1118,6 +1121,8 @@
|
||||
sessionId={s.id}
|
||||
headerInPage={s.id === activeSession?.id}
|
||||
covered={aiHiddenVerdict !== false}
|
||||
previewTabName={(tab) =>
|
||||
tabNameFor(tab, getEffectiveWorkspaceId(s) ?? $workspaceStore ?? '')}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user