371 lines
13 KiB
TypeScript
371 lines
13 KiB
TypeScript
import { useCallback } from 'react';
|
|
import { removeChatRuntimeJob } from '../../mainChatPanel';
|
|
import { chatConnectionGateway } from '../data/chatConnectionGateway';
|
|
import { chatGateway } from '../data/chatGateway';
|
|
import type {
|
|
ChatComposerAttachment,
|
|
ChatConversationRequest,
|
|
ChatConversationSummary,
|
|
ChatMessage,
|
|
} from '../../mainChatPanel/types';
|
|
|
|
type PendingChatRequest = {
|
|
sessionId: string;
|
|
requestId: string;
|
|
text: string;
|
|
mode: 'queue' | 'direct';
|
|
chatTypeId: string;
|
|
chatTypeLabel: string;
|
|
chatTypeDescription: string;
|
|
chatTypeIsTemplate: boolean;
|
|
retryCount: number;
|
|
failed: boolean;
|
|
};
|
|
|
|
type UseConversationRoomActionsControllerOptions = {
|
|
activeSessionId: string;
|
|
requestedSessionId: string;
|
|
conversationItems: ChatConversationSummary[];
|
|
activeConversation: ChatConversationSummary | null;
|
|
editingConversationTitle: string;
|
|
isMobileViewport: boolean;
|
|
pendingRequestsRef: { current: PendingChatRequest[] };
|
|
sessionMessageCacheRef: { current: Map<string, ChatMessage[]> };
|
|
socketRef: { current: WebSocket | null };
|
|
setConversationItems: React.Dispatch<React.SetStateAction<ChatConversationSummary[]>>;
|
|
setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
|
|
setRequestItems: React.Dispatch<React.SetStateAction<ChatConversationRequest[]>>;
|
|
setActiveSessionId: (value: string) => void;
|
|
setDraft: (value: string) => void;
|
|
setComposerAttachments: React.Dispatch<React.SetStateAction<ChatComposerAttachment[]>>;
|
|
setCopiedMessageId: (value: number | null) => void;
|
|
setActivePreviewId: (value: string | null) => void;
|
|
setIsPreviewModalOpen: (value: boolean) => void;
|
|
setActiveSystemStatus: (value: string | null) => void;
|
|
setIsSystemStatusPending: (value: boolean) => void;
|
|
setIsResourceStripOpen: (value: boolean) => void;
|
|
setIsConversationPaneClosed: (value: boolean) => void;
|
|
setIsMobileConversationView: (value: boolean) => void;
|
|
setRenamingConversationSessionId: (value: string | null | ((current: string | null) => string | null)) => void;
|
|
setEditingConversationTitle: (value: string) => void;
|
|
setIsEditingConversationTitle: (value: boolean) => void;
|
|
updatePendingMessageStatus: (requestId: string, status: 'retrying' | 'failed' | null, retryCount?: number) => void;
|
|
sendChatRequest: (socket: WebSocket, request: PendingChatRequest) => void;
|
|
createLocalMessage: (text: string) => ChatMessage;
|
|
replaceChatSessionInUrl: (sessionId: string) => void;
|
|
messageApi: {
|
|
error: (content: string) => void;
|
|
};
|
|
};
|
|
|
|
export function useConversationRoomActionsController({
|
|
activeSessionId,
|
|
requestedSessionId,
|
|
conversationItems,
|
|
activeConversation,
|
|
editingConversationTitle,
|
|
isMobileViewport,
|
|
pendingRequestsRef,
|
|
sessionMessageCacheRef,
|
|
socketRef,
|
|
setConversationItems,
|
|
setMessages,
|
|
setRequestItems,
|
|
setActiveSessionId,
|
|
setDraft,
|
|
setComposerAttachments,
|
|
setCopiedMessageId,
|
|
setActivePreviewId,
|
|
setIsPreviewModalOpen,
|
|
setActiveSystemStatus,
|
|
setIsSystemStatusPending,
|
|
setIsResourceStripOpen,
|
|
setIsConversationPaneClosed,
|
|
setIsMobileConversationView,
|
|
setRenamingConversationSessionId,
|
|
setEditingConversationTitle,
|
|
setIsEditingConversationTitle,
|
|
updatePendingMessageStatus,
|
|
sendChatRequest,
|
|
createLocalMessage,
|
|
replaceChatSessionInUrl,
|
|
messageApi,
|
|
}: UseConversationRoomActionsControllerOptions) {
|
|
const removeOptimisticRequestMessages = useCallback(
|
|
(requestId: string) => {
|
|
setMessages((previous) => previous.filter((message) => message.clientRequestId !== requestId));
|
|
},
|
|
[setMessages],
|
|
);
|
|
|
|
const retryPendingRequest = useCallback(
|
|
(requestId: string) => {
|
|
const currentRequest = pendingRequestsRef.current.find(
|
|
(request) => request.requestId === requestId && request.sessionId === activeSessionId,
|
|
);
|
|
|
|
if (!currentRequest) {
|
|
setMessages((previous) => [
|
|
...previous.slice(-39),
|
|
createLocalMessage('재전송할 요청 정보를 찾지 못했습니다. 같은 내용을 다시 보내 주세요.'),
|
|
]);
|
|
return;
|
|
}
|
|
|
|
const resetRequest: PendingChatRequest = {
|
|
...currentRequest,
|
|
retryCount: 0,
|
|
failed: false,
|
|
};
|
|
|
|
setActiveSystemStatus('전송 재시도 중...');
|
|
setIsSystemStatusPending(true);
|
|
|
|
const socket = socketRef.current;
|
|
|
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
updatePendingMessageStatus(requestId, 'retrying', 0);
|
|
pendingRequestsRef.current = [
|
|
...pendingRequestsRef.current.filter((request) => request.requestId !== requestId),
|
|
resetRequest,
|
|
];
|
|
return;
|
|
}
|
|
|
|
try {
|
|
sendChatRequest(socket, resetRequest);
|
|
updatePendingMessageStatus(requestId, null, 0);
|
|
pendingRequestsRef.current = pendingRequestsRef.current.filter((request) => request.requestId !== requestId);
|
|
} catch {
|
|
updatePendingMessageStatus(requestId, 'retrying', 0);
|
|
pendingRequestsRef.current = [
|
|
...pendingRequestsRef.current.filter((request) => request.requestId !== requestId),
|
|
resetRequest,
|
|
];
|
|
}
|
|
},
|
|
[
|
|
activeSessionId,
|
|
createLocalMessage,
|
|
pendingRequestsRef,
|
|
sendChatRequest,
|
|
setActiveSystemStatus,
|
|
setIsSystemStatusPending,
|
|
setMessages,
|
|
socketRef,
|
|
updatePendingMessageStatus,
|
|
],
|
|
);
|
|
|
|
const cancelPendingRequest = useCallback(
|
|
(requestId: string) => {
|
|
const currentRequest = pendingRequestsRef.current.find(
|
|
(request) => request.requestId === requestId && request.sessionId === activeSessionId,
|
|
);
|
|
|
|
if (!currentRequest) {
|
|
removeOptimisticRequestMessages(requestId);
|
|
setActiveSystemStatus('미접수 요청을 화면에서 제거했습니다.');
|
|
setIsSystemStatusPending(false);
|
|
return;
|
|
}
|
|
|
|
pendingRequestsRef.current = pendingRequestsRef.current.filter((request) => request.requestId !== requestId);
|
|
removeOptimisticRequestMessages(requestId);
|
|
setRequestItems((previous) =>
|
|
previous.filter((item) => !(item.sessionId === activeSessionId && item.requestId === requestId)),
|
|
);
|
|
setActiveSystemStatus('실패한 요청을 취소했습니다.');
|
|
setIsSystemStatusPending(false);
|
|
},
|
|
[
|
|
activeSessionId,
|
|
pendingRequestsRef,
|
|
removeOptimisticRequestMessages,
|
|
setActiveSystemStatus,
|
|
setIsSystemStatusPending,
|
|
setRequestItems,
|
|
],
|
|
);
|
|
|
|
const removeQueuedComposerRequest = useCallback(
|
|
async (requestId: string) => {
|
|
try {
|
|
await removeChatRuntimeJob(requestId);
|
|
setRequestItems((previous) =>
|
|
previous.map((item) =>
|
|
item.sessionId === activeSessionId && item.requestId === requestId
|
|
? {
|
|
...item,
|
|
status: 'removed',
|
|
statusMessage: '대기열에서 제거되었습니다.',
|
|
terminalAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
: item,
|
|
),
|
|
);
|
|
setActiveSystemStatus('대기 요청을 삭제했습니다.');
|
|
setIsSystemStatusPending(false);
|
|
} catch (error) {
|
|
setMessages((previous) => [
|
|
...previous.slice(-39),
|
|
createLocalMessage(error instanceof Error ? error.message : '대기 요청 제거 중 오류가 발생했습니다.'),
|
|
]);
|
|
}
|
|
},
|
|
[activeSessionId, createLocalMessage, setActiveSystemStatus, setIsSystemStatusPending, setMessages, setRequestItems],
|
|
);
|
|
|
|
const deleteStoredRequest = useCallback(
|
|
async (requestId: string) => {
|
|
try {
|
|
await chatGateway.deleteConversationRequest(activeSessionId, requestId);
|
|
setMessages((previous) => previous.filter((message) => message.clientRequestId !== requestId));
|
|
setRequestItems((previous) =>
|
|
previous.filter((item) => !(item.sessionId === activeSessionId && item.requestId === requestId)),
|
|
);
|
|
setConversationItems((previous) =>
|
|
previous.map((item) =>
|
|
item.sessionId === activeSessionId
|
|
? {
|
|
...item,
|
|
currentRequestId: item.currentRequestId === requestId ? null : item.currentRequestId,
|
|
currentJobStatus: item.currentRequestId === requestId ? null : item.currentJobStatus,
|
|
currentJobMessage: item.currentRequestId === requestId ? null : item.currentJobMessage,
|
|
currentQueueSize: item.currentRequestId === requestId ? 0 : item.currentQueueSize,
|
|
}
|
|
: item,
|
|
),
|
|
);
|
|
setActiveSystemStatus('요청을 삭제했습니다.');
|
|
setIsSystemStatusPending(false);
|
|
} catch (error) {
|
|
setMessages((previous) => [
|
|
...previous.slice(-39),
|
|
createLocalMessage(error instanceof Error ? error.message : '요청 삭제 중 오류가 발생했습니다.'),
|
|
]);
|
|
}
|
|
},
|
|
[
|
|
activeSessionId,
|
|
createLocalMessage,
|
|
setActiveSystemStatus,
|
|
setConversationItems,
|
|
setIsSystemStatusPending,
|
|
setMessages,
|
|
setRequestItems,
|
|
],
|
|
);
|
|
|
|
const handleRenameConversation = useCallback(async () => {
|
|
if (!activeConversation) {
|
|
return;
|
|
}
|
|
|
|
const sessionId = activeConversation.sessionId;
|
|
const previousTitle = activeConversation.title;
|
|
const trimmedTitle = editingConversationTitle.trim();
|
|
|
|
if (!trimmedTitle || trimmedTitle === previousTitle) {
|
|
setIsEditingConversationTitle(false);
|
|
setEditingConversationTitle(previousTitle);
|
|
return;
|
|
}
|
|
|
|
setRenamingConversationSessionId(sessionId);
|
|
setConversationItems((previous) =>
|
|
previous.map((entry) => (entry.sessionId === sessionId ? { ...entry, title: trimmedTitle } : entry)),
|
|
);
|
|
setEditingConversationTitle(trimmedTitle);
|
|
setIsEditingConversationTitle(false);
|
|
|
|
try {
|
|
const item = await chatGateway.renameConversation(sessionId, trimmedTitle);
|
|
setConversationItems((previous) => previous.map((entry) => (entry.sessionId === item.sessionId ? item : entry)));
|
|
setEditingConversationTitle(item.title);
|
|
} catch (error) {
|
|
setConversationItems((previous) =>
|
|
previous.map((entry) => (entry.sessionId === sessionId ? { ...entry, title: previousTitle } : entry)),
|
|
);
|
|
setEditingConversationTitle(previousTitle);
|
|
messageApi.error(error instanceof Error ? error.message : '채팅방 이름 변경에 실패했습니다.');
|
|
} finally {
|
|
setRenamingConversationSessionId((current) => (current === sessionId ? null : current));
|
|
}
|
|
}, [
|
|
activeConversation,
|
|
editingConversationTitle,
|
|
messageApi,
|
|
setConversationItems,
|
|
setEditingConversationTitle,
|
|
setIsEditingConversationTitle,
|
|
setRenamingConversationSessionId,
|
|
]);
|
|
|
|
const handleDeleteConversation = useCallback(
|
|
async (sessionId: string) => {
|
|
try {
|
|
await chatGateway.deleteConversation(sessionId);
|
|
const remaining = conversationItems.filter((entry) => entry.sessionId !== sessionId);
|
|
sessionMessageCacheRef.current.delete(sessionId);
|
|
setConversationItems(remaining);
|
|
|
|
if (sessionId === activeSessionId) {
|
|
replaceChatSessionInUrl('');
|
|
chatConnectionGateway.resetLastReceivedEventId('');
|
|
setActiveSessionId('');
|
|
setMessages([]);
|
|
setRequestItems([]);
|
|
setDraft('');
|
|
setComposerAttachments([]);
|
|
setCopiedMessageId(null);
|
|
setActivePreviewId(null);
|
|
setIsPreviewModalOpen(false);
|
|
setActiveSystemStatus(null);
|
|
setIsSystemStatusPending(false);
|
|
setIsResourceStripOpen(false);
|
|
setIsConversationPaneClosed(false);
|
|
setIsMobileConversationView(!isMobileViewport);
|
|
} else if (requestedSessionId === sessionId) {
|
|
replaceChatSessionInUrl(activeSessionId);
|
|
}
|
|
} catch (error) {
|
|
messageApi.error(error instanceof Error ? error.message : '대화방 삭제 중 오류가 발생했습니다.');
|
|
}
|
|
},
|
|
[
|
|
activeSessionId,
|
|
conversationItems,
|
|
isMobileViewport,
|
|
messageApi,
|
|
replaceChatSessionInUrl,
|
|
requestedSessionId,
|
|
sessionMessageCacheRef,
|
|
setActivePreviewId,
|
|
setActiveSessionId,
|
|
setActiveSystemStatus,
|
|
setComposerAttachments,
|
|
setConversationItems,
|
|
setCopiedMessageId,
|
|
setDraft,
|
|
setIsConversationPaneClosed,
|
|
setIsMobileConversationView,
|
|
setIsPreviewModalOpen,
|
|
setIsResourceStripOpen,
|
|
setIsSystemStatusPending,
|
|
setMessages,
|
|
setRequestItems,
|
|
],
|
|
);
|
|
|
|
return {
|
|
cancelPendingRequest,
|
|
deleteStoredRequest,
|
|
handleDeleteConversation,
|
|
handleRenameConversation,
|
|
removeQueuedComposerRequest,
|
|
retryPendingRequest,
|
|
};
|
|
}
|