@@ -4,7 +4,6 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
44
55import Icon from "@/components/common/icon" ;
66import { useUserInfo } from "@/hooks/use-user-info" ;
7- import { useCreateChatSession } from "@/lib/tanstack/mutation/chat" ;
87import { useGetChatMessages } from "@/lib/tanstack/query/chat" ;
98import { cn } from "@repo/ui" ;
109
@@ -19,6 +18,7 @@ interface Message {
1918interface ChatProps {
2019 sessionId : string ;
2120 onSessionCreated : ( newSessionId : string ) => void ;
21+ isNewChat : boolean ;
2222}
2323
2424const AssistantMessage = memo ( ( { content } : { content : string } ) => {
@@ -34,20 +34,27 @@ const AssistantMessage = memo(({ content }: { content: string }) => {
3434} ) ;
3535AssistantMessage . displayName = "AssistantMessage" ;
3636
37- export default function Chat ( { sessionId, onSessionCreated } : ChatProps ) {
37+ export default function Chat ( { sessionId, onSessionCreated, isNewChat } : ChatProps ) {
3838 const { userInfo, isLoading : isUserLoading } = useUserInfo ( ) ;
39- const { data : chatHistory , isLoading : isHistoryLoading } = useGetChatMessages ( sessionId ) ;
40- const { mutateAsync : createChatSession } = useCreateChatSession ( ) ;
39+ const { data : chatHistory , isLoading : isHistoryLoading } = useGetChatMessages ( {
40+ sessionId,
41+ isNewChat,
42+ } ) ;
4143 const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
4244 const [ streamingContent , setStreamingContent ] = useState < string | null > ( null ) ;
4345 const [ isLoading , setIsLoading ] = useState ( false ) ;
4446 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
45- const formRef = useRef < HTMLFormElement > ( null ) ;
47+ const chatFormRef = useRef < HTMLFormElement > ( null ) ;
48+ const startChatFormRef = useRef < HTMLFormElement > ( null ) ;
49+
50+ const chatButtonDisabled = isUserLoading || isLoading ;
4651
4752 useEffect ( ( ) => {
48- setMessages ( [ ] ) ;
49- setStreamingContent ( null ) ;
50- } , [ sessionId ] ) ;
53+ if ( ! isNewChat ) {
54+ setMessages ( [ ] ) ;
55+ setStreamingContent ( null ) ;
56+ }
57+ } , [ sessionId , isNewChat ] ) ;
5158
5259 useEffect ( ( ) => {
5360 if ( chatHistory ?. result && chatHistory . result . length > 0 && messages . length === 0 ) {
@@ -86,6 +93,9 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
8693 const data = line . split ( "data: " ) [ 1 ] ;
8794 if ( data ) {
8895 const parsed = JSON . parse ( data ) ;
96+ if ( typeof parsed . data === "object" && "session_id" in parsed . data ) {
97+ onSessionCreated ( parsed . data . session_id ) ;
98+ }
8999 if ( parsed . type === "chunk" && parsed . data ) {
90100 botResponse += parsed . data ;
91101 setStreamingContent ( botResponse ) ;
@@ -99,93 +109,91 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
99109 }
100110 } , [ ] ) ;
101111
102- const onSubmitQuestion = useCallback (
103- async ( e : React . FormEvent < HTMLFormElement > ) => {
104- e . preventDefault ( ) ;
105- const formData = new FormData ( e . target as HTMLFormElement ) ;
106- const question = formData . get ( "question" ) as string ;
107- if ( question . trim ( ) === "" || isLoading || isUserLoading || ! userInfo ) return ;
112+ const onSubmitQuestion = async ( e : React . FormEvent < HTMLFormElement > ) => {
113+ e . preventDefault ( ) ;
114+ const formData = new FormData ( e . target as HTMLFormElement ) ;
115+ const question = formData . get ( "question" ) as string ;
116+ if ( question . trim ( ) === "" || chatButtonDisabled || ! userInfo ) return ;
108117
109- e . currentTarget . reset ( ) ;
110- ( e . currentTarget . querySelector ( "textarea" ) as HTMLTextAreaElement ) . style . height = "auto" ;
118+ chatFormRef . current ? .reset ( ) ;
119+ ( e . currentTarget . querySelector ( "textarea" ) as HTMLTextAreaElement ) . style . height = "auto" ;
111120
112- setMessages ( ( prev ) => [ ...prev , { role : "user" , content : question } ] ) ;
113- setIsLoading ( true ) ;
121+ setMessages ( ( prev ) => [ ...prev , { role : "user" , content : question } ] ) ;
122+ setIsLoading ( true ) ;
114123
115- try {
116- const response = await fetch ( "/api/chat" , {
117- method : "POST" ,
118- headers : { "Content-Type" : "application/json" } ,
119- body : JSON . stringify ( { userId : userInfo . id , message : question , session_id : sessionId } ) ,
120- } ) ;
121- await onStreamResponse ( response ) ;
122- } catch ( error ) {
123- console . error ( "Failed to send message:" , error ) ;
124- } finally {
125- setIsLoading ( false ) ;
126- }
127- } ,
128- [ isLoading , isUserLoading , userInfo , sessionId , onStreamResponse ]
129- ) ;
124+ try {
125+ const response = await fetch ( "/api/chat" , {
126+ method : "POST" ,
127+ headers : { "Content-Type" : "application/json" } ,
128+ body : JSON . stringify ( { userId : userInfo . id , message : question , session_id : sessionId } ) ,
129+ } ) ;
130+ await onStreamResponse ( response ) ;
131+ } catch ( error ) {
132+ console . error ( "Failed to send message:" , error ) ;
133+ } finally {
134+ setIsLoading ( false ) ;
135+ }
136+ } ;
130137
131- const onCreateSessionAndSendMessage = useCallback (
132- async ( e : React . FormEvent < HTMLFormElement > ) => {
133- e . preventDefault ( ) ;
134- const formData = new FormData ( e . target as HTMLFormElement ) ;
135- const title = formData . get ( "title" ) as string ;
136- const question = formData . get ( "question" ) as string ;
137- if ( ! title . trim ( ) || ! question . trim ( ) || isLoading || isUserLoading || ! userInfo ) return ;
138+ const onCreateSessionAndSendMessage = async ( e : React . FormEvent < HTMLFormElement > ) => {
139+ e . preventDefault ( ) ;
140+ const formData = new FormData ( e . target as HTMLFormElement ) ;
141+ const question = formData . get ( "question" ) as string ;
142+ if ( ! question . trim ( ) || chatButtonDisabled || ! userInfo ) return ;
138143
139- setIsLoading ( true ) ;
140- try {
141- const newSession = await createChatSession ( { title } ) ;
142- const newSessionId = newSession . result . sessionId ;
143- onSessionCreated ( newSessionId ) ;
144- setMessages ( [ { role : "user" , content : question } ] ) ;
144+ setMessages ( [ { role : "user" , content : question } ] ) ;
145+ setIsLoading ( true ) ;
145146
146- const response = await fetch ( "/api/chat" , {
147- method : "POST" ,
148- headers : { "Content-Type" : "application/json" } ,
149- body : JSON . stringify ( {
150- userId : userInfo . id ,
151- message : question ,
152- session_id : newSessionId ,
153- } ) ,
154- } ) ;
155- await onStreamResponse ( response ) ;
156- } catch ( error ) {
157- console . error ( "Failed to create chat session or send message:" , error ) ;
158- } finally {
159- setIsLoading ( false ) ;
160- }
161- } ,
162- [ isLoading , isUserLoading , userInfo , createChatSession , onSessionCreated , onStreamResponse ]
163- ) ;
147+ try {
148+ const response = await fetch ( "/api/chat" , {
149+ method : "POST" ,
150+ headers : { "Content-Type" : "application/json" } ,
151+ body : JSON . stringify ( {
152+ userId : userInfo . id ,
153+ message : question ,
154+ } ) ,
155+ } ) ;
156+ startChatFormRef . current ?. reset ( ) ;
157+ await onStreamResponse ( response ) ;
158+ } catch ( error ) {
159+ console . error ( "Failed to create chat session or send message:" , error ) ;
160+ } finally {
161+ setIsLoading ( false ) ;
162+ }
163+ } ;
164164
165165 if ( sessionId === "" ) {
166166 return (
167- < div className = "flex w-80 max-w-lg flex-col justify-between rounded-lg border bg-white p-4" >
167+ < div className = "flex h-full w-full flex-col justify-between rounded-lg border bg-white p-4" >
168168 < h3 className = "mb-4 text-lg font-semibold" > 새로운 대화 시작하기</ h3 >
169- < form onSubmit = { onCreateSessionAndSendMessage } className = "flex flex-col gap-4" >
170- < input
171- name = "title"
172- placeholder = "대화 주제를 입력하세요."
173- className = "rounded-md border p-2 outline-none"
174- disabled = { isUserLoading }
175- required
176- />
169+ < form
170+ ref = { startChatFormRef }
171+ onSubmit = { onCreateSessionAndSendMessage }
172+ className = "flex flex-col gap-4"
173+ >
177174 < textarea
178175 name = "question"
179- placeholder = "첫 번째 질문을 입력하세요."
176+ placeholder = "질문을 입력하세요."
180177 className = "rounded-md border p-2 outline-none"
181178 rows = { 5 }
182179 disabled = { isUserLoading }
183180 required
181+ onKeyDown = { ( e ) => {
182+ if ( e . key === "Enter" && ! e . shiftKey ) {
183+ e . preventDefault ( ) ;
184+ if ( ! chatButtonDisabled && startChatFormRef . current ) {
185+ startChatFormRef . current . requestSubmit ( ) ;
186+ }
187+ }
188+ } }
184189 />
185190 < button
186191 type = "submit"
187- className = "flex items-center justify-center rounded-md bg-black p-2 text-white disabled:bg-gray-400"
188- disabled = { isUserLoading || isLoading }
192+ className = { cn (
193+ "flex items-center justify-center rounded-md bg-black p-2 text-white disabled:bg-gray-400" ,
194+ chatButtonDisabled && "animate-pulse"
195+ ) }
196+ disabled = { chatButtonDisabled }
189197 >
190198 { isLoading ? "생각 중..." : "질문하고 대화 시작" }
191199 </ button >
@@ -196,14 +204,14 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
196204
197205 if ( isHistoryLoading ) {
198206 return (
199- < div className = "flex h-full w-80 max-w-lg flex-col items-center justify-center rounded-lg border bg-white" >
207+ < div className = "flex h-full w-full flex-col items-center justify-center rounded-lg border bg-white" >
200208 < p className = "animate-pulse" > 대화 기록을 불러오는 중...</ p >
201209 </ div >
202210 ) ;
203211 }
204212
205213 return (
206- < div className = "flex h-full w-80 max-w-lg flex-col justify-between rounded-lg border bg-white" >
214+ < div className = "flex h-full w-full flex-col justify-between rounded-lg border bg-white" >
207215 < div className = "flex-1 space-y-4 overflow-y-auto p-4" >
208216 { messages . map ( ( message , index ) => (
209217 < div
@@ -238,7 +246,7 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
238246 < div ref = { messagesEndRef } />
239247 </ div >
240248 < form
241- ref = { formRef }
249+ ref = { chatFormRef }
242250 onSubmit = { onSubmitQuestion }
243251 className = "flex items-center gap-2 border-t p-2"
244252 >
@@ -247,7 +255,7 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
247255 rows = { 1 }
248256 className = "max-h-24 flex-1 resize-none overflow-y-auto bg-transparent p-1 outline-none"
249257 placeholder = "챗봇에게 질문을 적어주세요."
250- disabled = { isLoading || isUserLoading }
258+ disabled = { chatButtonDisabled }
251259 onInput = { ( e ) => {
252260 const target = e . currentTarget ;
253261 target . style . height = "auto" ;
@@ -256,16 +264,16 @@ export default function Chat({ sessionId, onSessionCreated }: ChatProps) {
256264 onKeyDown = { ( e ) => {
257265 if ( e . key === "Enter" && ! e . shiftKey ) {
258266 e . preventDefault ( ) ;
259- if ( ! isLoading && formRef . current ) {
260- formRef . current . requestSubmit ( ) ;
267+ if ( ! chatButtonDisabled && chatFormRef . current ) {
268+ chatFormRef . current . requestSubmit ( ) ;
261269 }
262270 }
263271 } }
264272 />
265273 < button
266274 type = "submit"
267275 className = "rounded-full bg-black p-1 disabled:bg-gray-400"
268- disabled = { isLoading || isUserLoading }
276+ disabled = { chatButtonDisabled }
269277 >
270278 < Icon . send fill = "#fff" size = { 16 } />
271279 </ button >
0 commit comments