chore(ui): remove delete-part actions and use trash for delete

This commit is contained in:
Shantur Rathore
2026-02-26 10:25:38 +00:00
parent b91dbb1a60
commit f1bd681618
4 changed files with 22 additions and 311 deletions

View File

@@ -1,5 +1,5 @@
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, untrack } from "solid-js"
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, MessageSquareX, Trash2 } from "lucide-solid"
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, Trash2 } from "lucide-solid"
import MessageItem from "./message-item"
import ToolCall from "./tool-call"
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
@@ -12,7 +12,6 @@ import { formatTokenTotal } from "../lib/formatters"
import { sessions, setActiveParentSession, setActiveSession } from "../stores/sessions"
import { setActiveInstanceId } from "../stores/instances"
import { showAlertDialog } from "../stores/alerts"
import { deleteMessagePart } from "../stores/session-actions"
import { deleteMessage } from "../stores/session-actions"
import { useI18n } from "../lib/i18n"
import type { DeleteHoverState } from "../types/delete-hover"
@@ -199,7 +198,6 @@ interface MessageContentItemProps {
onFork?: (messageId?: string) => void
onContentRendered?: () => void
showDeleteMessage?: boolean
deleteHover?: () => DeleteHoverState
onDeleteHoverChange?: (state: DeleteHoverState) => void
}
@@ -288,7 +286,6 @@ function MessageContentItem(props: MessageContentItemProps) {
isQueued={isQueued()}
showAgentMeta={showAgentMeta()}
showDeleteMessage={props.showDeleteMessage}
deleteHover={props.deleteHover}
onDeleteHoverChange={props.onDeleteHoverChange}
onRevert={props.onRevert}
onFork={props.onFork}
@@ -307,15 +304,12 @@ interface ToolCallItemProps {
partId: string
onContentRendered?: () => void
showDeleteMessage?: boolean
deleteHover?: () => DeleteHoverState
onDeleteHoverChange?: (state: DeleteHoverState) => void
}
function ToolCallItem(props: ToolCallItemProps) {
const { t } = useI18n()
const [deleting, setDeleting] = createSignal(false)
const [deletingMessage, setDeletingMessage] = createSignal(false)
const [hoverDeletePart, setHoverDeletePart] = createSignal(false)
const record = createMemo(() => props.store().getMessage(props.messageId))
const messageInfo = createMemo(() => props.store().getMessageInfo(props.messageId))
@@ -332,14 +326,6 @@ function ToolCallItem(props: ToolCallItemProps) {
const messageVersion = createMemo(() => record()?.revision ?? 0)
const partVersion = createMemo(() => partEntry()?.revision ?? 0)
const deleteDisabled = createMemo(() => {
if (deleting()) return true
// Avoid deleting while a tool is actively running to prevent confusing UI states.
if (isToolStateRunning(toolState())) return true
// Avoid deleting permission prompts from here; those are interactive.
return Boolean(toolPart()?.pendingPermission)
})
const taskSessionId = createMemo(() => {
const state = toolState()
if (!state) return ""
@@ -363,26 +349,6 @@ function ToolCallItem(props: ToolCallItemProps) {
navigateToTaskSession(location)
}
const handleDeleteToolPart = async (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
if (deleteDisabled()) return
setDeleting(true)
try {
await deleteMessagePart(props.instanceId, props.sessionId, props.messageId, props.partId)
} catch (error) {
showAlertDialog(t("messageBlock.tool.deletePart.failed.message"), {
title: t("messageBlock.tool.deletePart.failed.title"),
detail: error instanceof Error ? error.message : String(error),
variant: "error",
})
} finally {
setDeleting(false)
}
}
const handleDeleteMessage = async (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
@@ -404,18 +370,10 @@ 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 (
<Show when={toolPart()}>
{(resolvedToolPart) => (
<div
class="delete-hover-scope"
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
>
<div class="delete-hover-scope">
<div class="tool-call-header-label">
<div class="tool-call-header-meta">
<span class="tool-call-icon">{TOOL_ICON}</span>
@@ -423,7 +381,7 @@ function ToolCallItem(props: ToolCallItemProps) {
<span class="tool-name">{toolName() || t("messageBlock.tool.unknown")}</span>
</div>
<div class="flex items-center gap-2">
<div class="flex items-center gap-2">
<Show when={taskSessionId()}>
<button
class="tool-call-header-button"
@@ -437,41 +395,22 @@ function ToolCallItem(props: ToolCallItemProps) {
</button>
</Show>
<Show when={props.showDeleteMessage}>
<button
class="tool-call-header-button"
type="button"
disabled={deleteDisabled()}
onClick={handleDeleteToolPart}
onMouseEnter={() => {
setHoverDeletePart(true)
props.onDeleteHoverChange?.({ kind: "part", messageId: props.messageId, partId: props.partId, partType: "tool" })
}}
onMouseLeave={() => {
setHoverDeletePart(false)
props.onDeleteHoverChange?.({ kind: "none" })
}}
title={deleting() ? t("messageBlock.tool.deletePart.deleting") : t("messageBlock.tool.deletePart.label")}
aria-label={deleting() ? t("messageBlock.tool.deletePart.deleting") : t("messageBlock.tool.deletePart.label")}
disabled={deletingMessage()}
onClick={handleDeleteMessage}
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "message", messageId: props.messageId })}
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
<Show when={props.showDeleteMessage}>
<button
class="tool-call-header-button"
type="button"
disabled={deletingMessage()}
onClick={handleDeleteMessage}
onMouseEnter={() => props.onDeleteHoverChange?.({ kind: "message", messageId: props.messageId })}
onMouseLeave={() => props.onDeleteHoverChange?.({ kind: "none" })}
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
</div>
</Show>
</div>
</div>
<ToolCall
toolCall={resolvedToolPart()}
@@ -758,7 +697,6 @@ export default function MessageBlock(props: MessageBlockProps) {
messageIndex={props.messageIndex}
lastAssistantIndex={props.lastAssistantIndex}
showDeleteMessage={index() === 0}
deleteHover={props.deleteHover}
onDeleteHoverChange={props.onDeleteHoverChange}
onRevert={props.onRevert}
onFork={props.onFork}
@@ -777,7 +715,6 @@ export default function MessageBlock(props: MessageBlockProps) {
messageId={toolItem.messageId}
partId={toolItem.partId}
showDeleteMessage={index() === 0}
deleteHover={props.deleteHover}
onDeleteHoverChange={props.onDeleteHoverChange}
onContentRendered={props.onContentRendered}
/>
@@ -820,9 +757,7 @@ export default function MessageBlock(props: MessageBlockProps) {
instanceId={props.instanceId}
sessionId={props.sessionId}
messageId={(item as CompactionDisplayItem).messageId}
partId={(item as CompactionDisplayItem).partId}
showDeleteMessage={index() === 0}
deleteHover={props.deleteHover}
onDeleteHoverChange={props.onDeleteHoverChange}
/>
</Match>
@@ -833,11 +768,9 @@ export default function MessageBlock(props: MessageBlockProps) {
instanceId={props.instanceId}
sessionId={props.sessionId}
messageId={(item as ReasoningDisplayItem).messageId}
partId={(item as ReasoningDisplayItem).partId}
showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta}
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
showDeleteMessage={index() === 0}
deleteHover={props.deleteHover}
onDeleteHoverChange={props.onDeleteHoverChange}
/>
</Match>
@@ -871,17 +804,13 @@ interface CompactionCardProps {
instanceId: string
sessionId: string
messageId: string
partId: string
showDeleteMessage?: boolean
deleteHover?: () => DeleteHoverState
onDeleteHoverChange?: (state: DeleteHoverState) => void
}
function CompactionCard(props: CompactionCardProps) {
const { t } = useI18n()
const [deleting, setDeleting] = createSignal(false)
const [deletingMessage, setDeletingMessage] = createSignal(false)
const [hoverDeletePart, setHoverDeletePart] = createSignal(false)
const isAuto = () => Boolean((props.part as any)?.auto)
const label = () => (isAuto() ? t("messageBlock.compaction.autoLabel") : t("messageBlock.compaction.manualLabel"))
const borderColor = () => props.borderColor ?? (isAuto() ? "var(--session-status-compacting-fg)" : USER_BORDER_COLOR)
@@ -889,26 +818,6 @@ function CompactionCard(props: CompactionCardProps) {
const containerClass = () =>
`message-compaction-card ${isAuto() ? "message-compaction-card--auto" : "message-compaction-card--manual"}`
const canDelete = () => Boolean(props.partId) && !deleting()
const handleDelete = async (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
if (!canDelete()) return
setDeleting(true)
try {
await deleteMessagePart(props.instanceId, props.sessionId, props.messageId, props.partId)
} catch (error) {
showAlertDialog(t("messagePart.actions.deleteFailedMessage"), {
title: t("messagePart.actions.deleteFailedTitle"),
detail: error instanceof Error ? error.message : String(error),
variant: "error",
})
} finally {
setDeleting(false)
}
}
const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage()
const handleDeleteMessage = async (event: MouseEvent) => {
@@ -930,15 +839,9 @@ 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 (
<div
class={`delete-hover-scope ${containerClass()} relative`}
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
style={{ "border-left": `4px solid ${borderColor()}` }}
role="status"
aria-label={t("messageBlock.compaction.ariaLabel")}
@@ -955,28 +858,9 @@ function CompactionCard(props: CompactionCardProps) {
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
<button
type="button"
class="tool-call-header-button"
disabled={!canDelete()}
onClick={handleDelete}
onMouseEnter={() => {
setHoverDeletePart(true)
props.onDeleteHoverChange?.({ kind: "part", messageId: props.messageId, partId: props.partId, partType: "compaction" })
}}
onMouseLeave={() => {
setHoverDeletePart(false)
props.onDeleteHoverChange?.({ kind: "none" })
}}
title={t("messagePart.actions.deleteTitle")}
aria-label={t("messagePart.actions.deleteTitle")}
>
{deleting() ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")}
</button>
</div>
<div class="message-compaction-row">
@@ -1097,7 +981,7 @@ function StepCard(props: StepCardProps) {
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
@@ -1137,25 +1021,16 @@ interface ReasoningCardProps {
instanceId: string
sessionId: string
messageId: string
partId: string
showAgentMeta?: boolean
defaultExpanded?: boolean
showDeleteMessage?: boolean
deleteHover?: () => DeleteHoverState
onDeleteHoverChange?: (state: DeleteHoverState) => void
}
function ReasoningCard(props: ReasoningCardProps) {
const { t } = useI18n()
const [expanded, setExpanded] = createSignal(Boolean(props.defaultExpanded))
const [deleting, setDeleting] = createSignal(false)
const [deletingMessage, setDeletingMessage] = 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(() => {
setExpanded(Boolean(props.defaultExpanded))
@@ -1222,27 +1097,6 @@ function ReasoningCard(props: ReasoningCardProps) {
const viewHideLabel = () =>
expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")
const hasDeleteTarget = () => Boolean(props.partId)
const canDelete = () => hasDeleteTarget() && !deleting()
const handleDelete = async (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
if (!canDelete()) return
setDeleting(true)
try {
await deleteMessagePart(props.instanceId, props.sessionId, props.messageId, props.partId)
} catch (error) {
showAlertDialog(t("messagePart.actions.deleteFailedMessage"), {
title: t("messagePart.actions.deleteFailedTitle"),
detail: error instanceof Error ? error.message : String(error),
variant: "error",
})
} finally {
setDeleting(false)
}
}
const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage()
const handleDeleteMessage = async (event: MouseEvent) => {
@@ -1265,10 +1119,7 @@ function ReasoningCard(props: ReasoningCardProps) {
}
return (
<div
class="delete-hover-scope message-reasoning-card"
data-delete-part-hover={(hoverDeletePart() || isDeleteHoveredFromStore()) ? "true" : undefined}
>
<div class="delete-hover-scope message-reasoning-card">
<div class="message-reasoning-header">
<button
type="button"
@@ -1313,27 +1164,6 @@ function ReasoningCard(props: ReasoningCardProps) {
</Show>
</button>
<Show when={hasDeleteTarget()}>
<button
type="button"
class="message-action-button"
onClick={handleDelete}
disabled={!canDelete()}
onMouseEnter={() => {
setHoverDeletePart(true)
props.onDeleteHoverChange?.({ kind: "part", messageId: props.messageId, partId: props.partId, partType: "reasoning" })
}}
onMouseLeave={() => {
setHoverDeletePart(false)
props.onDeleteHoverChange?.({ kind: "none" })
}}
aria-label={t("messagePart.actions.deleteTitle")}
title={t("messagePart.actions.deleteTitle")}
>
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
<Show when={props.showDeleteMessage}>
<button
type="button"
@@ -1345,7 +1175,7 @@ function ReasoningCard(props: ReasoningCardProps) {
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>

View File

@@ -1,5 +1,5 @@
import { For, Show, createSignal } from "solid-js"
import { Copy, MessageSquareX, Split, Trash2, Undo } from "lucide-solid"
import { Copy, Split, Trash2, Undo } from "lucide-solid"
import type { MessageInfo, ClientPart, SDKAssistantMessageV2 } from "../types/message"
import { partHasRenderableText } from "../types/message"
import type { MessageRecord } from "../stores/message-v2/types"
@@ -7,7 +7,7 @@ import MessagePart from "./message-part"
import { copyToClipboard } from "../lib/clipboard"
import { useI18n } from "../lib/i18n"
import { showAlertDialog } from "../stores/alerts"
import { deleteMessage, deleteMessagePart } from "../stores/session-actions"
import { deleteMessage } from "../stores/session-actions"
import { isTauriHost } from "../lib/runtime-env"
import type { DeleteHoverState } from "../types/delete-hover"
@@ -23,21 +23,13 @@ interface MessageItemProps {
showAgentMeta?: boolean
onContentRendered?: () => void
showDeleteMessage?: boolean
deleteHover?: () => DeleteHoverState
onDeleteHoverChange?: (state: DeleteHoverState) => void
}
export default function MessageItem(props: MessageItemProps) {
const { t } = useI18n()
const [copied, setCopied] = createSignal(false)
const [deletingParts, setDeletingParts] = createSignal<Set<string>>(new Set())
const [deletingMessage, setDeletingMessage] = createSignal(false)
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 createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
@@ -201,50 +193,6 @@ export default function MessageItem(props: MessageItemProps) {
setTimeout(() => setCopied(false), 2000)
}
const deletableTextPartId = () => {
const part = props.parts.find((candidate) => {
if (!candidate || candidate.type !== "text") return false
const id = (candidate as any).id
if (typeof id !== "string" || id.length === 0) return false
return !Boolean((candidate as any).synthetic)
})
return (part as any)?.id as string | undefined
}
const isDeletingPart = (partId?: string) => {
if (!partId) return false
return deletingParts().has(partId)
}
const setPartDeleting = (partId: string, value: boolean) => {
setDeletingParts((prev) => {
const next = new Set(prev)
if (value) {
next.add(partId)
} else {
next.delete(partId)
}
return next
})
}
const handleDeletePart = async (partId?: string) => {
if (!partId) return
if (isDeletingPart(partId)) return
setPartDeleting(partId, true)
try {
await deleteMessagePart(props.instanceId, props.sessionId, props.record.id, partId)
} catch (error) {
showAlertDialog(t("messagePart.actions.deleteFailedMessage"), {
title: t("messagePart.actions.deleteFailedTitle"),
detail: error instanceof Error ? error.message : String(error),
variant: "error",
})
} finally {
setPartDeleting(partId, false)
}
}
const handleDeleteMessage = async () => {
if (deletingMessage()) return
setDeletingMessage(true)
@@ -366,7 +314,7 @@ export default function MessageItem(props: MessageItemProps) {
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
</div>
@@ -382,28 +330,6 @@ export default function MessageItem(props: MessageItemProps) {
<Copy class="w-3.5 h-3.5" aria-hidden="true" />
</button>
<Show when={deletableTextPartId()}>
{(partId) => (
<button
class="message-action-button"
onClick={() => void handleDeletePart(partId())}
disabled={isDeletingPart(partId())}
onMouseEnter={() => {
setHoveredDeletePartId(partId())
props.onDeleteHoverChange?.({ kind: "part", messageId: props.record.id, partId: partId(), partType: "text" })
}}
onMouseLeave={() => {
setHoveredDeletePartId(null)
props.onDeleteHoverChange?.({ kind: "none" })
}}
title={isDeletingPart(partId()) ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")}
aria-label={isDeletingPart(partId()) ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")}
>
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
)}
</Show>
<Show when={props.showDeleteMessage}>
<button
class="message-action-button"
@@ -414,7 +340,7 @@ export default function MessageItem(props: MessageItemProps) {
title={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
aria-label={deletingMessage() ? t("messageItem.actions.deletingMessage") : t("messageItem.actions.deleteMessage")}
>
<MessageSquareX class="w-3.5 h-3.5" aria-hidden="true" />
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
</Show>
</div>
@@ -452,16 +378,8 @@ export default function MessageItem(props: MessageItemProps) {
<For each={messageParts()}>
{(part) => {
const partId = typeof (part as any)?.id === "string" ? ((part as any).id as string) : ""
const isHoveredDeleteTarget = () =>
Boolean(partId) && (hoveredDeletePartId() === partId || isDeleteHoveredFromStore(partId))
return (
<div
class="delete-hover-scope message-part-shell"
data-part-id={partId}
data-delete-part-hover={isHoveredDeleteTarget() ? "true" : undefined}
>
<div class="message-part-shell">
<MessagePart
part={part}
messageType={props.record.role}
@@ -481,12 +399,9 @@ export default function MessageItem(props: MessageItemProps) {
{(attachment) => {
const name = getAttachmentName(attachment)
const isImage = isImageAttachment(attachment)
const isHoveredDeleteTarget = () =>
Boolean(attachment.id) && (hoveredDeletePartId() === attachment.id || isDeleteHoveredFromStore(attachment.id))
return (
<div
class={`delete-hover-scope attachment-chip ${isImage ? "attachment-chip-image" : ""}`}
data-delete-part-hover={isHoveredDeleteTarget() ? "true" : undefined}
class={`attachment-chip ${isImage ? "attachment-chip-image" : ""}`}
title={name}
>
<Show when={isImage} fallback={
@@ -516,29 +431,6 @@ export default function MessageItem(props: MessageItemProps) {
</svg>
</button>
</Show>
<button
type="button"
onClick={() => void handleDeletePart(attachment.id)}
class="attachment-remove"
disabled={isDeletingPart(attachment.id)}
onMouseEnter={() => {
if (attachment.id) {
setHoveredDeletePartId(attachment.id)
props.onDeleteHoverChange?.({ kind: "part", messageId: props.record.id, partId: attachment.id, partType: "file" })
}
}}
onMouseLeave={() => {
setHoveredDeletePartId(null)
props.onDeleteHoverChange?.({ kind: "none" })
}}
aria-label={t("messagePart.actions.deleteTitle")}
title={t("messagePart.actions.deleteTitle")}
>
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<Show when={isImage}>
<div class="attachment-chip-preview">
<img src={attachment.url} alt={name} />

View File

@@ -419,16 +419,6 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
if (hover.kind === "message") {
return hover.messageId === segment.messageId
}
if (hover.kind === "part") {
if (hover.messageId !== segment.messageId) return false
if (segment.type === "tool") {
return segment.toolPartIds?.includes(hover.partId) ?? false
}
if (segment.type === "compaction") {
return segment.partId === hover.partId
}
return segment.partIds?.includes(hover.partId) ?? false
}
return false
}

View File

@@ -1,4 +1,3 @@
export type DeleteHoverState =
| { kind: "none" }
| { kind: "message"; messageId: string }
| { kind: "part"; messageId: string; partId: string; partType?: string }