@gendive/chatllm 0.8.1 → 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.
@@ -265,6 +265,55 @@ interface ChatUIProps {
265
265
  enableAutoExtraction?: boolean;
266
266
  /** 정보 추출 설정 */
267
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>;
268
317
  }
269
318
  interface SendMessageParams {
270
319
  messages: {
@@ -437,6 +486,16 @@ interface UseChatUIReturn {
437
486
  compressionState: CompressionState | null;
438
487
  /** 수동으로 정보 추출 트리거 */
439
488
  extractUserInfo: () => Promise<void>;
489
+ /**
490
+ * @description 세션 목록 로딩 상태
491
+ * @Todo vibecode - 외부 스토리지 사용 시 세션 목록 로드 중
492
+ */
493
+ isSessionsLoading: boolean;
494
+ /**
495
+ * @description 단일 세션 로딩 상태
496
+ * @Todo vibecode - 외부 스토리지 사용 시 세션 상세 로드 중
497
+ */
498
+ isSessionLoading: boolean;
440
499
  }
441
500
 
442
501
  /**
@@ -489,6 +548,55 @@ interface UseChatUIOptions {
489
548
  globalMemoryConfig?: GlobalMemoryConfig;
490
549
  /** 자동 정보 추출 활성화 (기본: true) */
491
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>;
492
600
  }
493
601
  declare const useChatUI: (options: UseChatUIOptions) => UseChatUIReturn;
494
602
 
@@ -265,6 +265,55 @@ interface ChatUIProps {
265
265
  enableAutoExtraction?: boolean;
266
266
  /** 정보 추출 설정 */
267
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>;
268
317
  }
269
318
  interface SendMessageParams {
270
319
  messages: {
@@ -437,6 +486,16 @@ interface UseChatUIReturn {
437
486
  compressionState: CompressionState | null;
438
487
  /** 수동으로 정보 추출 트리거 */
439
488
  extractUserInfo: () => Promise<void>;
489
+ /**
490
+ * @description 세션 목록 로딩 상태
491
+ * @Todo vibecode - 외부 스토리지 사용 시 세션 목록 로드 중
492
+ */
493
+ isSessionsLoading: boolean;
494
+ /**
495
+ * @description 단일 세션 로딩 상태
496
+ * @Todo vibecode - 외부 스토리지 사용 시 세션 상세 로드 중
497
+ */
498
+ isSessionLoading: boolean;
440
499
  }
441
500
 
442
501
  /**
@@ -489,6 +548,55 @@ interface UseChatUIOptions {
489
548
  globalMemoryConfig?: GlobalMemoryConfig;
490
549
  /** 자동 정보 추출 활성화 (기본: true) */
491
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>;
492
600
  }
493
601
  declare const useChatUI: (options: UseChatUIOptions) => UseChatUIReturn;
494
602
 
@@ -568,7 +568,15 @@ var useChatUI = (options) => {
568
568
  // Memory options
569
569
  useGlobalMemoryEnabled = true,
570
570
  globalMemoryConfig,
571
- 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
572
580
  } = options;
573
581
  const [sessions, setSessions] = (0, import_react3.useState)([]);
574
582
  const [currentSessionId, setCurrentSessionId] = (0, import_react3.useState)(null);
@@ -586,6 +594,8 @@ var useChatUI = (options) => {
586
594
  ...initialPersonalization
587
595
  });
588
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);
589
599
  const abortControllerRef = (0, import_react3.useRef)(null);
590
600
  const memoryOptions = (0, import_react3.useMemo)(
591
601
  () => ({
@@ -609,6 +619,36 @@ var useChatUI = (options) => {
609
619
  const compressionState = currentSession?.compressionState || null;
610
620
  (0, import_react3.useEffect)(() => {
611
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
+ }
612
652
  const saved = localStorage.getItem(storageKey);
613
653
  if (saved) {
614
654
  try {
@@ -628,13 +668,14 @@ var useChatUI = (options) => {
628
668
  } catch {
629
669
  }
630
670
  }
631
- }, [storageKey]);
671
+ }, [storageKey, useExternalStorage, onLoadSessions, initialModel, models, onError]);
632
672
  (0, import_react3.useEffect)(() => {
633
673
  if (typeof window === "undefined") return;
674
+ if (useExternalStorage) return;
634
675
  if (sessions.length > 0) {
635
676
  localStorage.setItem(storageKey, JSON.stringify(sessions));
636
677
  }
637
- }, [sessions, storageKey]);
678
+ }, [sessions, storageKey, useExternalStorage]);
638
679
  (0, import_react3.useEffect)(() => {
639
680
  if (typeof window === "undefined") return;
640
681
  localStorage.setItem(`${storageKey}_personalization`, JSON.stringify(personalization));
@@ -787,7 +828,29 @@ ${newConversation}
787
828
  const estimateTokens = (0, import_react3.useCallback)((messages2) => {
788
829
  return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
789
830
  }, []);
790
- 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
+ }
791
854
  const now = Date.now();
792
855
  const newSess = {
793
856
  id: generateId("session"),
@@ -799,15 +862,60 @@ ${newConversation}
799
862
  };
800
863
  setSessions((prev) => [newSess, ...prev]);
801
864
  setCurrentSessionId(newSess.id);
802
- }, [selectedModel]);
803
- 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
+ }
804
897
  const session = sessions.find((s) => s.id === id);
