fix(ui): enable tool-part delete and long-press group toggle
This commit is contained in:
@@ -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"), {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "その他のオプション",
|
||||
|
||||
@@ -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": "Больше настроек",
|
||||
|
||||
@@ -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": "更多选项",
|
||||
|
||||
Reference in New Issue
Block a user