Skip to content

Commit b15fb21

Browse files
authored
feat(app): add compact ui (anomalyco#15578)
1 parent c8866e6 commit b15fb21

File tree

23 files changed

+87
-17
lines changed

23 files changed

+87
-17
lines changed

packages/app/src/pages/session.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,6 @@ export default function Page() {
10991099
anchor={anchor}
11001100
onRegisterMessage={scrollSpy.register}
11011101
onUnregisterMessage={scrollSpy.unregister}
1102-
lastUserMessageID={lastUserMessage()?.id}
11031102
/>
11041103
</Show>
11051104
</Match>

packages/app/src/pages/session/message-timeline.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ export function MessageTimeline(props: {
105105
anchor: (id: string) => string
106106
onRegisterMessage: (el: HTMLDivElement, id: string) => void
107107
onUnregisterMessage: (id: string) => void
108-
lastUserMessageID?: string
109108
}) {
110109
let touchGesture: number | undefined
111110

@@ -601,7 +600,6 @@ export function MessageTimeline(props: {
601600
<SessionTurn
602601
sessionID={sessionID() ?? ""}
603602
messageID={message.id}
604-
lastUserMessageID={props.lastUserMessageID}
605603
showReasoningSummaries={settings.general.showReasoningSummaries()}
606604
shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}
607605
editToolDefaultOpen={settings.general.editToolPartsExpanded()}

packages/ui/src/components/message-part.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,33 @@
225225
}
226226
}
227227

228+
[data-component="compaction-part"] {
229+
width: 100%;
230+
display: flex;
231+
flex-direction: column;
232+
align-items: stretch;
233+
234+
[data-slot="compaction-part-divider"] {
235+
display: flex;
236+
align-items: center;
237+
gap: 12px;
238+
padding: 10px 0;
239+
width: 100%;
240+
}
241+
242+
[data-slot="compaction-part-line"] {
243+
flex: 1 1 auto;
244+
height: 1px;
245+
background: var(--border-weak-base);
246+
}
247+
248+
[data-slot="compaction-part-label"] {
249+
flex: 0 0 auto;
250+
white-space: nowrap;
251+
text-align: center;
252+
}
253+
}
254+
228255
[data-component="reasoning-part"] {
229256
width: 100%;
230257
color: var(--text-base);

packages/ui/src/components/message-part.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,21 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
10371037
)
10381038
}
10391039

1040+
PART_MAPPING["compaction"] = function CompactionPartDisplay() {
1041+
const i18n = useI18n()
1042+
return (
1043+
<div data-component="compaction-part">
1044+
<div data-slot="compaction-part-divider">
1045+
<span data-slot="compaction-part-line" />
1046+
<span data-slot="compaction-part-label" class="text-12-regular text-text-weak">
1047+
{i18n.t("ui.messagePart.compaction")}
1048+
</span>
1049+
<span data-slot="compaction-part-line" />
1050+
</div>
1051+
</div>
1052+
)
1053+
}
1054+
10401055
PART_MAPPING["text"] = function TextPartDisplay(props) {
10411056
const data = useData()
10421057
const i18n = useI18n()

packages/ui/src/components/session-turn.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
max-width: 100%;
3838
}
3939

40+
[data-slot="session-turn-compaction"] {
41+
width: 100%;
42+
min-width: 0;
43+
align-self: stretch;
44+
}
45+
4046
[data-slot="session-turn-thinking"] {
4147
display: flex;
4248
align-items: center;

packages/ui/src/components/session-turn.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Binary } from "@opencode-ai/util/binary"
66
import { getDirectory, getFilename } from "@opencode-ai/util/path"
77
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
88
import { Dynamic } from "solid-js/web"
9-
import { AssistantParts, Message, PART_MAPPING } from "./message-part"
9+
import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part"
1010
import { Card } from "./card"
1111
import { Accordion } from "./accordion"
1212
import { StickyAccordionHeader } from "./sticky-accordion-header"
@@ -139,7 +139,6 @@ export function SessionTurn(
139139
props: ParentProps<{
140140
sessionID: string
141141
messageID: string
142-
lastUserMessageID?: string
143142
showReasoningSummaries?: boolean
144143
shellToolDefaultOpen?: boolean
145144
editToolDefaultOpen?: boolean
@@ -187,25 +186,27 @@ export function SessionTurn(
187186
return msg
188187
})
189188

190-
const lastUserMessageID = createMemo(() => {
191-
if (props.lastUserMessageID) return props.lastUserMessageID
192-
189+
const pending = createMemo(() => {
193190
const messages = allMessages() ?? emptyMessages
194-
for (let i = messages.length - 1; i >= 0; i--) {
195-
const msg = messages[i]
196-
if (msg?.role === "user") return msg.id
197-
}
198-
return undefined
191+
return messages.findLast(
192+
(item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number",
193+
)
194+
})
195+
const active = createMemo(() => {
196+
const msg = message()
197+
const item = pending()
198+
if (!msg || !item) return false
199+
return item.parentID === msg.id
199200
})
200-
201-
const isLastUserMessage = createMemo(() => props.messageID === lastUserMessageID())
202201

203202
const parts = createMemo(() => {
204203
const msg = message()
205204
if (!msg) return emptyParts
206205
return list(data.store.part?.[msg.id], emptyParts)
207206
})
208207

208+
const compaction = createMemo(() => parts().find((part) => part.type === "compaction"))
209+
209210
const diffs = createMemo(() => {
210211
const files = message()?.summary?.diffs
211212
if (!files?.length) return emptyDiffs
@@ -285,7 +286,7 @@ export function SessionTurn(
285286
})
286287

287288
const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle)
288-
const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
289+
const working = createMemo(() => status().type !== "idle" && active())
289290
const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true)
290291

291292
const assistantCopyPartID = createMemo(() => {
@@ -365,6 +366,13 @@ export function SessionTurn(
365366
<div data-slot="session-turn-message-content" aria-live="off">
366367
<Message message={msg()} parts={parts()} interrupted={interrupted()} />
367368
</div>
369+
<Show when={compaction()}>
370+
{(part) => (
371+
<div data-slot="session-turn-compaction">
372+
<Part part={part()} message={msg()} hideDetails />
373+
</div>
374+
)}
375+
</Show>
368376
<Show when={assistantMessages().length > 0}>
369377
<div data-slot="session-turn-assistant-content" aria-hidden={working()}>
370378
<AssistantParts
@@ -386,7 +394,7 @@ export function SessionTurn(
386394
</Show>
387395
</div>
388396
</Show>
389-
<SessionRetry status={status()} show={isLastUserMessage()} />
397+
<SessionRetry status={status()} show={active()} />
390398
<Show when={edited() > 0 && !working()}>
391399
<div data-slot="session-turn-diffs">
392400
<Collapsible open={open()} onOpenChange={setOpen} variant="ghost">

packages/ui/src/i18n/ar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const dict = {
6060
"ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية",
6161

6262
"ui.messagePart.questions.dismissed": "تم رفض الأسئلة",
63+
"ui.messagePart.compaction": "تم ضغط السجل",
6364
"ui.messagePart.context.read.one": "{{count}} قراءة",
6465
"ui.messagePart.context.read.other": "{{count}} قراءات",
6566
"ui.messagePart.context.search.one": "{{count}} بحث",

packages/ui/src/i18n/br.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const dict = {
6060
"ui.sessionTurn.status.consideringNextSteps": "Considerando próximos passos",
6161

6262
"ui.messagePart.questions.dismissed": "Perguntas descartadas",
63+
"ui.messagePart.compaction": "Histórico compactado",
6364
"ui.messagePart.context.read.one": "{{count}} leitura",
6465
"ui.messagePart.context.read.other": "{{count}} leituras",
6566
"ui.messagePart.context.search.one": "{{count}} pesquisa",

packages/ui/src/i18n/bs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const dict = {
6464
"ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka",
6565

6666
"ui.messagePart.questions.dismissed": "Pitanja odbačena",
67+
"ui.messagePart.compaction": "Historija sažeta",
6768
"ui.messagePart.context.read.one": "{{count}} čitanje",
6869
"ui.messagePart.context.read.other": "{{count}} čitanja",
6970
"ui.messagePart.context.search.one": "{{count}} pretraga",

packages/ui/src/i18n/da.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const dict = {
5959
"ui.sessionTurn.status.consideringNextSteps": "Overvejer næste skridt",
6060

6161
"ui.messagePart.questions.dismissed": "Spørgsmål afvist",
62+
"ui.messagePart.compaction": "Historik komprimeret",
6263
"ui.messagePart.context.read.one": "{{count}} læsning",
6364
"ui.messagePart.context.read.other": "{{count}} læsninger",
6465
"ui.messagePart.context.search.one": "{{count}} søgning",

0 commit comments

Comments
 (0)