Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 27 additions & 36 deletions packages/ui/src/components/session-turn.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,62 +92,53 @@
min-width: 0;
}

[data-slot="session-turn-diffs"]
> [data-component="collapsible"]
> [data-slot="collapsible-trigger"][aria-expanded="true"] {
position: sticky;
top: var(--sticky-accordion-top, 0px);
z-index: 20;
height: 40px;
padding-bottom: 8px;
background-color: var(--background-stronger);
}

[data-component="session-turn-diffs-trigger"] {
width: 100%;
[data-slot="session-turn-diffs-header"] {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
padding: 0;
}

[data-slot="session-turn-diffs-title"] {
display: inline-flex;
align-items: baseline;
gap: 8px;
padding-bottom: 8px;
}

[data-slot="session-turn-diffs-label"] {
font-variant-numeric: tabular-nums;
color: var(--text-strong);
font-family: var(--font-family-sans);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-large);
}

[data-slot="session-turn-diffs-count"] {
color: var(--text-base);
[data-slot="session-turn-diffs-toggle"] {
color: var(--text-interactive-base);
font-family: var(--font-family-sans);
font-variant-numeric: tabular-nums;
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-x-large);
line-height: var(--line-height-large);
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease;
margin-left: 4px;
}

[data-slot="session-turn-diffs-meta"] {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
[data-component="session-turn-diffs-group"]:hover [data-slot="session-turn-diffs-toggle"] {
opacity: 1;
}

[data-slot="collapsible-arrow"] {
margin-left: -6px;
transform: translateY(2px);
}
[data-component="session-turn-diffs-group"][data-show-all] [data-slot="session-turn-diffs-toggle"] {
opacity: 1;
}

[data-slot="session-turn-diffs-more"] {
color: var(--text-weak);
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
line-height: var(--line-height-large);
padding: 6px 0;
cursor: pointer;
transition: color 0.15s ease;

[data-component="diff-changes"][data-variant="bars"] {
transform: translateY(1px);
&:hover {
color: var(--text-link-base);
}
}

Expand Down
209 changes: 102 additions & 107 deletions packages/ui/src/components/session-turn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions
import { Card } from "./card"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { Collapsible } from "./collapsible"
import { DiffChanges } from "./diff-changes"
import { Icon } from "./icon"
import { TextShimmer } from "./text-shimmer"
Expand Down Expand Up @@ -241,23 +240,20 @@ export function SessionTurn(
}, [])
.reverse()
})
const MAX_FILES = 10
const edited = createMemo(() => diffs().length)
const [state, setState] = createStore({
open: false,
showAll: false,
expanded: [] as string[],
})
const open = () => state.open
const showAll = () => state.showAll
const expanded = () => state.expanded

createEffect(
on(
open,
(value, prev) => {
if (!value && prev) setState("expanded", [])
},
{ defer: true },
),
)
const overflow = createMemo(() => Math.max(0, edited() - MAX_FILES))
const visible = createMemo(() => (showAll() ? diffs() : diffs().slice(0, MAX_FILES)))
const toggleAll = () => {
autoScroll.pause()
setState("showAll", !showAll())
}

