fix(ui): sync delete-hover overlays across preview and stream
This commit is contained in:
@@ -199,6 +199,7 @@ interface MessageContentItemProps {
|
|||||||
onFork?: (messageId?: string) => void
|
onFork?: (messageId?: string) => void
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +288,7 @@ function MessageContentItem(props: MessageContentItemProps) {
|
|||||||
isQueued={isQueued()}
|
isQueued={isQueued()}
|
||||||
showAgentMeta={showAgentMeta()}
|
showAgentMeta={showAgentMeta()}
|
||||||
showDeleteMessage={props.showDeleteMessage}
|
showDeleteMessage={props.showDeleteMessage}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
@@ -305,6 +307,7 @@ interface ToolCallItemProps {
|
|||||||
partId: string
|
partId: string
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,10 +404,18 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDeleteHoveredFromStore = () => {
|
||||||
|
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
||||||
|
return hover.kind === "part" && hover.messageId === props.messageId && hover.partId === props.partId
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={toolPart()}>
|
<Show when={toolPart()}>
|
||||||
{(resolvedToolPart) => (
|
{(resolvedToolPart) => (
|
||||||
<div class="delete-hover-scope" data-delete-part-hover={hoverDeletePart() ? "true" : undefined}>
|
<div
|
||||||
|
class="delete-hover-scope"
|
||||||
|
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
|
||||||
|
>
|
||||||
<div class="tool-call-header-label">
|
<div class="tool-call-header-label">
|
||||||
<div class="tool-call-header-meta">
|
<div class="tool-call-header-meta">
|
||||||
<span class="tool-call-icon">{TOOL_ICON}</span>
|
<span class="tool-call-icon">{TOOL_ICON}</span>
|
||||||
@@ -747,6 +758,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
messageIndex={props.messageIndex}
|
messageIndex={props.messageIndex}
|
||||||
lastAssistantIndex={props.lastAssistantIndex}
|
lastAssistantIndex={props.lastAssistantIndex}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onRevert={props.onRevert}
|
onRevert={props.onRevert}
|
||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
@@ -765,6 +777,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
messageId={toolItem.messageId}
|
messageId={toolItem.messageId}
|
||||||
partId={toolItem.partId}
|
partId={toolItem.partId}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
onContentRendered={props.onContentRendered}
|
onContentRendered={props.onContentRendered}
|
||||||
/>
|
/>
|
||||||
@@ -809,6 +822,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
messageId={(item as CompactionDisplayItem).messageId}
|
messageId={(item as CompactionDisplayItem).messageId}
|
||||||
partId={(item as CompactionDisplayItem).partId}
|
partId={(item as CompactionDisplayItem).partId}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -823,6 +837,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta}
|
showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta}
|
||||||
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
|
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
|
||||||
showDeleteMessage={index() === 0}
|
showDeleteMessage={index() === 0}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
onDeleteHoverChange={props.onDeleteHoverChange}
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -858,6 +873,7 @@ interface CompactionCardProps {
|
|||||||
messageId: string
|
messageId: string
|
||||||
partId: string
|
partId: string
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,10 +930,15 @@ function CompactionCard(props: CompactionCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDeleteHoveredFromStore = () => {
|
||||||
|
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
||||||
|
return hover.kind === "part" && hover.messageId === props.messageId && hover.partId === props.partId
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`delete-hover-scope ${containerClass()} relative`}
|
class={`delete-hover-scope ${containerClass()} relative`}
|
||||||
data-delete-part-hover={hoverDeletePart() ? "true" : undefined}
|
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
|
||||||
style={{ "border-left": `4px solid ${borderColor()}` }}
|
style={{ "border-left": `4px solid ${borderColor()}` }}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label={t("messageBlock.compaction.ariaLabel")}
|
aria-label={t("messageBlock.compaction.ariaLabel")}
|
||||||
@@ -1120,6 +1141,7 @@ interface ReasoningCardProps {
|
|||||||
showAgentMeta?: boolean
|
showAgentMeta?: boolean
|
||||||
defaultExpanded?: boolean
|
defaultExpanded?: boolean
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,6 +1152,11 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
const [hoverDeletePart, setHoverDeletePart] = createSignal(false)
|
const [hoverDeletePart, setHoverDeletePart] = createSignal(false)
|
||||||
|
|
||||||
|
const isDeleteHoveredFromStore = () => {
|
||||||
|
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
||||||
|
return hover.kind === "part" && hover.messageId === props.messageId && hover.partId === props.partId
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setExpanded(Boolean(props.defaultExpanded))
|
setExpanded(Boolean(props.defaultExpanded))
|
||||||
})
|
})
|
||||||
@@ -1238,7 +1265,10 @@ function ReasoningCard(props: ReasoningCardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="delete-hover-scope message-reasoning-card" data-delete-part-hover={hoverDeletePart() ? "true" : undefined}>
|
<div
|
||||||
|
class="delete-hover-scope message-reasoning-card"
|
||||||
|
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
|
||||||
|
>
|
||||||
<div class="message-reasoning-header">
|
<div class="message-reasoning-header">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface MessageItemProps {
|
|||||||
showAgentMeta?: boolean
|
showAgentMeta?: boolean
|
||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
showDeleteMessage?: boolean
|
showDeleteMessage?: boolean
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,11 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||||
const [hoveredDeletePartId, setHoveredDeletePartId] = createSignal<string | null>(null)
|
const [hoveredDeletePartId, setHoveredDeletePartId] = createSignal<string | null>(null)
|
||||||
|
|
||||||
|
const isDeleteHoveredFromStore = (partId: string) => {
|
||||||
|
const hover = props.deleteHover?.() ?? ({ kind: "none" } as DeleteHoverState)
|
||||||
|
return hover.kind === "part" && hover.messageId === props.record.id && hover.partId === partId
|
||||||
|
}
|
||||||
|
|
||||||
const isUser = () => props.record.role === "user"
|
const isUser = () => props.record.role === "user"
|
||||||
const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
|
const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
|
||||||
|
|
||||||
@@ -447,7 +453,8 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
<For each={messageParts()}>
|
<For each={messageParts()}>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const partId = typeof (part as any)?.id === "string" ? ((part as any).id as string) : ""
|
const partId = typeof (part as any)?.id === "string" ? ((part as any).id as string) : ""
|
||||||
const isHoveredDeleteTarget = () => Boolean(partId) && hoveredDeletePartId() === partId
|
const isHoveredDeleteTarget = () =>
|
||||||
|
Boolean(partId) && (hoveredDeletePartId() === partId || isDeleteHoveredFromStore(partId))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -474,7 +481,8 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
{(attachment) => {
|
{(attachment) => {
|
||||||
const name = getAttachmentName(attachment)
|
const name = getAttachmentName(attachment)
|
||||||
const isImage = isImageAttachment(attachment)
|
const isImage = isImageAttachment(attachment)
|
||||||
const isHoveredDeleteTarget = () => hoveredDeletePartId() === attachment.id
|
const isHoveredDeleteTarget = () =>
|
||||||
|
Boolean(attachment.id) && (hoveredDeletePartId() === attachment.id || isDeleteHoveredFromStore(attachment.id))
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`delete-hover-scope attachment-chip ${isImage ? "attachment-chip-image" : ""}`}
|
class={`delete-hover-scope attachment-chip ${isImage ? "attachment-chip-image" : ""}`}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import type { Component } from "solid-js"
|
import type { Component } from "solid-js"
|
||||||
import MessageBlock from "./message-block"
|
import MessageBlock from "./message-block"
|
||||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
|
import type { DeleteHoverState } from "../types/delete-hover"
|
||||||
|
|
||||||
interface MessagePreviewProps {
|
interface MessagePreviewProps {
|
||||||
instanceId: string
|
instanceId: string
|
||||||
sessionId: string
|
sessionId: string
|
||||||
messageId: string
|
messageId: string
|
||||||
store: () => InstanceMessageStore
|
store: () => InstanceMessageStore
|
||||||
|
deleteHover?: () => DeleteHoverState
|
||||||
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
||||||
@@ -24,6 +27,8 @@ const MessagePreview: Component<MessagePreviewProps> = (props) => {
|
|||||||
showThinking={() => false}
|
showThinking={() => false}
|
||||||
thinkingDefaultExpanded={() => false}
|
thinkingDefaultExpanded={() => false}
|
||||||
showUsageMetrics={() => false}
|
showUsageMetrics={() => false}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -963,6 +963,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
showToolSegments={showTimelineToolsPreference()}
|
showToolSegments={showTimelineToolsPreference()}
|
||||||
deleteHover={deleteHover}
|
deleteHover={deleteHover}
|
||||||
|
onDeleteHoverChange={setDeleteHover}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ interface MessageTimelineProps {
|
|||||||
sessionId: string
|
sessionId: string
|
||||||
showToolSegments?: boolean
|
showToolSegments?: boolean
|
||||||
deleteHover?: () => DeleteHoverState
|
deleteHover?: () => DeleteHoverState
|
||||||
|
onDeleteHoverChange?: (state: DeleteHoverState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_TOOLTIP_LENGTH = 220
|
const MAX_TOOLTIP_LENGTH = 220
|
||||||
@@ -497,6 +498,8 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
sessionId={props.sessionId}
|
sessionId={props.sessionId}
|
||||||
store={store}
|
store={store}
|
||||||
|
deleteHover={props.deleteHover}
|
||||||
|
onDeleteHoverChange={props.onDeleteHoverChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user