feat(app): update message part ui to v2 (#34394)

This commit is contained in:
Aarav Sareen 2026-06-29 16:13:06 +05:30 committed by GitHub
parent b5f92c9f48
commit 0a5e617da8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 44 deletions

View file

@ -1013,6 +1013,7 @@ export function MessageTimeline(props: {
message={message()}
showAssistantCopyPartID={assistantCopyPartID(row().userMessageID)}
turnDurationMs={turnDurationMs(row().userMessageID)}
useV2Actions={settings.general.newLayoutDesigns()}
defaultOpen={defaultOpen()}
toolOpen={toolOpen[part().id] ?? defaultOpen()}
onToolOpenChange={(open) => setToolOpen(part().id, open)}
@ -1120,6 +1121,7 @@ export function MessageTimeline(props: {
message={message()}
parts={getMsgParts(userMessageRow().userMessageID)}
actions={props.actions}
useV2Actions={settings.general.newLayoutDesigns()}
/>
</div>
</div>

View file

@ -11,6 +11,7 @@ import {
onCleanup,
Index,
type JSX,
type ComponentProps,
} from "solid-js"
import { createStore } from "solid-js/store"
import stripAnsi from "strip-ansi"
@ -49,6 +50,8 @@ import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/core/ut
import { checksum } from "@opencode-ai/core/util/encode"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { Spinner } from "@opencode-ai/ui/spinner"
import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
import { AnimatedCountList } from "./tool-count-summary"
@ -161,6 +164,7 @@ export interface MessageProps {
actions?: UserActions
showAssistantCopyPartID?: string | null
showReasoningSummaries?: boolean
useV2Actions?: boolean
}
export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise<void> | void
@ -182,6 +186,46 @@ export interface MessagePartProps {
onContentRendered?: () => void
showAssistantCopyPartID?: string | null
turnDurationMs?: number
useV2Actions?: boolean
}
function MessageActionButton(
props: Pick<ComponentProps<"button">, "disabled" | "onMouseDown" | "onClick" | "aria-label"> & {
icon: "check" | "copy" | "reset"
label: JSX.Element
useV2?: boolean
},
) {
return (
<Show
when={props.useV2}
fallback={
<Tooltip value={props.label} placement="top" gutter={4}>
<IconButton
icon={props.icon}
size="normal"
variant="ghost"
disabled={props.disabled}
onMouseDown={props.onMouseDown}
onClick={props.onClick}
aria-label={props["aria-label"]}
/>
</Tooltip>
}
>
<TooltipV2 value={props.label} placement="top" gutter={4}>
<IconButtonV2
icon={<Icon name={props.icon} size="small" />}
size="normal"
variant="ghost-muted"
disabled={props.disabled}
onMouseDown={props.onMouseDown}
onClick={props.onClick}
aria-label={props["aria-label"]}
/>
</TooltipV2>
</Show>
)
}
export type PartComponent = Component<MessagePartProps>
@ -640,6 +684,7 @@ export function AssistantParts(props: {
messages: AssistantMessage[]
showAssistantCopyPartID?: string | null
turnDurationMs?: number
useV2Actions?: boolean
working?: boolean
showReasoningSummaries?: boolean
shellToolDefaultOpen?: boolean
@ -724,6 +769,7 @@ export function AssistantParts(props: {
message={message()!}
showAssistantCopyPartID={props.showAssistantCopyPartID}
turnDurationMs={props.turnDurationMs}
useV2Actions={props.useV2Actions}
defaultOpen={partDefaultOpen(item()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)}
/>
</Show>
@ -851,7 +897,12 @@ 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} />
<UserMessageDisplay
message={userMessage() as UserMessage}
parts={props.parts}
actions={props.actions}
useV2Actions={props.useV2Actions}
/>
)}
</Match>
<Match when={props.message.role === "assistant" && props.message}>
@ -861,6 +912,7 @@ export function Message(props: MessageProps) {
parts={props.parts}
showAssistantCopyPartID={props.showAssistantCopyPartID}
showReasoningSummaries={props.showReasoningSummaries}
useV2Actions={props.useV2Actions}
/>
)}
</Match>
@ -873,6 +925,7 @@ export function AssistantMessageDisplay(props: {
parts: PartType[]
showAssistantCopyPartID?: string | null
showReasoningSummaries?: boolean
useV2Actions?: boolean
}) {
const emptyTools: ToolPart[] = []
const part = createMemo(() => index(props.parts))
@ -932,6 +985,7 @@ export function AssistantMessageDisplay(props: {
part={item()!}
message={props.message}
showAssistantCopyPartID={props.showAssistantCopyPartID}
useV2Actions={props.useV2Actions}
/>
</Show>
)
@ -1052,7 +1106,12 @@ export function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean; onS
)
}
export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[]; actions?: UserActions }) {
export function UserMessageDisplay(props: {
message: UserMessage
parts: PartType[]
actions?: UserActions
useV2Actions?: boolean
}) {
const data = useData()
const dialog = useDialog()
const i18n = useI18n()
@ -1191,38 +1250,30 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
</span>
</Show>
<Show when={props.actions?.revert}>
<Tooltip value={i18n.t("ui.message.revertMessage")} placement="top" gutter={4}>
<IconButton
icon="reset"
size="normal"
variant="ghost"
disabled={!!busy()}
onMouseDown={(e) => e.preventDefault()}
onClick={(event) => {
event.stopPropagation()
revert()
}}
aria-label={i18n.t("ui.message.revertMessage")}
/>
</Tooltip>
</Show>
<Tooltip
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")}
placement="top"
gutter={4}
>
<IconButton
icon={copied() ? "check" : "copy"}
size="normal"
variant="ghost"
onMouseDown={(e) => e.preventDefault()}
<MessageActionButton
icon="reset"
label={i18n.t("ui.message.revertMessage")}
useV2={props.useV2Actions}
disabled={!!busy()}
onMouseDown={(event) => event.preventDefault()}
onClick={(event) => {
event.stopPropagation()
void handleCopy()
revert()
}}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")}
aria-label={i18n.t("ui.message.revertMessage")}
/>
</Tooltip>
</Show>
<MessageActionButton
icon={copied() ? "check" : "copy"}
label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")}
useV2={props.useV2Actions}
onMouseDown={(event) => event.preventDefault()}
onClick={(event) => {
event.stopPropagation()
void handleCopy()
}}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")}
/>
</div>
</>
</Show>
@ -1286,6 +1337,7 @@ export function Part(props: MessagePartProps) {
onContentRendered={props.onContentRendered}
showAssistantCopyPartID={props.showAssistantCopyPartID}
turnDurationMs={props.turnDurationMs}
useV2Actions={props.useV2Actions}
/>
</Show>
)
@ -1566,20 +1618,14 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
</div>
<Show when={showCopy()}>
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
<Tooltip
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
placement="top"
gutter={4}
>
<IconButton
icon={copied() ? "check" : "copy"}
size="normal"
variant="ghost"
onMouseDown={(e) => e.preventDefault()}
onClick={handleCopy}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
/>
</Tooltip>
<MessageActionButton
icon={copied() ? "check" : "copy"}
label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
useV2={props.useV2Actions}
onMouseDown={(event) => event.preventDefault()}
onClick={handleCopy}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
/>
<Show when={meta()}>
<span data-slot="text-part-meta" class="text-12-regular text-text-weak cursor-default">
{meta()}