Initial import
This commit is contained in:
206
src/app/main/chatV2/hooks/useConversationViewController.ts
Normal file
206
src/app/main/chatV2/hooks/useConversationViewController.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { ChatComposerAttachment, ChatMessage } from '../../mainChatPanel/types';
|
||||
|
||||
type PreviewItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
url: string;
|
||||
kind: 'image' | 'video' | 'markdown' | 'code' | 'document' | 'pdf' | 'file';
|
||||
source: 'message' | 'context';
|
||||
};
|
||||
|
||||
type UseConversationViewControllerOptions = {
|
||||
activeSessionId: string;
|
||||
activeView: 'chat' | 'runtime' | 'errors';
|
||||
previewItems: PreviewItem[];
|
||||
selectedChatTypeId: string | null;
|
||||
composerRef: { current: { focus: (options?: { cursor?: 'start' | 'end' | 'all' }) => void } | null };
|
||||
sessionMessageCacheRef: { current: Map<string, ChatMessage[]> };
|
||||
setActiveSystemStatus: (value: string | null) => void;
|
||||
setComposerAttachments: React.Dispatch<React.SetStateAction<ChatComposerAttachment[]>>;
|
||||
setCopiedMessageId: (value: number | null) => void;
|
||||
setDraft: React.Dispatch<React.SetStateAction<string>>;
|
||||
setIsResourceStripOpen: (value: boolean) => void;
|
||||
setIsSystemStatusPending: (value: boolean) => void;
|
||||
setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
|
||||
};
|
||||
|
||||
export function useConversationViewController({
|
||||
activeSessionId,
|
||||
activeView,
|
||||
composerRef,
|
||||
previewItems,
|
||||
selectedChatTypeId,
|
||||
sessionMessageCacheRef,
|
||||
setActiveSystemStatus,
|
||||
setComposerAttachments,
|
||||
setCopiedMessageId,
|
||||
setDraft,
|
||||
setIsResourceStripOpen,
|
||||
setIsSystemStatusPending,
|
||||
setMessages,
|
||||
}: UseConversationViewControllerOptions) {
|
||||
const previousSessionIdRef = useRef(activeSessionId);
|
||||
const [activePreviewId, setActivePreviewId] = useState<string | null>(null);
|
||||
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||
const [previewText, setPreviewText] = useState('');
|
||||
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
|
||||
const [previewError, setPreviewError] = useState('');
|
||||
const [previewContentType, setPreviewContentType] = useState('');
|
||||
|
||||
const activePreview = previewItems.find((item) => item.id === activePreviewId) ?? previewItems[0] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
const hasSessionChanged = previousSessionIdRef.current !== activeSessionId;
|
||||
|
||||
if (!hasSessionChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
previousSessionIdRef.current = activeSessionId;
|
||||
|
||||
setMessages(sessionMessageCacheRef.current.get(activeSessionId)?.slice() ?? []);
|
||||
setDraft('');
|
||||
setComposerAttachments([]);
|
||||
setCopiedMessageId(null);
|
||||
setActivePreviewId(null);
|
||||
setIsPreviewModalOpen(false);
|
||||
setActiveSystemStatus(null);
|
||||
setIsSystemStatusPending(false);
|
||||
setIsResourceStripOpen(false);
|
||||
}, [
|
||||
activeSessionId,
|
||||
sessionMessageCacheRef,
|
||||
setActiveSystemStatus,
|
||||
setComposerAttachments,
|
||||
setCopiedMessageId,
|
||||
setDraft,
|
||||
setIsResourceStripOpen,
|
||||
setIsSystemStatusPending,
|
||||
setMessages,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activePreviewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewItems.some((item) => item.id === activePreviewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActivePreviewId(null);
|
||||
setIsPreviewModalOpen(false);
|
||||
}, [activePreviewId, previewItems]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPreviewModalOpen || !activePreview) {
|
||||
setPreviewText('');
|
||||
setPreviewError('');
|
||||
setPreviewContentType('');
|
||||
setIsPreviewLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activePreview.kind === 'image' || activePreview.kind === 'video' || activePreview.kind === 'pdf') {
|
||||
setPreviewText('');
|
||||
setPreviewError('');
|
||||
setPreviewContentType('');
|
||||
setIsPreviewLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
setIsPreviewLoading(true);
|
||||
setPreviewError('');
|
||||
setPreviewContentType('');
|
||||
|
||||
fetch(activePreview.url, {
|
||||
cache: 'no-store',
|
||||
signal: controller.signal,
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
setPreviewContentType(response.headers.get('content-type') ?? '');
|
||||
const text = await response.text();
|
||||
setPreviewText(text.slice(0, 20000));
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewText('');
|
||||
setPreviewContentType('');
|
||||
setPreviewError(error instanceof Error ? error.message : 'preview를 가져오지 못했습니다.');
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
setIsPreviewLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [activePreview, isPreviewModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== 'chat') {
|
||||
return;
|
||||
}
|
||||
|
||||
composerRef.current?.focus({ cursor: 'end' });
|
||||
}, [activeView, composerRef, selectedChatTypeId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== 'chat') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const handleWindowKeyDown = (event: KeyboardEvent) => {
|
||||
const isTextEntryTarget =
|
||||
event.target instanceof HTMLElement &&
|
||||
(event.target.isContentEditable ||
|
||||
['input', 'textarea', 'select'].includes(event.target.tagName.toLowerCase()) ||
|
||||
event.target.closest('[contenteditable="true"]'));
|
||||
|
||||
if (event.metaKey || event.ctrlKey || event.altKey || isTextEntryTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === '/') {
|
||||
event.preventDefault();
|
||||
composerRef.current?.focus({ cursor: 'end' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key.length === 1) {
|
||||
event.preventDefault();
|
||||
composerRef.current?.focus({ cursor: 'end' });
|
||||
setDraft((previous) => previous + event.key);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleWindowKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleWindowKeyDown);
|
||||
};
|
||||
}, [activeView, composerRef, setDraft]);
|
||||
|
||||
return {
|
||||
activePreview,
|
||||
activePreviewId,
|
||||
isPreviewLoading,
|
||||
isPreviewModalOpen,
|
||||
previewContentType,
|
||||
previewError,
|
||||
previewText,
|
||||
setActivePreviewId,
|
||||
setIsPreviewModalOpen,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user