Skip to content

Commit 1acd8e7

Browse files
Merge pull request #10419 from gitbutlerapp/handling-context-better
Manual context compaction
2 parents 3948e73 + 9f65b80 commit 1acd8e7

File tree

15 files changed

+670
-111
lines changed

15 files changed

+670
-111
lines changed

apps/desktop/src/components/BranchList.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@
166166
commitQuery.result
167167
)}
168168
>
169-
{#snippet children([localAndRemoteCommits, upstreamOnlyCommits, branchDetails, commit])}
169+
{#snippet children([localAndRemoteCommits, _upstreamOnlyCommits, branchDetails, commit])}
170+
{@const upstreamOnlyCommits = []}
170171
{@const firstBranch = i === 0}
171172
{@const lastBranch = i === branches.length - 1}
172173
{@const iconName = getIconFromCommitState(commit?.id, commit?.state)}

apps/desktop/src/components/codegen/CodegenClaudeMessage.svelte

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import CodegenToolCalls from '$components/codegen/CodegenToolCalls.svelte';
55
import CodegenUserMessage from '$components/codegen/CodegenUserMessage.svelte';
66
import { type Message } from '$lib/codegen/messages';
7+
import { Icon, Markdown } from '@gitbutler/ui';
78
89
type Props = {
910
message: Message;
@@ -12,27 +13,115 @@
1213
userAvatarUrl?: string;
1314
};
1415
const { message, onApproval, onRejection, userAvatarUrl }: Props = $props();
16+
17+
let expanded = $state(false);
1518
</script>
1619

1720
{#if message.type === 'user'}
1821
<CodegenUserMessage content={message.message} avatarUrl={userAvatarUrl} />
1922
{:else if message.type === 'claude'}
20-
<CodegenAssistantMessage content={message.message}>
21-
{#snippet extraContent()}
22-
<CodegenToolCalls toolCalls={message.toolCalls} />
23-
24-
{#if message.toolCallsPendingApproval.length > 0}
25-
{#each message.toolCallsPendingApproval as toolCall}
26-
<CodegenToolCall
27-
style="standalone"
28-
{toolCall}
29-
requiresApproval={{
30-
onApproval: async (id) => await onApproval?.(id),
31-
onRejection: async (id) => await onRejection?.(id)
32-
}}
33-
/>
34-
{/each}
35-
{/if}
36-
{/snippet}
37-
</CodegenAssistantMessage>
23+
{#if 'subtype' in message && message.subtype === 'compaction'}
24+
<CodegenAssistantMessage content="The conversation has been compacted.">
25+
{#snippet extraContent()}
26+
{@render compactionSummary(message.message)}
27+
{/snippet}
28+
</CodegenAssistantMessage>
29+
{:else}
30+
<CodegenAssistantMessage content={message.message}>
31+
{#snippet extraContent()}
32+
<CodegenToolCalls toolCalls={message.toolCalls} />
33+
34+
{#if message.toolCallsPendingApproval.length > 0}
35+
{#each message.toolCallsPendingApproval as toolCall}
36+
<CodegenToolCall
37+
style="standalone"
38+
{toolCall}
39+
requiresApproval={{
40+
onApproval: async (id) => await onApproval?.(id),
41+
onRejection: async (id) => await onRejection?.(id)
42+
}}
43+
/>
44+
{/each}
45+
{/if}
46+
{/snippet}
47+
</CodegenAssistantMessage>
48+
{/if}
3849
{/if}
50+
51+
{#snippet compactionSummary(summary: string)}
52+
<div class="compaction-summary__wrapper">
53+
<div class="compaction-summary" class:expanded>
54+
<!-- Header for multiple tool calls -->
55+
<button
56+
type="button"
57+
class="compaction-summary__header"
58+
onclick={() => (expanded = !expanded)}
59+
>
60+
<div class="compaction-summary__arrow" class:expanded>
61+
<Icon name="chevron-right" />
62+
</div>
63+
<span class="text-13 text-semibold">Compaction summary</span>
64+
</button>
65+
{#if expanded}
66+
<div class="compaction-summary__content">
67+
<Markdown content={summary} />
68+
</div>
69+
{/if}
70+
</div>
71+
</div>
72+
{/snippet}
73+
74+
<style lang="postcss">
75+
.compaction-summary__wrapper {
76+
container-name: assistant-message;
77+
container-type: inline-size;
78+
}
79+
80+
.compaction-summary {
81+
width: fit-content;
82+
max-width: var(--message-max-width);
83+
max-width: 100%;
84+
overflow: hidden;
85+
border: 1px solid var(--clr-border-2);
86+
border-radius: var(--radius-ml);
87+
88+
&.expanded {
89+
width: 100%;
90+
}
91+
}
92+
93+
.compaction-summary__header {
94+
display: flex;
95+
align-items: center;
96+
width: 100%;
97+
padding: 8px 12px 8px 8px;
98+
gap: 8px;
99+
border: none;
100+
cursor: pointer;
101+
transition: background-color var(--transition-fast);
102+
103+
&:hover {
104+
background-color: var(--clr-bg-1-muted);
105+
106+
.compaction-summary__arrow {
107+
color: var(--clr-text-2);
108+
}
109+
}
110+
}
111+
112+
.compaction-summary__arrow {
113+
display: flex;
114+
color: var(--clr-text-3);
115+
transition:
116+
,
117+
transform var(--transition-medium);
118+
119+
&.expanded {
120+
transform: rotate(90deg);
121+
}
122+
}
123+
124+
.compaction-summary__content {
125+
padding: 12px;
126+
}
127+
</style>

apps/desktop/src/components/codegen/CodegenInput.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
interface Props {
77
value: string;
88
loading: boolean;
9+
compacting: boolean;
910
onsubmit: () => Promise<void>;
1011
onAbort?: () => Promise<void>;
1112
actions: Snippet;
@@ -15,6 +16,7 @@
1516
let {
1617
value = $bindable(),
1718
loading,
19+
compacting,
1820
onsubmit,
1921
onAbort,
2022
actions,
@@ -102,7 +104,7 @@
102104
</div>
103105

104106
<div class="dialog-input__actions-item">
105-
{#if showAbortButton && onAbort}
107+
{#if !compacting && showAbortButton && onAbort}
106108
<div class="flex" in:fade={{ duration: 150 }} out:fade={{ duration: 100 }}>
107109
<AsyncButton
108110
kind="outline"

0 commit comments

Comments
 (0)