Skip to content

Commit 2bf006e

Browse files
committed
feat: 변경된 채팅 시작 로직 반영
1 parent 14e7840 commit 2bf006e

11 files changed

Lines changed: 255 additions & 147 deletions

File tree

apps/www/src/app/api/chat/route.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ export async function POST(req: NextRequest) {
77
const body = await req.json();
88
const { userId, message, session_id } = body;
99

10+
const requestBody = session_id ? { userId, message, session_id } : { userId, message };
11+
1012
const response = await fetch(`${baseUrl}/chat/stream`, {
1113
method: "POST",
1214
headers: {
1315
"Content-Type": "application/json",
1416
},
15-
body: JSON.stringify({
16-
userId,
17-
message,
18-
session_id,
19-
}),
17+
body: JSON.stringify(requestBody),
2018
});
2119

2220
if (!response.ok) {

apps/www/src/components/bookmarks/graph/chat-bot/chat.tsx

Lines changed: 91 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
44

55
import Icon from "@/components/common/icon";
66
import { useUserInfo } from "@/hooks/use-user-info";
7-
import { useCreateChatSession } from "@/lib/tanstack/mutation/chat";
87
import { useGetChatMessages } from "@/lib/tanstack/query/chat";
98
import { cn } from "@repo/ui";
109

@@ -19,6 +18,7 @@ interface Message {
1918
interface ChatProps {
2019
sessionId: string;
2120
onSessionCreated: (newSessionId: string) => void;
21+
isNewChat: boolean;
2222
}
2323

2424
const AssistantMessage = memo(({ content }: { content: string }) => {
@@ -34,20 +34,27 @@ const AssistantMessage = memo(({ content }: { content: string }) => {
3434
});
3535
AssistantMessage.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

Comments
 (0)