import type { ChatMessage } from '@/types'; export function messageFingerprint(msg: ChatMessage): string { const attachmentKey = (msg.attachments ?? []) .map((a) => `${a.file_id ?? ''}:${a.name}:${a.content_type}:${a.size ?? ''}`) .join('|'); return `${msg.role}::${String(msg.content)}::${attachmentKey}`; } export function mergeServerWithPendingUsers(serverMessages: ChatMessage[], localMessages: ChatMessage[]): ChatMessage[] { const counts = new Map(); for (const message of serverMessages) { const key = messageFingerprint(message); counts.set(key, (counts.get(key) ?? 0) + 1); } const pendingUsers: ChatMessage[] = []; for (const message of localMessages) { const key = messageFingerprint(message); const count = counts.get(key) ?? 0; if (count > 0) { counts.set(key, count - 1); continue; } if (message.role === 'user') { pendingUsers.push(message); } } return [...serverMessages, ...pendingUsers]; } export function shouldMergePendingUsers( serverMessages: ChatMessage[], localMessages: ChatMessage[], waitingForReply: boolean ): boolean { if (waitingForReply) { return true; } const lastLocal = localMessages[localMessages.length - 1]; if (lastLocal?.role !== 'user') { return false; } const counts = new Map(); for (const message of serverMessages) { const key = messageFingerprint(message); counts.set(key, (counts.get(key) ?? 0) + 1); } for (const message of localMessages) { if (message.role !== 'user') { continue; } const key = messageFingerprint(message); const count = counts.get(key) ?? 0; if (count > 0) { counts.set(key, count - 1); continue; } return true; } return false; } export function getTaskCardMessageIndexes(messages: ChatMessage[]): Set { const latestByTask = new Map(); messages.forEach((message, index) => { if (message.role !== 'assistant' || !message.task_id) { return; } latestByTask.set(message.task_id, index); }); return new Set(latestByTask.values()); }