diff options
| author | Adam <[email protected]> | 2026-03-12 19:07:19 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-03-12 19:07:23 -0500 |
| commit | c173988aaa2f84ecdfe7d3c7cdfd7d3a525aee02 (patch) | |
| tree | 9d88817716a8344901ce19cb7e8ed0437dcb3de6 /packages/ui/src | |
| parent | 268855dc5a770e9cd6718ea62b935afdea2dda7f (diff) | |
| download | opencode-c173988aaa2f84ecdfe7d3c7cdfd7d3a525aee02.tar.gz opencode-c173988aaa2f84ecdfe7d3c7cdfd7d3a525aee02.zip | |
feat(app): interruption state
Diffstat (limited to 'packages/ui/src')
| -rw-r--r-- | packages/ui/src/components/message-part.css | 8 | ||||
| -rw-r--r-- | packages/ui/src/components/message-part.tsx | 34 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 13 |
3 files changed, 21 insertions, 34 deletions
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 3648d5079..f01408a38 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -23,10 +23,6 @@ max-width: 100%; gap: 0; - &[data-interrupted] { - color: var(--text-weak); - } - [data-slot="user-message-attachments"] { display: flex; flex-wrap: wrap; @@ -165,10 +161,6 @@ text-align: right; } - [data-slot="user-message-copy-wrapper"][data-interrupted] { - gap: 12px; - } - &:hover [data-slot="user-message-copy-wrapper"], &:focus-within [data-slot="user-message-copy-wrapper"] { opacity: 1; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 500c73c5e..ee83ffa15 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -131,7 +131,6 @@ export interface MessageProps { parts: PartType[] actions?: UserActions showAssistantCopyPartID?: string | null - interrupted?: boolean showReasoningSummaries?: boolean } @@ -691,12 +690,7 @@ export function Message(props: MessageProps) { <Switch> <Match when={props.message.role === "user" && props.message}> {(userMessage) => ( - <UserMessageDisplay - message={userMessage() as UserMessage} - parts={props.parts} - actions={props.actions} - interrupted={props.interrupted} - /> + <UserMessageDisplay message={userMessage() as UserMessage} parts={props.parts} actions={props.actions} /> )} </Match> <Match when={props.message.role === "assistant" && props.message}> @@ -887,12 +881,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { ) } -export function UserMessageDisplay(props: { - message: UserMessage - parts: PartType[] - actions?: UserActions - interrupted?: boolean -}) { +export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[]; actions?: UserActions }) { const data = useData() const dialog = useDialog() const i18n = useI18n() @@ -947,10 +936,7 @@ export function UserMessageDisplay(props: { return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0") }) - const metaTail = createMemo(() => { - const items = [stamp(), props.interrupted ? i18n.t("ui.message.interrupted") : ""] - return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0") - }) + const metaTail = stamp const openImagePreview = (url: string, alt?: string) => { dialog.show(() => <ImagePreview src={url} alt={alt} />) @@ -981,7 +967,7 @@ export function UserMessageDisplay(props: { } return ( - <div data-component="user-message" data-interrupted={props.interrupted ? "" : undefined}> + <div data-component="user-message"> <Show when={attachments().length > 0}> <div data-slot="user-message-attachments"> <For each={attachments()}> @@ -1021,7 +1007,7 @@ export function UserMessageDisplay(props: { <HighlightedText text={text()} references={inlineFiles()} agents={agents()} /> </div> </div> - <div data-slot="user-message-copy-wrapper" data-interrupted={props.interrupted ? "" : undefined}> + <div data-slot="user-message-copy-wrapper"> <Show when={metaHead() || metaTail()}> <span data-slot="user-message-meta-wrap"> <Show when={metaHead()}> @@ -1305,14 +1291,13 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { ) } -PART_MAPPING["compaction"] = function CompactionPartDisplay() { - const i18n = useI18n() +export function MessageDivider(props: { label: string }) { return ( <div data-component="compaction-part"> <div data-slot="compaction-part-divider"> <span data-slot="compaction-part-line" /> <span data-slot="compaction-part-label" class="text-12-regular text-text-weak"> - {i18n.t("ui.messagePart.compaction")} + {props.label} </span> <span data-slot="compaction-part-line" /> </div> @@ -1320,6 +1305,11 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() { ) } +PART_MAPPING["compaction"] = function CompactionPartDisplay() { + const i18n = useI18n() + return <MessageDivider label={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.tsx b/packages/ui/src/components/session-turn.tsx index 8783e8957..41e160d16 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -7,7 +7,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, PART_MAPPING, type UserActions } from "./message-part" +import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" @@ -276,6 +276,11 @@ export function SessionTurn( ) const interrupted = createMemo(() => assistantMessages().some((m) => m.error?.name === "MessageAbortedError")) + const divider = createMemo(() => { + if (compaction()) return i18n.t("ui.messagePart.compaction") + if (interrupted()) return i18n.t("ui.message.interrupted") + return "" + }) const error = createMemo( () => assistantMessages().find((m) => m.error && m.error.name !== "MessageAbortedError")?.error, ) @@ -384,11 +389,11 @@ export function SessionTurn( class={props.classes?.container} > <div data-slot="session-turn-message-content" aria-live="off"> - <Message message={message()!} parts={parts()} actions={props.actions} interrupted={interrupted()} /> + <Message message={message()!} parts={parts()} actions={props.actions} /> </div> - <Show when={compaction()}> + <Show when={divider()}> <div data-slot="session-turn-compaction"> - <Part part={compaction()!} message={message()!} hideDetails /> + <MessageDivider label={divider()} /> </div> </Show> <Show when={assistantMessages().length > 0}> |
