From f4c3b5b8263340cd1e12ea670702cefcaae80021 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Sat, 11 Jul 2026 13:49:44 +0100 Subject: [PATCH] feat: add file change summary cards to assistant messages Display artifact changes in chat with visual indicators for create/modify/delete actions, including file counts, color-coded status badges, and scrollable file list --- Components/ChatArea.svelte | 219 +++++++++++++++++- Components/ChatWindow.svelte | 8 +- Conversations/Artifact.ts | 41 +++- Enums/ArtifactAction.ts | 28 +++ Enums/Copy.ts | 5 + Services/AIServices/AIToolService.ts | 57 +++-- Services/ConversationFileSystemService.ts | 2 + Services/VaultCacheService.ts | 9 +- __tests__/Conversations/Artifact.test.ts | 49 +++- .../ConversationFileSystemService.test.ts | 12 +- 10 files changed, 389 insertions(+), 41 deletions(-) create mode 100644 Enums/ArtifactAction.ts diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index 752320c..d2df80f 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -12,6 +12,8 @@ import { setIcon } from "obsidian"; import { fade } from "svelte/transition"; import GraphAnimation from "./GraphAnimation.svelte"; + import { ArtifactAction, artifactActionToCopy } from "Enums/ArtifactAction"; + import { basename } from "path-browserify"; export let messages: ConversationContent[] = []; export let currentThought: string | null = null; @@ -109,6 +111,14 @@ }; } + function tallyArtifactsByAction(artifacts: { action: ArtifactAction }[]): Partial> { + const tally: Partial> = {}; + for (const artifact of artifacts) { + tally[artifact.action] = (tally[artifact.action] ?? 0) + 1; + } + return tally; + } + $: if (scrollToBottomButton) { setIcon(scrollToBottomButton, "arrow-down"); } @@ -158,6 +168,35 @@
+ {#if message.artifacts.length > 0} + {@const artifactTally = tallyArtifactsByAction(message.artifacts)} +
+ {message.artifacts.length} FILES CHANGED +
+ {#each Object.values(ArtifactAction) as action} + {#if artifactTally[action]} +
+ + {artifactTally[action]} +
+ {/if} + {/each} +
+
+ {#each message.artifacts as artifact} +
+ +
+ {basename(artifact.filePath)} + {artifactActionToCopy(artifact.action)} +
+ {/each} +
+
+ {/if} {/if} @@ -270,6 +309,7 @@ } .message-group.latest { + margin-top: var(--size-4-2); min-height: 100%; } @@ -316,7 +356,7 @@ .message-bubble.assistant { word-wrap: break-word; - max-width: 100%; + width: 100%; } .message-text-user-container { @@ -333,7 +373,182 @@ .message-text-user-container { padding-bottom: var(--size-4-2); } - + + .artifacts-container { + display: grid; + grid-template-rows: auto auto; + grid-template-columns: auto auto; + width: 100%; + margin-top: var(--size-4-3); + gap: var(--size-4-4); + } + + .artifacts-container-title { + grid-row: 1; + grid-column: 1; + font-size: var(--font-smallest); + } + + .artifacts-tally { + grid-row: 1; + grid-column: 2; + display: flex; + flex-direction: row; + } + + .artifact-tally-container { + display: flex; + flex-direction: row; + margin-left: auto; + align-items: center; + } + + .artifact-tally-ellipse { + flex-shrink: 0; + width: 6px; + height: 6px; + border-radius: 50%; + margin-left: var(--size-4-2); + } + + .artifact-create { + background: var(--color-green); + } + + .artifact-modify { + background: var(--color-blue); + } + + .artifact-delete { + background: var(--color-red); + } + + .artifact-tally-count { + font-size: var(--font-smallest); + margin-left: var(--size-4-1); + } + + .artifacts-list-container { + grid-row: 2; + grid-column: 1 / 3; + display: flex; + flex-direction: column; + overflow: scroll; + width: 100%; + max-height: 200px; + gap: var(--size-4-1); + } + + .artifacts-list-container::-webkit-scrollbar { + display: none; + } + + .artifact-card { + display: grid; + grid-template-rows: auto; + grid-template-columns: auto auto 1fr auto; + gap: var(--size-4-3); + align-items: center; + height: 30px; + flex-shrink: 0; + background-color: var(--background-secondary-alt); + border-radius: var(--size-4-2); + padding: 0 var(--size-4-1) 0 var(--size-4-2); + } + + .artifact-ellipse { + grid-row: 1; + grid-column: 1; + flex-shrink: 0; + width: 10px; + height: 10px; + border-radius: 50%; + align-self: center; + } + + .artifact-ellipse-create { + background: var(--color-green); + } + + .artifact-ellipse-modify { + background: var(--color-blue); + } + + .artifact-ellipse-delete { + background: var(--color-red); + } + + .artifact-icon { + grid-row: 1; + grid-column: 2; + display: flex; + align-items: center; + justify-content: center; + } + + .artifact-name { + grid-row: 1; + grid-column: 3; + display: flex; + align-items: center; + justify-content: start; + font-size: var(--font-smaller); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .artifact-action { + grid-row: 1; + grid-column: 4; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-smallest); + font-weight: var(--font-semibold); + border-radius: var(--size-4-2); + padding: var(--size-2-1) var(--size-4-2); + } + + .artifact-action-create { + background-color: color-mix( + in srgb, + var(--color-green) 25%, + black 20% + ); + color: color-mix( + in srgb, + var(--color-green) 100%, + white 10% + ); + } + + .artifact-action-modify { + background-color: color-mix( + in srgb, + var(--color-blue) 25%, + black 20% + ); + color: color-mix( + in srgb, + var(--color-blue) 100%, + white 10% + ); + } + + .artifact-action-delete { + background-color: color-mix( + in srgb, + var(--color-red) 25%, + black 20% + ); + color: color-mix( + in srgb, + var(--color-red) 100%, + white 10% + ); + } + .conversation-empty-container { display: grid; height: 100%; diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index c0613c4..c4c70b6 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -1,4 +1,6 @@