Skip to content

Commit e6b50fa

Browse files
committed
feat: Add message deletion functionality across chat components, enabling users to delete messages and their children with confirmation prompts, enhancing message management capabilities
1 parent 72aeb5b commit e6b50fa

6 files changed

Lines changed: 186 additions & 8 deletions

File tree

src/renderer/src/components/pages/chat/ChatLogic.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ChatLogicProps {
2121
onEditAndResendMessage: (messageId: string, newContent: string) => Promise<void>
2222
onToggleFavorite: (messageId: string) => void
2323
onModelChangeForMessage: (messageId: string, newModelId: string) => Promise<void>
24+
onDeleteMessage: (messageId: string) => Promise<void>
2425
}) => React.ReactNode
2526
}
2627

@@ -131,6 +132,7 @@ export default function ChatLogic({
131132
onEditMessage: messageOperations.handleEditMessage,
132133
onEditAndResendMessage: messageOperations.handleEditAndResendMessage,
133134
onToggleFavorite: messageOperations.handleToggleFavorite,
134-
onModelChangeForMessage: messageOperations.handleModelChangeForMessage
135+
onModelChangeForMessage: messageOperations.handleModelChangeForMessage,
136+
onDeleteMessage: messageOperations.handleDeleteMessage
135137
})
136138
}

