mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-22 08:03:38 +00:00
fix: give the agent page's API and Slack sections tabs on small screens (#11901)
* fix: give the agent page's API and Slack panel a tab on small screens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the agent pane mounted across the small-screen breakpoint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: make the covered agent pane inert on the API tab Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: flatten the agent page's API and Slack sections into its small-screen tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: fall back to the API tab when the agent has no Slack section Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e6c8ef18a1
commit
394d345e0c
@@ -41,7 +41,8 @@
|
||||
runsAs,
|
||||
anyone,
|
||||
canShare,
|
||||
channels
|
||||
channels,
|
||||
section
|
||||
}: {
|
||||
agentPath: string
|
||||
workspace: string
|
||||
@@ -65,10 +66,12 @@
|
||||
/** Whether the user can change its Slack settings: anyone who can edit it. */
|
||||
canShare: boolean
|
||||
channels: SlackAgentChannel[]
|
||||
/** Shows only this section, without the panel's own tabs: the page offers them as its tabs. */
|
||||
section?: 'api' | 'slack'
|
||||
} = $props()
|
||||
|
||||
let pickedTab = $state('api')
|
||||
let tab = $derived(chat ? pickedTab : 'api')
|
||||
let tab = $derived(section ?? (chat ? pickedTab : 'api'))
|
||||
let handle = $derived(`+${agentPath.split('/').pop()}`)
|
||||
|
||||
let userSettings: UserSettings | undefined = $state(undefined)
|
||||
@@ -100,7 +103,11 @@
|
||||
/>
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
<Tabs bind:selected={() => tab, (t) => (pickedTab = t)} wrapperClass="flex-none w-full">
|
||||
<Tabs
|
||||
bind:selected={() => tab, (t) => (pickedTab = t)}
|
||||
hideTabs={section !== undefined}
|
||||
wrapperClass="flex-none w-full"
|
||||
>
|
||||
<Tab value="api" label="API" />
|
||||
{#if chat}
|
||||
<Tab value="slack" label="Slack" />
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import AgentEditorHost from '$lib/components/flows/content/AgentEditorHost.svelte'
|
||||
import AgentConfigModal from '$lib/components/flows/content/AgentConfigModal.svelte'
|
||||
import RunForm from '$lib/components/RunForm.svelte'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { Alert, Tab, Tabs } from '$lib/components/common'
|
||||
import { keepsManagedMemory } from '$lib/components/flows/agentFormFields'
|
||||
import { agentMenuItems, deleteAgent } from '$lib/components/flows/agentActions'
|
||||
import AgentHistory from '$lib/components/flows/AgentHistory.svelte'
|
||||
@@ -49,6 +49,10 @@
|
||||
let deleteOpen = $state(false)
|
||||
let evalsModal: AgentEvalsModal | undefined = $state(undefined)
|
||||
let clientWidth = $state(window.innerWidth)
|
||||
let pickedMobileTab = $state('agent')
|
||||
// The page is reused across agents, and an agent without managed memory has no Slack tab.
|
||||
let mobileTab = $derived(pickedMobileTab === 'slack' && !chatAvailable ? 'api' : pickedMobileTab)
|
||||
let wide = $derived(clientWidth >= 1024)
|
||||
|
||||
const slack = resource(
|
||||
() => ({ workspace: ws, path }),
|
||||
@@ -62,7 +66,7 @@
|
||||
// while the chat shows, and the chat's composer owns the key then.
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || event.key !== 'Enter' || !(event.ctrlKey || event.metaKey)) return
|
||||
if (!runForm || testPane?.mode === 'chat') return
|
||||
if (!runForm || testPane?.mode === 'chat' || (!wide && mobileTab !== 'agent')) return
|
||||
if (isValid) {
|
||||
event.preventDefault()
|
||||
runForm.run()
|
||||
@@ -207,67 +211,97 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
</DetailPageHeader>
|
||||
<div class="flex-1 min-h-0" bind:clientWidth>
|
||||
<!-- Below `lg` the split would squeeze the chat, so the panel's sections become tabs of the
|
||||
page, as on a script's or a flow's page. They cover the agent rather than replacing it:
|
||||
unmounting the host destroys the chat and any turn in flight, so the agent pane keeps one
|
||||
parent at every width. -->
|
||||
{#if !wide && ws && config}
|
||||
<Tabs
|
||||
bind:selected={() => mobileTab, (t) => (pickedMobileTab = t)}
|
||||
wrapperClass="flex-none no-scrollbar"
|
||||
>
|
||||
<Tab value="agent" label={testPane?.mode === 'chat' ? 'Chat' : 'Run form'} />
|
||||
<Tab value="api" label="API" />
|
||||
{#if chatAvailable}
|
||||
<Tab value="slack" label="Slack" />
|
||||
{/if}
|
||||
</Tabs>
|
||||
{/if}
|
||||
<div class="relative flex-1 min-h-0" bind:clientWidth>
|
||||
<Splitpanes>
|
||||
<Pane size={65} minSize={50}>
|
||||
{#key `${ws}:${path}:${reloaded}`}
|
||||
<AgentEditorHost
|
||||
bind:this={host}
|
||||
{path}
|
||||
workspace={ws}
|
||||
enableAi={$copilotInfo.enabled}
|
||||
view
|
||||
onOpenConfig={() => configModal?.open()}
|
||||
>
|
||||
{#snippet viewForm({ schema, run, loading, actions })}
|
||||
{#if config && !chatAvailable}
|
||||
<Alert type="info" size="xs" title="Managed memory is off" class="mb-4">
|
||||
Each run is answered on its own inputs, so this agent has no chat. That suits
|
||||
reusing it as a step in a flow.{canEdit
|
||||
? ' Turn on managed memory in its configuration to chat with it.'
|
||||
: ''}
|
||||
</Alert>
|
||||
{/if}
|
||||
<RunForm
|
||||
runnable={{ schema, path }}
|
||||
runAction={run}
|
||||
schedulable={false}
|
||||
detailed={false}
|
||||
autofocus
|
||||
{loading}
|
||||
{actions}
|
||||
bind:isValid
|
||||
bind:this={runForm}
|
||||
/>
|
||||
{/snippet}
|
||||
</AgentEditorHost>
|
||||
{/key}
|
||||
<Pane size={wide ? 65 : 100} minSize={50}>
|
||||
<div class="h-full" inert={!wide && mobileTab !== 'agent'}>
|
||||
{@render agentPane()}
|
||||
</div>
|
||||
</Pane>
|
||||
<!-- ponytail: no panel below `lg`, where the split would squeeze the chat; give it a tab
|
||||
there, as a script's page does, once people use it from small screens. -->
|
||||
{#if ws && config && clientWidth >= 1024}
|
||||
{#if wide && ws && config}
|
||||
<Pane size={35} minSize={15}>
|
||||
<AgentUsePanel
|
||||
agentPath={path}
|
||||
workspace={ws}
|
||||
isAdmin={!!($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
chat={chatAvailable}
|
||||
slackLoading={slack.loading && !slack.current}
|
||||
slackTeamName={slack.current?.slack_team_name}
|
||||
botName={slack.current?.bot_name}
|
||||
missingScopes={slack.current?.missing_scopes ?? []}
|
||||
signingSecretSet={slack.current?.signing_secret_set ?? true}
|
||||
runsAs={slack.current?.runs_as}
|
||||
anyone={slack.current?.anyone ?? false}
|
||||
canShare={slack.current?.can_share ?? false}
|
||||
channels={slack.current?.channels ?? []}
|
||||
/>
|
||||
{@render usePane(ws)}
|
||||
</Pane>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
{#if !wide && ws && config}
|
||||
<div class={['absolute inset-0 z-10 bg-surface', mobileTab === 'agent' && 'hidden']}>
|
||||
{@render usePane(ws, mobileTab === 'slack' ? 'slack' : 'api')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{#snippet agentPane()}
|
||||
{#key `${ws}:${path}:${reloaded}`}
|
||||
<AgentEditorHost
|
||||
bind:this={host}
|
||||
{path}
|
||||
workspace={ws}
|
||||
enableAi={$copilotInfo.enabled}
|
||||
view
|
||||
onOpenConfig={() => configModal?.open()}
|
||||
>
|
||||
{#snippet viewForm({ schema, run, loading, actions })}
|
||||
{#if config && !chatAvailable}
|
||||
<Alert type="info" size="xs" title="Managed memory is off" class="mb-4">
|
||||
Each run is answered on its own inputs, so this agent has no chat. That suits reusing it
|
||||
as a step in a flow.{canEdit
|
||||
? ' Turn on managed memory in its configuration to chat with it.'
|
||||
: ''}
|
||||
</Alert>
|
||||
{/if}
|
||||
<RunForm
|
||||
runnable={{ schema, path }}
|
||||
runAction={run}
|
||||
schedulable={false}
|
||||
detailed={false}
|
||||
autofocus
|
||||
{loading}
|
||||
{actions}
|
||||
bind:isValid
|
||||
bind:this={runForm}
|
||||
/>
|
||||
{/snippet}
|
||||
</AgentEditorHost>
|
||||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet usePane(ws: string, section?: 'api' | 'slack')}
|
||||
<AgentUsePanel
|
||||
agentPath={path}
|
||||
workspace={ws}
|
||||
isAdmin={!!($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
chat={chatAvailable}
|
||||
slackLoading={slack.loading && !slack.current}
|
||||
slackTeamName={slack.current?.slack_team_name}
|
||||
botName={slack.current?.bot_name}
|
||||
missingScopes={slack.current?.missing_scopes ?? []}
|
||||
signingSecretSet={slack.current?.signing_secret_set ?? true}
|
||||
runsAs={slack.current?.runs_as}
|
||||
anyone={slack.current?.anyone ?? false}
|
||||
canShare={slack.current?.can_share ?? false}
|
||||
channels={slack.current?.channels ?? []}
|
||||
{section}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
{#if ws}
|
||||
<AgentEvalsModal bind:this={evalsModal} agentPath={path} workspace={ws} />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user