diff --git a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx index 70d5982987..90b284d6c4 100644 --- a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx +++ b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx @@ -1,13 +1,11 @@ import { loggerService } from '@logger' import ContextMenu from '@renderer/components/ContextMenu' import { useSession } from '@renderer/hooks/agents/useSession' +import { useTopicMessages } from '@renderer/hooks/useMessageOperations' import { getGroupedMessages } from '@renderer/services/MessagesService' -import { useAppDispatch, useAppSelector } from '@renderer/store' -import { selectMessagesForTopic } from '@renderer/store/newMessage' -import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' -import { Topic } from '@renderer/types' +import { type Topic, TopicType } from '@renderer/types' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' -import { memo, useEffect, useMemo } from 'react' +import { memo, useMemo } from 'react' import styled from 'styled-components' import MessageGroup from './MessageGroup' @@ -22,23 +20,10 @@ type Props = { } const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { - const dispatch = useAppDispatch() const { session } = useSession(agentId, sessionId) const sessionTopicId = useMemo(() => buildAgentSessionTopicId(sessionId), [sessionId]) - const messages = useAppSelector((state) => selectMessagesForTopic(state, sessionTopicId)) - - // Load messages when session changes or when messages are empty - useEffect(() => { - if (sessionId) { - // Only load if we don't have messages yet - // This prevents overwriting messages that were just added - const hasMessages = messages && messages.length > 0 - if (!hasMessages) { - logger.info('Loading messages for agent session', { sessionId }) - dispatch(loadTopicMessagesThunk(sessionTopicId, false)) // Don't force reload if we have messages in Redux - } - } - }, [dispatch, sessionId, sessionTopicId, messages?.length]) + // Use the same hook as Messages.tsx for consistent behavior + const messages = useTopicMessages(sessionTopicId) const displayMessages = useMemo(() => { if (!messages || messages.length === 0) return [] @@ -58,6 +43,7 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { const derivedTopic = useMemo( () => ({ id: sessionTopicId, + type: TopicType.Session, assistantId: sessionAssistantId, name: sessionName, createdAt: sessionCreatedAt, diff --git a/src/renderer/src/services/db/AgentMessageDataSource.ts b/src/renderer/src/services/db/AgentMessageDataSource.ts index b4dda0c2d9..0398a9886a 100644 --- a/src/renderer/src/services/db/AgentMessageDataSource.ts +++ b/src/renderer/src/services/db/AgentMessageDataSource.ts @@ -373,6 +373,15 @@ export class AgentMessageDataSource implements MessageDataSource { // 2. Or just hide from UI without actual deletion } + async deleteMessages(topicId: string, _messageIds: string[]): Promise { + // Agent session messages cannot be deleted in batch + logger.warn(`deleteMessages called for agent session ${topicId}, operation not supported`) + + // In a full implementation, you might want to: + // 1. Implement batch soft delete in backend + // 2. Update local state accordingly + } + async deleteMessagesByAskId(topicId: string, _askId: string): Promise { // Agent session messages cannot be deleted logger.warn(`deleteMessagesByAskId called for agent session ${topicId}, operation not supported`) diff --git a/src/renderer/src/services/db/DbService.ts b/src/renderer/src/services/db/DbService.ts index 135c265b85..881791e752 100644 --- a/src/renderer/src/services/db/DbService.ts +++ b/src/renderer/src/services/db/DbService.ts @@ -86,9 +86,9 @@ class DbService implements MessageDataSource { return source.deleteMessage(topicId, messageId) } - async deleteMessagesByAskId(topicId: string, askId: string): Promise { + async deleteMessages(topicId: string, messageIds: string[]): Promise { const source = this.getDataSource(topicId) - return source.deleteMessagesByAskId(topicId, askId) + return source.deleteMessages(topicId, messageIds) } // ============ Block Operations ============ diff --git a/src/renderer/src/services/db/DexieMessageDataSource.ts b/src/renderer/src/services/db/DexieMessageDataSource.ts index b84664733c..7b3efd1edc 100644 --- a/src/renderer/src/services/db/DexieMessageDataSource.ts +++ b/src/renderer/src/services/db/DexieMessageDataSource.ts @@ -203,39 +203,48 @@ export class DexieMessageDataSource implements MessageDataSource { } } - async deleteMessagesByAskId(topicId: string, askId: string): Promise { + async deleteMessages(topicId: string, messageIds: string[]): Promise { try { await db.transaction('rw', db.topics, db.message_blocks, db.files, async () => { const topic = await db.topics.get(topicId) if (!topic) return - // Find all messages with the given askId - const messagesToDelete = topic.messages.filter((m) => m.askId === askId || m.id === askId) - const blockIdsToDelete = messagesToDelete.flatMap((m) => m.blocks || []) + // Collect all block IDs from messages to be deleted + const allBlockIds: string[] = [] + const messagesToDelete: Message[] = [] + + for (const messageId of messageIds) { + const message = topic.messages.find((m) => m.id === messageId) + if (message) { + messagesToDelete.push(message) + if (message.blocks && message.blocks.length > 0) { + allBlockIds.push(...message.blocks) + } + } + } // Delete blocks and handle files - if (blockIdsToDelete.length > 0) { - const blocks = await db.message_blocks.where('id').anyOf(blockIdsToDelete).toArray() + if (allBlockIds.length > 0) { + const blocks = await db.message_blocks.where('id').anyOf(allBlockIds).toArray() const files = blocks .filter((block) => block.type === 'file' || block.type === 'image') .map((block: any) => block.file) .filter((file) => file !== undefined) + // Clean up files if (!isEmpty(files)) { await Promise.all(files.map((file) => FileManager.deleteFile(file.id, false))) } - - await db.message_blocks.bulkDelete(blockIdsToDelete) + await db.message_blocks.bulkDelete(allBlockIds) } - // Filter out deleted messages - const remainingMessages = topic.messages.filter((m) => m.askId !== askId && m.id !== askId) + // Remove messages from topic + const remainingMessages = topic.messages.filter((m) => !messageIds.includes(m.id)) await db.topics.update(topicId, { messages: remainingMessages }) }) - store.dispatch(updateTopicUpdatedAt({ topicId })) } catch (error) { - logger.error(`Failed to delete messages with askId ${askId} from topic ${topicId}:`, error as Error) + logger.error(`Failed to delete messages from topic ${topicId}:`, error as Error) throw error } } diff --git a/src/renderer/src/services/db/types.ts b/src/renderer/src/services/db/types.ts index 8fc24c8199..0376725ae5 100644 --- a/src/renderer/src/services/db/types.ts +++ b/src/renderer/src/services/db/types.ts @@ -64,9 +64,9 @@ export interface MessageDataSource { deleteMessage(topicId: string, messageId: string): Promise /** - * Delete messages by askId (user query + assistant responses) + * Delete multiple messages and their blocks */ - deleteMessagesByAskId(topicId: string, askId: string): Promise + deleteMessages(topicId: string, messageIds: string[]): Promise // ============ Block Operations ============ /** diff --git a/src/renderer/src/store/thunk/messageThunk.v2.ts b/src/renderer/src/store/thunk/messageThunk.v2.ts index c0c0e38e04..5d1617bfb6 100644 --- a/src/renderer/src/store/thunk/messageThunk.v2.ts +++ b/src/renderer/src/store/thunk/messageThunk.v2.ts @@ -5,10 +5,7 @@ import { loggerService } from '@logger' import { dbService } from '@renderer/services/db' -import type { Topic } from '@renderer/types' -import { TopicType } from '@renderer/types' import type { Message, MessageBlock } from '@renderer/types/newMessage' -import { isAgentSessionTopicId } from '@renderer/utils/agentSession' import type { AppDispatch, RootState } from '../index' import { upsertManyBlocks } from '../messageBlock' @@ -80,42 +77,6 @@ export const getRawTopicV2 = async (topicId: string): Promise<{ id: string; mess // Phase 2.2 - Batch 2: Helper functions // ================================================================= -/** - * Get a full topic object with type information - * This builds on getRawTopicV2 to provide additional metadata - */ -export const getTopicV2 = async (topicId: string): Promise => { - try { - const rawTopic = await dbService.getRawTopic(topicId) - if (!rawTopic) { - logger.info('Topic not found', { topicId }) - return undefined - } - - // Construct the full Topic object - const topic: Topic = { - id: rawTopic.id, - type: isAgentSessionTopicId(topicId) ? TopicType.Session : TopicType.Chat, - messages: rawTopic.messages, - assistantId: '', // These fields would need to be fetched from appropriate source - name: '', - createdAt: Date.now(), - updatedAt: Date.now() - } - - logger.info('Retrieved topic with type via DbService', { - topicId, - type: topic.type, - messageCount: topic.messages.length - }) - - return topic - } catch (error) { - logger.error('Failed to get topic:', { topicId, error }) - return undefined - } -} - /** * Update file reference count * Only applies to Dexie data source, no-op for agent sessions