Initial import

This commit is contained in:
how2ice
2026-04-21 03:33:23 +09:00
commit 9e4b70f1f1
495 changed files with 94680 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
import { useEffect, type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
import { mergeRecoveredChatMessages } from '../../mainChatPanel/chatUtils';
import { chatGateway } from '../data/chatGateway';
import type {
ChatConversationRequest,
ChatConversationSummary,
ChatMessage,
} from '../../mainChatPanel/types';
const INITIAL_CONVERSATION_MESSAGE_LIMIT = 3;
const OLDER_CONVERSATION_MESSAGE_PAGE_SIZE = 20;
function getCachedSessionMessages(cache: Map<string, ChatMessage[]>, sessionId: string) {
const normalizedSessionId = sessionId.trim();
if (!normalizedSessionId) {
return [] as ChatMessage[];
}
return cache.get(normalizedSessionId) ?? [];
}
function getBestAvailableSessionMessages(
cache: Map<string, ChatMessage[]>,
sessionId: string,
currentSessionId: string,
currentMessages: ChatMessage[],
) {
const cachedMessages = getCachedSessionMessages(cache, sessionId);
if (sessionId !== currentSessionId || currentMessages.length === 0) {
return cachedMessages;
}
return mergeRecoveredChatMessages(cachedMessages, currentMessages);
}
type UseConversationRoomDataOptions = {
activeSessionId: string;
connectionState: 'connecting' | 'connected' | 'disconnected';
shouldBlockConversationWhileLoading: (sessionId: string) => boolean;
captureViewportRestoreSnapshot: () => void;
sessionMessageCacheRef: MutableRefObject<Map<string, ChatMessage[]>>;
messagesRef: MutableRefObject<ChatMessage[]>;
pendingViewportRestoreRef: MutableRefObject<boolean>;
shouldRestoreConversationAfterReconnectRef: MutableRefObject<boolean>;
setConversationItems: Dispatch<SetStateAction<ChatConversationSummary[]>>;
setMessages: Dispatch<SetStateAction<ChatMessage[]>>;
setRequestItems: Dispatch<SetStateAction<ChatConversationRequest[]>>;
setConversationLoadingLabel: Dispatch<SetStateAction<string>>;
setIsConversationContentLoading: Dispatch<SetStateAction<boolean>>;
setIsDeferringAuxiliaryChatRequests: Dispatch<SetStateAction<boolean>>;
setHasOlderMessages: Dispatch<SetStateAction<boolean>>;
setOldestLoadedMessageId: Dispatch<SetStateAction<number | null>>;
setIsLoadingOlderMessages: Dispatch<SetStateAction<boolean>>;
queueViewportPrependRestore: (previousScrollHeight: number, previousScrollTop: number) => void;
viewportRef: MutableRefObject<HTMLDivElement | null>;
};
export function useConversationRoomData({
activeSessionId,
connectionState,
shouldBlockConversationWhileLoading,
captureViewportRestoreSnapshot,
sessionMessageCacheRef,
messagesRef,
pendingViewportRestoreRef,
shouldRestoreConversationAfterReconnectRef,
setConversationItems,
setMessages,
setRequestItems,
setConversationLoadingLabel,
setIsConversationContentLoading,
setIsDeferringAuxiliaryChatRequests,
setHasOlderMessages,
setOldestLoadedMessageId,
setIsLoadingOlderMessages,
queueViewportPrependRestore,
viewportRef,
}: UseConversationRoomDataOptions) {
useEffect(() => {
if (!activeSessionId.trim()) {
setMessages([]);
setRequestItems([]);
setIsConversationContentLoading(false);
setIsLoadingOlderMessages(false);
setHasOlderMessages(false);
setOldestLoadedMessageId(null);
return;
}
let isCancelled = false;
const requestedSessionId = activeSessionId;
const loadConversationDetail = async () => {
captureViewportRestoreSnapshot();
pendingViewportRestoreRef.current = true;
setConversationLoadingLabel('대화 내용을 불러오는 중입니다.');
setIsConversationContentLoading(shouldBlockConversationWhileLoading(requestedSessionId));
setIsDeferringAuxiliaryChatRequests(true);
try {
const response = await chatGateway.getConversationDetail(requestedSessionId, {
limit: INITIAL_CONVERSATION_MESSAGE_LIMIT,
});
if (!isCancelled && response.item.sessionId === requestedSessionId) {
setConversationItems((previous) => {
const exists = previous.some((item) => item.sessionId === response.item.sessionId);
if (!exists) {
return [response.item, ...previous];
}
return previous.map((item) => (item.sessionId === response.item.sessionId ? response.item : item));
});
const baseMessages = getBestAvailableSessionMessages(
sessionMessageCacheRef.current,
requestedSessionId,
activeSessionId,
messagesRef.current,
);
const nextMessages = mergeRecoveredChatMessages(baseMessages, response.messages);
sessionMessageCacheRef.current.set(requestedSessionId, nextMessages);
setMessages(nextMessages);
setRequestItems(response.requests);
setHasOlderMessages(response.hasOlderMessages);
setOldestLoadedMessageId(response.oldestLoadedMessageId);
}
} catch {
if (!isCancelled) {
const cachedMessages = getBestAvailableSessionMessages(
sessionMessageCacheRef.current,
requestedSessionId,
activeSessionId,
messagesRef.current,
);
if (cachedMessages.length > 0) {
setMessages(cachedMessages);
}
}
} finally {
if (!isCancelled) {
setIsConversationContentLoading(false);
setIsDeferringAuxiliaryChatRequests(false);
}
}
};
void loadConversationDetail();
return () => {
isCancelled = true;
};
}, [
activeSessionId,
captureViewportRestoreSnapshot,
messagesRef,
pendingViewportRestoreRef,
sessionMessageCacheRef,
setConversationItems,
setConversationLoadingLabel,
setIsConversationContentLoading,
setIsDeferringAuxiliaryChatRequests,
setHasOlderMessages,
setMessages,
setOldestLoadedMessageId,
setRequestItems,
shouldBlockConversationWhileLoading,
]);
useEffect(() => {
if (connectionState !== 'connected' || !shouldRestoreConversationAfterReconnectRef.current) {
return;
}
let isCancelled = false;
const requestedSessionId = activeSessionId;
const restoreConversationAfterReconnect = async () => {
setIsDeferringAuxiliaryChatRequests(true);
try {
const response = await chatGateway.getConversationDetail(requestedSessionId, {
limit: Math.max(INITIAL_CONVERSATION_MESSAGE_LIMIT, messagesRef.current.length || 0),
});
if (!isCancelled && response.item.sessionId === requestedSessionId) {
const baseMessages = getBestAvailableSessionMessages(
sessionMessageCacheRef.current,
requestedSessionId,
activeSessionId,
messagesRef.current,
);
const nextMessages = mergeRecoveredChatMessages(baseMessages, response.messages);
const hasMessageDiff = nextMessages !== baseMessages;
if (hasMessageDiff) {
captureViewportRestoreSnapshot();
pendingViewportRestoreRef.current = true;
setConversationLoadingLabel('채팅방을 다시 연결하고 내용을 복구하는 중입니다.');
setIsConversationContentLoading(true);
}
setConversationItems((previous) => {
const exists = previous.some((item) => item.sessionId === response.item.sessionId);
if (!exists) {
return [response.item, ...previous];
}
return previous.map((item) => (item.sessionId === response.item.sessionId ? response.item : item));
});
setRequestItems(response.requests);
setHasOlderMessages(response.hasOlderMessages);
setOldestLoadedMessageId(response.oldestLoadedMessageId);
if (hasMessageDiff) {
sessionMessageCacheRef.current.set(requestedSessionId, nextMessages);
setMessages(nextMessages);
window.requestAnimationFrame(() => {
if (!isCancelled) {
setIsConversationContentLoading(false);
}
});
}
}
} catch {
if (!isCancelled) {
setIsConversationContentLoading(false);
}
} finally {
if (!isCancelled) {
shouldRestoreConversationAfterReconnectRef.current = false;
if (!pendingViewportRestoreRef.current) {
setIsConversationContentLoading(false);
}
setIsDeferringAuxiliaryChatRequests(false);
}
}
};
void restoreConversationAfterReconnect();
return () => {
isCancelled = true;
};
}, [
activeSessionId,
captureViewportRestoreSnapshot,
connectionState,
messagesRef,
pendingViewportRestoreRef,
sessionMessageCacheRef,
setConversationItems,
setConversationLoadingLabel,
setIsConversationContentLoading,
setIsDeferringAuxiliaryChatRequests,
setHasOlderMessages,
setMessages,
setOldestLoadedMessageId,
setRequestItems,
shouldRestoreConversationAfterReconnectRef,
]);
const loadOlderMessages = async () => {
const requestedSessionId = activeSessionId.trim();
const oldestVisibleMessageId = messagesRef.current[0]?.id ?? null;
if (!requestedSessionId || oldestVisibleMessageId == null) {
return;
}
setIsLoadingOlderMessages(true);
try {
const response = await chatGateway.getConversationDetail(requestedSessionId, {
limit: OLDER_CONVERSATION_MESSAGE_PAGE_SIZE,
beforeMessageId: oldestVisibleMessageId,
});
if (response.item.sessionId !== requestedSessionId || response.messages.length === 0) {
setHasOlderMessages(response.hasOlderMessages);
setOldestLoadedMessageId(response.oldestLoadedMessageId);
return;
}
const viewport = viewportRef.current;
const previousScrollHeight = viewport?.scrollHeight ?? 0;
const previousScrollTop = viewport?.scrollTop ?? 0;
const nextMessages = mergeRecoveredChatMessages(messagesRef.current, response.messages);
queueViewportPrependRestore(previousScrollHeight, previousScrollTop);
sessionMessageCacheRef.current.set(requestedSessionId, nextMessages);
setMessages(nextMessages);
setRequestItems(response.requests);
setHasOlderMessages(response.hasOlderMessages);
setOldestLoadedMessageId(response.oldestLoadedMessageId);
} finally {
setIsLoadingOlderMessages(false);
}
};
return {
loadOlderMessages,
};
}