fix(ui): enable tool-part delete and long-press group toggle

This commit is contained in:
VooDisss
2026-03-03 09:26:17 +02:00
parent ed322a16bf
commit ec0bffe0c2
7 changed files with 80 additions and 16 deletions

View File

@@ -11,7 +11,7 @@ import { useI18n } from "../lib/i18n"
import { copyToClipboard } from "../lib/clipboard"
import { showToastNotification } from "../lib/notifications"
import { showAlertDialog } from "../stores/alerts"
import { deleteMessage } from "../stores/session-actions"
import { deleteMessage, deleteMessagePart } from "../stores/session-actions"
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
import type { DeleteHoverState } from "../types/delete-hover"
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
@@ -192,7 +192,17 @@ export default function MessageSection(props: MessageSectionProps) {
handleToggleTimelineSelection(segment.id)
return
}
const newSelected = new Set(selectedTimelineIds())
const selected = selectedTimelineIds()
const hasAnySelected = group.some((s) => selected.has(s.id))
if (!hasAnySelected) {
setSelectedTimelineIds((prev) => {
const next = new Set(prev)
for (const s of group) next.add(s.id)
return next
})
return
}
const newSelected = new Set(selected)
for (const s of group) newSelected.delete(s.id)
setSelectedTimelineIds(newSelected)
}
@@ -311,12 +321,39 @@ export default function MessageSection(props: MessageSectionProps) {
const [deleteHover, setDeleteHover] = createSignal<DeleteHoverState>({ kind: "none" })
const [selectedForDeletion, setSelectedForDeletion] = createSignal<Set<string>>(new Set<string>())
const isDeleteMode = createMemo(() => selectedForDeletion().size > 0)
const selectedDeleteCount = createMemo(() => selectedForDeletion().size)
const selectedToolParts = createMemo(() => {
const selected = selectedTimelineIds()
if (selected.size === 0) return [] as { messageId: string; partId: string }[]
const segments = timelineSegments()
const segmentById = new Map<string, TimelineSegment>()
for (const segment of segments) segmentById.set(segment.id, segment)
const toolParts: { messageId: string; partId: string }[] = []
const seen = new Set<string>()
for (const segId of selected) {
const segment = segmentById.get(segId)
if (!segment || segment.type !== "tool") continue
for (const partId of segment.toolPartIds ?? []) {
if (!partId) continue
const key = `${segment.messageId}:${partId}`
if (seen.has(key)) continue
seen.add(key)
toolParts.push({ messageId: segment.messageId, partId })
}
}
return toolParts
})
const deleteMessageIds = createMemo(() => selectedForDeletion())
const deleteToolParts = createMemo(() => {
const messageIds = deleteMessageIds()
return selectedToolParts().filter((entry) => !messageIds.has(entry.messageId))
})
const isDeleteMode = createMemo(() => deleteMessageIds().size > 0 || deleteToolParts().length > 0)
const selectedDeleteCount = createMemo(() => deleteMessageIds().size + deleteToolParts().length)
const selectedTokenTotal = createMemo(() => {
const selected = selectedForDeletion()
if (selected.size === 0) return 0
const selected = deleteMessageIds()
const toolParts = deleteToolParts()
if (selected.size === 0 && toolParts.length === 0) return 0
// Fresh-from-store chars: read parts directly via buildRecordDisplayData +
// getPartCharCount so the toolbar stays consistent with the xray overlay
// (which also reads live from the store). Falls back to segment totalChars
@@ -339,6 +376,27 @@ export default function MessageSection(props: MessageSectionProps) {
}
total += Math.max(Math.round(chars / 4), 1)
}
if (toolParts.length > 0) {
const partFallbackChars = new Map<string, number>()
for (const segment of timelineSegments()) {
if (segment.type !== "tool") continue
for (const partId of segment.toolPartIds ?? []) {
if (!partId || partFallbackChars.has(partId)) continue
partFallbackChars.set(partId, segment.totalChars)
}
}
for (const { messageId, partId } of toolParts) {
let chars = 0
const record = s.getMessage(messageId)
const partRecord = record?.parts?.[partId]
if (partRecord?.data) {
chars = getPartCharCount(partRecord.data)
} else {
chars = partFallbackChars.get(partId) ?? 0
}
total += Math.max(Math.round(chars / 4), 1)
}
}
return total
})
@@ -377,10 +435,12 @@ export default function MessageSection(props: MessageSectionProps) {
return
}
const segments = timelineSegments()
const segmentById = new Map<string, TimelineSegment>()
for (const segment of segments) segmentById.set(segment.id, segment)
const affectedMessageIds = new Set<string>()
for (const segId of timelineIds) {
const segment = segments.find((s) => s.id === segId)
if (segment) affectedMessageIds.add(segment.messageId)
const segment = segmentById.get(segId)
if (segment && segment.type !== "tool") affectedMessageIds.add(segment.messageId)
}
setSelectedForDeletion(affectedMessageIds)
})
@@ -395,8 +455,9 @@ export default function MessageSection(props: MessageSectionProps) {
}
const deleteSelectedMessages = async () => {
const selected = selectedForDeletion()
if (selected.size === 0) return
const selected = deleteMessageIds()
const toolParts = deleteToolParts()
if (selected.size === 0 && toolParts.length === 0) return
const idsInSessionOrder = messageIds()
const toDelete: string[] = []
@@ -411,6 +472,9 @@ export default function MessageSection(props: MessageSectionProps) {
for (const messageId of toDelete) {
await deleteMessage(props.instanceId, props.sessionId, messageId)
}
for (const { messageId, partId } of toolParts) {
await deleteMessagePart(props.instanceId, props.sessionId, messageId, partId)
}
clearDeleteMode()
} catch (error) {
showAlertDialog(t("messageSection.bulkDelete.failedMessage"), {

View File

@@ -83,7 +83,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "Select message for deletion",
"messageSection.bulkDelete.toolbarAriaLabel": "Selected messages ({count})",
"messageSection.bulkDelete.toolbarAriaLabel": "Selected items ({count})",
"messageSection.bulkDelete.deleteSelectedTitle": "Delete selected messages",
"messageSection.bulkDelete.selectAllTitle": "Select all messages",
"messageSection.bulkDelete.moreOptionsTitle": "More options",

View File

@@ -85,7 +85,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "Seleccionar mensaje para eliminar",
"messageSection.bulkDelete.toolbarAriaLabel": "Mensajes seleccionados ({count})",
"messageSection.bulkDelete.toolbarAriaLabel": "Elementos seleccionados ({count})",
"messageSection.bulkDelete.deleteSelectedTitle": "Eliminar mensajes seleccionados",
"messageSection.bulkDelete.selectAllTitle": "Seleccionar todos los mensajes",
"messageSection.bulkDelete.moreOptionsTitle": "Más opciones",

View File

@@ -85,7 +85,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "Sélectionner le message pour suppression",
"messageSection.bulkDelete.toolbarAriaLabel": "Messages sélectionnés ({count})",
"messageSection.bulkDelete.toolbarAriaLabel": "Éléments sélectionnés ({count})",
"messageSection.bulkDelete.deleteSelectedTitle": "Supprimer les messages sélectionnés",
"messageSection.bulkDelete.selectAllTitle": "Tout sélectionner",
"messageSection.bulkDelete.moreOptionsTitle": "Plus d'options",

View File

@@ -85,7 +85,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "削除するメッセージを選択",
"messageSection.bulkDelete.toolbarAriaLabel": "選択したメッセージ{count}",
"messageSection.bulkDelete.toolbarAriaLabel": "選択した項目{count}",
"messageSection.bulkDelete.deleteSelectedTitle": "選択したメッセージを削除",
"messageSection.bulkDelete.selectAllTitle": "すべて選択",
"messageSection.bulkDelete.moreOptionsTitle": "その他のオプション",

View File

@@ -85,7 +85,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "Выбрать сообщение для удаления",
"messageSection.bulkDelete.toolbarAriaLabel": "Выбранные сообщения ({count})",
"messageSection.bulkDelete.toolbarAriaLabel": "Выбранные элементы ({count})",
"messageSection.bulkDelete.deleteSelectedTitle": "Удалить выбранные сообщения",
"messageSection.bulkDelete.selectAllTitle": "Выбрать все сообщения",
"messageSection.bulkDelete.moreOptionsTitle": "Больше настроек",

View File

@@ -85,7 +85,7 @@ export const messagingMessages = {
"messageItem.selection.checkboxAriaLabel": "选择要删除的消息",
"messageSection.bulkDelete.toolbarAriaLabel": "已选择的消息{count}",
"messageSection.bulkDelete.toolbarAriaLabel": "已选择的项目{count}",
"messageSection.bulkDelete.deleteSelectedTitle": "删除已选择的消息",
"messageSection.bulkDelete.selectAllTitle": "全选消息",
"messageSection.bulkDelete.moreOptionsTitle": "更多选项",