feat(ui): redesign modified files section in session turn (#20348)
Co-authored-by: David Hill <iamdavidhill@gmail.com>pull/20297/merge
parent
3deee3a02b
commit
9d57f21f9f
|
|
@ -92,33 +92,15 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-diffs"]
|
[data-slot="session-turn-diffs-header"] {
|
||||||
> [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%;
|
|
||||||
display: flex;
|
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;
|
align-items: baseline;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-diffs-label"] {
|
[data-slot="session-turn-diffs-label"] {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
|
@ -126,28 +108,38 @@
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-diffs-count"] {
|
[data-slot="session-turn-diffs-toggle"] {
|
||||||
color: var(--text-base);
|
color: var(--text-interactive-base);
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
font-weight: var(--font-weight-regular);
|
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"] {
|
[data-component="session-turn-diffs-group"]:hover [data-slot="session-turn-diffs-toggle"] {
|
||||||
display: inline-flex;
|
opacity: 1;
|
||||||
align-items: center;
|
}
|
||||||
gap: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
[data-slot="collapsible-arrow"] {
|
[data-component="session-turn-diffs-group"][data-show-all] [data-slot="session-turn-diffs-toggle"] {
|
||||||
margin-left: -6px;
|
opacity: 1;
|
||||||
transform: translateY(2px);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="diff-changes"][data-variant="bars"] {
|
[data-slot="session-turn-diffs-more"] {
|
||||||
transform: translateY(1px);
|
color: var(--text-weak);
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 0 0 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-link-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions
|
||||||
import { Card } from "./card"
|
import { Card } from "./card"
|
||||||
import { Accordion } from "./accordion"
|
import { Accordion } from "./accordion"
|
||||||
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
||||||
import { Collapsible } from "./collapsible"
|
|
||||||
import { DiffChanges } from "./diff-changes"
|
import { DiffChanges } from "./diff-changes"
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
|
|
@ -241,23 +240,20 @@ export function SessionTurn(
|
||||||
}, [])
|
}, [])
|
||||||
.reverse()
|
.reverse()
|
||||||
})
|
})
|
||||||
|
const MAX_FILES = 10
|
||||||
const edited = createMemo(() => diffs().length)
|
const edited = createMemo(() => diffs().length)
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
open: false,
|
showAll: false,
|
||||||
expanded: [] as string[],
|
expanded: [] as string[],
|
||||||
})
|
})
|
||||||
const open = () => state.open
|
const showAll = () => state.showAll
|
||||||
const expanded = () => state.expanded
|
const expanded = () => state.expanded
|
||||||
|
const overflow = createMemo(() => Math.max(0, edited() - MAX_FILES))
|
||||||
createEffect(
|
const visible = createMemo(() => (showAll() ? diffs() : diffs().slice(0, MAX_FILES)))
|
||||||
on(
|
const toggleAll = () => {
|
||||||
open,
|
autoScroll.pause()
|
||||||
(value, prev) => {
|
setState("showAll", !showAll())
|
||||||
if (!value && prev) setState("expanded", [])
|
}
|
||||||
},
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const assistantMessages = createMemo(
|
const assistantMessages = createMemo(
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -425,101 +421,100 @@ export function SessionTurn(
|
||||||
</Show>
|
</Show>
|
||||||
<SessionRetry status={status()} show={active()} />
|
<SessionRetry status={status()} show={active()} />
|
||||||
<Show when={edited() > 0 && !working()}>
|
<Show when={edited() > 0 && !working()}>
|
||||||
<div data-slot="session-turn-diffs">
|
<div
|
||||||
<Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost">
|
data-slot="session-turn-diffs"
|
||||||
<Collapsible.Trigger>
|
data-component="session-turn-diffs-group"
|
||||||
<div data-component="session-turn-diffs-trigger">
|
data-show-all={showAll() || undefined}
|
||||||
<div data-slot="session-turn-diffs-title">
|
>
|
||||||
<span data-slot="session-turn-diffs-label">{i18n.t("ui.sessionReview.change.modified")}</span>
|
<div data-slot="session-turn-diffs-header">
|
||||||
<span data-slot="session-turn-diffs-count">
|
<span data-slot="session-turn-diffs-label">
|
||||||
{edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
|
{edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "}
|
||||||
</span>
|
{i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
|
||||||
<div data-slot="session-turn-diffs-meta">
|
</span>
|
||||||
<DiffChanges changes={diffs()} variant="bars" />
|
<DiffChanges changes={diffs()} />
|
||||||
<Collapsible.Arrow />
|
<Show when={overflow() > 0}>
|
||||||
</div>
|
<span data-slot="session-turn-diffs-toggle" onClick={toggleAll}>
|
||||||
</div>
|
{showAll() ? i18n.t("ui.sessionTurn.diffs.showLess") : i18n.t("ui.sessionTurn.diffs.showAll")}
|
||||||
</div>
|
</span>
|
||||||
</Collapsible.Trigger>
|
</Show>
|
||||||
<Collapsible.Content>
|
</div>
|
||||||
<Show when={open()}>
|
<div data-component="session-turn-diffs-content">
|
||||||
<div data-component="session-turn-diffs-content">
|
<Accordion
|
||||||
<Accordion
|
multiple
|
||||||
multiple
|
style={{ "--sticky-accordion-offset": "40px" }}
|
||||||
style={{ "--sticky-accordion-offset": "40px" }}
|
value={expanded()}
|
||||||
value={expanded()}
|
onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
|
||||||
onChange={(value) =>
|
>
|
||||||
setState("expanded", Array.isArray(value) ? value : value ? [value] : [])
|
<For each={visible()}>
|
||||||
}
|
{(diff) => {
|
||||||
>
|
const active = createMemo(() => expanded().includes(diff.file))
|
||||||
<For each={diffs()}>
|
const [shown, setShown] = createSignal(false)
|
||||||
{(diff) => {
|
|
||||||
const active = createMemo(() => expanded().includes(diff.file))
|
|
||||||
const [visible, setVisible] = createSignal(false)
|
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
active,
|
active,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
setVisible(false)
|
setShown(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!active()) return
|
if (!active()) return
|
||||||
setVisible(true)
|
setShown(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion.Item value={diff.file}>
|
<Accordion.Item value={diff.file}>
|
||||||
<StickyAccordionHeader>
|
<StickyAccordionHeader>
|
||||||
<Accordion.Trigger>
|
<Accordion.Trigger>
|
||||||
<div data-slot="session-turn-diff-trigger">
|
<div data-slot="session-turn-diff-trigger">
|
||||||
<span data-slot="session-turn-diff-path">
|
<span data-slot="session-turn-diff-path">
|
||||||
<Show when={diff.file.includes("/")}>
|
<Show when={diff.file.includes("/")}>
|
||||||
<span data-slot="session-turn-diff-directory">
|
<span data-slot="session-turn-diff-directory">
|
||||||
{`\u202A${getDirectory(diff.file)}\u202C`}
|
{`\u202A${getDirectory(diff.file)}\u202C`}
|
||||||
</span>
|
</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>
|
|
||||||
</Show>
|
</Show>
|
||||||
</Accordion.Content>
|
<span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
|
||||||
</Accordion.Item>
|
</span>
|
||||||
)
|
<div data-slot="session-turn-diff-meta">
|
||||||
}}
|
<span data-slot="session-turn-diff-changes">
|
||||||
</For>
|
<DiffChanges changes={diff} />
|
||||||
</Accordion>
|
</span>
|
||||||
</div>
|
<span data-slot="session-turn-diff-chevron">
|
||||||
</Show>
|
<Icon name="chevron-down" size="small" />
|
||||||
</Collapsible.Content>
|
</span>
|
||||||
</Collapsible>
|
</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>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={error()}>
|
<Show when={error()}>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ export const dict: Record<string, string> = {
|
||||||
"ui.sessionTurn.steps.hide": "Hide steps",
|
"ui.sessionTurn.steps.hide": "Hide steps",
|
||||||
"ui.sessionTurn.summary.response": "Response",
|
"ui.sessionTurn.summary.response": "Response",
|
||||||
"ui.sessionTurn.diff.showMore": "Show more changes ({{count}})",
|
"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.retrying": "retrying",
|
||||||
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",
|
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue