feat(ui): add multi-select message deletion

This commit is contained in:
Shantur Rathore
2026-02-26 15:25:47 +00:00
parent 2991de528a
commit ab9e188b02
15 changed files with 639 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
import { For, Show, createSignal } from "solid-js"
import { For, Show, createEffect, createSignal, onCleanup } from "solid-js"
import { Copy, ListStart, Split, Trash, Undo } from "lucide-solid"
import type { MessageInfo, ClientPart, SDKAssistantMessageV2 } from "../types/message"
import { partHasRenderableText } from "../types/message"
@@ -27,6 +27,8 @@ interface MessageItemProps {
isQueued?: boolean
parts: ClientPart[]
onRevert?: (messageId: string) => void
selectedMessageIds?: () => Set<string>
onToggleSelectedMessage?: (messageId: string, selected: boolean) => void
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
onFork?: (messageId?: string) => void
showAgentMeta?: boolean
@@ -41,6 +43,46 @@ export default function MessageItem(props: MessageItemProps) {
const [deletingMessage, setDeletingMessage] = createSignal(false)
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
const isSelectedForDeletion = () => Boolean(props.selectedMessageIds?.().has(props.record.id))
let topRowEl: HTMLDivElement | undefined
let actionsEl: HTMLDivElement | undefined
let speakerPrimaryEl: HTMLDivElement | undefined
let metaMeasureEl: HTMLSpanElement | undefined
const [showMetaInline, setShowMetaInline] = createSignal(true)
const metaText = () => agentMeta()
const updateMetaLayout = () => {
const text = metaText()
if (!text) return
if (!topRowEl || !actionsEl || !speakerPrimaryEl || !metaMeasureEl) return
const rowWidth = topRowEl.getBoundingClientRect().width
const actionsWidth = actionsEl.getBoundingClientRect().width
const primaryWidth = speakerPrimaryEl.getBoundingClientRect().width
const metaWidth = metaMeasureEl.getBoundingClientRect().width
// Allow for the flex gap between left and actions.
const availableLeft = Math.max(0, rowWidth - actionsWidth - 12)
setShowMetaInline(primaryWidth + metaWidth + 8 <= availableLeft)
}
createEffect(() => {
const text = metaText()
if (!text || typeof ResizeObserver === "undefined") {
setShowMetaInline(true)
return
}
updateMetaLayout()
const observer = new ResizeObserver(() => updateMetaLayout())
if (topRowEl) observer.observe(topRowEl)
if (actionsEl) observer.observe(actionsEl)
if (speakerPrimaryEl) observer.observe(speakerPrimaryEl)
onCleanup(() => observer.disconnect())
})
const isUser = () => props.record.role === "user"
const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt
@@ -284,14 +326,47 @@ export default function MessageItem(props: MessageItemProps) {
return (
<div class={containerClass()}>
<header class={`message-item-header ${isUser() ? "pb-0.5" : "pb-0"}`}>
<div class="message-item-header-row message-item-header-row--top">
<div class="message-speaker">
<span class="message-speaker-label" data-role={isUser() ? "user" : "assistant"}>
{speakerLabel()}
</span>
<div class="message-item-header-row message-item-header-row--top" ref={(el) => (topRowEl = el)}>
<div class="message-header-left">
<div class="message-speaker-primary" ref={(el) => (speakerPrimaryEl = el)}>
<Show when={props.showDeleteMessage}>
<input
class="message-select-checkbox"
type="checkbox"
checked={isSelectedForDeletion()}
onClick={(event) => {
event.stopPropagation()
}}
onChange={(event) => {
event.stopPropagation()
const next = Boolean((event.currentTarget as HTMLInputElement).checked)
props.onToggleSelectedMessage?.(props.record.id, next)
}}
aria-label={t("messageItem.selection.checkboxAriaLabel")}
title={t("messageItem.selection.checkboxAriaLabel")}
/>
</Show>
<span class="message-speaker-label" data-role={isUser() ? "user" : "assistant"}>
{speakerLabel()}
</span>
</div>
<Show when={metaText() && showMetaInline()}>
<span class="message-agent-meta-inline">{metaText()}</span>
</Show>
<Show when={metaText()}>
<span
ref={(el) => (metaMeasureEl = el)}
class="message-agent-meta-inline message-agent-meta-inline--measure"
>
{metaText()}
</span>
</Show>
</div>
<div class="message-item-actions">
<div class="message-item-actions" ref={(el) => (actionsEl = el)}>
<Show when={isUser()}>
<div class="message-action-group">
<button
@@ -394,12 +469,10 @@ export default function MessageItem(props: MessageItemProps) {
</div>
</div>
<Show when={agentMeta()}>
{(meta) => (
<div class="message-item-header-row message-item-header-row--bottom">
<span class="message-agent-meta">{meta()}</span>
</div>
)}
<Show when={metaText() && !showMetaInline()}>
<div class="message-item-header-row message-item-header-row--meta">
<span class="message-agent-meta-block">{metaText()}</span>
</div>
</Show>
</header>