@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.
@@ -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
 
@@ -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
 
@@ -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,