src/renderer/src/components/pages/chat/ChatWindow.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
179179
onEditMessage,
180180
onEditAndResendMessage,
181181
onToggleFavorite,
182-
onModelChangeForMessage
182+
onModelChangeForMessage,
183+
onDeleteMessage
183184
}) => (
184185
<>
185186
{/* 消息树侧边栏 */}
@@ -220,12 +221,14 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
220221
onEditAndResendMessage={onEditAndResendMessage}
221222
onToggleFavorite={onToggleFavorite}
222223
onModelChange={onModelChangeForMessage}
224+
onDeleteMessage={onDeleteMessage}
223225
onSwitchBranch={handleSwitchBranch}
224226
// 折叠相关props
225227
collapsedMessages={collapsedMessagesForChat}
226228
onToggleMessageCollapse={handleToggleMessageCollapse}
227229
// 设置相关props
228230
onOpenSettings={handleOpenSettings}
231+
onDeleteMessage={onDeleteMessage}
229232
/>
230233
<ChatInput
231234
ref={chatInputRef}

src/renderer/src/components/pages/chat/MessageItem.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
BulbOutlined,
1515
PictureOutlined,
1616
DownOutlined,
17-
UpOutlined
17+
UpOutlined,
18+
DeleteOutlined
1819
} from '@ant-design/icons'
1920
import { ChatMessage, LLMConfig } from '../../../types/type'
2021
import BranchNavigator from './BranchNavigator'
@@ -44,6 +45,7 @@ interface MessageItemProps {
4445
onEditAndResend?: (messageId: string, newContent: string) => void
4546
onToggleFavorite?: (messageId: string) => void
4647
onModelChange?: (messageId: string, newModelId: string) => void
48+
onDelete?: (messageId: string) => void
4749
// 折叠相关
4850
isCollapsed?: boolean
4951
onToggleCollapse?: (messageId: string) => void
@@ -65,6 +67,7 @@ export default function MessageItem({
6567
onEditAndResend,
6668
onToggleFavorite,
6769
onModelChange,
70+
onDelete,
6871
isCollapsed = false,
6972
onToggleCollapse
7073
}: MessageItemProps) {
@@ -161,6 +164,10 @@ export default function MessageItem({
161164
onToggleCollapse?.(message.id)
162165
}
163166

167+
const handleDelete = () => {
168+
onDelete?.(message.id)
169+
}
170+
164171
// 生成折叠状态下的预览文本
165172
const getPreviewText = (content: string, maxLength: number = 80) => {
166173
if (!content) return '消息已折叠,点击展开按钮查看内容'
@@ -412,6 +419,18 @@ export default function MessageItem({
412419
/>
413420
</Tooltip>
414421
)}
422+
{onDelete && (
423+
<Tooltip title="删除">
424+
<Button
425+
type="text"
426+
size="small"
427+
icon={<DeleteOutlined />}
428+
onClick={handleDelete}
429+
disabled={isCurrentlyStreaming}
430+
className="message-delete-btn"
431+
/>
432+
</Tooltip>
433+
)}
415434
</div>
416435
</div>
417436
</div>

src/renderer/src/components/pages/chat/MessageList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface MessageListProps {
1717
onEditAndResendMessage?: (messageId: string, newContent: string) => void
1818
onToggleFavorite?: (messageId: string) => void
1919
onModelChange?: (messageId: string, newModelId: string) => void
20+
onDeleteMessage?: (messageId: string) => void
2021
onSwitchBranch?: (messageId: string, branchIndex: number) => void
2122
onStopGeneration?: () => void
2223
// 折叠相关
@@ -38,6 +39,7 @@ export default function MessageList({
3839
onEditAndResendMessage,
3940
onToggleFavorite,
4041
onModelChange,
42+
onDeleteMessage,
4143
onSwitchBranch,
4244
collapsedMessages = [],
4345
onToggleMessageCollapse,
@@ -159,6 +161,7 @@ export default function MessageList({
159161
onEditAndResend={onEditAndResendMessage}
160162
onToggleFavorite={onToggleFavorite}
161163
onModelChange={onModelChange}
164+
onDelete={onDeleteMessage}
162165
// 折叠相关
163166
isCollapsed={collapsedMessages.includes(message.id)}
164167
onToggleCollapse={onToggleMessageCollapse}

src/renderer/src/components/pages/chat/hooks/useMessageOperations.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback } from 'react'
22
import { App } from 'antd'
33
import { v4 as uuidv4 } from 'uuid'
44
import { usePagesStore } from '../../../../stores/pagesStore'
5+
import { useSettingsStore } from '../../../../stores/settingsStore'
56
import { useMessagesStore } from '../../../../stores/messagesStore'
67
import { ChatMessage } from '../../../../types/type'
78
import { MessageTree } from '../messageTree'
@@ -23,6 +24,7 @@ export interface UseMessageOperationsReturn {
2324
handleEditAndResendMessage: (messageId: string, newContent: string) => Promise<void>
2425
handleToggleFavorite: (messageId: string) => void
2526
handleModelChangeForMessage: (messageId: string, newModelId: string) => Promise<void>
27+
handleDeleteMessage: (messageId: string) => Promise<void>
2628
}
2729

2830
export function useMessageOperations({
@@ -33,8 +35,9 @@ export function useMessageOperations({
3335
isLoading,
3436
setIsLoading
3537
}: UseMessageOperationsProps): UseMessageOperationsReturn {
36-
const { addMessageToParent, toggleMessageFavorite } = useMessagesStore()
37-
const { message } = App.useApp()
38+
const { addMessageToParent, toggleMessageFavorite, deleteMessageAndChildren } = useMessagesStore()
39+
const { settings } = useSettingsStore()
40+
const { message, modal } = App.useApp()
3841

3942
const handleSendMessage = useCallback(
4043
async (content: string, customModelId?: string, customParentId?: string) => {
@@ -287,7 +290,6 @@ export function useMessageOperations({
287290
async (messageId: string, newModelId: string) => {
288291
if (!chat || isLoading) return
289292

290-
const { settings } = usePagesStore.getState()
291293
const llmConfig = settings.llmConfigs?.find((config: any) => config.id === newModelId)
292294
if (!llmConfig) {
293295
message.error('所选模型配置不存在')
@@ -337,16 +339,72 @@ export function useMessageOperations({
337339
chatId,
338340
aiService,
339341
messageTree,
340-
setIsLoading
342+
setIsLoading,
343+
settings
341344
]
342345
)
343346

347+
const handleDeleteMessage = useCallback(
348+
async (messageId: string) => {
349+
if (!chat) return
350+
351+
// 找到要删除的消息
352+
const messageToDelete = chat.messages?.find((msg: any) => msg.id === messageId)
353+
if (!messageToDelete) return
354+
355+
// 计算要删除的消息总数(包括子分支)
356+
const countChildrenRecursively = (msgId: string): number => {
357+
let count = 1 // 当前消息
358+
const msg = chat.messages?.find((m: any) => m.id === msgId)
359+
if (msg?.children) {
360+
msg.children.forEach((childId: string) => {
361+
count += countChildrenRecursively(childId)
362+
})
363+
}
364+
return count
365+
}
366+
367+
const totalCount = countChildrenRecursively(messageId)
368+
const hasChildren = totalCount > 1
369+
370+
// 显示确认对话框
371+
const confirmText = hasChildren
372+
? `确定要删除这条消息及其所有子分支吗?(共 ${totalCount} 条消息)`
373+
: '确定要删除这条消息吗?'
374+
375+
const confirmed = await new Promise<boolean>((resolve) => {
376+
modal.confirm({
377+
title: '删除消息',
378+
content: confirmText,
379+
okText: '删除',
380+
cancelText: '取消',
381+
okType: 'danger',
382+
onOk: () => resolve(true),
383+
onCancel: () => resolve(false)
384+
})
385+
})
386+
387+
if (!confirmed) return
388+
389+
try {
390+
// 调用删除方法
391+
deleteMessageAndChildren(chatId, messageId)
392+
message.success(hasChildren ? `已删除 ${totalCount} 条消息` : '已删除消息')
393+
} catch (error) {
394+
console.error('Delete message failed:', error)
395+
message.error('删除消息失败')
396+
}
397+
},
398+
[chat, chatId, deleteMessageAndChildren, message, modal]
399+
)
400+
344401
return {
345402
handleSendMessage,
346403
handleRetryMessage,
347404
handleEditMessage,
348405
handleEditAndResendMessage,
349406
handleToggleFavorite,
350-
handleModelChangeForMessage
407+
handleModelChangeForMessage,
408+
handleDeleteMessage
351409
}
352410
}

src/renderer/src/stores/messagesStore.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface MessagesActions {
2525
updateMessageContent: (chatId: string, messageId: string, content: string) => void
2626
updateMessageReasoning: (chatId: string, messageId: string, reasoning_content: string) => void
2727
removeMessage: (chatId: string, messageId: string) => void
28+
deleteMessageAndChildren: (chatId: string, messageId: string) => void
2829
toggleMessageFavorite: (chatId: string, messageId: string) => void
2930

3031
// 流式消息处理 - 优化版本
@@ -204,6 +205,98 @@ export const useMessagesStore = create<MessagesState & MessagesActions>()(
204205
}
205206
},
206207

208+
deleteMessageAndChildren: (chatId, messageId) => {
209+
try {
210+
const { updatePage } = usePagesStore.getState()
211+
const page = usePagesStore.getState().findPageById(chatId)
212+
213+
if (page && page.type === 'regular' && page.messages) {
214+
// 递归查找所有需要删除的消息ID(包括子分支)
215+
const messagesToDelete = new Set<string>()
216+
217+
const findChildrenRecursively = (currentMessageId: string) => {
218+
messagesToDelete.add(currentMessageId)
219+
const message = page.messages?.find(msg => msg.id === currentMessageId)
220+
if (message && message.children) {
221+
message.children.forEach(childId => {
222+
findChildrenRecursively(childId)
223+
})
224+
}
225+
}
226+
227+
findChildrenRecursively(messageId)
228+
229+
// 过滤掉要删除的消息
230+
const updatedMessages = page.messages?.filter(msg => !messagesToDelete.has(msg.id)) || []
231+
232+
// 更新父消息的children数组(如果被删除的消息有父消息)
233+
const messageToDelete = page.messages?.find(msg => msg.id === messageId)
234+
let finalMessages = updatedMessages
235+
236+
if (messageToDelete?.parentId) {
237+
finalMessages = updatedMessages.map(msg =>
238+
msg.id === messageToDelete.parentId
239+
? {
240+
...msg,
241+
children: (msg.children || []).filter(childId => childId !== messageId)
242+
}
243+
: msg
244+
)
245+
}
246+
247+
// 更新当前路径,移除已删除的消息
248+
let newCurrentPath = page.currentPath || []
249+
250+
// 如果当前路径包含被删除的消息,需要调整路径
251+
if (messagesToDelete.has(newCurrentPath[newCurrentPath.length - 1])) {
252+
// 找到被删除消息在路径中的位置
253+
const deletedMessageIndex = newCurrentPath.findIndex(id => messagesToDelete.has(id))
254+
if (deletedMessageIndex !== -1) {
255+
// 截取到被删除消息之前的路径
256+
newCurrentPath = newCurrentPath.slice(0, deletedMessageIndex)
257+
258+
// 如果删除了路径中的消息,尝试找到同级的其他消息作为替代
259+
if (messageToDelete?.parentId && deletedMessageIndex > 0) {
260+
const parentId = newCurrentPath[newCurrentPath.length - 1]
261+
const parentMessage = finalMessages.find(msg => msg.id === parentId)
262+
if (parentMessage?.children && parentMessage.children.length > 0) {
263+
// 选择同级的第一个子消息继续路径
264+
newCurrentPath.push(parentMessage.children[0])
265+
}
266+
}
267+
}
268+
}
269+
270+
// 更新消息映射(如果存在)
271+
let updatedMessageMap = page.messageMap
272+
if (updatedMessageMap) {
273+
const newMessageMap = { ...updatedMessageMap }
274+
messagesToDelete.forEach(id => {
275+
delete newMessageMap[id]
276+
})
277+
278+
// 更新父消息在messageMap中的children
279+
if (messageToDelete?.parentId && newMessageMap[messageToDelete.parentId]) {
280+
newMessageMap[messageToDelete.parentId] = {
281+
...newMessageMap[messageToDelete.parentId],
282+
children: (newMessageMap[messageToDelete.parentId].children || []).filter(childId => childId !== messageId)
283+
}
284+
}
285+
286+
updatedMessageMap = newMessageMap
287+
}
288+
289+
updatePage(chatId, {
290+
messages: finalMessages,
291+
currentPath: newCurrentPath,
292+
messageMap: updatedMessageMap
293+
})
294+
}
295+
} catch (error) {
296+
handleStoreError('messagesStore', 'deleteMessageAndChildren', error)
297+
}
298+
},
299+
207300
toggleMessageFavorite: (chatId, messageId) => {
208301
try {
209302
const { updatePage } = usePagesStore.getState()

0 commit comments

Comments
 (0)