diff --git a/src/components/chat/utils/messageKeys.ts b/src/components/chat/utils/messageKeys.ts new file mode 100644 index 00000000..410161fe --- /dev/null +++ b/src/components/chat/utils/messageKeys.ts @@ -0,0 +1,38 @@ +import type { ChatMessage } from '../types/types'; + +const toMessageKeyPart = (value: unknown): string | null => { + if (typeof value !== 'string' && typeof value !== 'number') { + return null; + } + + const normalized = String(value).trim(); + return normalized.length > 0 ? normalized : null; +}; + +export const getIntrinsicMessageKey = (message: ChatMessage): string | null => { + const candidates = [ + message.id, + message.messageId, + message.toolId, + message.toolCallId, + message.blobId, + message.rowid, + message.sequence, + ]; + + for (const candidate of candidates) { + const keyPart = toMessageKeyPart(candidate); + if (keyPart) { + return `message-${message.type}-${keyPart}`; + } + } + + const timestamp = new Date(message.timestamp).getTime(); + if (!Number.isFinite(timestamp)) { + return null; + } + + const contentPreview = typeof message.content === 'string' ? message.content.slice(0, 48) : ''; + const toolName = typeof message.toolName === 'string' ? message.toolName : ''; + return `message-${message.type}-${timestamp}-${toolName}-${contentPreview}`; +}; diff --git a/src/components/chat/view/subcomponents/ChatMessagesPane.tsx b/src/components/chat/view/subcomponents/ChatMessagesPane.tsx index 261273b4..f63ea339 100644 --- a/src/components/chat/view/subcomponents/ChatMessagesPane.tsx +++ b/src/components/chat/view/subcomponents/ChatMessagesPane.tsx @@ -7,6 +7,7 @@ import ProviderSelectionEmptyState from './ProviderSelectionEmptyState'; import type { ChatMessage, Provider } from '../../types/types'; import type { Project, ProjectSession } from '../../../../types/app'; import AssistantThinkingIndicator from './AssistantThinkingIndicator'; +import { getIntrinsicMessageKey } from '../../utils/messageKeys'; interface ChatMessagesPaneProps { scrollContainerRef: RefObject; @@ -47,44 +48,6 @@ interface ChatMessagesPaneProps { isLoading: boolean; } -const toMessageKeyPart = (value: unknown): string | null => { - if (typeof value !== 'string' && typeof value !== 'number') { - return null; - } - const normalized = String(value).trim(); - return normalized.length > 0 ? normalized : null; -}; - -const getIntrinsicMessageKey = (message: ChatMessage): string | null => { - const candidates = [ - message.id, - message.messageId, - message.toolId, - message.toolCallId, - message.blobId, - message.rowid, - message.sequence, - ]; - - for (const candidate of candidates) { - const keyPart = toMessageKeyPart(candidate); - if (keyPart) { - return `message-${message.type}-${keyPart}`; - } - } - - const timestamp = new Date(message.timestamp).getTime(); - if (!Number.isFinite(timestamp)) { - return null; - } - - const contentPreview = typeof message.content === 'string' ? message.content.slice(0, 48) : ''; - const toolName = typeof message.toolName === 'string' ? message.toolName : ''; - return `message-${message.type}-${timestamp}-${toolName}-${contentPreview}`; -}; - - - export default function ChatMessagesPane({ scrollContainerRef, onWheel,