const assistantMessages = createMemo(
() => {
Expand Down Expand Up @@ -428,101 +424,100 @@ export function SessionTurn(
</Show>
<SessionRetry status={status()} show={active()} />
<Show when={edited() > 0 && !working()}>
<div data-slot="session-turn-diffs">
<Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost">
<Collapsible.Trigger>
<div data-component="session-turn-diffs-trigger">
<div data-slot="session-turn-diffs-title">
<span data-slot="session-turn-diffs-label">{i18n.t("ui.sessionReview.change.modified")}</span>
<span data-slot="session-turn-diffs-count">
{edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
</span>
<div data-slot="session-turn-diffs-meta">
<DiffChanges changes={diffs()} variant="bars" />
<Collapsible.Arrow />
</div>
</div>
</div>
</Collapsible.Trigger>
<Collapsible.Content>
<Show when={open()}>
<div data-component="session-turn-diffs-content">
<Accordion
multiple
style={{ "--sticky-accordion-offset": "40px" }}
value={expanded()}
onChange={(value) =>
setState("expanded", Array.isArray(value) ? value : value ? [value] : [])
}
>
<For each={diffs()}>
{(diff) => {
const active = createMemo(() => expanded().includes(diff.file))
const [visible, setVisible] = createSignal(false)

createEffect(
on(
active,
(value) => {
if (!value) {
setVisible(false)
return
}

requestAnimationFrame(() => {
if (!active()) return
setVisible(true)
})
},
{ defer: true },
),
)

return (
<Accordion.Item value={diff.file}>
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-turn-diff-trigger">
<span data-slot="session-turn-diff-path">
<Show when={diff.file.includes("/")}>
<span data-slot="session-turn-diff-directory">
{`\u202A${getDirectory(diff.file)}\u202C`}
</span>
</Show>
<span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
</span>
<div data-slot="session-turn-diff-meta">
<span data-slot="session-turn-diff-changes">
<DiffChanges changes={diff} />
</span>
<span data-slot="session-turn-diff-chevron">
<Icon name="chevron-down" size="small" />
</span>
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Show when={visible()}>
<div data-slot="session-turn-diff-view" data-scrollable>
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: diff.file, contents: diff.before }}
after={{ name: diff.file, contents: diff.after }}
/>
</div>
<div
data-slot="session-turn-diffs"
data-component="session-turn-diffs-group"
data-show-all={showAll() || undefined}
>
<div data-slot="session-turn-diffs-header">
<span data-slot="session-turn-diffs-label">
{edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "}
{i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
</span>
<DiffChanges changes={diffs()} />
<Show when={overflow() > 0}>
<span data-slot="session-turn-diffs-toggle" onClick={toggleAll}>
{showAll() ? i18n.t("ui.sessionTurn.diffs.showLess") : i18n.t("ui.sessionTurn.diffs.showAll")}
</span>
</Show>
</div>
<div data-component="session-turn-diffs-content">
<Accordion
multiple
style={{ "--sticky-accordion-offset": "40px" }}
value={expanded()}
onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
>
<For each={visible()}>
{(diff) => {
const active = createMemo(() => expanded().includes(diff.file))
const [shown, setShown] = createSignal(false)

createEffect(
on(
active,
(value) => {
if (!value) {
setShown(false)
return
}

requestAnimationFrame(() => {
if (!active()) return
setShown(true)
})
},
{ defer: true },
),
)

return (
<Accordion.Item value={diff.file}>
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-turn-diff-trigger">
<span data-slot="session-turn-diff-path">
<Show when={diff.file.includes("/")}>
<span data-slot="session-turn-diff-directory">
{`\u202A${getDirectory(diff.file)}\u202C`}
</span>
</Show>
</Accordion.Content>
</Accordion.Item>
)
}}
</For>
</Accordion>
</div>
</Show>
</Collapsible.Content>
</Collapsible>
<span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
</span>
<div data-slot="session-turn-diff-meta">
<span data-slot="session-turn-diff-changes">
<DiffChanges changes={diff} />
</span>
<span data-slot="session-turn-diff-chevron">
<Icon name="chevron-down" size="small" />
</span>
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Show when={shown()}>
<div data-slot="session-turn-diff-view" data-scrollable>
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: diff.file, contents: diff.before }}
after={{ name: diff.file, contents: diff.after }}
/>
</div>
</Show>
</Accordion.Content>
</Accordion.Item>
)
}}
</For>
</Accordion>
<Show when={!showAll() && overflow() > 0}>
<div data-slot="session-turn-diffs-more" onClick={toggleAll}>
{i18n.t("ui.sessionTurn.diffs.more", { count: String(overflow()) })}
</div>
</Show>
</div>
</div>
</Show>
<Show when={error()}>
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export const dict: Record<string, string> = {
"ui.sessionTurn.steps.hide": "Hide steps",
"ui.sessionTurn.summary.response": "Response",
"ui.sessionTurn.diff.showMore": "Show more changes ({{count}})",
"ui.sessionTurn.diffs.changed": "Changed",
"ui.sessionTurn.diffs.showAll": "Show all",
"ui.sessionTurn.diffs.showLess": "Show less",
"ui.sessionTurn.diffs.more": "+{{count}} more files",

"ui.sessionTurn.retry.retrying": "retrying",
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",
Expand Down
Loading