diff options
| author | Filip <[email protected]> | 2026-03-02 08:57:34 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-02 13:27:34 +0530 |
| commit | bf2cc3aa2f0f0576317d048852d83d45a4724c46 (patch) | |
| tree | 19fb1349fca81579f6c09d6d20294da54cee2048 /packages/ui/src/components | |
| parent | 4b9e19f72f8ebbf05f6b951fd96cf68ba0b23957 (diff) | |
| download | opencode-bf2cc3aa2f0f0576317d048852d83d45a4724c46.tar.gz opencode-bf2cc3aa2f0f0576317d048852d83d45a4724c46.zip | |
feat(app): show which messages are queued (#15587)
Diffstat (limited to 'packages/ui/src/components')
| -rw-r--r-- | packages/ui/src/components/message-part.css | 22 | ||||
| -rw-r--r-- | packages/ui/src/components/message-part.tsx | 17 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 27 |
3 files changed, 60 insertions, 6 deletions
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 6727bb22f..c23a16ee1 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -46,12 +46,18 @@ overflow: hidden; background: var(--surface-weak); border: 1px solid var(--border-weak-base); - transition: border-color 0.15s ease; + transition: + border-color 0.15s ease, + opacity 0.3s ease; &:hover { border-color: var(--border-strong-base); } + &[data-queued] { + opacity: 0.6; + } + &[data-type="image"] { width: 48px; height: 48px; @@ -101,6 +107,11 @@ border: 1px solid var(--border-weak-base); padding: 8px 12px; border-radius: 6px; + transition: opacity 0.3s ease; + + &[data-queued] { + opacity: 0.6; + } [data-highlight="file"] { color: var(--syntax-property); @@ -113,6 +124,14 @@ max-width: 100%; } + [data-slot="user-message-queued-indicator"] { + margin-top: 6px; + margin-right: 2px; + font-size: var(--font-size-small); + color: var(--text-weak); + user-select: none; + } + [data-slot="user-message-copy-wrapper"] { min-height: 24px; margin-top: 4px; @@ -149,6 +168,7 @@ align-items: center; justify-content: flex-end; overflow: hidden; + gap: 6px; } [data-slot="user-message-meta-tail"] { diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 02a99f9dd..39a2b4c23 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -92,6 +92,7 @@ export interface MessageProps { parts: PartType[] showAssistantCopyPartID?: string | null interrupted?: boolean + queued?: boolean showReasoningSummaries?: boolean } @@ -500,6 +501,7 @@ export function Message(props: MessageProps) { message={userMessage() as UserMessage} parts={props.parts} interrupted={props.interrupted} + queued={props.queued} /> )} </Match> @@ -679,7 +681,12 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { ) } -export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[]; interrupted?: boolean }) { +export function UserMessageDisplay(props: { + message: UserMessage + parts: PartType[] + interrupted?: boolean + queued?: boolean +}) { const data = useData() const dialog = useDialog() const i18n = useI18n() @@ -759,6 +766,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp <div data-slot="user-message-attachment" data-type={file.mime.startsWith("image/") ? "image" : "file"} + data-queued={props.queued ? "" : undefined} onClick={() => { if (file.mime.startsWith("image/") && file.url) { openImagePreview(file.url, file.filename) @@ -787,9 +795,14 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp <Show when={text()}> <> <div data-slot="user-message-body"> - <div data-slot="user-message-text"> + <div data-slot="user-message-text" data-queued={props.queued ? "" : undefined}> <HighlightedText text={text()} references={inlineFiles()} agents={agents()} /> </div> + <Show when={props.queued}> + <div data-slot="user-message-queued-indicator"> + <TextShimmer text={i18n.t("ui.message.queued")} /> + </div> + </Show> </div> <div data-slot="user-message-copy-wrapper" data-interrupted={props.interrupted ? "" : undefined}> <Show when={metaHead() || metaTail()}> diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index e329b1170..c441bcf61 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -192,11 +192,31 @@ export function SessionTurn( (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", ) }) + + const pendingUser = createMemo(() => { + const item = pending() + if (!item?.parentID) return + const messages = allMessages() ?? emptyMessages + const result = Binary.search(messages, item.parentID, (m) => m.id) + const msg = result.found ? messages[result.index] : messages.find((m) => m.id === item.parentID) + if (!msg || msg.role !== "user") return + return msg + }) + const active = createMemo(() => { const msg = message() + const parent = pendingUser() + if (!msg || !parent) return false + return parent.id === msg.id + }) + + const queued = createMemo(() => { + const id = message()?.id + if (!id) return false + if (!pendingUser()) return false const item = pending() - if (!msg || !item) return false - return item.parentID === msg.id + if (!item) return false + return id > item.id }) const parts = createMemo(() => { @@ -334,6 +354,7 @@ export function SessionTurn( ) const showThinking = createMemo(() => { if (!working() || !!error()) return false + if (queued()) return false if (status().type === "retry") return false if (showReasoningSummaries()) return assistantVisible() === 0 if (assistantTailVisible() === "text") return false @@ -364,7 +385,7 @@ export function SessionTurn( class={props.classes?.container} > <div data-slot="session-turn-message-content" aria-live="off"> - <Message message={msg()} parts={parts()} interrupted={interrupted()} /> + <Message message={msg()} parts={parts()} interrupted={interrupted()} queued={queued()} /> </div> <Show when={compaction()}> {(part) => ( |
