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 { copyToClipboard } from "../lib/clipboard"
|
||||||
import { showToastNotification } from "../lib/notifications"
|
import { showToastNotification } from "../lib/notifications"
|
||||||
import { showAlertDialog } from "../stores/alerts"
|
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 { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
import type { DeleteHoverState } from "../types/delete-hover"
|
import type { DeleteHoverState } from "../types/delete-hover"
|
||||||
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
|
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
|
||||||
@@ -192,7 +192,17 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
handleToggleTimelineSelection(segment.id)
|
handleToggleTimelineSelection(segment.id)
|
||||||
return
|
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)
|
for (const s of group) newSelected.delete(s.id)
|
||||||
setSelectedTimelineIds(newSelected)
|
setSelectedTimelineIds(newSelected)
|
||||||
}
|
}
|
||||||
@@ -311,12 +321,39 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
const [deleteHover, setDeleteHover] = createSignal<DeleteHoverState>({ kind: "none" })
|
const [deleteHover, setDeleteHover] = createSignal<DeleteHoverState>({ kind: "none" })
|
||||||
|
|
||||||
const [selectedForDeletion, setSelectedForDeletion] = createSignal<Set<string>>(new Set<string>())
|
const [selectedForDeletion, setSelectedForDeletion] = createSignal<Set<string>>(new Set<string>())
|
||||||
const isDeleteMode = createMemo(() => selectedForDeletion().size > 0)
|
const selectedToolParts = createMemo(() => {
|
||||||
const selectedDeleteCount = createMemo(() => selectedForDeletion().size)
|
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 selectedTokenTotal = createMemo(() => {
|
||||||
const selected = selectedForDeletion()
|
const selected = deleteMessageIds()
|
||||||
if (selected.size === 0) return 0
|
const toolParts = deleteToolParts()
|
||||||
|
if (selected.size === 0 && toolParts.length === 0) return 0
|
||||||
// Fresh-from-store chars: read parts directly via buildRecordDisplayData +
|
// Fresh-from-store chars: read parts directly via buildRecordDisplayData +
|
||||||
// getPartCharCount so the toolbar stays consistent with the xray overlay
|
// getPartCharCount so the toolbar stays consistent with the xray overlay
|
||||||
// (which also reads live from the store). Falls back to segment totalChars
|
// (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)
|
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
|
return total
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -377,10 +435,12 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const segments = timelineSegments()
|
const segments = timelineSegments()
|
||||||
|
const segmentById = new Map<string, TimelineSegment>()
|
||||||
|
for (const segment of segments) segmentById.set(segment.id, segment)
|
||||||
const affectedMessageIds = new Set<string>()
|
const affectedMessageIds = new Set<string>()
|
||||||
for (const segId of timelineIds) {
|
for (const segId of timelineIds) {
|
||||||
const segment = segments.find((s) => s.id === segId)
|
const segment = segmentById.get(segId)
|
||||||
if (segment) affectedMessageIds.add(segment.messageId)
|
if (segment && segment.type !== "tool") affectedMessageIds.add(segment.messageId)
|
||||||
}
|
}
|
||||||
setSelectedForDeletion(affectedMessageIds)
|
setSelectedForDeletion(affectedMessageIds)
|
||||||
})
|
})
|
||||||
@@ -395,8 +455,9 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteSelectedMessages = async () => {
|
const deleteSelectedMessages = async () => {
|
||||||
const selected = selectedForDeletion()
|
const selected = deleteMessageIds()
|
||||||
if (selected.size === 0) return
|
const toolParts = deleteToolParts()
|
||||||
|
if (selected.size === 0 && toolParts.length === 0) return
|
||||||
|
|
||||||
const idsInSessionOrder = messageIds()
|
const idsInSessionOrder = messageIds()
|
||||||
const toDelete: string[] = []
|
const toDelete: string[] = []
|
||||||
@@ -411,6 +472,9 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
for (const messageId of toDelete) {
|
for (const messageId of toDelete) {
|
||||||
await deleteMessage(props.instanceId, props.sessionId, messageId)
|
await deleteMessage(props.instanceId, props.sessionId, messageId)
|
||||||
}
|
}
|
||||||
|
for (const { messageId, partId } of toolParts) {
|
||||||
|
await deleteMessagePart(props.instanceId, props.sessionId, messageId, partId)
|
||||||
|
}
|
||||||
clearDeleteMode()
|
clearDeleteMode()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlertDialog(t("messageSection.bulkDelete.failedMessage"), {
|
showAlertDialog(t("messageSection.bulkDelete.failedMessage"), {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "Select message for deletion",
|
"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.deleteSelectedTitle": "Delete selected messages",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "Select all messages",
|
"messageSection.bulkDelete.selectAllTitle": "Select all messages",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "More options",
|
"messageSection.bulkDelete.moreOptionsTitle": "More options",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "Seleccionar mensaje para eliminar",
|
"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.deleteSelectedTitle": "Eliminar mensajes seleccionados",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "Seleccionar todos los mensajes",
|
"messageSection.bulkDelete.selectAllTitle": "Seleccionar todos los mensajes",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "Más opciones",
|
"messageSection.bulkDelete.moreOptionsTitle": "Más opciones",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "Sélectionner le message pour suppression",
|
"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.deleteSelectedTitle": "Supprimer les messages sélectionnés",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "Tout sélectionner",
|
"messageSection.bulkDelete.selectAllTitle": "Tout sélectionner",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "Plus d'options",
|
"messageSection.bulkDelete.moreOptionsTitle": "Plus d'options",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "削除するメッセージを選択",
|
"messageItem.selection.checkboxAriaLabel": "削除するメッセージを選択",
|
||||||
|
|
||||||
"messageSection.bulkDelete.toolbarAriaLabel": "選択したメッセージ({count})",
|
"messageSection.bulkDelete.toolbarAriaLabel": "選択した項目({count})",
|
||||||
"messageSection.bulkDelete.deleteSelectedTitle": "選択したメッセージを削除",
|
"messageSection.bulkDelete.deleteSelectedTitle": "選択したメッセージを削除",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "すべて選択",
|
"messageSection.bulkDelete.selectAllTitle": "すべて選択",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "その他のオプション",
|
"messageSection.bulkDelete.moreOptionsTitle": "その他のオプション",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "Выбрать сообщение для удаления",
|
"messageItem.selection.checkboxAriaLabel": "Выбрать сообщение для удаления",
|
||||||
|
|
||||||
"messageSection.bulkDelete.toolbarAriaLabel": "Выбранные сообщения ({count})",
|
"messageSection.bulkDelete.toolbarAriaLabel": "Выбранные элементы ({count})",
|
||||||
"messageSection.bulkDelete.deleteSelectedTitle": "Удалить выбранные сообщения",
|
"messageSection.bulkDelete.deleteSelectedTitle": "Удалить выбранные сообщения",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "Выбрать все сообщения",
|
"messageSection.bulkDelete.selectAllTitle": "Выбрать все сообщения",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "Больше настроек",
|
"messageSection.bulkDelete.moreOptionsTitle": "Больше настроек",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const messagingMessages = {
|
|||||||
|
|
||||||
"messageItem.selection.checkboxAriaLabel": "选择要删除的消息",
|
"messageItem.selection.checkboxAriaLabel": "选择要删除的消息",
|
||||||
|
|
||||||
"messageSection.bulkDelete.toolbarAriaLabel": "已选择的消息({count})",
|
"messageSection.bulkDelete.toolbarAriaLabel": "已选择的项目({count})",
|
||||||
"messageSection.bulkDelete.deleteSelectedTitle": "删除已选择的消息",
|
"messageSection.bulkDelete.deleteSelectedTitle": "删除已选择的消息",
|
||||||
"messageSection.bulkDelete.selectAllTitle": "全选消息",
|
"messageSection.bulkDelete.selectAllTitle": "全选消息",
|
||||||
"messageSection.bulkDelete.moreOptionsTitle": "更多选项",
|
"messageSection.bulkDelete.moreOptionsTitle": "更多选项",
|
||||||
|
|||||||
Reference in New Issue
Block a user