chat-ui-agent-demo / src /lib /components /agent /AgentDiff.svelte
Mike0021's picture
Deploy verified chat and coding agent UX
ba0ce97 verified
Raw History Blame Contribute Delete
3.31 kB
<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>