chore: exclude local resource artifacts from main sync
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { chatGateway } from '../data/chatGateway';
|
||||
import type { ChatComposerAttachment, ChatConversationRequest, ChatMessage } from '../../mainChatPanel/types';
|
||||
import { buildComposerFilePickKey } from '../../mainChatPanel/composerFilePickKey';
|
||||
|
||||
export type ComposerFilePickResult = {
|
||||
items: {
|
||||
@@ -11,19 +12,26 @@ export type ComposerFilePickResult = {
|
||||
}[];
|
||||
};
|
||||
|
||||
function buildComposerFilePickKey(file: File) {
|
||||
return `${file.name}:${file.size}:${file.type}:${file.lastModified}`;
|
||||
}
|
||||
|
||||
type PendingChatRequest = {
|
||||
sessionId: string;
|
||||
requestId: string;
|
||||
text: string;
|
||||
mode: 'queue' | 'direct';
|
||||
origin?: 'composer' | 'prompt';
|
||||
parentRequestId?: string | null;
|
||||
omitPromptHistory?: boolean;
|
||||
chatTypeId: string;
|
||||
chatTypeLabel: string;
|
||||
chatTypeDescription: string;
|
||||
chatTypeBaseDescription?: string;
|
||||
defaultContextIds?: string[];
|
||||
defaultContexts?: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}>;
|
||||
customContextTitle?: string | null;
|
||||
customContextContent?: string | null;
|
||||
retryCount: number;
|
||||
failed: boolean;
|
||||
};
|
||||
@@ -31,9 +39,20 @@ type PendingChatRequest = {
|
||||
type PendingContextConfirm = {
|
||||
mode: 'queue' | 'direct';
|
||||
text: string;
|
||||
origin?: 'composer' | 'prompt';
|
||||
parentRequestId?: string | null;
|
||||
chatTypeId: string;
|
||||
chatTypeLabel: string;
|
||||
chatTypeDescription: string;
|
||||
chatTypeBaseDescription?: string;
|
||||
defaultContextIds?: string[];
|
||||
defaultContexts?: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}>;
|
||||
customContextTitle?: string | null;
|
||||
customContextContent?: string | null;
|
||||
includedContextCount: number;
|
||||
omittedContextCount: number;
|
||||
omitPromptHistory?: boolean;
|
||||
@@ -43,6 +62,15 @@ type SelectedChatType = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
baseDescription?: string;
|
||||
defaultContextIds?: string[];
|
||||
defaultContexts?: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}>;
|
||||
customContextTitle?: string | null;
|
||||
customContextContent?: string | null;
|
||||
} | null;
|
||||
|
||||
type RecentContextSummary = {
|
||||
@@ -64,6 +92,7 @@ type UseConversationComposerControllerOptions = {
|
||||
composerRef: { current: { focus: (options?: { cursor?: 'start' | 'end' | 'all' }) => void } | null };
|
||||
messagesRef: { current: ChatMessage[] };
|
||||
pendingRequestsRef: { current: PendingChatRequest[] };
|
||||
promptRequestIdsRef?: { current: Set<string> };
|
||||
shouldStickToBottomRef: { current: boolean };
|
||||
setDraft: (value: string) => void;
|
||||
setComposerAttachments: React.Dispatch<React.SetStateAction<ChatComposerAttachment[]>>;
|
||||
@@ -80,6 +109,7 @@ type UseConversationComposerControllerOptions = {
|
||||
requestedAt?: string,
|
||||
options?: {
|
||||
requestId?: string;
|
||||
requestOrigin?: 'composer' | 'prompt';
|
||||
mode?: 'queue' | 'direct';
|
||||
queueSize?: number;
|
||||
jobMessage?: string | null;
|
||||
@@ -95,6 +125,7 @@ type UseConversationComposerControllerOptions = {
|
||||
previous: ChatComposerAttachment[],
|
||||
next: ChatComposerAttachment[],
|
||||
) => ChatComposerAttachment[];
|
||||
ensureSessionReady?: (sessionId: string) => Promise<boolean>;
|
||||
sendChatRequest: (socket: WebSocket, request: PendingChatRequest) => void;
|
||||
scrollViewportToBottom: () => void;
|
||||
};
|
||||
@@ -104,6 +135,14 @@ type SendMessageOptions = {
|
||||
draftText?: string;
|
||||
};
|
||||
|
||||
function createClientRequestId() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return `client-${crypto.randomUUID()}`;
|
||||
}
|
||||
|
||||
return `client-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
export function useConversationComposerController({
|
||||
activeSessionId,
|
||||
appConfigChat,
|
||||
@@ -115,6 +154,7 @@ export function useConversationComposerController({
|
||||
composerRef,
|
||||
messagesRef,
|
||||
pendingRequestsRef,
|
||||
promptRequestIdsRef,
|
||||
shouldStickToBottomRef,
|
||||
setDraft,
|
||||
setComposerAttachments,
|
||||
@@ -133,11 +173,13 @@ export function useConversationComposerController({
|
||||
buildOutgoingMessageText,
|
||||
summarizeRecentContext,
|
||||
mergeComposerAttachments,
|
||||
ensureSessionReady,
|
||||
sendChatRequest,
|
||||
scrollViewportToBottom,
|
||||
}: UseConversationComposerControllerOptions) {
|
||||
const composerUploadQueueRef = useRef(Promise.resolve<ComposerFilePickResult>({ items: [] }));
|
||||
const activeComposerUploadCountRef = useRef(0);
|
||||
const latestComposerUploadAttemptByKeyRef = useRef(new Map<string, string>());
|
||||
|
||||
const handleComposerFilesPicked = useCallback(
|
||||
async (files: File[]): Promise<ComposerFilePickResult> => {
|
||||
@@ -145,6 +187,13 @@ export function useConversationComposerController({
|
||||
return { items: [] };
|
||||
}
|
||||
|
||||
const batchAttemptId = `composer-upload-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const fileKeys = files.map((file) => buildComposerFilePickKey(file));
|
||||
|
||||
fileKeys.forEach((key) => {
|
||||
latestComposerUploadAttemptByKeyRef.current.set(key, batchAttemptId);
|
||||
});
|
||||
|
||||
const uploadBatch = async (): Promise<ComposerFilePickResult> => {
|
||||
activeComposerUploadCountRef.current += 1;
|
||||
|
||||
@@ -160,6 +209,12 @@ export function useConversationComposerController({
|
||||
const failedItems: Array<{ fileName: string; reason: string }> = [];
|
||||
|
||||
uploadResults.forEach((result, index) => {
|
||||
const fileKey = fileKeys[index];
|
||||
|
||||
if (!fileKey || latestComposerUploadAttemptByKeyRef.current.get(fileKey) !== batchAttemptId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status === 'fulfilled') {
|
||||
uploadedItems.push(result.value);
|
||||
return;
|
||||
@@ -218,6 +273,7 @@ export function useConversationComposerController({
|
||||
activeSessionId,
|
||||
composerUploadQueueRef,
|
||||
createLocalMessage,
|
||||
latestComposerUploadAttemptByKeyRef,
|
||||
mergeComposerAttachments,
|
||||
setComposerAttachments,
|
||||
setIsComposerAttachmentUploading,
|
||||
@@ -235,22 +291,60 @@ export function useConversationComposerController({
|
||||
}, [composerRef, scrollViewportToBottom]);
|
||||
|
||||
const executeSendMessage = useCallback(
|
||||
(request: PendingContextConfirm) => {
|
||||
const { mode, text, chatTypeId, chatTypeLabel, chatTypeDescription, omitPromptHistory } = request;
|
||||
const requestId = `client-${Date.now().toString(36)}`;
|
||||
async (request: PendingContextConfirm) => {
|
||||
const {
|
||||
mode,
|
||||
text,
|
||||
origin,
|
||||
parentRequestId,
|
||||
chatTypeId,
|
||||
chatTypeLabel,
|
||||
chatTypeDescription,
|
||||
chatTypeBaseDescription,
|
||||
defaultContextIds,
|
||||
defaultContexts,
|
||||
customContextTitle,
|
||||
customContextContent,
|
||||
omitPromptHistory,
|
||||
} = request;
|
||||
|
||||
if (ensureSessionReady) {
|
||||
setActiveSystemStatus('새 채팅방 준비 중...');
|
||||
setIsSystemStatusPending(true);
|
||||
const isSessionReady = await ensureSessionReady(activeSessionId);
|
||||
|
||||
if (!isSessionReady) {
|
||||
setActiveSystemStatus(null);
|
||||
setIsSystemStatusPending(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const requestId = createClientRequestId();
|
||||
const outgoingRequest: PendingChatRequest = {
|
||||
sessionId: activeSessionId,
|
||||
requestId,
|
||||
text,
|
||||
mode,
|
||||
origin: origin ?? 'composer',
|
||||
parentRequestId: parentRequestId?.trim() || null,
|
||||
omitPromptHistory: omitPromptHistory === true,
|
||||
chatTypeId,
|
||||
chatTypeLabel,
|
||||
chatTypeDescription,
|
||||
chatTypeBaseDescription,
|
||||
defaultContextIds,
|
||||
defaultContexts,
|
||||
customContextTitle,
|
||||
customContextContent,
|
||||
retryCount: 0,
|
||||
failed: false,
|
||||
};
|
||||
|
||||
if (origin === 'prompt') {
|
||||
promptRequestIdsRef?.current.add(requestId);
|
||||
}
|
||||
|
||||
if (mode === 'queue') {
|
||||
const queuedAt = new Date().toISOString();
|
||||
const optimisticUserMessage: ChatMessage = {
|
||||
@@ -260,11 +354,13 @@ export function useConversationComposerController({
|
||||
};
|
||||
const optimisticActivityMessage = createActivityLogPlaceholder(requestId, [
|
||||
'# 상태: 요청을 접수했습니다.',
|
||||
'# 진행: 대기열 등록을 준비하고 있습니다.',
|
||||
'# 진행: 순서를 기다리기 전에 요청 내용을 정리하고 있습니다.',
|
||||
]);
|
||||
upsertRequestItem({
|
||||
sessionId: activeSessionId,
|
||||
requestId,
|
||||
requestOrigin: origin ?? 'composer',
|
||||
parentRequestId: parentRequestId?.trim() || null,
|
||||
status: 'queued',
|
||||
statusMessage: '대기열 등록',
|
||||
userMessageId: optimisticUserMessage.id,
|
||||
@@ -280,6 +376,7 @@ export function useConversationComposerController({
|
||||
});
|
||||
syncConversationPreviewForRequest(activeSessionId, text, queuedAt, {
|
||||
requestId,
|
||||
requestOrigin: origin ?? 'composer',
|
||||
mode: 'queue',
|
||||
queueSize: 1,
|
||||
jobMessage: '대기열 등록 중',
|
||||
@@ -301,11 +398,13 @@ export function useConversationComposerController({
|
||||
};
|
||||
const optimisticActivityMessage = createActivityLogPlaceholder(requestId, [
|
||||
'# 상태: 즉시 요청을 접수했습니다.',
|
||||
'# 진행: 즉시 실행 대기 중입니다.',
|
||||
'# 진행: 요청 내용을 정리한 뒤 답변을 준비합니다.',
|
||||
]);
|
||||
upsertRequestItem({
|
||||
sessionId: activeSessionId,
|
||||
requestId,
|
||||
requestOrigin: origin ?? 'composer',
|
||||
parentRequestId: parentRequestId?.trim() || null,
|
||||
status: 'accepted',
|
||||
statusMessage: '요청을 접수했습니다.',
|
||||
userMessageId: optimisticUserMessage.id,
|
||||
@@ -321,6 +420,7 @@ export function useConversationComposerController({
|
||||
});
|
||||
syncConversationPreviewForRequest(activeSessionId, text, new Date().toISOString(), {
|
||||
requestId,
|
||||
requestOrigin: origin ?? 'composer',
|
||||
mode: 'direct',
|
||||
queueSize: 0,
|
||||
jobMessage: '즉시 요청 실행 대기 중',
|
||||
@@ -367,13 +467,16 @@ export function useConversationComposerController({
|
||||
setDraft('');
|
||||
setComposerAttachments([]);
|
||||
focusComposerAfterSend();
|
||||
return true;
|
||||
},
|
||||
[
|
||||
activeSessionId,
|
||||
createActivityLogPlaceholder,
|
||||
createChatMessage,
|
||||
ensureSessionReady,
|
||||
focusComposerAfterSend,
|
||||
pendingRequestsRef,
|
||||
promptRequestIdsRef,
|
||||
sendChatRequest,
|
||||
setActiveSystemStatus,
|
||||
setComposerAttachments,
|
||||
@@ -422,6 +525,11 @@ export function useConversationComposerController({
|
||||
chatTypeId: selectedChatType.id,
|
||||
chatTypeLabel: selectedChatType.name,
|
||||
chatTypeDescription: selectedChatType.description,
|
||||
chatTypeBaseDescription: selectedChatType.baseDescription,
|
||||
defaultContextIds: selectedChatType.defaultContextIds,
|
||||
defaultContexts: selectedChatType.defaultContexts,
|
||||
customContextTitle: selectedChatType.customContextTitle,
|
||||
customContextContent: selectedChatType.customContextContent,
|
||||
includedContextCount: recentContext.includedCount,
|
||||
omittedContextCount: recentContext.omittedCount,
|
||||
});
|
||||
@@ -434,6 +542,11 @@ export function useConversationComposerController({
|
||||
chatTypeId: selectedChatType.id,
|
||||
chatTypeLabel: selectedChatType.name,
|
||||
chatTypeDescription: selectedChatType.description,
|
||||
chatTypeBaseDescription: selectedChatType.baseDescription,
|
||||
defaultContextIds: selectedChatType.defaultContextIds,
|
||||
defaultContexts: selectedChatType.defaultContexts,
|
||||
customContextTitle: selectedChatType.customContextTitle,
|
||||
customContextContent: selectedChatType.customContextContent,
|
||||
includedContextCount: 0,
|
||||
omittedContextCount: 0,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user