805
898
  if (session) {
806
899
  setCurrentSessionId(id);
807
900
  setSelectedModel(session.model);
808
901
  }
809
- }, [sessions]);
810
- 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
+ }
811
919
  setSessions((prev) => {
812
920
  const filtered = prev.filter((s) => s.id !== id);
813
921
  if (currentSessionId === id) {
@@ -818,16 +926,30 @@ ${newConversation}
818
926
  }
819
927
  return filtered;
820
928
  });
821
- }, [currentSessionId, storageKey]);
822
- const renameSession = (0, import_react3.useCallback)((id, newTitle) => {
929
+ }, [currentSessionId, storageKey, useExternalStorage, onDeleteSessionCallback, onError]);
930
+ const renameSession = (0, import_react3.useCallback)(async (id, newTitle) => {
823
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
+ }
824
946
  setSessions(
825
947
  (prev) => prev.map(
826
948
  (s) => s.id === id ? { ...s, title: newTitle.trim(), updatedAt: Date.now() } : s
827
949
  )
828
950
  );
829
951
  onTitleChange?.(id, newTitle.trim());
830
- }, [onTitleChange]);
952
+ }, [onTitleChange, useExternalStorage, onUpdateSessionTitle, onError]);
831
953
  const setModel = (0, import_react3.useCallback)((model) => {
832
954
  setSelectedModel(model);
833
955
  if (currentSessionId) {
@@ -1095,6 +1217,21 @@ ${contextSummary}` },
1095
1217
  }
1096
1218
  }
1097
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
+ }
1098
1235
  } catch (error) {
1099
1236
  if (error instanceof Error && error.name === "AbortError") {
1100
1237
  return;
@@ -1136,7 +1273,9 @@ ${contextSummary}` },
1136
1273
  onSendMessage,
1137
1274
  onError,
1138
1275
  generateTitleCallback,
1139
- onTitleChange
1276
+ onTitleChange,
1277
+ useExternalStorage,
1278
+ onSaveMessages
1140
1279
  ]);
1141
1280
  const saveEdit = (0, import_react3.useCallback)(async (content) => {
1142
1281
  if (!editingMessageId || !currentSession || !currentSessionId) return;
@@ -1389,7 +1528,18 @@ ${currentSession.contextSummary}` },
1389
1528
  content: m.content
1390
1529
  }));
1391
1530
  await infoExtraction.extractInfo(recentMessages);
1392
- }
1531
+ },
1532
+ // External Storage Loading States
1533
+ /**
1534
+ * @description 세션 목록 로딩 상태
1535
+ * @Todo vibecode - 외부 스토리지 사용 시
1536
+ */
1537
+ isSessionsLoading,
1538
+ /**
1539
+ * @description 단일 세션 로딩 상태
1540
+ * @Todo vibecode - 외부 스토리지 사용 시
1541
+ */
1542
+ isSessionLoading
1393
1543
  };
1394
1544
  };
1395
1545
 
@@ -5202,7 +5352,15 @@ var ChatUI = ({
5202
5352
  onSessionChange,
5203
5353
  onError,
5204
5354
  onTitleChange,
5205
- generateTitle: generateTitle2
5355
+ generateTitle: generateTitle2,
5356
+ // External Storage Props
5357
+ useExternalStorage = false,
5358
+ onLoadSessions,
5359
+ onCreateSession,
5360
+ onLoadSession,
5361
+ onDeleteSession,
5362
+ onUpdateSessionTitle,
5363
+ onSaveMessages
5206
5364
  }) => {
5207
5365
  import_react13.default.useEffect(() => {
5208
5366
  injectStyles();
@@ -5221,7 +5379,15 @@ var ChatUI = ({
5221
5379
  onSessionChange,
5222
5380
  onError,
5223
5381
  onTitleChange,
5224
- generateTitle: generateTitle2
5382
+ generateTitle: generateTitle2,
5383
+ // External Storage Options
5384
+ useExternalStorage,
5385
+ onLoadSessions,
5386
+ onCreateSession,
5387
+ onLoadSession,
5388
+ onDeleteSession,
5389
+ onUpdateSessionTitle,
5390
+ onSaveMessages
5225
5391
  };
5226
5392
  const {
5227
5393
  sessions,