File size: 3,307 Bytes
ba0ce97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<script lang="ts">
	import { untrack } from "svelte";
	import type { MessageAgentDiffUpdate } from "$lib/types/MessageUpdate";
	import CarbonChevronRight from "~icons/carbon/chevron-right";
	import CarbonCopy from "~icons/carbon/copy";
	import CarbonCheckmark from "~icons/carbon/checkmark";

	interface Props {
		update: MessageAgentDiffUpdate;
		openByDefault?: boolean;
	}

	let { update, openByDefault = true }: Props = $props();
	let open = $state(untrack(() => openByDefault));
	let copied = $state(false);
	let lines = $derived(update.patch.split("\n"));

	async function copyPatch() {
		try {
			await navigator.clipboard.writeText(update.patch);
			copied = true;
			setTimeout(() => (copied = false), 1200);
		} catch {
			copied = false;
		}
	}
</script>

<section
	class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900/50"
>
	<header class="flex min-h-11 items-center gap-1 px-2 sm:min-h-10">
		<button
			type="button"
			class="flex min-h-11 min-w-0 flex-1 items-center gap-2 rounded-lg px-1 text-left outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 sm:min-h-9"
			aria-expanded={open}
			onclick={() => (open = !open)}
		>
			<CarbonChevronRight
				class="size-4 shrink-0 text-gray-400 transition-transform {open ? 'rotate-90' : ''}"
			/>
			<span class="truncate font-mono text-xs font-semibold text-gray-800 dark:text-gray-100">
				{update.path}
			</span>
			<span
				class="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-semibold tracking-wide text-gray-500 uppercase dark:bg-gray-800 dark:text-gray-400"
			>
				{update.operation}
			</span>
			{#if update.additions !== undefined}
				<span class="shrink-0 text-xs text-emerald-600 dark:text-emerald-400">
					+{update.additions}
				</span>
			{/if}
			{#if update.deletions !== undefined}
				<span class="shrink-0 text-xs text-red-600 dark:text-red-400">-{update.deletions}</span>
			{/if}
		</button>
		<button
			type="button"
			class="grid size-9 shrink-0 place-items-center rounded-lg text-gray-500 outline-hidden hover:bg-gray-100 hover:text-gray-800 focus-visible:ring-2 focus-visible:ring-blue-500 dark:hover:bg-gray-800 dark:hover:text-gray-100"
			aria-label={copied ? "Patch copied" : `Copy patch for ${update.path}`}
			onclick={copyPatch}
		>
			{#if copied}<CarbonCheckmark class="size-4" />{:else}<CarbonCopy class="size-4" />{/if}
		</button>
	</header>
	{#if open}
		<pre
			aria-label="Unified diff for {update.path}"
			class="scrollbar-custom max-h-80 overflow-auto border-t border-gray-200 bg-gray-50 py-2 font-mono text-[11px] leading-relaxed dark:border-gray-700 dark:bg-gray-950/70">{#each lines as line, index (`${index}-${line}`)}<span
					class="block min-w-max px-3 {line.startsWith('+') && !line.startsWith('+++')
						? 'bg-emerald-500/10 text-emerald-800 dark:text-emerald-300'
						: line.startsWith('-') && !line.startsWith('---')
							? 'bg-red-500/10 text-red-800 dark:text-red-300'
							: line.startsWith('@@')
								? 'bg-blue-500/10 text-blue-700 dark:text-blue-300'
								: 'text-gray-600 dark:text-gray-400'}">{line || " "}</span
				>{/each}{#if update.truncated}<span
					class="block px-3 py-1 text-amber-700 dark:text-amber-300">… diff truncated</span
				>{/if}</pre>
	{/if}
</section>