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 }; socketRef: { current: WebSocket | null }; setConversationItems: React.Dispatch>; setMessages: React.Dispatch>; setRequestItems: React.Dispatch>; setActiveSessionId: (value: string) => void; setDraft: (value: string) => void; setComposerAttachments: React.Dispatch>; 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, }; }