mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: move the agent meta line and system prompt below the conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ea75ded53b
commit
3009339e2a
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Bot } from 'lucide-svelte'
|
||||
import type { Snippet } from 'svelte'
|
||||
import { Badge } from '$lib/components/common'
|
||||
import GfmMarkdown from './GfmMarkdown.svelte'
|
||||
@@ -27,11 +26,26 @@
|
||||
let textOutput = $derived(typeof result.output === 'string' ? result.output : undefined)
|
||||
</script>
|
||||
|
||||
<!-- pt-2 clears the toggle group and control bar the result viewer puts directly
|
||||
above this, which otherwise sit on the meta line. -->
|
||||
<div class="flex flex-col gap-2 w-full pt-2">
|
||||
<div class="flex flex-col gap-2 w-full pt-1">
|
||||
{#if view === 'transcript'}
|
||||
<AgentTranscript messages={result.messages} {workspaceId} />
|
||||
{:else if textOutput !== undefined}
|
||||
{#if textOutput === ''}
|
||||
<span class="text-tertiary text-xs">The agent returned no answer</span>
|
||||
{:else}
|
||||
<!-- A model writes this answer, and what it writes is steerable by whatever
|
||||
reached its context — a user message, a tool's output. So it is
|
||||
untrusted input and goes through the shared sanitizing chain, which is
|
||||
also what makes it inert on the public replay page. -->
|
||||
<GfmMarkdown md={textOutput} noPadding />
|
||||
{/if}
|
||||
{:else}
|
||||
{@render structuredOutput(result.output)}
|
||||
{/if}
|
||||
|
||||
<!-- What the run cost sits under what it produced: it is the footnote to the
|
||||
answer, not the heading above it. -->
|
||||
<div class="flex items-center gap-2 flex-wrap text-xs">
|
||||
<Bot size={14} class="text-tertiary shrink-0" />
|
||||
{#if summary.toolCalls > 0}
|
||||
<Badge color="blue">
|
||||
{summary.toolCalls}
|
||||
@@ -51,20 +65,4 @@
|
||||
<Badge color="gray">{formatTokenCount(summary.cachedTokens)} cached</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if view === 'transcript'}
|
||||
<AgentTranscript messages={result.messages} {workspaceId} />
|
||||
{:else if textOutput !== undefined}
|
||||
{#if textOutput === ''}
|
||||
<span class="text-tertiary text-xs">The agent returned no answer</span>
|
||||
{:else}
|
||||
<!-- A model writes this answer, and what it writes is steerable by whatever
|
||||
reached its context — a user message, a tool's output. So it is
|
||||
untrusted input and goes through the shared sanitizing chain, which is
|
||||
also what makes it inert on the public replay page. -->
|
||||
<GfmMarkdown md={textOutput} noPadding />
|
||||
{/if}
|
||||
{:else}
|
||||
{@render structuredOutput(result.output)}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -19,8 +19,15 @@
|
||||
|
||||
let { messages, workspaceId }: Props = $props()
|
||||
|
||||
let entries = $derived(buildTranscript(messages))
|
||||
const all = $derived(buildTranscript(messages))
|
||||
// The prompt is the run's configuration, not its opening line: it is the same
|
||||
// on every run of the step, and reading the conversation from it buries what
|
||||
// the user actually asked. Kept reachable, at the end.
|
||||
const entries = $derived(all.filter((entry) => entry.kind !== 'system'))
|
||||
const systemPrompt = $derived(all.find((entry) => entry.kind === 'system'))
|
||||
|
||||
// Out of the entry index space, since the prompt is rendered outside the list.
|
||||
const SYSTEM_PROMPT_KEY = -1
|
||||
let expanded = new SvelteSet<number>()
|
||||
// A tool's own job holds what the envelope does not: its logs, how long it
|
||||
// took, and whether it succeeded. Fetched when a row is opened rather than
|
||||
@@ -89,16 +96,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if entry.kind === 'system'}
|
||||
<div class="px-2 mb-1">
|
||||
<ChatCollapsibleCard
|
||||
label="System prompt"
|
||||
expanded={expanded.has(index)}
|
||||
onToggle={() => toggle(index, entry)}
|
||||
>
|
||||
<div class="whitespace-pre-wrap text-2xs text-primary">{entry.content}</div>
|
||||
</ChatCollapsibleCard>
|
||||
</div>
|
||||
{:else if entry.kind === 'search'}
|
||||
<div class="px-2 mb-1">
|
||||
<ChatCollapsibleCard
|
||||
@@ -153,4 +150,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if systemPrompt}
|
||||
<div class="px-2 pt-2">
|
||||
<ChatCollapsibleCard
|
||||
label="System prompt"
|
||||
expanded={expanded.has(SYSTEM_PROMPT_KEY)}
|
||||
onToggle={() => toggle(SYSTEM_PROMPT_KEY, systemPrompt)}
|
||||
>
|
||||
<div class="whitespace-pre-wrap text-2xs text-primary">{systemPrompt.content}</div>
|
||||
</ChatCollapsibleCard>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import LogViewer from './LogViewer.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import AgentTranscript from './AgentTranscript.svelte'
|
||||
import { parseAgentResult } from './aiAgentResult'
|
||||
|
||||
interface Props {
|
||||
waitingForExecutor?: boolean
|
||||
@@ -36,10 +34,6 @@
|
||||
downloadLogs = true,
|
||||
tagLabel = undefined
|
||||
}: Props = $props()
|
||||
|
||||
// An agent step's own logs are worker chatter; what happened is its conversation.
|
||||
// Derived from the result rather than passed in, so every caller gets it.
|
||||
let agentResult = $derived(parseAgentResult(result))
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -65,22 +59,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex flex-col gap-1">
|
||||
<span class="text-emphasis text-xs font-semibold">{agentResult ? 'Transcript' : 'Logs'}</span>
|
||||
{#if agentResult}
|
||||
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-auto p-2">
|
||||
<AgentTranscript messages={agentResult.messages} {workspaceId} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-hidden">
|
||||
<LogViewer
|
||||
{tagLabel}
|
||||
download={downloadLogs}
|
||||
content={logs ?? ''}
|
||||
{jobId}
|
||||
isLoading={waitingForExecutor}
|
||||
{tag}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<span class="text-emphasis text-xs font-semibold">Logs</span>
|
||||
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-hidden">
|
||||
<LogViewer
|
||||
{tagLabel}
|
||||
download={downloadLogs}
|
||||
content={logs ?? ''}
|
||||
{jobId}
|
||||
isLoading={waitingForExecutor}
|
||||
{tag}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
import type { FlowEditorContext, OutputViewerJob } from './flows/types'
|
||||
import { getContext } from 'svelte'
|
||||
import { getStringError } from './copilot/chat/utils'
|
||||
import AgentTranscript from './AgentTranscript.svelte'
|
||||
import { parseAgentResult } from './aiAgentResult'
|
||||
|
||||
interface Props {
|
||||
lang: Script['language']
|
||||
@@ -57,10 +55,6 @@
|
||||
)
|
||||
const logJob = $derived(testJob ?? selectedJob)
|
||||
const preview = $derived.by(() => outputPickerInner?.getPreview?.())
|
||||
// The trace of an agent step is its conversation, which its own result carries.
|
||||
const agentResult = $derived(
|
||||
logJob?.type === 'CompletedJob' ? parseAgentResult(logJob.result) : undefined
|
||||
)
|
||||
</script>
|
||||
|
||||
<Splitpanes horizontal>
|
||||
@@ -105,10 +99,6 @@
|
||||
customEmptyMessage="Using pinned data"
|
||||
{tagLabel}
|
||||
/>
|
||||
{:else if agentResult}
|
||||
<div class="h-full overflow-auto p-2">
|
||||
<AgentTranscript messages={agentResult.messages} workspaceId={logJob?.workspace_id} />
|
||||
</div>
|
||||
{:else}
|
||||
<LogViewer
|
||||
small
|
||||
|
||||
Reference in New Issue
Block a user