@gendive/chatllm 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/index.d.mts +118 -0
- package/dist/react/index.d.ts +118 -0
- package/dist/react/index.js +200 -15
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +200 -15
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react/index.d.mts
CHANGED
|
@@ -248,6 +248,11 @@ interface ChatUIProps {
|
|
|
248
248
|
onError?: (error: Error) => void;
|
|
249
249
|
/** @Todo vibecode - 세션 제목 변경 핸들러 */
|
|
250
250
|
onTitleChange?: (sessionId: string, newTitle: string) => void;
|
|
251
|
+
/**
|
|
252
|
+
* @description 첫 메시지 전송 시 LLM으로 제목 생성 콜백
|
|
253
|
+
* @Todo vibecode - 자동 제목 생성 기능
|
|
254
|
+
*/
|
|
255
|
+
generateTitle?: (firstMessage: string) => Promise<string> | string;
|
|
251
256
|
/** 재압축 임계값 - 새 메시지 수 (기본: 10) */
|
|
252
257
|
recompressionThreshold?: number;
|
|
253
258
|
/** 토큰 한도 (기본: 8000) */
|
|
@@ -260,6 +265,55 @@ interface ChatUIProps {
|
|
|
260
265
|
enableAutoExtraction?: boolean;
|
|
261
266
|
/** 정보 추출 설정 */
|
|
262
267
|
infoExtractionConfig?: Partial<InfoExtractionConfig>;
|
|
268
|
+
/**
|
|
269
|
+
* @description 외부 스토리지 사용 여부
|
|
270
|
+
* @Todo vibecode - true 시 localStorage 대신 콜백 사용
|
|
271
|
+
*/
|
|
272
|
+
useExternalStorage?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* @description 세션 목록 로드 콜백
|
|
275
|
+
* @Todo vibecode - 컴포넌트 마운트 시 호출
|
|
276
|
+
*/
|
|
277
|
+
onLoadSessions?: () => Promise<{
|
|
278
|
+
id: string;
|
|
279
|
+
title: string;
|
|
280
|
+
}[]>;
|
|
281
|
+
/**
|
|
282
|
+
* @description 세션 생성 콜백
|
|
283
|
+
* @Todo vibecode - 새 채팅 생성 시 호출
|
|
284
|
+
*/
|
|
285
|
+
onCreateSession?: () => Promise<{
|
|
286
|
+
id: string;
|
|
287
|
+
title: string;
|
|
288
|
+
}>;
|
|
289
|
+
/**
|
|
290
|
+
* @description 세션 상세 로드 콜백 (메시지 포함)
|
|
291
|
+
* @Todo vibecode - 세션 선택 시 호출 (lazy loading)
|
|
292
|
+
*/
|
|
293
|
+
onLoadSession?: (sessionId: string) => Promise<{
|
|
294
|
+
id: string;
|
|
295
|
+
title: string;
|
|
296
|
+
messages: ChatMessage[];
|
|
297
|
+
memory_data?: object;
|
|
298
|
+
}>;
|
|
299
|
+
/**
|
|
300
|
+
* @description 세션 삭제 콜백
|
|
301
|
+
* @Todo vibecode - 세션 삭제 시 호출
|
|
302
|
+
*/
|
|
303
|
+
onDeleteSession?: (sessionId: string) => Promise<void>;
|
|
304
|
+
/**
|
|
305
|
+
* @description 세션 제목 수정 콜백
|
|
306
|
+
* @Todo vibecode - 세션 제목 변경 시 호출
|
|
307
|
+
*/
|
|
308
|
+
onUpdateSessionTitle?: (sessionId: string, title: string) => Promise<void>;
|
|
309
|
+
/**
|
|
310
|
+
* @description 메시지 저장 콜백
|
|
311
|
+
* @Todo vibecode - 메시지 전송 완료 후 호출
|
|
312
|
+
*/
|
|
313
|
+
onSaveMessages?: (sessionId: string, messages: {
|
|
314
|
+
role: 'USER' | 'ASSISTANT';
|
|
315
|
+
message: string;
|
|
316
|
+
}[]) => Promise<void>;
|
|
263
317
|
}
|
|
264
318
|
interface SendMessageParams {
|
|
265
319
|
messages: {
|
|
@@ -432,6 +486,16 @@ interface UseChatUIReturn {
|
|
|
432
486
|
compressionState: CompressionState | null;
|
|
433
487
|
/** 수동으로 정보 추출 트리거 */
|
|
434
488
|
extractUserInfo: () => Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* @description 세션 목록 로딩 상태
|
|
491
|
+
* @Todo vibecode - 외부 스토리지 사용 시 세션 목록 로드 중
|
|
492
|
+
*/
|
|
493
|
+
isSessionsLoading: boolean;
|
|
494
|
+
/**
|
|
495
|
+
* @description 단일 세션 로딩 상태
|
|
496
|
+
* @Todo vibecode - 외부 스토리지 사용 시 세션 상세 로드 중
|
|
497
|
+
*/
|
|
498
|
+
isSessionLoading: boolean;
|
|
435
499
|
}
|
|
436
500
|
|
|
437
501
|
/**
|
|
@@ -473,12 +537,66 @@ interface UseChatUIOptions {
|
|
|
473
537
|
onError?: (error: Error) => void;
|
|
474
538
|
/** @Todo vibecode - 세션 제목 변경 핸들러 */
|
|
475
539
|
onTitleChange?: (sessionId: string, newTitle: string) => void;
|
|
540
|
+
/**
|
|
541
|
+
* @description 첫 메시지 전송 시 LLM으로 제목 생성 콜백
|
|
542
|
+
* @Todo vibecode - 자동 제목 생성 기능
|
|
543
|
+
*/
|
|
544
|
+
generateTitle?: (firstMessage: string) => Promise<string> | string;
|
|
476
545
|
/** 글로벌 메모리 사용 여부 (기본: true) */
|
|
477
546
|
useGlobalMemoryEnabled?: boolean;
|
|
478
547
|
/** 글로벌 메모리 설정 */
|
|
479
548
|
globalMemoryConfig?: GlobalMemoryConfig;
|
|
480
549
|
/** 자동 정보 추출 활성화 (기본: true) */
|
|
481
550
|
enableAutoExtraction?: boolean;
|
|
551
|
+
/**
|
|
552
|
+
* @description 외부 스토리지 사용 여부
|
|
553
|
+
* @Todo vibecode - true 시 localStorage 대신 콜백 사용
|
|
554
|
+
*/
|
|
555
|
+
useExternalStorage?: boolean;
|
|
556
|
+
/**
|
|
557
|
+
* @description 세션 목록 로드 콜백
|
|
558
|
+
* @Todo vibecode - 컴포넌트 마운트 시 호출
|
|
559
|
+
*/
|
|
560
|
+
onLoadSessions?: () => Promise<{
|
|
561
|
+
id: string;
|
|
562
|
+
title: string;
|
|
563
|
+
}[]>;
|
|
564
|
+
/**
|
|
565
|
+
* @description 세션 생성 콜백
|
|
566
|
+
* @Todo vibecode - 새 채팅 생성 시 호출
|
|
567
|
+
*/
|
|
568
|
+
onCreateSession?: () => Promise<{
|
|
569
|
+
id: string;
|
|
570
|
+
title: string;
|
|
571
|
+
}>;
|
|
572
|
+
/**
|
|
573
|
+
* @description 세션 상세 로드 콜백 (메시지 포함)
|
|
574
|
+
* @Todo vibecode - 세션 선택 시 호출 (lazy loading)
|
|
575
|
+
*/
|
|
576
|
+
onLoadSession?: (sessionId: string) => Promise<{
|
|
577
|
+
id: string;
|
|
578
|
+
title: string;
|
|
579
|
+
messages: ChatMessage[];
|
|
580
|
+
memory_data?: object;
|
|
581
|
+
}>;
|
|
582
|
+
/**
|
|
583
|
+
* @description 세션 삭제 콜백
|
|
584
|
+
* @Todo vibecode - 세션 삭제 시 호출
|
|
585
|
+
*/
|
|
586
|
+
onDeleteSession?: (sessionId: string) => Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* @description 세션 제목 수정 콜백
|
|
589
|
+
* @Todo vibecode - 세션 제목 변경 시 호출
|
|
590
|
+
*/
|
|
591
|
+
onUpdateSessionTitle?: (sessionId: string, title: string) => Promise<void>;
|
|
592
|
+
/**
|
|
593
|
+
* @description 메시지 저장 콜백
|
|
594
|
+
* @Todo vibecode - 메시지 전송 완료 후 호출
|
|
595
|
+
*/
|
|
596
|
+
onSaveMessages?: (sessionId: string, messages: {
|
|
597
|
+
role: 'USER' | 'ASSISTANT';
|
|
598
|
+
message: string;
|
|
599
|
+
}[]) => Promise<void>;
|
|
482
600
|
}
|
|
483
601
|
declare const useChatUI: (options: UseChatUIOptions) => UseChatUIReturn;
|
|
484
602
|
|
package/dist/react/index.d.ts
CHANGED
|
@@ -248,6 +248,11 @@ interface ChatUIProps {
|
|
|
248
248
|
onError?: (error: Error) => void;
|
|
249
249
|
/** @Todo vibecode - 세션 제목 변경 핸들러 */
|
|
250
250
|
onTitleChange?: (sessionId: string, newTitle: string) => void;
|
|
251
|
+
/**
|
|
252
|
+
* @description 첫 메시지 전송 시 LLM으로 제목 생성 콜백
|
|
253
|
+
* @Todo vibecode - 자동 제목 생성 기능
|
|
254
|
+
*/
|
|
255
|
+
generateTitle?: (firstMessage: string) => Promise<string> | string;
|
|
251
256
|
/** 재압축 임계값 - 새 메시지 수 (기본: 10) */
|
|
252
257
|
recompressionThreshold?: number;
|
|
253
258
|
/** 토큰 한도 (기본: 8000) */
|
|
@@ -260,6 +265,55 @@ interface ChatUIProps {
|
|
|
260
265
|
enableAutoExtraction?: boolean;
|
|
261
266
|
/** 정보 추출 설정 */
|
|
262
267
|
infoExtractionConfig?: Partial<InfoExtractionConfig>;
|
|
268
|
+
/**
|
|
269
|
+
* @description 외부 스토리지 사용 여부
|
|
270
|
+
* @Todo vibecode - true 시 localStorage 대신 콜백 사용
|
|
271
|
+
*/
|
|
272
|
+
useExternalStorage?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* @description 세션 목록 로드 콜백
|
|
275
|
+
* @Todo vibecode - 컴포넌트 마운트 시 호출
|
|
276
|
+
*/
|
|
277
|
+
onLoadSessions?: () => Promise<{
|
|
278
|
+
id: string;
|
|
279
|
+
title: string;
|
|
280
|
+
}[]>;
|
|
281
|
+
/**
|
|
282
|
+
* @description 세션 생성 콜백
|
|
283
|
+
* @Todo vibecode - 새 채팅 생성 시 호출
|
|
284
|
+
*/
|
|
285
|
+
onCreateSession?: () => Promise<{
|
|
286
|
+
id: string;
|
|
287
|
+
title: string;
|
|
288
|
+
}>;
|
|
289
|
+
/**
|
|
290
|
+
* @description 세션 상세 로드 콜백 (메시지 포함)
|
|
291
|
+
* @Todo vibecode - 세션 선택 시 호출 (lazy loading)
|
|
292
|
+
*/
|
|
293
|
+
onLoadSession?: (sessionId: string) => Promise<{
|
|
294
|
+
id: string;
|
|
295
|
+
title: string;
|
|
296
|
+
messages: ChatMessage[];
|
|
297
|
+
memory_data?: object;
|
|
298
|
+
}>;
|
|
299
|
+
/**
|
|
300
|
+
* @description 세션 삭제 콜백
|
|
301
|
+
* @Todo vibecode - 세션 삭제 시 호출
|
|
302
|
+
*/
|
|
303
|
+
onDeleteSession?: (sessionId: string) => Promise<void>;
|
|
304
|
+
/**
|
|
305
|
+
* @description 세션 제목 수정 콜백
|
|
306
|
+
* @Todo vibecode - 세션 제목 변경 시 호출
|
|
307
|
+
*/
|
|
308
|
+
onUpdateSessionTitle?: (sessionId: string, title: string) => Promise<void>;
|
|
309
|
+
/**
|
|
310
|
+
* @description 메시지 저장 콜백
|
|
311
|
+
* @Todo vibecode - 메시지 전송 완료 후 호출
|
|
312
|
+
*/
|
|
313
|
+
onSaveMessages?: (sessionId: string, messages: {
|
|
314
|
+
role: 'USER' | 'ASSISTANT';
|
|
315
|
+
message: string;
|
|
316
|
+
}[]) => Promise<void>;
|
|
263
317
|
}
|
|
264
318
|
interface SendMessageParams {
|
|
265
319
|
messages: {
|
|
@@ -432,6 +486,16 @@ interface UseChatUIReturn {
|
|
|
432
486
|
compressionState: CompressionState | null;
|
|
433
487
|
/** 수동으로 정보 추출 트리거 */
|
|
434
488
|
extractUserInfo: () => Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* @description 세션 목록 로딩 상태
|
|
491
|
+
* @Todo vibecode - 외부 스토리지 사용 시 세션 목록 로드 중
|
|
492
|
+
*/
|
|
493
|
+
isSessionsLoading: boolean;
|
|
494
|
+
/**
|
|
495
|
+
* @description 단일 세션 로딩 상태
|
|
496
|
+
* @Todo vibecode - 외부 스토리지 사용 시 세션 상세 로드 중
|
|
497
|
+
*/
|
|
498
|
+
isSessionLoading: boolean;
|
|
435
499
|
}
|
|
436
500
|
|
|
437
501
|
/**
|
|
@@ -473,12 +537,66 @@ interface UseChatUIOptions {
|
|
|
473
537
|
onError?: (error: Error) => void;
|
|
474
538
|
/** @Todo vibecode - 세션 제목 변경 핸들러 */
|
|
475
539
|
onTitleChange?: (sessionId: string, newTitle: string) => void;
|
|
540
|
+
/**
|
|
541
|
+
* @description 첫 메시지 전송 시 LLM으로 제목 생성 콜백
|
|
542
|
+
* @Todo vibecode - 자동 제목 생성 기능
|
|
543
|
+
*/
|
|
544
|
+
generateTitle?: (firstMessage: string) => Promise<string> | string;
|
|
476
545
|
/** 글로벌 메모리 사용 여부 (기본: true) */
|
|
477
546
|
useGlobalMemoryEnabled?: boolean;
|
|
478
547
|
/** 글로벌 메모리 설정 */
|
|
479
548
|
globalMemoryConfig?: GlobalMemoryConfig;
|
|
480
549
|
/** 자동 정보 추출 활성화 (기본: true) */
|
|
481
550
|
enableAutoExtraction?: boolean;
|
|
551
|
+
/**
|
|
552
|
+
* @description 외부 스토리지 사용 여부
|
|
553
|
+
* @Todo vibecode - true 시 localStorage 대신 콜백 사용
|
|
554
|
+
*/
|
|
555
|
+
useExternalStorage?: boolean;
|
|
556
|
+
/**
|
|
557
|
+
* @description 세션 목록 로드 콜백
|
|
558
|
+
* @Todo vibecode - 컴포넌트 마운트 시 호출
|
|
559
|
+
*/
|
|
560
|
+
onLoadSessions?: () => Promise<{
|
|
561
|
+
id: string;
|
|
562
|
+
title: string;
|
|
563
|
+
}[]>;
|
|
564
|
+
/**
|
|
565
|
+
* @description 세션 생성 콜백
|
|
566
|
+
* @Todo vibecode - 새 채팅 생성 시 호출
|
|
567
|
+
*/
|
|
568
|
+
onCreateSession?: () => Promise<{
|
|
569
|
+
id: string;
|
|
570
|
+
title: string;
|
|
571
|
+
}>;
|
|
572
|
+
/**
|
|
573
|
+
* @description 세션 상세 로드 콜백 (메시지 포함)
|
|
574
|
+
* @Todo vibecode - 세션 선택 시 호출 (lazy loading)
|
|
575
|
+
*/
|
|
576
|
+
onLoadSession?: (sessionId: string) => Promise<{
|
|
577
|
+
id: string;
|
|
578
|
+
title: string;
|
|
579
|
+
messages: ChatMessage[];
|
|
580
|
+
memory_data?: object;
|
|
581
|
+
}>;
|
|
582
|
+
/**
|
|
583
|
+
* @description 세션 삭제 콜백
|
|
584
|
+
* @Todo vibecode - 세션 삭제 시 호출
|
|
585
|
+
*/
|
|
586
|
+
onDeleteSession?: (sessionId: string) => Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* @description 세션 제목 수정 콜백
|
|
589
|
+
* @Todo vibecode - 세션 제목 변경 시 호출
|
|
590
|
+
*/
|
|
591
|
+
onUpdateSessionTitle?: (sessionId: string, title: string) => Promise<void>;
|
|
592
|
+
/**
|
|
593
|
+
* @description 메시지 저장 콜백
|
|
594
|
+
* @Todo vibecode - 메시지 전송 완료 후 호출
|
|
595
|
+
*/
|
|
596
|
+
onSaveMessages?: (sessionId: string, messages: {
|
|
597
|
+
role: 'USER' | 'ASSISTANT';
|
|
598
|
+
message: string;
|
|
599
|
+
}[]) => Promise<void>;
|
|
482
600
|
}
|
|
483
601
|
declare const useChatUI: (options: UseChatUIOptions) => UseChatUIReturn;
|
|
484
602
|
|
package/dist/react/index.js
CHANGED
|
@@ -564,10 +564,19 @@ var useChatUI = (options) => {
|
|
|
564
564
|
onSessionChange,
|
|
565
565
|
onError,
|
|
566
566
|
onTitleChange,
|
|
567
|
+
generateTitle: generateTitleCallback,
|
|
567
568
|
// Memory options
|
|
568
569
|
useGlobalMemoryEnabled = true,
|
|
569
570
|
globalMemoryConfig,
|
|
570
|
-
enableAutoExtraction = true
|
|
571
|
+
enableAutoExtraction = true,
|
|
572
|
+
// External storage options
|
|
573
|
+
useExternalStorage = false,
|
|
574
|
+
onLoadSessions,
|
|
575
|
+
onCreateSession,
|
|
576
|
+
onLoadSession,
|
|
577
|
+
onDeleteSession: onDeleteSessionCallback,
|
|
578
|
+
onUpdateSessionTitle,
|
|
579
|
+
onSaveMessages
|
|
571
580
|
} = options;
|
|
572
581
|
const [sessions, setSessions] = (0, import_react3.useState)([]);
|
|
573
582
|
const [currentSessionId, setCurrentSessionId] = (0, import_react3.useState)(null);
|
|
@@ -585,6 +594,8 @@ var useChatUI = (options) => {
|
|
|
585
594
|
...initialPersonalization
|
|
586
595
|
});
|
|
587
596
|
const [activeAlternatives, setActiveAlternatives] = (0, import_react3.useState)({});
|
|
597
|
+
const [isSessionsLoading, setIsSessionsLoading] = (0, import_react3.useState)(false);
|
|
598
|
+
const [isSessionLoading, setIsSessionLoading] = (0, import_react3.useState)(false);
|
|
588
599
|
const abortControllerRef = (0, import_react3.useRef)(null);
|
|
589
600
|
const memoryOptions = (0, import_react3.useMemo)(
|
|
590
601
|
() => ({
|
|
@@ -608,6 +619,36 @@ var useChatUI = (options) => {
|
|
|
608
619
|
const compressionState = currentSession?.compressionState || null;
|
|
609
620
|
(0, import_react3.useEffect)(() => {
|
|
610
621
|
if (typeof window === "undefined") return;
|
|
622
|
+
if (useExternalStorage && onLoadSessions) {
|
|
623
|
+
setIsSessionsLoading(true);
|
|
624
|
+
onLoadSessions().then((sessionList) => {
|
|
625
|
+
const sessionsWithoutMessages = sessionList.map((s) => ({
|
|
626
|
+
id: s.id,
|
|
627
|
+
title: s.title,
|
|
628
|
+
messages: [],
|
|
629
|
+
// 메시지는 세션 선택 시 로드
|
|
630
|
+
model: initialModel || models[0]?.id || "",
|
|
631
|
+
createdAt: Date.now(),
|
|
632
|
+
updatedAt: Date.now()
|
|
633
|
+
}));
|
|
634
|
+
setSessions(sessionsWithoutMessages);
|
|
635
|
+
if (sessionsWithoutMessages.length > 0) {
|
|
636
|
+
setCurrentSessionId(sessionsWithoutMessages[0].id);
|
|
637
|
+
}
|
|
638
|
+
}).catch((error) => {
|
|
639
|
+
onError?.(error instanceof Error ? error : new Error("Failed to load sessions"));
|
|
640
|
+
}).finally(() => {
|
|
641
|
+
setIsSessionsLoading(false);
|
|
642
|
+
});
|
|
643
|
+
const savedPersonalization2 = localStorage.getItem(`${storageKey}_personalization`);
|
|
644
|
+
if (savedPersonalization2) {
|
|
645
|
+
try {
|
|
646
|
+
setPersonalization(JSON.parse(savedPersonalization2));
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
611
652
|
const saved = localStorage.getItem(storageKey);
|
|
612
653
|
if (saved) {
|
|
613
654
|
try {
|
|
@@ -627,13 +668,14 @@ var useChatUI = (options) => {
|
|
|
627
668
|
} catch {
|
|
628
669
|
}
|
|
629
670
|
}
|
|
630
|
-
}, [storageKey]);
|
|
671
|
+
}, [storageKey, useExternalStorage, onLoadSessions, initialModel, models, onError]);
|
|
631
672
|
(0, import_react3.useEffect)(() => {
|
|
632
673
|
if (typeof window === "undefined") return;
|
|
674
|
+
if (useExternalStorage) return;
|
|
633
675
|
if (sessions.length > 0) {
|
|
634
676
|
localStorage.setItem(storageKey, JSON.stringify(sessions));
|
|
635
677
|
}
|
|
636
|
-
}, [sessions, storageKey]);
|
|
678
|
+
}, [sessions, storageKey, useExternalStorage]);
|
|
637
679
|
(0, import_react3.useEffect)(() => {
|
|
638
680
|
if (typeof window === "undefined") return;
|
|
639
681
|
localStorage.setItem(`${storageKey}_personalization`, JSON.stringify(personalization));
|
|
@@ -786,7 +828,29 @@ ${newConversation}
|
|
|
786
828
|
const estimateTokens = (0, import_react3.useCallback)((messages2) => {
|
|
787
829
|
return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
|
|
788
830
|
}, []);
|
|
789
|
-
const newSession = (0, import_react3.useCallback)(() => {
|
|
831
|
+
const newSession = (0, import_react3.useCallback)(async () => {
|
|
832
|
+
if (useExternalStorage && onCreateSession) {
|
|
833
|
+
setIsSessionLoading(true);
|
|
834
|
+
try {
|
|
835
|
+
const created = await onCreateSession();
|
|
836
|
+
const now2 = Date.now();
|
|
837
|
+
const newSess2 = {
|
|
838
|
+
id: created.id,
|
|
839
|
+
title: created.title,
|
|
840
|
+
messages: [],
|
|
841
|
+
model: selectedModel,
|
|
842
|
+
createdAt: now2,
|
|
843
|
+
updatedAt: now2
|
|
844
|
+
};
|
|
845
|
+
setSessions((prev) => [newSess2, ...prev]);
|
|
846
|
+
setCurrentSessionId(newSess2.id);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
onError?.(error instanceof Error ? error : new Error("Failed to create session"));
|
|
849
|
+
} finally {
|
|
850
|
+
setIsSessionLoading(false);
|
|
851
|
+
}
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
790
854
|
const now = Date.now();
|
|
791
855
|
const newSess = {
|
|
792
856
|
id: generateId("session"),
|
|
@@ -798,15 +862,60 @@ ${newConversation}
|
|
|
798
862
|
};
|
|
799
863
|
setSessions((prev) => [newSess, ...prev]);
|
|
800
864
|
setCurrentSessionId(newSess.id);
|
|
801
|
-
}, [selectedModel]);
|
|
802
|
-
const selectSession = (0, import_react3.useCallback)((id) => {
|
|
865
|
+
}, [selectedModel, useExternalStorage, onCreateSession, onError]);
|
|
866
|
+
const selectSession = (0, import_react3.useCallback)(async (id) => {
|
|
867
|
+
if (useExternalStorage && onLoadSession) {
|
|
868
|
+
setIsSessionLoading(true);
|
|
869
|
+
try {
|
|
870
|
+
const sessionDetail = await onLoadSession(id);
|
|
871
|
+
const loadedMessages = sessionDetail.messages.map((m, idx) => ({
|
|
872
|
+
id: m.id || generateId("msg"),
|
|
873
|
+
role: typeof m.role === "string" ? m.role.toLowerCase() : m.role,
|
|
874
|
+
content: m.content,
|
|
875
|
+
timestamp: m.timestamp || Date.now() - (sessionDetail.messages.length - idx) * 1e3,
|
|
876
|
+
model: m.model,
|
|
877
|
+
alternatives: m.alternatives,
|
|
878
|
+
sources: m.sources
|
|
879
|
+
}));
|
|
880
|
+
setSessions(
|
|
881
|
+
(prev) => prev.map(
|
|
882
|
+
(s) => s.id === id ? { ...s, title: sessionDetail.title, messages: loadedMessages, updatedAt: Date.now() } : s
|
|
883
|
+
)
|
|
884
|
+
);
|
|
885
|
+
setCurrentSessionId(id);
|
|
886
|
+
const existingSession = sessions.find((s) => s.id === id);
|
|
887
|
+
if (existingSession) {
|
|
888
|
+
setSelectedModel(existingSession.model);
|
|
889
|
+
}
|
|
890
|
+
} catch (error) {
|
|
891
|
+
onError?.(error instanceof Error ? error : new Error("Failed to load session"));
|
|
892
|
+
} finally {
|
|
893
|
+
setIsSessionLoading(false);
|
|
894
|
+
}
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
803
897
|
const session = sessions.find((s) => s.id === id);
|
|
804
898
|
if (session) {
|
|
805
899
|
setCurrentSessionId(id);
|
|
806
900
|
setSelectedModel(session.model);
|
|
807
901
|
}
|
|
808
|
-
}, [sessions]);
|
|
809
|
-
const deleteSession = (0, import_react3.useCallback)((id) => {
|
|
902
|
+
}, [sessions, useExternalStorage, onLoadSession, onError]);
|
|
903
|
+
const deleteSession = (0, import_react3.useCallback)(async (id) => {
|
|
904
|
+
if (useExternalStorage && onDeleteSessionCallback) {
|
|
905
|
+
try {
|
|
906
|
+
await onDeleteSessionCallback(id);
|
|
907
|
+
setSessions((prev) => {
|
|
908
|
+
const filtered = prev.filter((s) => s.id !== id);
|
|
909
|
+
if (currentSessionId === id) {
|
|
910
|
+
setCurrentSessionId(filtered.length > 0 ? filtered[0].id : null);
|
|
911
|
+
}
|
|
912
|
+
return filtered;
|
|
913
|
+
});
|
|
914
|
+
} catch (error) {
|
|
915
|
+
onError?.(error instanceof Error ? error : new Error("Failed to delete session"));
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
810
919
|
setSessions((prev) => {
|
|
811
920
|
const filtered = prev.filter((s) => s.id !== id);
|
|
812
921
|
if (currentSessionId === id) {
|
|
@@ -817,16 +926,30 @@ ${newConversation}
|
|
|
817
926
|
}
|
|
818
927
|
return filtered;
|
|
819
928
|
});
|
|
820
|
-
}, [currentSessionId, storageKey]);
|
|
821
|
-
const renameSession = (0, import_react3.useCallback)((id, newTitle) => {
|
|
929
|
+
}, [currentSessionId, storageKey, useExternalStorage, onDeleteSessionCallback, onError]);
|
|
930
|
+
const renameSession = (0, import_react3.useCallback)(async (id, newTitle) => {
|
|
822
931
|
if (!newTitle.trim()) return;
|
|
932
|
+
if (useExternalStorage && onUpdateSessionTitle) {
|
|
933
|
+
try {
|
|
934
|
+
await onUpdateSessionTitle(id, newTitle.trim());
|
|
935
|
+
setSessions(
|
|
936
|
+
(prev) => prev.map(
|
|
937
|
+
(s) => s.id === id ? { ...s, title: newTitle.trim(), updatedAt: Date.now() } : s
|
|
938
|
+
)
|
|
939
|
+
);
|
|
940
|
+
onTitleChange?.(id, newTitle.trim());
|
|
941
|
+
} catch (error) {
|
|
942
|
+
onError?.(error instanceof Error ? error : new Error("Failed to update session title"));
|
|
943
|
+
}
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
823
946
|
setSessions(
|
|
824
947
|
(prev) => prev.map(
|
|
825
948
|
(s) => s.id === id ? { ...s, title: newTitle.trim(), updatedAt: Date.now() } : s
|
|
826
949
|
)
|
|
827
950
|
);
|
|
828
951
|
onTitleChange?.(id, newTitle.trim());
|
|
829
|
-
}, [onTitleChange]);
|
|
952
|
+
}, [onTitleChange, useExternalStorage, onUpdateSessionTitle, onError]);
|
|
830
953
|
const setModel = (0, import_react3.useCallback)((model) => {
|
|
831
954
|
setSelectedModel(model);
|
|
832
955
|
if (currentSessionId) {
|
|
@@ -906,6 +1029,7 @@ ${finalContent}`;
|
|
|
906
1029
|
setQuotedText(null);
|
|
907
1030
|
setSelectedAction(null);
|
|
908
1031
|
const capturedSessionId = sessionId;
|
|
1032
|
+
const isFirstMessage = !sessions.find((s) => s.id === capturedSessionId)?.messages.length;
|
|
909
1033
|
setSessions(
|
|
910
1034
|
(prev) => prev.map((s) => {
|
|
911
1035
|
if (s.id === capturedSessionId) {
|
|
@@ -920,6 +1044,19 @@ ${finalContent}`;
|
|
|
920
1044
|
return s;
|
|
921
1045
|
})
|
|
922
1046
|
);
|
|
1047
|
+
if (isFirstMessage && generateTitleCallback) {
|
|
1048
|
+
Promise.resolve(generateTitleCallback(finalContent)).then((generatedTitle) => {
|
|
1049
|
+
if (generatedTitle && generatedTitle.trim()) {
|
|
1050
|
+
setSessions(
|
|
1051
|
+
(prev) => prev.map(
|
|
1052
|
+
(s) => s.id === capturedSessionId ? { ...s, title: generatedTitle.trim(), updatedAt: Date.now() } : s
|
|
1053
|
+
)
|
|
1054
|
+
);
|
|
1055
|
+
onTitleChange?.(capturedSessionId, generatedTitle.trim());
|
|
1056
|
+
}
|
|
1057
|
+
}).catch(() => {
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
923
1060
|
setIsLoading(true);
|
|
924
1061
|
abortControllerRef.current = new AbortController();
|
|
925
1062
|
try {
|
|
@@ -1080,6 +1217,21 @@ ${contextSummary}` },
|
|
|
1080
1217
|
}
|
|
1081
1218
|
}
|
|
1082
1219
|
}
|
|
1220
|
+
if (useExternalStorage && onSaveMessages && capturedSessionId) {
|
|
1221
|
+
const updatedSession = sessions.find((s) => s.id === capturedSessionId);
|
|
1222
|
+
const assistantContent = updatedSession?.messages.find(
|
|
1223
|
+
(m) => m.id === assistantMessageId
|
|
1224
|
+
)?.content || "";
|
|
1225
|
+
if (assistantContent) {
|
|
1226
|
+
const messagesToSave = [
|
|
1227
|
+
{ role: "USER", message: finalContent },
|
|
1228
|
+
{ role: "ASSISTANT", message: assistantContent }
|
|
1229
|
+
];
|
|
1230
|
+
onSaveMessages(capturedSessionId, messagesToSave).catch((saveError) => {
|
|
1231
|
+
console.error("[useChatUI] Failed to save messages:", saveError);
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1083
1235
|
} catch (error) {
|
|
1084
1236
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1085
1237
|
return;
|
|
@@ -1119,7 +1271,11 @@ ${contextSummary}` },
|
|
|
1119
1271
|
buildSystemPrompt,
|
|
1120
1272
|
compressContext,
|
|
1121
1273
|
onSendMessage,
|
|
1122
|
-
onError
|
|
1274
|
+
onError,
|
|
1275
|
+
generateTitleCallback,
|
|
1276
|
+
onTitleChange,
|
|
1277
|
+
useExternalStorage,
|
|
1278
|
+
onSaveMessages
|
|
1123
1279
|
]);
|
|
1124
1280
|
const saveEdit = (0, import_react3.useCallback)(async (content) => {
|
|
1125
1281
|
if (!editingMessageId || !currentSession || !currentSessionId) return;
|
|
@@ -1372,7 +1528,18 @@ ${currentSession.contextSummary}` },
|
|
|
1372
1528
|
content: m.content
|
|
1373
1529
|
}));
|
|
1374
1530
|
await infoExtraction.extractInfo(recentMessages);
|
|
1375
|
-
}
|
|
1531
|
+
},
|
|
1532
|
+
// External Storage Loading States
|
|
1533
|
+
/**
|
|
1534
|
+
* @description 세션 목록 로딩 상태
|
|
1535
|
+
* @Todo vibecode - 외부 스토리지 사용 시
|
|
1536
|
+
*/
|
|
1537
|
+
isSessionsLoading,
|
|
1538
|
+
/**
|
|
1539
|
+
* @description 단일 세션 로딩 상태
|
|
1540
|
+
* @Todo vibecode - 외부 스토리지 사용 시
|
|
1541
|
+
*/
|
|
1542
|
+
isSessionLoading
|
|
1376
1543
|
};
|
|
1377
1544
|
};
|
|
1378
1545
|
|
|
@@ -5184,7 +5351,16 @@ var ChatUI = ({
|
|
|
5184
5351
|
onSendMessage,
|
|
5185
5352
|
onSessionChange,
|
|
5186
5353
|
onError,
|
|
5187
|
-
onTitleChange
|
|
5354
|
+
onTitleChange,
|
|
5355
|
+
generateTitle: generateTitle2,
|
|
5356
|
+
// External Storage Props
|
|
5357
|
+
useExternalStorage = false,
|
|
5358
|
+
onLoadSessions,
|
|
5359
|
+
onCreateSession,
|
|
5360
|
+
onLoadSession,
|
|
5361
|
+
onDeleteSession,
|
|
5362
|
+
onUpdateSessionTitle,
|
|
5363
|
+
onSaveMessages
|
|
5188
5364
|
}) => {
|
|
5189
5365
|
import_react13.default.useEffect(() => {
|
|
5190
5366
|
injectStyles();
|
|
@@ -5202,7 +5378,16 @@ var ChatUI = ({
|
|
|
5202
5378
|
onSendMessage,
|
|
5203
5379
|
onSessionChange,
|
|
5204
5380
|
onError,
|
|
5205
|
-
onTitleChange
|
|
5381
|
+
onTitleChange,
|
|
5382
|
+
generateTitle: generateTitle2,
|
|
5383
|
+
// External Storage Options
|
|
5384
|
+
useExternalStorage,
|
|
5385
|
+
onLoadSessions,
|
|
5386
|
+
onCreateSession,
|
|
5387
|
+
onLoadSession,
|
|
5388
|
+
onDeleteSession,
|
|
5389
|
+
onUpdateSessionTitle,
|
|
5390
|
+
onSaveMessages
|
|
5206
5391
|
};
|
|
5207
5392
|
const {
|
|
5208
5393
|
sessions,
|