Files
windmill/frontend/src/lib/components/copilot/chat/ToolDiffCard.svelte
T
Ruben FiszelandClaude Opus 5.5 468cf1d994 feat: read and change workspace settings from ai sessions and mcp (#11741)
* feat: read and change workspace settings from ai sessions and mcp

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: cover wmill workspace settings commands in the settings guidance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: say unknown settings keys are warned and ee-only ones refused

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: cover datatable_permissions and protection_rules in the settings guidance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:06:26 +00:00

121 lines
4.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import type { ToolDisplayMessage } from './shared'
import ChatCollapsibleCard from './ChatCollapsibleCard.svelte'
import ToolCodeDiffView from './ToolCodeDiffView.svelte'
import ToolPreviewCard from './ToolPreviewCard.svelte'
import ToolConfirmationFooter from './ToolConfirmationFooter.svelte'
import { Check, XCircle } from 'lucide-svelte'
import {
diffIsWholeFile,
diffLineCounts,
toolCodeDiff,
toolDiffLineCounts,
toolDiffLines
} from './toolCodeDiff'
interface Props {
message: ToolDisplayMessage
hidePreviewChip?: boolean
}
let { message, hidePreviewChip = false }: Props = $props()
const diff = $derived(toolCodeDiff(message))
const isStreaming = $derived(Boolean(message.isStreamingArguments))
const hasDiff = $derived(Boolean(diff && (diff.before !== '' || diff.after !== '')))
const awaitingConfirmation = $derived(Boolean(message.isLoading && message.needsConfirmation))
// The card body is a bordered box: opened with nothing in it, it draws an empty line.
const hasBody = $derived(hasDiff || Boolean(message.error) || awaitingConfirmation)
// Keyed by call id: a bare flag would carry the expansion onto the next message that
// reuses this instance. An active or failed call opens by itself, and remains open
// when it settles so the new diff does not disappear under the user. A call whose diff is a
// whole file (`write_script`, full-code `edit_code`, raw app writes and deletes) opens only
// on failure. The key also changes on failure, so a collapse made while running cannot hide
// the error.
const toggleKey = $derived(`${message.tool_call_id}:${message.error ? 'error' : ''}`)
let toggled = $state<{ id: string; open: boolean } | undefined>(undefined)
const opensByDefault = $derived(
Boolean(
message.error ||
(hasDiff &&
!diffIsWholeFile(message) &&
(message.isLoading || message.isQueued || message.isStreamingArguments))
)
)
$effect(() => {
if (opensByDefault && toggled?.id !== toggleKey) {
toggled = { id: toggleKey, open: true }
}
})
// The Apply/Reject footer is inside the body, so a pending confirmation keeps it open.
const expanded = $derived(
awaitingConfirmation ||
(hasBody && (toggled?.id === toggleKey ? toggled.open : opensByDefault))
)
const lines = $derived(diff && expanded ? toolDiffLines(diff, isStreaming) : undefined)
const counts = $derived(
diff && expanded
? isStreaming
? diffLineCounts(diff, true)
: toolDiffLineCounts(lines ?? [])
: undefined
)
const isRunning = $derived(Boolean(message.isLoading && !message.needsConfirmation))
const showPreviewChip = $derived(
Boolean(!hidePreviewChip && message.previewCard && !message.isLoading && !message.error)
)
</script>
{#snippet headerRight()}
<div class="flex items-center gap-2 shrink-0">
{#if counts && (counts.added > 0 || counts.removed > 0)}
<span class="font-mono text-2xs">
<span class="text-green-600 dark:text-green-400">+{counts.added}</span>
<span class="text-red-600 dark:text-red-400">−{counts.removed}</span>
</span>
{/if}
{#if showPreviewChip && message.previewCard}
<ToolPreviewCard card={message.previewCard} />
{/if}
</div>
{/snippet}
<ChatCollapsibleCard
label={message.content}
{expanded}
onToggle={() => (toggled = { id: toggleKey, open: !expanded })}
toggleable={hasBody && !awaitingConfirmation}
shimmer={isRunning}
class={message.isQueued && !message.error
? 'opacity-60 hover:opacity-100 transition-opacity'
: ''}
labelClass="truncate"
contentClass="p-0 overflow-hidden space-y-0"
{headerRight}
>
{#if diff && hasDiff && expanded}
<ToolCodeDiffView {diff} diffLines={lines} streaming={isStreaming} />
{/if}
{#if awaitingConfirmation}
<ToolConfirmationFooter
class="px-3 py-2 border-t border-border-light"
toolCallId={message.tool_call_id}
rejectIcon={XCircle}
rejectDestructive
confirmLabel="Apply"
confirmIcon={Check}
/>
{/if}
{#if message.error}
<div
class="px-3 py-2 text-2xs text-red-600 dark:text-red-400 whitespace-pre-wrap break-words {hasDiff
? 'border-t border-border-light'
: ''}"
>
{message.error}
</div>
{/if}
</ChatCollapsibleCard>