feat(app): update message part ui to v2 (#34394)
This commit is contained in:
parent
b5f92c9f48
commit
0a5e617da8
2 changed files with 92 additions and 44 deletions
|
|
@ -1013,6 +1013,7 @@ export function MessageTimeline(props: {
|
||||||
message={message()}
|
message={message()}
|
||||||
showAssistantCopyPartID={assistantCopyPartID(row().userMessageID)}
|
showAssistantCopyPartID={assistantCopyPartID(row().userMessageID)}
|
||||||
turnDurationMs={turnDurationMs(row().userMessageID)}
|
turnDurationMs={turnDurationMs(row().userMessageID)}
|
||||||
|
useV2Actions={settings.general.newLayoutDesigns()}
|
||||||
defaultOpen={defaultOpen()}
|
defaultOpen={defaultOpen()}
|
||||||
toolOpen={toolOpen[part().id] ?? defaultOpen()}
|
toolOpen={toolOpen[part().id] ?? defaultOpen()}
|
||||||
onToolOpenChange={(open) => setToolOpen(part().id, open)}
|
onToolOpenChange={(open) => setToolOpen(part().id, open)}
|
||||||
|
|
@ -1120,6 +1121,7 @@ export function MessageTimeline(props: {
|
||||||
message={message()}
|
message={message()}
|
||||||
parts={getMsgParts(userMessageRow().userMessageID)}
|
parts={getMsgParts(userMessageRow().userMessageID)}
|
||||||
actions={props.actions}
|
actions={props.actions}
|
||||||
|
useV2Actions={settings.general.newLayoutDesigns()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
onCleanup,
|
onCleanup,
|
||||||
Index,
|
Index,
|
||||||
type JSX,
|
type JSX,
|
||||||
|
type ComponentProps,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import stripAnsi from "strip-ansi"
|
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 { checksum } from "@opencode-ai/core/util/encode"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
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 { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
|
import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
|
||||||
import { AnimatedCountList } from "./tool-count-summary"
|
import { AnimatedCountList } from "./tool-count-summary"
|
||||||
|
|
@ -161,6 +164,7 @@ export interface MessageProps {
|
||||||
actions?: UserActions
|
actions?: UserActions
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
showReasoningSummaries?: boolean
|
showReasoningSummaries?: boolean
|
||||||
|
useV2Actions?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise<void> | void
|
export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise<void> | void
|
||||||
|
|
@ -182,6 +186,46 @@ export interface MessagePartProps {
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
turnDurationMs?: number
|
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>
|
export type PartComponent = Component<MessagePartProps>
|
||||||
|
|
@ -640,6 +684,7 @@ export function AssistantParts(props: {
|
||||||
messages: AssistantMessage[]
|
messages: AssistantMessage[]
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
turnDurationMs?: number
|
turnDurationMs?: number
|
||||||
|
useV2Actions?: boolean
|
||||||
working?: boolean
|
working?: boolean
|
||||||
showReasoningSummaries?: boolean
|
showReasoningSummaries?: boolean
|
||||||
shellToolDefaultOpen?: boolean
|
shellToolDefaultOpen?: boolean
|
||||||
|
|
@ -724,6 +769,7 @@ export function AssistantParts(props: {
|
||||||
message={message()!}
|
message={message()!}
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
turnDurationMs={props.turnDurationMs}
|
turnDurationMs={props.turnDurationMs}
|
||||||
|
useV2Actions={props.useV2Actions}
|
||||||
defaultOpen={partDefaultOpen(item()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)}
|
defaultOpen={partDefaultOpen(item()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -851,7 +897,12 @@ export function Message(props: MessageProps) {
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={props.message.role === "user" && props.message}>
|
<Match when={props.message.role === "user" && props.message}>
|
||||||
{(userMessage) => (
|
{(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>
|
||||||
<Match when={props.message.role === "assistant" && props.message}>
|
<Match when={props.message.role === "assistant" && props.message}>
|
||||||
|
|
@ -861,6 +912,7 @@ export function Message(props: MessageProps) {
|
||||||
parts={props.parts}
|
parts={props.parts}
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
showReasoningSummaries={props.showReasoningSummaries}
|
showReasoningSummaries={props.showReasoningSummaries}
|
||||||
|
useV2Actions={props.useV2Actions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
|
|
@ -873,6 +925,7 @@ export function AssistantMessageDisplay(props: {
|
||||||
parts: PartType[]
|
parts: PartType[]
|
||||||
showAssistantCopyPartID?: string | null
|
showAssistantCopyPartID?: string | null
|
||||||
showReasoningSummaries?: boolean
|
showReasoningSummaries?: boolean
|
||||||
|
useV2Actions?: boolean
|
||||||
}) {
|
}) {
|
||||||
const emptyTools: ToolPart[] = []
|
const emptyTools: ToolPart[] = []
|
||||||
const part = createMemo(() => index(props.parts))
|
const part = createMemo(() => index(props.parts))
|
||||||
|
|
@ -932,6 +985,7 @@ export function AssistantMessageDisplay(props: {
|
||||||
part={item()!}
|
part={item()!}
|
||||||
message={props.message}
|
message={props.message}
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
|
useV2Actions={props.useV2Actions}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</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 data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
@ -1191,38 +1250,30 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.actions?.revert}>
|
<Show when={props.actions?.revert}>
|
||||||
<Tooltip value={i18n.t("ui.message.revertMessage")} placement="top" gutter={4}>
|
<MessageActionButton
|
||||||
<IconButton
|
icon="reset"
|
||||||
icon="reset"
|
label={i18n.t("ui.message.revertMessage")}
|
||||||
size="normal"
|
useV2={props.useV2Actions}
|
||||||
variant="ghost"
|
disabled={!!busy()}
|
||||||
disabled={!!busy()}
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
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()}
|
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation()
|
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>
|
</div>
|
||||||
</>
|
</>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -1286,6 +1337,7 @@ export function Part(props: MessagePartProps) {
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||||
turnDurationMs={props.turnDurationMs}
|
turnDurationMs={props.turnDurationMs}
|
||||||
|
useV2Actions={props.useV2Actions}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|
@ -1566,20 +1618,14 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
||||||
</div>
|
</div>
|
||||||
<Show when={showCopy()}>
|
<Show when={showCopy()}>
|
||||||
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
|
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
|
||||||
<Tooltip
|
<MessageActionButton
|
||||||
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
|
icon={copied() ? "check" : "copy"}
|
||||||
placement="top"
|
label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
|
||||||
gutter={4}
|
useV2={props.useV2Actions}
|
||||||
>
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
<IconButton
|
onClick={handleCopy}
|
||||||
icon={copied() ? "check" : "copy"}
|
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
|
||||||
size="normal"
|
/>
|
||||||
variant="ghost"
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
onClick={handleCopy}
|
|
||||||
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Show when={meta()}>
|
<Show when={meta()}>
|
||||||
<span data-slot="text-part-meta" class="text-12-regular text-text-weak cursor-default">
|
<span data-slot="text-part-meta" class="text-12-regular text-text-weak cursor-default">
|
||||||
{meta()}
|
{meta()}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue