import type { ChatMessage } from '@/types'; const INVISIBLE_CONTENT_CHARS = /[\u200B-\u200D\uFEFF]/g; export function normalizedMessageText(content: unknown): string { if (typeof content === 'string') { return content.replace(INVISIBLE_CONTENT_CHARS, '').trim(); } if (content == null) { return ''; } return String(content).replace(INVISIBLE_CONTENT_CHARS, '').trim(); } export function hasVisibleChatContent(msg: ChatMessage): boolean { if (normalizedMessageText(msg.content)) { return true; } return Boolean(msg.attachments?.length); } export function shouldDisplayChatMessage(msg: ChatMessage): boolean { return msg.role !== 'assistant' || hasVisibleChatContent(msg); } 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()); }