mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-03 08:02:19 +00:00
fix: space chat rows evenly after thinking and answers (#11315)
* fix: space chat rows evenly after thinking and answers Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: keep a flow step's answer actions and space its step label Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: pick the answer that shows actions from later answers, not the next row Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: group answer actions by the step run's job, not its label Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: state the per-run action row rule in AssistantMessage comments Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
62d0088de5
commit
0e53d53b07
@@ -271,6 +271,27 @@
|
||||
// Shared with the agent run viewer, which needs the same programmatic-scroll
|
||||
// guard for the same reason.
|
||||
const sticker = createBottomSticker()
|
||||
|
||||
// Per message: whether it is the last answer of its turn — per flow step run, since each
|
||||
// run's last answer carries the only link to that run. Keyed on the answer's own job (a step
|
||||
// label repeats when a step runs in a loop), and on answers rather than the next row: a
|
||||
// flow's tool rows have their own job, and a stopped turn can end on a tool row.
|
||||
const showsAnswerActions = $derived.by(() => {
|
||||
const shows: boolean[] = new Array(messages.length).fill(false)
|
||||
const answeredLater = new Set<string | undefined>()
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i]
|
||||
if (message.role === 'user' || message.role === 'summary') {
|
||||
answeredLater.clear()
|
||||
} else if (message.role === 'assistant' && message.content) {
|
||||
const run = message.jobId ?? message.stepName
|
||||
shows[i] = !answeredLater.has(run)
|
||||
answeredLater.add(run)
|
||||
}
|
||||
}
|
||||
return shows
|
||||
})
|
||||
|
||||
function scrollDown() {
|
||||
sticker.scrollToEnd(scrollElement)
|
||||
}
|
||||
@@ -831,6 +852,7 @@ the panel, or the Escape-to-stop focus check would wrongly reject them. -->
|
||||
{availableContext}
|
||||
bind:editingMessageIndex
|
||||
isLast={messageIndex === messages.length - 1}
|
||||
showAnswerActions={showsAnswerActions[messageIndex]}
|
||||
/>
|
||||
{/each}
|
||||
{#if freeTierExhausted}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
messageIndex: number
|
||||
editingMessageIndex: number | null
|
||||
isLast?: boolean
|
||||
showAnswerActions?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -53,7 +54,8 @@
|
||||
messageIndex,
|
||||
availableContext,
|
||||
editingMessageIndex = $bindable(null),
|
||||
isLast = false
|
||||
isLast = false,
|
||||
showAnswerActions = true
|
||||
}: Props = $props()
|
||||
|
||||
// The edit box edits a copy of THIS message's original context, not the live
|
||||
@@ -82,8 +84,8 @@
|
||||
{:else}
|
||||
<div
|
||||
class={twMerge(
|
||||
'mb-2 min-w-0',
|
||||
message.role === 'tool' && 'mb-1',
|
||||
'mb-1 min-w-0',
|
||||
message.role === 'user' && 'mb-2',
|
||||
message.role === 'user' && messageIndex > 0 && 'mt-4 mb-6',
|
||||
isLast && '!mb-12',
|
||||
message.role !== 'user' || !chatHost.supportsMessageEditing
|
||||
@@ -138,10 +140,22 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class={twMerge('text-sm py-1 px-2', message.role === 'tool' && 'text-primary py-0')}>
|
||||
<!-- Assistant and tool rows carry their own 4px above and below their text, so every
|
||||
pair of consecutive rows sits the same distance apart. -->
|
||||
<div
|
||||
class={twMerge(
|
||||
'text-sm px-2',
|
||||
message.role === 'user' && 'py-1',
|
||||
message.role === 'tool' && 'text-primary'
|
||||
)}
|
||||
>
|
||||
{#if message.role === 'assistant'}
|
||||
<div class="px-[1px] group/answer"
|
||||
><AssistantMessage {message} workspace={messageWorkspace} /></div
|
||||
><AssistantMessage
|
||||
{message}
|
||||
workspace={messageWorkspace}
|
||||
showActions={showAnswerActions}
|
||||
/></div
|
||||
>
|
||||
{:else if message.role === 'tool'}
|
||||
<div class="px-[1px]"
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
// Workspace the message's paths are resolved against: the one the chat
|
||||
// operates on, which is not always the one being navigated.
|
||||
workspace: string | undefined
|
||||
// Only the last answer of a run (a copilot turn, or a flow step's run) gets the copy /
|
||||
// timestamp / run row: it stays in the layout while invisible, so on an answer between
|
||||
// tool calls it would double the gap below it.
|
||||
showActions?: boolean
|
||||
}
|
||||
|
||||
let { message, workspace }: Props = $props()
|
||||
let { message, workspace, showActions = true }: Props = $props()
|
||||
|
||||
// The run this answer came out of. Only a flow chat has one — a copilot turn runs in
|
||||
// the browser — so the job link is absent rather than empty elsewhere.
|
||||
@@ -137,7 +141,7 @@
|
||||
the padding the message column already carries. -->
|
||||
{#if stepName}
|
||||
<div
|
||||
class="flex items-center gap-2 -ml-6 mb-1 text-2xs text-tertiary"
|
||||
class="flex items-center gap-2 -ml-6 pt-1 text-2xs text-tertiary"
|
||||
title="Answered by {stepName}"
|
||||
>
|
||||
<Bot size={16} class="shrink-0" />
|
||||
@@ -151,7 +155,7 @@
|
||||
expanded={reasoningExpanded}
|
||||
onToggle={() => (reasoningToggled = !reasoningExpanded)}
|
||||
shimmer={reasoningStreaming}
|
||||
class="mb-2"
|
||||
class={message.content ? 'mb-1' : ''}
|
||||
labelClass="truncate"
|
||||
contentClass="font-main text-secondary {markdownProse.xs}"
|
||||
>
|
||||
@@ -160,16 +164,18 @@
|
||||
{/if}
|
||||
|
||||
{#if s3Object}
|
||||
<DisplayResult result={s3Object} workspaceId={workspace} noControls={true} />
|
||||
<div class="py-1">
|
||||
<DisplayResult result={s3Object} workspaceId={workspace} noControls={true} />
|
||||
</div>
|
||||
{:else if message.content}
|
||||
<div class="w-full space-y-2 {markdownProse.sm}">
|
||||
<div class="w-full space-y-2 py-1 {markdownProse.sm}">
|
||||
<Markdown md={message.content} {plugins} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if message.content}
|
||||
{#if message.content && showActions}
|
||||
<!-- Kept in flow while invisible, so revealing it on hover does not nudge the message
|
||||
below. A thinking-only row has no answer to copy, and the next row links its run. -->
|
||||
below. A thinking-only row has no answer to copy, and its run's last answer links the run. -->
|
||||
<div
|
||||
class="flex items-center gap-2 text-2xs text-tertiary opacity-0 transition-opacity duration-150 group-hover/answer:opacity-100 focus-within:opacity-100"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user