From b15fb211917de83b39d9ec3a1d66ae4353d1e6e0 Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Sun, 1 Mar 2026 15:41:47 +0100 Subject: feat(app): add compact ui (#15578) --- packages/ui/src/components/message-part.css | 27 ++++++++++++++++++++++ packages/ui/src/components/message-part.tsx | 15 ++++++++++++ packages/ui/src/components/session-turn.css | 6 +++++ packages/ui/src/components/session-turn.tsx | 36 ++++++++++++++++++----------- 4 files changed, 70 insertions(+), 14 deletions(-) (limited to 'packages/ui/src/components') diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 58227f625..6727bb22f 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -225,6 +225,33 @@ } } +[data-component="compaction-part"] { + width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + + [data-slot="compaction-part-divider"] { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 0; + width: 100%; + } + + [data-slot="compaction-part-line"] { + flex: 1 1 auto; + height: 1px; + background: var(--border-weak-base); + } + + [data-slot="compaction-part-label"] { + flex: 0 0 auto; + white-space: nowrap; + text-align: center; + } +} + [data-component="reasoning-part"] { width: 100%; color: var(--text-base); diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 6b6dfe2e5..02a99f9dd 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1037,6 +1037,21 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { ) } +PART_MAPPING["compaction"] = function CompactionPartDisplay() { + const i18n = useI18n() + return ( +
+
+ + + {i18n.t("ui.messagePart.compaction")} + + +
+
+ ) +} + PART_MAPPING["text"] = function TextPartDisplay(props) { const data = useData() const i18n = useI18n() diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 9639e6635..4af87b361 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -37,6 +37,12 @@ max-width: 100%; } + [data-slot="session-turn-compaction"] { + width: 100%; + min-width: 0; + align-self: stretch; + } + [data-slot="session-turn-thinking"] { display: flex; align-items: center; diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index bd4f2843a..e329b1170 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -6,7 +6,7 @@ import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { AssistantParts, Message, PART_MAPPING } from "./message-part" +import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" @@ -139,7 +139,6 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string - lastUserMessageID?: string showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean @@ -187,18 +186,18 @@ export function SessionTurn( return msg }) - const lastUserMessageID = createMemo(() => { - if (props.lastUserMessageID) return props.lastUserMessageID - + const pending = createMemo(() => { const messages = allMessages() ?? emptyMessages - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg?.role === "user") return msg.id - } - return undefined + return messages.findLast( + (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", + ) + }) + const active = createMemo(() => { + const msg = message() + const item = pending() + if (!msg || !item) return false + return item.parentID === msg.id }) - - const isLastUserMessage = createMemo(() => props.messageID === lastUserMessageID()) const parts = createMemo(() => { const msg = message() @@ -206,6 +205,8 @@ export function SessionTurn( return list(data.store.part?.[msg.id], emptyParts) }) + const compaction = createMemo(() => parts().find((part) => part.type === "compaction")) + const diffs = createMemo(() => { const files = message()?.summary?.diffs if (!files?.length) return emptyDiffs @@ -285,7 +286,7 @@ export function SessionTurn( }) const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle) - const working = createMemo(() => status().type !== "idle" && isLastUserMessage()) + const working = createMemo(() => status().type !== "idle" && active()) const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true) const assistantCopyPartID = createMemo(() => { @@ -365,6 +366,13 @@ export function SessionTurn(
+ + {(part) => ( +
+ +
+ )} +
0}>
- + 0 && !working()}>
-- cgit v1.2.3