2 import { XCircle } from '@lucide/svelte';
3 import { MAX_HEIGHT_CODE_BLOCK, RESULT_STAT_SEPARATOR } from '$lib/constants';
4 import { computeLineDiff, prefixFor, type AgenticSection } from '$lib/utils';
5 import { parseEditFileMeta } from './parsers/edit-file';
6 import ToolCallBlock from './ToolCallBlock.svelte';
9 section: AgenticSection;
12 onToggle?: () => void;
15 let { section, open, isStreaming, onToggle }: Props = $props();
17 const editFileMeta = $derived(parseEditFileMeta(section));
18 const editDiffs = $derived(
19 (editFileMeta?.edits ?? []).map((edit) => computeLineDiff(edit.oldText, edit.newText))
23 <ToolCallBlock {section} {open} {isStreaming} meta={editFileMeta} {onToggle}>
24 {#snippet titleSnippet()}
25 <span class="text-muted-foreground">Edit file </span>
26 <span class="font-mono">{editFileMeta?.filePath}</span>
27 {#if editFileMeta?.errorMessage}
28 <span class="ml-1 text-xs italic text-muted-foreground/70">(failed)</span>
32 {#snippet children(meta, _ctx)}
33 {#if meta?.errorMessage}
35 class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
37 <XCircle class="mt-0.5 h-3 w-3 shrink-0" />
38 <span>{meta.errorMessage}</span>
40 {:else if meta && meta.edits.length > 0}
41 {#each editDiffs as diffLines, ei (ei)}
42 <div class={ei === 0 ? '' : 'mt-3'}>
43 <div class="mb-1.5 text-xs text-muted-foreground/70 italic">
44 Edit {ei + 1} of {meta.edits.length}
46 <div class="diff-block" style:max-height={MAX_HEIGHT_CODE_BLOCK}>
47 <div class="diff-pre">
48 {#each diffLines as line, li (li)}
49 <div class="diff-line diff-{line.kind}">
50 <span class="diff-old-num">{line.oldLine ?? ''}</span>
51 <span class="diff-marker">{prefixFor(line.kind)}</span>
52 <span class="diff-new-num">{line.newLine ?? ''}</span>
53 <span class="diff-text">{line.text || ' '}</span>
60 <div class="mt-1.5 text-xs text-muted-foreground/70 italic">
61 {#if meta.resultMessage}
62 {meta.resultMessage}{meta.editsApplied != null ? RESULT_STAT_SEPARATOR : ''}{/if}
63 {#if meta.editsApplied != null}
64 <span class="font-mono">{meta.editsApplied}</span>
65 {meta.editsApplied === 1 ? 'edit' : 'edits'} applied
69 <div class="rounded bg-muted/20 p-2 text-xs text-muted-foreground/70 italic">No edits</div>
77 border-radius: 0.75rem;
79 border-color: color-mix(in oklch, var(--border) 30%, transparent);
80 background: var(--code-background);
81 box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
84 :global(.dark) .diff-block {
85 border-color: color-mix(in oklch, var(--border) 20%, transparent);
88 /* Each row is a 4-column grid: old-line#, marker, new-line#, text.
89 * The gutters stay fixed-width so the text column lines up unversally. */
92 grid-template-columns: 3.25rem 1.5rem 3.25rem 1fr;
93 font-family: var(--font-mono);
102 padding-right: 0.5rem;
104 color: color-mix(in oklch, var(--muted-foreground) 70%, transparent);
105 font-variant-numeric: tabular-nums;
110 color: color-mix(in oklch, var(--muted-foreground) 70%, transparent);
114 .diff-line.diff-add {
115 background-color: #f0fff4;
118 .diff-line.diff-add .diff-new-num,
119 .diff-line.diff-add .diff-marker {
123 .diff-line.diff-remove {
124 background-color: #ffeef0;
127 .diff-line.diff-remove .diff-old-num,
128 .diff-line.diff-remove .diff-marker {
132 .diff-line.diff-add .diff-old-num,
133 .diff-line.diff-remove .diff-new-num {
134 /* Empty gutter columns for add/remove rows mirror git unification
135 * (added lines don't have an old number, removed lines don't have a
136 * new number). Keep them visible so columns stay aligned across
142 padding-left: 0.4rem;
143 padding-right: 0.5rem;
149 :global(.dark) .diff-line.diff-add {
150 background-color: #033a16;
153 :global(.dark) .diff-line.diff-add .diff-new-num,
154 :global(.dark) .diff-line.diff-add .diff-marker {
157 :global(.dark) .diff-line.diff-remove {
158 background-color: #67060c;
161 :global(.dark) .diff-line.diff-remove .diff-old-num,
162 :global(.dark) .diff-line.diff-remove .diff-marker {