chore: update live chat and work server changes
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,19 @@ import { useCallback } from 'react';
|
||||
import { chatGateway } from '../data/chatGateway';
|
||||
import type { ChatComposerAttachment, ChatConversationRequest, ChatMessage } from '../../mainChatPanel/types';
|
||||
|
||||
export type ComposerFilePickResult = {
|
||||
items: {
|
||||
key: string;
|
||||
fileName: string;
|
||||
status: 'uploaded' | 'failed';
|
||||
reason?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
function buildComposerFilePickKey(file: File) {
|
||||
return `${file.name}:${file.size}:${file.type}:${file.lastModified}`;
|
||||
}
|
||||
|
||||
type PendingChatRequest = {
|
||||
sessionId: string;
|
||||
requestId: string;
|
||||
@@ -107,9 +120,9 @@ export function useConversationComposerController({
|
||||
scrollViewportToBottom,
|
||||
}: UseConversationComposerControllerOptions) {
|
||||
const handleComposerFilesPicked = useCallback(
|
||||
async (files: File[]) => {
|
||||
async (files: File[]): Promise<ComposerFilePickResult> => {
|
||||
if (files.length === 0 || isComposerAttachmentUploading) {
|
||||
return;
|
||||
return { items: [] };
|
||||
}
|
||||
|
||||
setIsComposerAttachmentUploading(true);
|
||||
@@ -117,7 +130,7 @@ export function useConversationComposerController({
|
||||
files.map((file) => chatGateway.uploadComposerFile(activeSessionId, file)),
|
||||
);
|
||||
const uploadedItems: ChatComposerAttachment[] = [];
|
||||
const failedFileNames: string[] = [];
|
||||
const failedItems: Array<{ fileName: string; reason: string }> = [];
|
||||
|
||||
uploadResults.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
@@ -125,7 +138,12 @@ export function useConversationComposerController({
|
||||
return;
|
||||
}
|
||||
|
||||
failedFileNames.push(files[index]?.name || `파일 ${index + 1}`);
|
||||
const fileName = files[index]?.name || `파일 ${index + 1}`;
|
||||
const reason =
|
||||
result.reason instanceof Error && result.reason.message.trim()
|
||||
? result.reason.message.trim()
|
||||
: '업로드 실패';
|
||||
failedItems.push({ fileName, reason });
|
||||
});
|
||||
|
||||
if (uploadedItems.length > 0) {
|
||||
@@ -134,14 +152,29 @@ export function useConversationComposerController({
|
||||
setShowScrollToBottom(false);
|
||||
}
|
||||
|
||||
if (failedFileNames.length > 0) {
|
||||
if (failedItems.length > 0) {
|
||||
setMessages((previous) => [
|
||||
...previous.slice(-39),
|
||||
createLocalMessage(`파일 업로드에 실패했습니다: ${failedFileNames.join(', ')}`),
|
||||
createLocalMessage(
|
||||
['파일 업로드에 실패했습니다:', ...failedItems.map((item) => `- ${item.fileName}: ${item.reason}`)].join('\n'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
setIsComposerAttachmentUploading(false);
|
||||
return {
|
||||
items: uploadResults.map((result, index) => ({
|
||||
key: buildComposerFilePickKey(files[index] as File),
|
||||
fileName: files[index]?.name || `파일 ${index + 1}`,
|
||||
status: result.status === 'fulfilled' ? 'uploaded' : 'failed',
|
||||
reason:
|
||||
result.status === 'fulfilled'
|
||||
? undefined
|
||||
: result.reason instanceof Error && result.reason.message.trim()
|
||||
? result.reason.message.trim()
|
||||
: '업로드 실패',
|
||||
})),
|
||||
};
|
||||
},
|
||||
[
|
||||
activeSessionId,
|
||||
|
||||
@@ -28,6 +28,7 @@ type UseConversationRoomDataOptions = {
|
||||
activeSessionId: string;
|
||||
oldestLoadedMessageId: number | null;
|
||||
reloadKey: number;
|
||||
shouldForceStickToBottomOnNextLoadRef: MutableRefObject<boolean>;
|
||||
connectionState: 'connecting' | 'connected' | 'disconnected';
|
||||
captureViewportRestoreSnapshot: (options?: { forceStickToBottom?: boolean }) => void;
|
||||
sessionMessageCacheRef: MutableRefObject<Map<string, ChatMessage[]>>;
|
||||
@@ -51,6 +52,7 @@ export function useConversationRoomData({
|
||||
activeSessionId,
|
||||
oldestLoadedMessageId,
|
||||
reloadKey,
|
||||
shouldForceStickToBottomOnNextLoadRef,
|
||||
connectionState,
|
||||
captureViewportRestoreSnapshot,
|
||||
sessionMessageCacheRef,
|
||||
@@ -93,11 +95,13 @@ export function useConversationRoomData({
|
||||
|
||||
const loadConversationDetail = async () => {
|
||||
const isSessionChanged = previousSessionIdRef.current !== requestedSessionId;
|
||||
const shouldForceStickToBottom = isSessionChanged || shouldForceStickToBottomOnNextLoadRef.current;
|
||||
|
||||
previousSessionIdRef.current = requestedSessionId;
|
||||
captureViewportRestoreSnapshot({
|
||||
forceStickToBottom: isSessionChanged,
|
||||
forceStickToBottom: shouldForceStickToBottom,
|
||||
});
|
||||
shouldForceStickToBottomOnNextLoadRef.current = false;
|
||||
pendingViewportRestoreRef.current = true;
|
||||
setConversationLoadingLabel('대화 내용을 불러오는 중입니다.');
|
||||
const cachedMessages = isSessionChanged ? [] : (sessionMessageCacheRef.current.get(requestedSessionId) ?? []);
|
||||
@@ -196,6 +200,7 @@ export function useConversationRoomData({
|
||||
pendingViewportRestoreRef,
|
||||
reloadKey,
|
||||
sessionMessageCacheRef,
|
||||
shouldForceStickToBottomOnNextLoadRef,
|
||||
setConversationItems,
|
||||
setConversationLoadingLabel,
|
||||
setIsConversationContentLoading,
|
||||
|
||||
@@ -12,6 +12,7 @@ type PreviewItem = {
|
||||
type UseConversationViewControllerOptions = {
|
||||
activeSessionId: string;
|
||||
activeView: 'chat' | 'runtime' | 'errors';
|
||||
isMobileViewport: boolean;
|
||||
previewItems: PreviewItem[];
|
||||
selectedChatTypeId: string | null;
|
||||
composerRef: { current: { focus: (options?: { cursor?: 'start' | 'end' | 'all' }) => void } | null };
|
||||
@@ -28,6 +29,7 @@ export function useConversationViewController({
|
||||
activeSessionId,
|
||||
activeView,
|
||||
composerRef,
|
||||
isMobileViewport,
|
||||
previewItems,
|
||||
selectedChatTypeId,
|
||||
setActiveSystemStatus,
|
||||
@@ -99,7 +101,12 @@ export function useConversationViewController({
|
||||
return;
|
||||
}
|
||||
|
||||
if (activePreview.kind === 'image' || activePreview.kind === 'video' || activePreview.kind === 'pdf') {
|
||||
if (
|
||||
activePreview.kind === 'image' ||
|
||||
activePreview.kind === 'video' ||
|
||||
activePreview.kind === 'pdf' ||
|
||||
activePreview.kind === 'file'
|
||||
) {
|
||||
setPreviewText('');
|
||||
setPreviewError('');
|
||||
setPreviewContentType('');
|
||||
@@ -146,12 +153,12 @@ export function useConversationViewController({
|
||||
}, [activePreview, isPreviewModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== 'chat') {
|
||||
if (activeView !== 'chat' || isMobileViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
composerRef.current?.focus({ cursor: 'end' });
|
||||
}, [activeView, composerRef, selectedChatTypeId]);
|
||||
}, [activeView, composerRef, isMobileViewport, selectedChatTypeId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== 'chat') {
|
||||
|
||||
Reference in New Issue
Block a user