124 lines
3.2 KiB
TypeScript
124 lines
3.2 KiB
TypeScript
import type { ChatMessage } from '@/types';
|
|
|
|
const INVISIBLE_CONTENT_CHARS = /[\u200B-\u200D\uFEFF]/g;
|
|
export const CHAT_WAITING_REFRESH_INTERVAL_MS = 1500;
|
|
export const CHAT_IDLE_REFRESH_INTERVAL_MS = 5000;
|
|
|
|
export function getSessionRefreshIntervalMs({
|
|
isLoading,
|
|
isThinking,
|
|
documentHidden,
|
|
}: {
|
|
isLoading: boolean;
|
|
isThinking: boolean;
|
|
documentHidden: boolean;
|
|
}): number | null {
|
|
if (documentHidden) {
|
|
return null;
|
|
}
|
|
if (isLoading || isThinking) {
|
|
return CHAT_WAITING_REFRESH_INTERVAL_MS;
|
|
}
|
|
return CHAT_IDLE_REFRESH_INTERVAL_MS;
|
|
}
|
|
|
|
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<string, number>();
|
|
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<string, number>();
|
|
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<number> {
|
|
const latestByTask = new Map<string, number>();
|
|
|
|
messages.forEach((message, index) => {
|
|
if (message.role !== 'assistant' || !message.task_id) {
|
|
return;
|
|
}
|
|
latestByTask.set(message.task_id, index);
|
|
});
|
|
|
|
return new Set(latestByTask.values());
|
|
}
|