Skip to content

Commit f90b2e6

Browse files
authored
update-ai-pill-ui (#10181)
* update ai pill ui * update ui for ai draggable chip
1 parent 1be5c3b commit f90b2e6

File tree

7 files changed

+140
-116
lines changed

7 files changed

+140
-116
lines changed

apps/desktop/src/components/BranchCard.svelte

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import Dropzone from '$components/Dropzone.svelte';
1212
import PrNumberUpdater from '$components/PrNumberUpdater.svelte';
1313
import ReduxResult from '$components/ReduxResult.svelte';
14+
1415
import { CodegenRuleDropData, CodegenRuleDropHandler } from '$lib/codegen/dropzone';
1516
import { MoveCommitDzHandler } from '$lib/commits/dropHandler';
1617
import { DRAG_STATE_SERVICE } from '$lib/dragging/dragStateService.svelte';
@@ -23,7 +24,7 @@
2324
import { STACK_SERVICE } from '$lib/stacks/stackService.svelte';
2425
import { UI_STATE } from '$lib/state/uiState.svelte';
2526
import { inject } from '@gitbutler/core/context';
26-
import { ReviewBadge, Icon, Tooltip, TestId, Button } from '@gitbutler/ui';
27+
import { ReviewBadge, Icon, Tooltip, TestId, Button, Badge } from '@gitbutler/ui';
2728
import { getTimeAgo } from '@gitbutler/ui/utils/timeAgo';
2829
import { isDefined } from '@gitbutler/ui/utils/typeguards';
2930
import type { DropzoneHandler } from '$lib/dragging/handler';
@@ -203,50 +204,66 @@
203204
<ReduxResult result={rule?.current} {projectId} stackId={args.stackId}>
204205
{#snippet children({ rule }, { projectId: _projectId, stackId: _stackId })}
205206
{#if rule}
206-
{#snippet button(text: string, tooltip?: string)}
207-
<Tooltip text={tooltip}>
207+
<ClaudeSessionDescriptor
208+
{projectId}
209+
sessionId={(rule.filters[0]! as RuleFilter & { type: 'claudeCodeSessionId' })
210+
.subject}
211+
>
212+
{#snippet loading()}
208213
<Button
209214
icon="ai-small"
210215
style="purple"
211216
size="tag"
212-
kind="outline"
217+
class="branch-header__ai-pill__name"
213218
shrinkable
214-
onclick={() => {
215-
if (!args.stackId) return;
216-
projectState.selectedClaudeSession.set({
217-
stackId: args.stackId,
218-
head: branchName
219-
});
220-
goto(codegenPath(projectId));
219+
reversedDirection
220+
disabled>Loading...</Button
221+
>
222+
{/snippet}
223+
{#snippet error()}
224+
<Badge size="tag" style="error" kind="solid" icon="ai-small">
225+
Session error
226+
</Badge>
227+
{/snippet}
228+
{#snippet children(descriptor)}
229+
<div
230+
class="branch-header__ai-pill"
231+
use:draggableChips={{
232+
label: descriptor,
233+
data: new CodegenRuleDropData(rule),
234+
chipType: 'ai-session',
235+
dropzoneRegistry,
236+
dragStateService
221237
}}
222238
>
223-
{text}
224-
</Button>
225-
</Tooltip>
226-
{/snippet}
227-
<div
228-
class="branch-header__shrinkable-button"
229-
use:draggableChips={{
230-
label: 'Codegen',
231-
data: new CodegenRuleDropData(rule),
232-
chipType: 'file',
233-
dropzoneRegistry,
234-
dragStateService
235-
}}
236-
>
237-
<ClaudeSessionDescriptor
238-
{projectId}
239-
sessionId={(rule.filters[0]! as RuleFilter & { type: 'claudeCodeSessionId' })
240-
.subject}
241-
>
242-
{#snippet fallback()}
243-
{@render button('Codegen')}
244-
{/snippet}
245-
{#snippet children(descriptor)}
246-
{@render button(descriptor, descriptor)}
247-
{/snippet}
248-
</ClaudeSessionDescriptor>
249-
</div>
239+
<Button
240+
icon="ai-small"
241+
style="purple"
242+
size="tag"
243+
shrinkable
244+
tooltip="Click to go to session or drag to another lane"
245+
tooltipMaxWidth={160}
246+
width="100%"
247+
maxWidth={140}
248+
onclick={() => {
249+
if (!args.stackId) return;
250+
projectState.selectedClaudeSession.set({
251+
stackId: args.stackId,
252+
head: branchName
253+
});
254+
goto(codegenPath(projectId));
255+
}}
256+
>
257+
{#snippet custom()}
258+
<div class="branch-header__ai-pill-label">
259+
<span class="truncate">{descriptor}</span>
260+
<Icon name="draggable" opacity={0.6} />
261+
</div>
262+
{/snippet}
263+
</Button>
264+
</div>
265+
{/snippet}
266+
</ClaudeSessionDescriptor>
250267
{/if}
251268
{/snippet}
252269
</ReduxResult>
@@ -426,10 +443,16 @@
426443
}
427444
}
428445
429-
.branch-header__shrinkable-button {
446+
.branch-header__ai-pill {
447+
display: flex;
448+
overflow: hidden;
449+
}
450+
451+
.branch-header__ai-pill-label {
430452
display: flex;
431453
align-items: center;
432-
max-width: 100px;
454+
padding-left: 4px;
433455
overflow: hidden;
456+
gap: 3px;
434457
}
435458
</style>

apps/desktop/src/components/ClaudeSessionDescriptor.svelte

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,42 @@
88
type Props = {
99
projectId: string;
1010
sessionId: string;
11-
fallback: Snippet;
11+
fallback?: Snippet;
12+
loading?: Snippet;
13+
error?: Snippet;
1214
children: Snippet<[string]>;
1315
};
1416
15-
const { projectId, sessionId, children, fallback }: Props = $props();
17+
const { projectId, sessionId, children, fallback, loading, error }: Props = $props();
1618
const claudeCodeService = inject(CLAUDE_CODE_SERVICE);
1719
const sessionDetails = $derived(claudeCodeService.sessionDetails(projectId, sessionId));
1820
</script>
1921

20-
{#snippet loading()}
21-
{@render fallback()}
22+
{#snippet loadingSnippet()}
23+
{#if loading}
24+
{@render loading()}
25+
{:else if fallback}
26+
{@render fallback()}
27+
{/if}
2228
{/snippet}
2329

24-
{#snippet error()}
25-
{@render fallback()}
30+
{#snippet errorSnippet()}
31+
{#if error}
32+
{@render error()}
33+
{:else if fallback}
34+
{@render fallback()}
35+
{/if}
2636
{/snippet}
2737

2838
{#snippet resultChildren(sessionDetails: ClaudeSessionDetails)}
2939
{@const title = sessionMessage(sessionDetails) ?? sessionId}
3040
{@render children(title)}
3141
{/snippet}
3242

33-
<ReduxResult {projectId} result={sessionDetails.current} {loading} {error} children={resultChildren}
43+
<ReduxResult
44+
{projectId}
45+
result={sessionDetails.current}
46+
loading={loadingSnippet}
47+
error={errorSnippet}
48+
children={resultChildren}
3449
></ReduxResult>

apps/desktop/src/components/RulesList.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@
214214
size="tag"
215215
kind="outline"
216216
tooltip="Automate actions for new code changes"
217+
tooltipMaxWidth={180}
217218
onclick={openAddRuleContextMenu}
218219
disabled={mode === 'edit' || mode === 'add'}
219220
loading={creatingRule.current.isLoading}>Add rule</Button

apps/desktop/src/lib/dragging/draggable.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { getColorFromCommitState } from '$components/lib';
22
import { type CommitStatusType } from '$lib/commits/commit';
33
import { ChangeDropData, type DropData } from '$lib/dragging/draggables';
44
import { getFileIcon } from '@gitbutler/ui/components/file/getFileIcon';
5+
import iconsJson from '@gitbutler/ui/data/icons.json';
56
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
67
import type { DragStateService } from '$lib/dragging/dragStateService.svelte';
78
import type { DropzoneRegistry } from '$lib/dragging/registry';
89

910
// Added to element being dragged (not the clone that follows the cursor).
1011
const DRAGGING_CLASS = 'dragging';
1112

13+
type chipType = 'file' | 'hunk' | 'ai-session';
14+
1215
export type DraggableConfig = {
1316
readonly selector?: string;
1417
readonly disabled?: boolean;
@@ -20,7 +23,7 @@ export type DraggableConfig = {
2023
readonly commitType?: CommitStatusType;
2124
readonly data?: DropData;
2225
readonly viewportId?: string;
23-
readonly chipType?: 'file' | 'hunk';
26+
readonly chipType?: chipType;
2427
readonly dropzoneRegistry: DropzoneRegistry;
2528
readonly dragStateService?: DragStateService;
2629
};
@@ -318,12 +321,37 @@ function createHunkChipContainer(label: string | undefined): HTMLDivElement {
318321
return containerEl;
319322
}
320323

324+
// AI Session chip
325+
function createAISessionChipContainer(label: string | undefined): HTMLDivElement {
326+
const containerEl = createElement('div', ['dragchip-ai-session-container']);
327+
// Add a sparkle emoji or icon for visual distinction
328+
const iconEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
329+
iconEl.classList.add('dragchip-ai-session-icon');
330+
iconEl.setAttribute('viewBox', '0 0 16 16');
331+
iconEl.setAttribute('fill-rule', 'evenodd');
332+
333+
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
334+
pathEl.setAttribute('fill', 'currentColor');
335+
pathEl.setAttribute('d', iconsJson['ai-small']);
336+
iconEl.appendChild(pathEl);
337+
338+
const labelEl = createElement(
339+
'span',
340+
['text-12', 'text-semibold', 'truncate', 'dragchip-ai-session-label'],
341+
label || 'AI Session'
342+
);
343+
344+
containerEl.appendChild(iconEl);
345+
containerEl.appendChild(labelEl);
346+
return containerEl;
347+
}
348+
321349
export function createChipsElement(
322350
opt: {
323351
childrenAmount: number;
324352
label: string | undefined;
325353
filePath: string | undefined;
326-
chipType: 'file' | 'hunk';
354+
chipType: chipType;
327355
} = {
328356
childrenAmount: 1,
329357
label: undefined,
@@ -340,8 +368,10 @@ export function createChipsElement(
340368
chipEl.appendChild(fileChipContainer);
341369
} else if (opt.chipType === 'hunk') {
342370
const hunkChipContainer = createHunkChipContainer(opt.label);
343-
344371
chipEl.appendChild(hunkChipContainer);
372+
} else if (opt.chipType === 'ai-session') {
373+
const aiSessionChipContainer = createAISessionChipContainer(opt.label);
374+
chipEl.appendChild(aiSessionChipContainer);
345375
}
346376

347377
if (opt.childrenAmount > 1) {

apps/desktop/src/styles/draggable.css

Lines changed: 15 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
height: 16px;
8686
}
8787

88-
/* Hunk drag */
88+
/* HUNK DRAG */
8989
.dragchip-hunk-container {
9090
display: flex;
9191
font-size: 12px;
@@ -106,72 +106,24 @@
106106
padding: 6px 7px;
107107
}
108108

109-
/* COMMIT DRAG CARD */
110-
.draggable-commit {
111-
display: flex;
112-
position: absolute;
113-
flex-direction: column;
114-
padding: 12px 12px 12px 16px;
115-
overflow: hidden;
116-
gap: 8px;
117-
border: 1px solid var(--clr-border-2);
118-
border-radius: var(--radius-m);
119-
background-color: var(--clr-bg-1);
120-
}
121-
122-
.draggable-commit-indicator {
123-
&::before {
124-
position: absolute;
125-
top: 0;
126-
left: 0;
127-
width: 4px;
128-
height: 100%;
129-
content: '';
130-
}
131-
}
132-
133-
.draggable-commit-localAndRemote {
134-
&::before {
135-
background-color: var(--clr-commit-remote);
136-
}
137-
}
138-
139-
.draggable-commit-local {
140-
color: var(--clr-text-1);
141-
&::before {
142-
background-color: var(--clr-commit-local);
143-
}
144-
}
145-
146-
.draggable-commit-integrated {
147-
color: var(--clr-text-1);
148-
&::before {
149-
background-color: var(--clr-commit-shadow);
150-
}
151-
}
152-
153-
.draggable-commit-author-img {
154-
width: 12px;
155-
height: 12px;
156-
border-radius: 50%;
157-
}
158-
159-
.draggable-commit-info {
109+
/* AI SESSION DRAG */
110+
.dragchip-ai-session-container {
160111
display: flex;
161112
align-items: center;
113+
max-width: 120px;
114+
padding: 5px 6px;
115+
gap: 6px;
116+
border: none;
117+
border-radius: var(--radius-m);
118+
background-color: var(--clr-theme-purp-element);
119+
color: var(--clr-theme-purp-on-element);
162120
}
163121

164-
.draggable-commit-info-text {
165-
overflow: hidden;
166-
color: var(--clr-text-2);
167-
text-overflow: ellipsis;
168-
white-space: nowrap;
169-
170-
&:not(:first-child):before {
171-
margin: 0 5px;
172-
content: '•';
173-
color: var(--clr-text-3);
174-
}
122+
.dragchip-ai-session-icon {
123+
flex-shrink: 0;
124+
width: 16px;
125+
height: 16px;
126+
color: currentColor;
175127
}
176128

177129
/* COMMIT DRAG CARD V3 */

0 commit comments

Comments
 (0)