chore: exclude local resource artifacts from main sync

This commit is contained in:
2026-05-15 10:16:45 +09:00
parent 442879313f
commit d38d022872
504 changed files with 17074 additions and 3642 deletions

View File

@@ -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,
});