react-optimistic-chat 1.2.1 → 2.0.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/index.d.mts +28 -7
- package/dist/index.d.ts +28 -7
- package/dist/index.js +172 -73
- package/dist/index.mjs +172 -73
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
|
+
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
4
|
+
import { InfiniteData } from '@tanstack/react-query';
|
|
3
5
|
|
|
4
6
|
type ChatRole = "AI" | "USER";
|
|
5
7
|
type BaseMessage = {
|
|
@@ -89,28 +91,39 @@ type CommonProps = {
|
|
|
89
91
|
disableVoice?: boolean;
|
|
90
92
|
placeholder?: string;
|
|
91
93
|
inputClassName?: string;
|
|
94
|
+
fetchNextPage?: () => void;
|
|
95
|
+
hasNextPage?: boolean;
|
|
96
|
+
isFetchingNextPage?: boolean;
|
|
92
97
|
className?: string;
|
|
93
98
|
};
|
|
94
99
|
type Props<T> = CommonProps & (MessageProps | RawProps<T>);
|
|
95
100
|
declare function ChatContainer<T>(props: Props<T>): react_jsx_runtime.JSX.Element;
|
|
96
101
|
|
|
97
102
|
type ExtraFromRaw$1<TRaw> = Omit<TRaw, keyof BaseMessage>;
|
|
103
|
+
type CustomMessage$1<TCustom> = BaseMessage & {
|
|
104
|
+
custom: TCustom;
|
|
105
|
+
};
|
|
106
|
+
type MessageMapper$1<TRaw> = CustomMessage$1<ExtraFromRaw$1<TRaw>>;
|
|
98
107
|
type MessageMapperResult$1 = Pick<BaseMessage, "id" | "role" | "content">;
|
|
99
|
-
type MessageMapper$1<TRaw> = Message<ExtraFromRaw$1<TRaw>>;
|
|
100
108
|
type Options$2<TRaw> = {
|
|
101
109
|
queryKey: readonly unknown[];
|
|
102
|
-
queryFn: () => Promise<TRaw[]>;
|
|
110
|
+
queryFn: (pageParam: unknown) => Promise<TRaw[]>;
|
|
111
|
+
initialPageParam: unknown;
|
|
112
|
+
getNextPageParam: (lastPage: MessageMapper$1<TRaw>[], allPages: MessageMapper$1<TRaw>[][]) => unknown;
|
|
103
113
|
mutationFn: (content: string) => Promise<TRaw>;
|
|
104
114
|
map: (raw: TRaw) => MessageMapperResult$1;
|
|
105
115
|
onError?: (error: unknown) => void;
|
|
106
116
|
staleTime?: number;
|
|
107
117
|
gcTime?: number;
|
|
108
118
|
};
|
|
109
|
-
declare function
|
|
119
|
+
declare function useChat<TRaw extends object>({ queryKey, queryFn, initialPageParam, getNextPageParam, mutationFn, map, onError, staleTime, gcTime, }: Options$2<TRaw>): {
|
|
110
120
|
messages: MessageMapper$1<TRaw>[];
|
|
111
121
|
sendUserMessage: (content: string) => void;
|
|
112
122
|
isPending: boolean;
|
|
113
123
|
isInitialLoading: boolean;
|
|
124
|
+
fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<InfiniteData<MessageMapper$1<TRaw>[], unknown>, Error>>;
|
|
125
|
+
hasNextPage: boolean;
|
|
126
|
+
isFetchingNextPage: boolean;
|
|
114
127
|
};
|
|
115
128
|
|
|
116
129
|
interface SpeechGrammar {
|
|
@@ -190,11 +203,16 @@ type VoiceRecognitionController = {
|
|
|
190
203
|
onTranscript: (text: string) => void;
|
|
191
204
|
};
|
|
192
205
|
type ExtraFromRaw<TRaw> = Omit<TRaw, keyof BaseMessage>;
|
|
206
|
+
type CustomMessage<TCustom> = BaseMessage & {
|
|
207
|
+
custom: TCustom;
|
|
208
|
+
};
|
|
209
|
+
type MessageMapper<TRaw> = CustomMessage<ExtraFromRaw<TRaw>>;
|
|
193
210
|
type MessageMapperResult = Pick<BaseMessage, "id" | "role" | "content">;
|
|
194
|
-
type MessageMapper<TRaw> = Message<ExtraFromRaw<TRaw>>;
|
|
195
211
|
type Options<TRaw> = {
|
|
196
212
|
queryKey: readonly unknown[];
|
|
197
|
-
queryFn: () => Promise<TRaw[]>;
|
|
213
|
+
queryFn: (pageParam: unknown) => Promise<TRaw[]>;
|
|
214
|
+
initialPageParam: unknown;
|
|
215
|
+
getNextPageParam: (lastPage: MessageMapper<TRaw>[], allPages: MessageMapper<TRaw>[][]) => unknown;
|
|
198
216
|
mutationFn: (content: string) => Promise<TRaw>;
|
|
199
217
|
map: (raw: TRaw) => MessageMapperResult;
|
|
200
218
|
voice: VoiceRecognitionController;
|
|
@@ -202,12 +220,15 @@ type Options<TRaw> = {
|
|
|
202
220
|
staleTime?: number;
|
|
203
221
|
gcTime?: number;
|
|
204
222
|
};
|
|
205
|
-
declare function
|
|
223
|
+
declare function useVoiceChat<TRaw extends object>({ queryKey, queryFn, initialPageParam, getNextPageParam, mutationFn, map, voice, onError, staleTime, gcTime, }: Options<TRaw>): {
|
|
206
224
|
messages: MessageMapper<TRaw>[];
|
|
207
225
|
isPending: boolean;
|
|
208
226
|
isInitialLoading: boolean;
|
|
209
227
|
startRecording: () => Promise<void>;
|
|
210
228
|
stopRecording: () => void;
|
|
229
|
+
fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<InfiniteData<MessageMapper<TRaw>[], unknown>, Error>>;
|
|
230
|
+
hasNextPage: boolean;
|
|
231
|
+
isFetchingNextPage: boolean;
|
|
211
232
|
};
|
|
212
233
|
|
|
213
|
-
export { ChatContainer, ChatInput, ChatList, ChatMessage, LoadingSpinner, type Message, SendingDots, useBrowserSpeechRecognition,
|
|
234
|
+
export { ChatContainer, ChatInput, ChatList, ChatMessage, LoadingSpinner, type Message, SendingDots, useBrowserSpeechRecognition, useChat, useVoiceChat };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
|
+
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
4
|
+
import { InfiniteData } from '@tanstack/react-query';
|
|
3
5
|
|
|
4
6
|
type ChatRole = "AI" | "USER";
|
|
5
7
|
type BaseMessage = {
|
|
@@ -89,28 +91,39 @@ type CommonProps = {
|
|
|
89
91
|
disableVoice?: boolean;
|
|
90
92
|
placeholder?: string;
|
|
91
93
|
inputClassName?: string;
|
|
94
|
+
fetchNextPage?: () => void;
|
|
95
|
+
hasNextPage?: boolean;
|
|
96
|
+
isFetchingNextPage?: boolean;
|
|
92
97
|
className?: string;
|
|
93
98
|
};
|
|
94
99
|
type Props<T> = CommonProps & (MessageProps | RawProps<T>);
|
|
95
100
|
declare function ChatContainer<T>(props: Props<T>): react_jsx_runtime.JSX.Element;
|
|
96
101
|
|
|
97
102
|
type ExtraFromRaw$1<TRaw> = Omit<TRaw, keyof BaseMessage>;
|
|
103
|
+
type CustomMessage$1<TCustom> = BaseMessage & {
|
|
104
|
+
custom: TCustom;
|
|
105
|
+
};
|
|
106
|
+
type MessageMapper$1<TRaw> = CustomMessage$1<ExtraFromRaw$1<TRaw>>;
|
|
98
107
|
type MessageMapperResult$1 = Pick<BaseMessage, "id" | "role" | "content">;
|
|
99
|
-
type MessageMapper$1<TRaw> = Message<ExtraFromRaw$1<TRaw>>;
|
|
100
108
|
type Options$2<TRaw> = {
|
|
101
109
|
queryKey: readonly unknown[];
|
|
102
|
-
queryFn: () => Promise<TRaw[]>;
|
|
110
|
+
queryFn: (pageParam: unknown) => Promise<TRaw[]>;
|
|
111
|
+
initialPageParam: unknown;
|
|
112
|
+
getNextPageParam: (lastPage: MessageMapper$1<TRaw>[], allPages: MessageMapper$1<TRaw>[][]) => unknown;
|
|
103
113
|
mutationFn: (content: string) => Promise<TRaw>;
|
|
104
114
|
map: (raw: TRaw) => MessageMapperResult$1;
|
|
105
115
|
onError?: (error: unknown) => void;
|
|
106
116
|
staleTime?: number;
|
|
107
117
|
gcTime?: number;
|
|
108
118
|
};
|
|
109
|
-
declare function
|
|
119
|
+
declare function useChat<TRaw extends object>({ queryKey, queryFn, initialPageParam, getNextPageParam, mutationFn, map, onError, staleTime, gcTime, }: Options$2<TRaw>): {
|
|
110
120
|
messages: MessageMapper$1<TRaw>[];
|
|
111
121
|
sendUserMessage: (content: string) => void;
|
|
112
122
|
isPending: boolean;
|
|
113
123
|
isInitialLoading: boolean;
|
|
124
|
+
fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<InfiniteData<MessageMapper$1<TRaw>[], unknown>, Error>>;
|
|
125
|
+
hasNextPage: boolean;
|
|
126
|
+
isFetchingNextPage: boolean;
|
|
114
127
|
};
|
|
115
128
|
|
|
116
129
|
interface SpeechGrammar {
|
|
@@ -190,11 +203,16 @@ type VoiceRecognitionController = {
|
|
|
190
203
|
onTranscript: (text: string) => void;
|
|
191
204
|
};
|
|
192
205
|
type ExtraFromRaw<TRaw> = Omit<TRaw, keyof BaseMessage>;
|
|
206
|
+
type CustomMessage<TCustom> = BaseMessage & {
|
|
207
|
+
custom: TCustom;
|
|
208
|
+
};
|
|
209
|
+
type MessageMapper<TRaw> = CustomMessage<ExtraFromRaw<TRaw>>;
|
|
193
210
|
type MessageMapperResult = Pick<BaseMessage, "id" | "role" | "content">;
|
|
194
|
-
type MessageMapper<TRaw> = Message<ExtraFromRaw<TRaw>>;
|
|
195
211
|
type Options<TRaw> = {
|
|
196
212
|
queryKey: readonly unknown[];
|
|
197
|
-
queryFn: () => Promise<TRaw[]>;
|
|
213
|
+
queryFn: (pageParam: unknown) => Promise<TRaw[]>;
|
|
214
|
+
initialPageParam: unknown;
|
|
215
|
+
getNextPageParam: (lastPage: MessageMapper<TRaw>[], allPages: MessageMapper<TRaw>[][]) => unknown;
|
|
198
216
|
mutationFn: (content: string) => Promise<TRaw>;
|
|
199
217
|
map: (raw: TRaw) => MessageMapperResult;
|
|
200
218
|
voice: VoiceRecognitionController;
|
|
@@ -202,12 +220,15 @@ type Options<TRaw> = {
|
|
|
202
220
|
staleTime?: number;
|
|
203
221
|
gcTime?: number;
|
|
204
222
|
};
|
|
205
|
-
declare function
|
|
223
|
+
declare function useVoiceChat<TRaw extends object>({ queryKey, queryFn, initialPageParam, getNextPageParam, mutationFn, map, voice, onError, staleTime, gcTime, }: Options<TRaw>): {
|
|
206
224
|
messages: MessageMapper<TRaw>[];
|
|
207
225
|
isPending: boolean;
|
|
208
226
|
isInitialLoading: boolean;
|
|
209
227
|
startRecording: () => Promise<void>;
|
|
210
228
|
stopRecording: () => void;
|
|
229
|
+
fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<InfiniteData<MessageMapper<TRaw>[], unknown>, Error>>;
|
|
230
|
+
hasNextPage: boolean;
|
|
231
|
+
isFetchingNextPage: boolean;
|
|
211
232
|
};
|
|
212
233
|
|
|
213
|
-
export { ChatContainer, ChatInput, ChatList, ChatMessage, LoadingSpinner, type Message, SendingDots, useBrowserSpeechRecognition,
|
|
234
|
+
export { ChatContainer, ChatInput, ChatList, ChatMessage, LoadingSpinner, type Message, SendingDots, useBrowserSpeechRecognition, useChat, useVoiceChat };
|
package/dist/index.js
CHANGED
|
@@ -54,8 +54,8 @@ __export(index_exports, {
|
|
|
54
54
|
LoadingSpinner: () => LoadingSpinner,
|
|
55
55
|
SendingDots: () => SendingDots,
|
|
56
56
|
useBrowserSpeechRecognition: () => useBrowserSpeechRecognition,
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
useChat: () => useChat,
|
|
58
|
+
useVoiceChat: () => useVoiceChat
|
|
59
59
|
});
|
|
60
60
|
module.exports = __toCommonJS(index_exports);
|
|
61
61
|
|
|
@@ -531,20 +531,30 @@ function ChatContainer(props) {
|
|
|
531
531
|
disableVoice,
|
|
532
532
|
placeholder,
|
|
533
533
|
inputClassName,
|
|
534
|
+
fetchNextPage,
|
|
535
|
+
hasNextPage,
|
|
536
|
+
isFetchingNextPage,
|
|
534
537
|
className
|
|
535
538
|
} = props;
|
|
536
539
|
const mappedMessages = typeof props.messageMapper === "function" ? props.messages.map(props.messageMapper) : messages;
|
|
537
540
|
(0, import_react5.useEffect)(() => {
|
|
538
541
|
const el = scrollRef.current;
|
|
539
542
|
if (!el) return;
|
|
540
|
-
|
|
541
|
-
const handleScroll = () => {
|
|
543
|
+
const handleScroll = async () => {
|
|
542
544
|
const isBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10;
|
|
543
545
|
setIsAtBottom(isBottom);
|
|
546
|
+
if (el.scrollTop === 0 && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
|
547
|
+
const prevScrollHeight = el.scrollHeight;
|
|
548
|
+
await fetchNextPage();
|
|
549
|
+
requestAnimationFrame(() => {
|
|
550
|
+
const newScrollHeight = el.scrollHeight;
|
|
551
|
+
el.scrollTop = newScrollHeight - prevScrollHeight;
|
|
552
|
+
});
|
|
553
|
+
}
|
|
544
554
|
};
|
|
545
555
|
el.addEventListener("scroll", handleScroll);
|
|
546
556
|
return () => el.removeEventListener("scroll", handleScroll);
|
|
547
|
-
}, []);
|
|
557
|
+
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
|
548
558
|
(0, import_react5.useEffect)(() => {
|
|
549
559
|
const el = scrollRef.current;
|
|
550
560
|
if (!el) return;
|
|
@@ -572,17 +582,20 @@ function ChatContainer(props) {
|
|
|
572
582
|
flex flex-col ${className || ""}
|
|
573
583
|
`,
|
|
574
584
|
children: [
|
|
575
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.
|
|
585
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
576
586
|
"div",
|
|
577
587
|
{
|
|
578
588
|
ref: scrollRef,
|
|
579
589
|
className: `flex-1 overflow-y-auto chatContainer-scroll p-2`,
|
|
580
|
-
children:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
590
|
+
children: [
|
|
591
|
+
hasNextPage && isFetchingNextPage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LoadingSpinner, { size: "sm" }) }),
|
|
592
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
593
|
+
ChatList,
|
|
594
|
+
__spreadValues(__spreadValues(__spreadValues({
|
|
595
|
+
messages: mappedMessages
|
|
596
|
+
}, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
|
|
597
|
+
)
|
|
598
|
+
]
|
|
586
599
|
}
|
|
587
600
|
),
|
|
588
601
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-shrink-0 relative", children: [
|
|
@@ -625,12 +638,26 @@ function ChatContainer(props) {
|
|
|
625
638
|
) });
|
|
626
639
|
}
|
|
627
640
|
|
|
628
|
-
// src/hooks/
|
|
641
|
+
// src/hooks/useChat.ts
|
|
629
642
|
var import_react_query = require("@tanstack/react-query");
|
|
630
643
|
var import_react6 = require("react");
|
|
631
|
-
function
|
|
644
|
+
function splitRawToMessage(raw, mapped) {
|
|
645
|
+
const custom = {};
|
|
646
|
+
const mappedValues = new Set(Object.values(mapped));
|
|
647
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
648
|
+
if (!mappedValues.has(value)) {
|
|
649
|
+
custom[key] = value;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return __spreadProps(__spreadValues({}, mapped), {
|
|
653
|
+
custom
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function useChat({
|
|
632
657
|
queryKey,
|
|
633
658
|
queryFn,
|
|
659
|
+
initialPageParam,
|
|
660
|
+
getNextPageParam,
|
|
634
661
|
mutationFn,
|
|
635
662
|
map,
|
|
636
663
|
onError,
|
|
@@ -640,17 +667,26 @@ function useOptimisticChat({
|
|
|
640
667
|
const [isPending, setIsPending] = (0, import_react6.useState)(false);
|
|
641
668
|
const queryClient = (0, import_react_query.useQueryClient)();
|
|
642
669
|
const {
|
|
643
|
-
data
|
|
644
|
-
isLoading: isInitialLoading
|
|
645
|
-
|
|
670
|
+
data,
|
|
671
|
+
isLoading: isInitialLoading,
|
|
672
|
+
fetchNextPage,
|
|
673
|
+
hasNextPage,
|
|
674
|
+
isFetchingNextPage
|
|
675
|
+
} = (0, import_react_query.useInfiniteQuery)({
|
|
646
676
|
queryKey,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
677
|
+
initialPageParam,
|
|
678
|
+
queryFn: async ({ pageParam }) => {
|
|
679
|
+
const raw = await queryFn(pageParam);
|
|
680
|
+
return raw.map((r) => {
|
|
681
|
+
const mapped = map(r);
|
|
682
|
+
return splitRawToMessage(r, mapped);
|
|
683
|
+
});
|
|
650
684
|
},
|
|
685
|
+
getNextPageParam,
|
|
651
686
|
staleTime,
|
|
652
687
|
gcTime
|
|
653
688
|
});
|
|
689
|
+
const messages = data ? [...data.pages].reverse().flat() : [];
|
|
654
690
|
const mutation = (0, import_react_query.useMutation)({
|
|
655
691
|
mutationFn,
|
|
656
692
|
// (content: string) => Promise<TMutationRaw>
|
|
@@ -661,38 +697,47 @@ function useOptimisticChat({
|
|
|
661
697
|
await queryClient.cancelQueries({ queryKey });
|
|
662
698
|
}
|
|
663
699
|
queryClient.setQueryData(queryKey, (old) => {
|
|
664
|
-
|
|
665
|
-
return
|
|
666
|
-
|
|
667
|
-
|
|
700
|
+
var _a;
|
|
701
|
+
if (!old) return old;
|
|
702
|
+
const pages = [...old.pages];
|
|
703
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
704
|
+
pages[0] = [
|
|
705
|
+
...firstPage,
|
|
668
706
|
{
|
|
669
707
|
id: crypto.randomUUID(),
|
|
670
708
|
role: "USER",
|
|
671
|
-
content
|
|
709
|
+
content,
|
|
710
|
+
custom: {}
|
|
672
711
|
},
|
|
673
|
-
// AI placeholder 추가
|
|
674
712
|
{
|
|
675
713
|
id: crypto.randomUUID(),
|
|
676
714
|
role: "AI",
|
|
677
715
|
content: "",
|
|
678
|
-
isLoading: true
|
|
716
|
+
isLoading: true,
|
|
717
|
+
custom: {}
|
|
679
718
|
}
|
|
680
719
|
];
|
|
720
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
721
|
+
pages
|
|
722
|
+
});
|
|
681
723
|
});
|
|
682
724
|
return prev ? { prev } : {};
|
|
683
725
|
},
|
|
684
726
|
onSuccess: (rawAiResponse) => {
|
|
685
|
-
const
|
|
727
|
+
const mapped = map(rawAiResponse);
|
|
728
|
+
const aiMessage = splitRawToMessage(rawAiResponse, mapped);
|
|
686
729
|
queryClient.setQueryData(queryKey, (old) => {
|
|
687
|
-
if (!old
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
next[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, next[lastIndex]), aiMessage), {
|
|
730
|
+
if (!old) return old;
|
|
731
|
+
const pages = [...old.pages];
|
|
732
|
+
const firstPage = [...pages[0]];
|
|
733
|
+
const lastIndex = firstPage.length - 1;
|
|
734
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, firstPage[lastIndex]), aiMessage), {
|
|
693
735
|
isLoading: false
|
|
694
736
|
});
|
|
695
|
-
|
|
737
|
+
pages[0] = firstPage;
|
|
738
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
739
|
+
pages
|
|
740
|
+
});
|
|
696
741
|
});
|
|
697
742
|
setIsPending(false);
|
|
698
743
|
},
|
|
@@ -715,17 +760,35 @@ function useOptimisticChat({
|
|
|
715
760
|
// (content: string) => void
|
|
716
761
|
isPending,
|
|
717
762
|
// 사용자가 채팅 전송 후 AI 응답이 올 때까지의 로딩
|
|
718
|
-
isInitialLoading
|
|
763
|
+
isInitialLoading,
|
|
719
764
|
// 초기 로딩 상태
|
|
765
|
+
// infinite query용
|
|
766
|
+
fetchNextPage,
|
|
767
|
+
hasNextPage,
|
|
768
|
+
isFetchingNextPage
|
|
720
769
|
};
|
|
721
770
|
}
|
|
722
771
|
|
|
723
|
-
// src/hooks/
|
|
772
|
+
// src/hooks/useVoiceChat.ts
|
|
724
773
|
var import_react_query2 = require("@tanstack/react-query");
|
|
725
774
|
var import_react7 = require("react");
|
|
726
|
-
function
|
|
775
|
+
function splitRawToMessage2(raw, mapped) {
|
|
776
|
+
const custom = {};
|
|
777
|
+
const mappedValues = new Set(Object.values(mapped));
|
|
778
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
779
|
+
if (!mappedValues.has(value)) {
|
|
780
|
+
custom[key] = value;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return __spreadProps(__spreadValues({}, mapped), {
|
|
784
|
+
custom
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
function useVoiceChat({
|
|
727
788
|
queryKey,
|
|
728
789
|
queryFn,
|
|
790
|
+
initialPageParam,
|
|
791
|
+
getNextPageParam,
|
|
729
792
|
mutationFn,
|
|
730
793
|
map,
|
|
731
794
|
voice,
|
|
@@ -738,17 +801,26 @@ function useVoiceOptimisticChat({
|
|
|
738
801
|
const currentTextRef = (0, import_react7.useRef)("");
|
|
739
802
|
const rollbackRef = (0, import_react7.useRef)(void 0);
|
|
740
803
|
const {
|
|
741
|
-
data
|
|
742
|
-
isLoading: isInitialLoading
|
|
743
|
-
|
|
804
|
+
data,
|
|
805
|
+
isLoading: isInitialLoading,
|
|
806
|
+
fetchNextPage,
|
|
807
|
+
hasNextPage,
|
|
808
|
+
isFetchingNextPage
|
|
809
|
+
} = (0, import_react_query2.useInfiniteQuery)({
|
|
744
810
|
queryKey,
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
811
|
+
initialPageParam,
|
|
812
|
+
queryFn: async ({ pageParam }) => {
|
|
813
|
+
const raw = await queryFn(pageParam);
|
|
814
|
+
return raw.map((r) => {
|
|
815
|
+
const mapped = map(r);
|
|
816
|
+
return splitRawToMessage2(r, mapped);
|
|
817
|
+
});
|
|
748
818
|
},
|
|
819
|
+
getNextPageParam,
|
|
749
820
|
staleTime,
|
|
750
821
|
gcTime
|
|
751
822
|
});
|
|
823
|
+
const messages = data ? data.pages.map((page) => [...page]).reverse().flat() : [];
|
|
752
824
|
const mutation = (0, import_react_query2.useMutation)({
|
|
753
825
|
mutationFn,
|
|
754
826
|
// (content: string) => Promise<TMutationRaw>
|
|
@@ -759,32 +831,41 @@ function useVoiceOptimisticChat({
|
|
|
759
831
|
await queryClient.cancelQueries({ queryKey });
|
|
760
832
|
}
|
|
761
833
|
queryClient.setQueryData(queryKey, (old) => {
|
|
762
|
-
|
|
763
|
-
return
|
|
764
|
-
|
|
765
|
-
|
|
834
|
+
var _a;
|
|
835
|
+
if (!old) return old;
|
|
836
|
+
const pages = [...old.pages];
|
|
837
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
838
|
+
pages[0] = [
|
|
839
|
+
...firstPage,
|
|
766
840
|
{
|
|
767
841
|
id: crypto.randomUUID(),
|
|
768
842
|
role: "AI",
|
|
769
843
|
content: "",
|
|
770
|
-
isLoading: true
|
|
844
|
+
isLoading: true,
|
|
845
|
+
custom: {}
|
|
771
846
|
}
|
|
772
847
|
];
|
|
848
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
849
|
+
pages
|
|
850
|
+
});
|
|
773
851
|
});
|
|
774
852
|
return prev ? { prev } : {};
|
|
775
853
|
},
|
|
776
854
|
onSuccess: (rawAiResponse) => {
|
|
777
|
-
const
|
|
855
|
+
const mapped = map(rawAiResponse);
|
|
856
|
+
const aiMessage = splitRawToMessage2(rawAiResponse, mapped);
|
|
778
857
|
queryClient.setQueryData(queryKey, (old) => {
|
|
779
|
-
if (!old
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
next[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, next[lastIndex]), aiMessage), {
|
|
858
|
+
if (!old) return old;
|
|
859
|
+
const pages = [...old.pages];
|
|
860
|
+
const firstPage = [...pages[0]];
|
|
861
|
+
const lastIndex = firstPage.length - 1;
|
|
862
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, firstPage[lastIndex]), aiMessage), {
|
|
785
863
|
isLoading: false
|
|
786
864
|
});
|
|
787
|
-
|
|
865
|
+
pages[0] = firstPage;
|
|
866
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
867
|
+
pages
|
|
868
|
+
});
|
|
788
869
|
});
|
|
789
870
|
setIsPending(false);
|
|
790
871
|
},
|
|
@@ -803,14 +884,24 @@ function useVoiceOptimisticChat({
|
|
|
803
884
|
if (prev) {
|
|
804
885
|
await queryClient.cancelQueries({ queryKey });
|
|
805
886
|
}
|
|
806
|
-
queryClient.setQueryData(queryKey, (old) =>
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
887
|
+
queryClient.setQueryData(queryKey, (old) => {
|
|
888
|
+
var _a;
|
|
889
|
+
if (!old) return old;
|
|
890
|
+
const pages = [...old.pages];
|
|
891
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
892
|
+
pages[0] = [
|
|
893
|
+
...firstPage,
|
|
894
|
+
{
|
|
895
|
+
id: crypto.randomUUID(),
|
|
896
|
+
role: "USER",
|
|
897
|
+
content: "",
|
|
898
|
+
custom: {}
|
|
899
|
+
}
|
|
900
|
+
];
|
|
901
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
902
|
+
pages
|
|
903
|
+
});
|
|
904
|
+
});
|
|
814
905
|
voice.start();
|
|
815
906
|
};
|
|
816
907
|
const onTranscript = (text) => {
|
|
@@ -818,13 +909,17 @@ function useVoiceOptimisticChat({
|
|
|
818
909
|
queryClient.setQueryData(queryKey, (old) => {
|
|
819
910
|
var _a;
|
|
820
911
|
if (!old) return old;
|
|
821
|
-
const
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
912
|
+
const pages = [...old.pages];
|
|
913
|
+
const firstPage = [...pages[0]];
|
|
914
|
+
const lastIndex = firstPage.length - 1;
|
|
915
|
+
if (((_a = firstPage[lastIndex]) == null ? void 0 : _a.role) !== "USER") return old;
|
|
916
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues({}, firstPage[lastIndex]), {
|
|
825
917
|
content: text
|
|
826
918
|
});
|
|
827
|
-
|
|
919
|
+
pages[0] = firstPage;
|
|
920
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
921
|
+
pages
|
|
922
|
+
});
|
|
828
923
|
});
|
|
829
924
|
};
|
|
830
925
|
(0, import_react7.useEffect)(() => {
|
|
@@ -850,8 +945,12 @@ function useVoiceOptimisticChat({
|
|
|
850
945
|
// 초기 로딩 상태
|
|
851
946
|
startRecording,
|
|
852
947
|
// 음성 인식 시작 함수
|
|
853
|
-
stopRecording
|
|
948
|
+
stopRecording,
|
|
854
949
|
// 음성 인식 종료 함수
|
|
950
|
+
// infinite query용
|
|
951
|
+
fetchNextPage,
|
|
952
|
+
hasNextPage,
|
|
953
|
+
isFetchingNextPage
|
|
855
954
|
};
|
|
856
955
|
}
|
|
857
956
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -863,6 +962,6 @@ function useVoiceOptimisticChat({
|
|
|
863
962
|
LoadingSpinner,
|
|
864
963
|
SendingDots,
|
|
865
964
|
useBrowserSpeechRecognition,
|
|
866
|
-
|
|
867
|
-
|
|
965
|
+
useChat,
|
|
966
|
+
useVoiceChat
|
|
868
967
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -490,20 +490,30 @@ function ChatContainer(props) {
|
|
|
490
490
|
disableVoice,
|
|
491
491
|
placeholder,
|
|
492
492
|
inputClassName,
|
|
493
|
+
fetchNextPage,
|
|
494
|
+
hasNextPage,
|
|
495
|
+
isFetchingNextPage,
|
|
493
496
|
className
|
|
494
497
|
} = props;
|
|
495
498
|
const mappedMessages = typeof props.messageMapper === "function" ? props.messages.map(props.messageMapper) : messages;
|
|
496
499
|
useEffect3(() => {
|
|
497
500
|
const el = scrollRef.current;
|
|
498
501
|
if (!el) return;
|
|
499
|
-
|
|
500
|
-
const handleScroll = () => {
|
|
502
|
+
const handleScroll = async () => {
|
|
501
503
|
const isBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10;
|
|
502
504
|
setIsAtBottom(isBottom);
|
|
505
|
+
if (el.scrollTop === 0 && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
|
506
|
+
const prevScrollHeight = el.scrollHeight;
|
|
507
|
+
await fetchNextPage();
|
|
508
|
+
requestAnimationFrame(() => {
|
|
509
|
+
const newScrollHeight = el.scrollHeight;
|
|
510
|
+
el.scrollTop = newScrollHeight - prevScrollHeight;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
503
513
|
};
|
|
504
514
|
el.addEventListener("scroll", handleScroll);
|
|
505
515
|
return () => el.removeEventListener("scroll", handleScroll);
|
|
506
|
-
}, []);
|
|
516
|
+
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
|
|
507
517
|
useEffect3(() => {
|
|
508
518
|
const el = scrollRef.current;
|
|
509
519
|
if (!el) return;
|
|
@@ -531,17 +541,20 @@ function ChatContainer(props) {
|
|
|
531
541
|
flex flex-col ${className || ""}
|
|
532
542
|
`,
|
|
533
543
|
children: [
|
|
534
|
-
/* @__PURE__ */
|
|
544
|
+
/* @__PURE__ */ jsxs4(
|
|
535
545
|
"div",
|
|
536
546
|
{
|
|
537
547
|
ref: scrollRef,
|
|
538
548
|
className: `flex-1 overflow-y-auto chatContainer-scroll p-2`,
|
|
539
|
-
children:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
549
|
+
children: [
|
|
550
|
+
hasNextPage && isFetchingNextPage && /* @__PURE__ */ jsx6("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx6(LoadingSpinner, { size: "sm" }) }),
|
|
551
|
+
/* @__PURE__ */ jsx6(
|
|
552
|
+
ChatList,
|
|
553
|
+
__spreadValues(__spreadValues(__spreadValues({
|
|
554
|
+
messages: mappedMessages
|
|
555
|
+
}, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
|
|
556
|
+
)
|
|
557
|
+
]
|
|
545
558
|
}
|
|
546
559
|
),
|
|
547
560
|
/* @__PURE__ */ jsxs4("div", { className: "flex-shrink-0 relative", children: [
|
|
@@ -584,12 +597,26 @@ function ChatContainer(props) {
|
|
|
584
597
|
) });
|
|
585
598
|
}
|
|
586
599
|
|
|
587
|
-
// src/hooks/
|
|
588
|
-
import {
|
|
600
|
+
// src/hooks/useChat.ts
|
|
601
|
+
import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
589
602
|
import { useState as useState4 } from "react";
|
|
590
|
-
function
|
|
603
|
+
function splitRawToMessage(raw, mapped) {
|
|
604
|
+
const custom = {};
|
|
605
|
+
const mappedValues = new Set(Object.values(mapped));
|
|
606
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
607
|
+
if (!mappedValues.has(value)) {
|
|
608
|
+
custom[key] = value;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return __spreadProps(__spreadValues({}, mapped), {
|
|
612
|
+
custom
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
function useChat({
|
|
591
616
|
queryKey,
|
|
592
617
|
queryFn,
|
|
618
|
+
initialPageParam,
|
|
619
|
+
getNextPageParam,
|
|
593
620
|
mutationFn,
|
|
594
621
|
map,
|
|
595
622
|
onError,
|
|
@@ -599,17 +626,26 @@ function useOptimisticChat({
|
|
|
599
626
|
const [isPending, setIsPending] = useState4(false);
|
|
600
627
|
const queryClient = useQueryClient();
|
|
601
628
|
const {
|
|
602
|
-
data
|
|
603
|
-
isLoading: isInitialLoading
|
|
604
|
-
|
|
629
|
+
data,
|
|
630
|
+
isLoading: isInitialLoading,
|
|
631
|
+
fetchNextPage,
|
|
632
|
+
hasNextPage,
|
|
633
|
+
isFetchingNextPage
|
|
634
|
+
} = useInfiniteQuery({
|
|
605
635
|
queryKey,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
636
|
+
initialPageParam,
|
|
637
|
+
queryFn: async ({ pageParam }) => {
|
|
638
|
+
const raw = await queryFn(pageParam);
|
|
639
|
+
return raw.map((r) => {
|
|
640
|
+
const mapped = map(r);
|
|
641
|
+
return splitRawToMessage(r, mapped);
|
|
642
|
+
});
|
|
609
643
|
},
|
|
644
|
+
getNextPageParam,
|
|
610
645
|
staleTime,
|
|
611
646
|
gcTime
|
|
612
647
|
});
|
|
648
|
+
const messages = data ? [...data.pages].reverse().flat() : [];
|
|
613
649
|
const mutation = useMutation({
|
|
614
650
|
mutationFn,
|
|
615
651
|
// (content: string) => Promise<TMutationRaw>
|
|
@@ -620,38 +656,47 @@ function useOptimisticChat({
|
|
|
620
656
|
await queryClient.cancelQueries({ queryKey });
|
|
621
657
|
}
|
|
622
658
|
queryClient.setQueryData(queryKey, (old) => {
|
|
623
|
-
|
|
624
|
-
return
|
|
625
|
-
|
|
626
|
-
|
|
659
|
+
var _a;
|
|
660
|
+
if (!old) return old;
|
|
661
|
+
const pages = [...old.pages];
|
|
662
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
663
|
+
pages[0] = [
|
|
664
|
+
...firstPage,
|
|
627
665
|
{
|
|
628
666
|
id: crypto.randomUUID(),
|
|
629
667
|
role: "USER",
|
|
630
|
-
content
|
|
668
|
+
content,
|
|
669
|
+
custom: {}
|
|
631
670
|
},
|
|
632
|
-
// AI placeholder 추가
|
|
633
671
|
{
|
|
634
672
|
id: crypto.randomUUID(),
|
|
635
673
|
role: "AI",
|
|
636
674
|
content: "",
|
|
637
|
-
isLoading: true
|
|
675
|
+
isLoading: true,
|
|
676
|
+
custom: {}
|
|
638
677
|
}
|
|
639
678
|
];
|
|
679
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
680
|
+
pages
|
|
681
|
+
});
|
|
640
682
|
});
|
|
641
683
|
return prev ? { prev } : {};
|
|
642
684
|
},
|
|
643
685
|
onSuccess: (rawAiResponse) => {
|
|
644
|
-
const
|
|
686
|
+
const mapped = map(rawAiResponse);
|
|
687
|
+
const aiMessage = splitRawToMessage(rawAiResponse, mapped);
|
|
645
688
|
queryClient.setQueryData(queryKey, (old) => {
|
|
646
|
-
if (!old
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
next[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, next[lastIndex]), aiMessage), {
|
|
689
|
+
if (!old) return old;
|
|
690
|
+
const pages = [...old.pages];
|
|
691
|
+
const firstPage = [...pages[0]];
|
|
692
|
+
const lastIndex = firstPage.length - 1;
|
|
693
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, firstPage[lastIndex]), aiMessage), {
|
|
652
694
|
isLoading: false
|
|
653
695
|
});
|
|
654
|
-
|
|
696
|
+
pages[0] = firstPage;
|
|
697
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
698
|
+
pages
|
|
699
|
+
});
|
|
655
700
|
});
|
|
656
701
|
setIsPending(false);
|
|
657
702
|
},
|
|
@@ -674,17 +719,35 @@ function useOptimisticChat({
|
|
|
674
719
|
// (content: string) => void
|
|
675
720
|
isPending,
|
|
676
721
|
// 사용자가 채팅 전송 후 AI 응답이 올 때까지의 로딩
|
|
677
|
-
isInitialLoading
|
|
722
|
+
isInitialLoading,
|
|
678
723
|
// 초기 로딩 상태
|
|
724
|
+
// infinite query용
|
|
725
|
+
fetchNextPage,
|
|
726
|
+
hasNextPage,
|
|
727
|
+
isFetchingNextPage
|
|
679
728
|
};
|
|
680
729
|
}
|
|
681
730
|
|
|
682
|
-
// src/hooks/
|
|
683
|
-
import {
|
|
731
|
+
// src/hooks/useVoiceChat.ts
|
|
732
|
+
import { useInfiniteQuery as useInfiniteQuery2, useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
684
733
|
import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
|
|
685
|
-
function
|
|
734
|
+
function splitRawToMessage2(raw, mapped) {
|
|
735
|
+
const custom = {};
|
|
736
|
+
const mappedValues = new Set(Object.values(mapped));
|
|
737
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
738
|
+
if (!mappedValues.has(value)) {
|
|
739
|
+
custom[key] = value;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return __spreadProps(__spreadValues({}, mapped), {
|
|
743
|
+
custom
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function useVoiceChat({
|
|
686
747
|
queryKey,
|
|
687
748
|
queryFn,
|
|
749
|
+
initialPageParam,
|
|
750
|
+
getNextPageParam,
|
|
688
751
|
mutationFn,
|
|
689
752
|
map,
|
|
690
753
|
voice,
|
|
@@ -697,17 +760,26 @@ function useVoiceOptimisticChat({
|
|
|
697
760
|
const currentTextRef = useRef4("");
|
|
698
761
|
const rollbackRef = useRef4(void 0);
|
|
699
762
|
const {
|
|
700
|
-
data
|
|
701
|
-
isLoading: isInitialLoading
|
|
702
|
-
|
|
763
|
+
data,
|
|
764
|
+
isLoading: isInitialLoading,
|
|
765
|
+
fetchNextPage,
|
|
766
|
+
hasNextPage,
|
|
767
|
+
isFetchingNextPage
|
|
768
|
+
} = useInfiniteQuery2({
|
|
703
769
|
queryKey,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
770
|
+
initialPageParam,
|
|
771
|
+
queryFn: async ({ pageParam }) => {
|
|
772
|
+
const raw = await queryFn(pageParam);
|
|
773
|
+
return raw.map((r) => {
|
|
774
|
+
const mapped = map(r);
|
|
775
|
+
return splitRawToMessage2(r, mapped);
|
|
776
|
+
});
|
|
707
777
|
},
|
|
778
|
+
getNextPageParam,
|
|
708
779
|
staleTime,
|
|
709
780
|
gcTime
|
|
710
781
|
});
|
|
782
|
+
const messages = data ? data.pages.map((page) => [...page]).reverse().flat() : [];
|
|
711
783
|
const mutation = useMutation2({
|
|
712
784
|
mutationFn,
|
|
713
785
|
// (content: string) => Promise<TMutationRaw>
|
|
@@ -718,32 +790,41 @@ function useVoiceOptimisticChat({
|
|
|
718
790
|
await queryClient.cancelQueries({ queryKey });
|
|
719
791
|
}
|
|
720
792
|
queryClient.setQueryData(queryKey, (old) => {
|
|
721
|
-
|
|
722
|
-
return
|
|
723
|
-
|
|
724
|
-
|
|
793
|
+
var _a;
|
|
794
|
+
if (!old) return old;
|
|
795
|
+
const pages = [...old.pages];
|
|
796
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
797
|
+
pages[0] = [
|
|
798
|
+
...firstPage,
|
|
725
799
|
{
|
|
726
800
|
id: crypto.randomUUID(),
|
|
727
801
|
role: "AI",
|
|
728
802
|
content: "",
|
|
729
|
-
isLoading: true
|
|
803
|
+
isLoading: true,
|
|
804
|
+
custom: {}
|
|
730
805
|
}
|
|
731
806
|
];
|
|
807
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
808
|
+
pages
|
|
809
|
+
});
|
|
732
810
|
});
|
|
733
811
|
return prev ? { prev } : {};
|
|
734
812
|
},
|
|
735
813
|
onSuccess: (rawAiResponse) => {
|
|
736
|
-
const
|
|
814
|
+
const mapped = map(rawAiResponse);
|
|
815
|
+
const aiMessage = splitRawToMessage2(rawAiResponse, mapped);
|
|
737
816
|
queryClient.setQueryData(queryKey, (old) => {
|
|
738
|
-
if (!old
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
next[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, next[lastIndex]), aiMessage), {
|
|
817
|
+
if (!old) return old;
|
|
818
|
+
const pages = [...old.pages];
|
|
819
|
+
const firstPage = [...pages[0]];
|
|
820
|
+
const lastIndex = firstPage.length - 1;
|
|
821
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues(__spreadValues({}, firstPage[lastIndex]), aiMessage), {
|
|
744
822
|
isLoading: false
|
|
745
823
|
});
|
|
746
|
-
|
|
824
|
+
pages[0] = firstPage;
|
|
825
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
826
|
+
pages
|
|
827
|
+
});
|
|
747
828
|
});
|
|
748
829
|
setIsPending(false);
|
|
749
830
|
},
|
|
@@ -762,14 +843,24 @@ function useVoiceOptimisticChat({
|
|
|
762
843
|
if (prev) {
|
|
763
844
|
await queryClient.cancelQueries({ queryKey });
|
|
764
845
|
}
|
|
765
|
-
queryClient.setQueryData(queryKey, (old) =>
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
846
|
+
queryClient.setQueryData(queryKey, (old) => {
|
|
847
|
+
var _a;
|
|
848
|
+
if (!old) return old;
|
|
849
|
+
const pages = [...old.pages];
|
|
850
|
+
const firstPage = (_a = pages[0]) != null ? _a : [];
|
|
851
|
+
pages[0] = [
|
|
852
|
+
...firstPage,
|
|
853
|
+
{
|
|
854
|
+
id: crypto.randomUUID(),
|
|
855
|
+
role: "USER",
|
|
856
|
+
content: "",
|
|
857
|
+
custom: {}
|
|
858
|
+
}
|
|
859
|
+
];
|
|
860
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
861
|
+
pages
|
|
862
|
+
});
|
|
863
|
+
});
|
|
773
864
|
voice.start();
|
|
774
865
|
};
|
|
775
866
|
const onTranscript = (text) => {
|
|
@@ -777,13 +868,17 @@ function useVoiceOptimisticChat({
|
|
|
777
868
|
queryClient.setQueryData(queryKey, (old) => {
|
|
778
869
|
var _a;
|
|
779
870
|
if (!old) return old;
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
871
|
+
const pages = [...old.pages];
|
|
872
|
+
const firstPage = [...pages[0]];
|
|
873
|
+
const lastIndex = firstPage.length - 1;
|
|
874
|
+
if (((_a = firstPage[lastIndex]) == null ? void 0 : _a.role) !== "USER") return old;
|
|
875
|
+
firstPage[lastIndex] = __spreadProps(__spreadValues({}, firstPage[lastIndex]), {
|
|
784
876
|
content: text
|
|
785
877
|
});
|
|
786
|
-
|
|
878
|
+
pages[0] = firstPage;
|
|
879
|
+
return __spreadProps(__spreadValues({}, old), {
|
|
880
|
+
pages
|
|
881
|
+
});
|
|
787
882
|
});
|
|
788
883
|
};
|
|
789
884
|
useEffect4(() => {
|
|
@@ -809,8 +904,12 @@ function useVoiceOptimisticChat({
|
|
|
809
904
|
// 초기 로딩 상태
|
|
810
905
|
startRecording,
|
|
811
906
|
// 음성 인식 시작 함수
|
|
812
|
-
stopRecording
|
|
907
|
+
stopRecording,
|
|
813
908
|
// 음성 인식 종료 함수
|
|
909
|
+
// infinite query용
|
|
910
|
+
fetchNextPage,
|
|
911
|
+
hasNextPage,
|
|
912
|
+
isFetchingNextPage
|
|
814
913
|
};
|
|
815
914
|
}
|
|
816
915
|
export {
|
|
@@ -821,6 +920,6 @@ export {
|
|
|
821
920
|
LoadingSpinner,
|
|
822
921
|
SendingDots,
|
|
823
922
|
useBrowserSpeechRecognition,
|
|
824
|
-
|
|
825
|
-
|
|
923
|
+
useChat,
|
|
924
|
+
useVoiceChat
|
|
826
925
|
};
|