- {messages.map((msg) => {
+
+ {messages.map((msg, i) => {
+ const keyId =
+ !msg.id || msg.id === 'null' ? `temp-${msg.sender}-${i}-${Math.random()}` : msg.id;
+ const prevMsg = messages[i - 1];
+ const showProfile = !prevMsg || prevMsg.sender !== msg.sender;
+
if (msg.sender === 'USER') {
- return
;
+ return
;
}
+ const isTyping = msg.type === 'TYPING';
+
+ const recommendations = getRecommendations(msg.type, msg.stepData);
+
return (
);
})}
- {isBotTyping &&
}
-
{showNewMessageAlert &&
}
diff --git a/src/domains/recommend/components/ChatSection.tsx b/src/domains/recommend/components/ChatSection.tsx
index 6aff917..f086d8c 100644
--- a/src/domains/recommend/components/ChatSection.tsx
+++ b/src/domains/recommend/components/ChatSection.tsx
@@ -1,58 +1,49 @@
'use client';
-import { useEffect, useRef, useState } from 'react';
+import { useState } from 'react';
import MessageInput from './user/MessageInput';
-import {
- fetchChatHistory,
- fetchGreeting,
- fetchSendStepMessage,
- fetchSendTextMessage,
-} from '../api/chat';
+import { fetchSendStepMessage, fetchSendTextMessage } from '../api/chat';
import { useAuthStore } from '@/domains/shared/store/auth';
-import {
- ChatMessage,
- stepPayload,
- StepRecommendation,
- RecommendationItem,
-} from '../types/recommend';
+import { ChatMessage, stepPayload } from '../types/recommend';
import ChatList from './ChatList';
+import { useChatInit } from '../hook/useChatInit';
+import { useSelectedOptions } from '../hook/useSelectedOptions';
function ChatSection() {
const [messages, setMessages] = useState
([]);
-
const [userCurrentStep, setUserCurrentStep] = useState(0);
- const [isBotTyping, setIsBotTyping] = useState(false);
+ const { selectedOptions, setOption, setStepOption } = useSelectedOptions();
- const selectedOptions = useRef<{
- selectedAlcoholStrength?: string;
- selectedAlcoholBaseType?: string;
- selectedCocktailType?: string;
- }>({});
+ const isInputDisabled =
+ selectedOptions.current.selectedSearchType !== 'QA' && userCurrentStep < 3;
const handleSendMessage = async (payload: stepPayload | { message: string; userId: string }) => {
- const typingTimer = setTimeout(() => setIsBotTyping(true), 300);
-
- try {
- if (!('currentStep' in payload)) {
- const botMessage = await fetchSendTextMessage(payload);
- clearTimeout(typingTimer);
- setIsBotTyping(false);
+ const tempTypingId = `typing-${Date.now()}`;
- if (!botMessage) return;
- setTimeout(() => setMessages((prev) => [...prev, botMessage]), 500);
- return;
- }
+ // Typing 메시지 임시 추가
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: tempTypingId,
+ sender: 'CHATBOT',
+ type: 'TYPING',
+ message: '',
+ createdAt: new Date().toISOString(),
+ },
+ ]);
- const botMessage = await fetchSendStepMessage(payload);
- clearTimeout(typingTimer);
- setIsBotTyping(false);
+ try {
+ const botMessage =
+ 'currentStep' in payload
+ ? await fetchSendStepMessage(payload)
+ : await fetchSendTextMessage(payload);
if (!botMessage) return;
- setTimeout(() => setMessages((prev) => [...prev, botMessage]), 500);
+
+ setMessages((prev) => prev.map((msg) => (msg.id === tempTypingId ? botMessage : msg)));
} catch (err) {
- clearTimeout(typingTimer);
- setIsBotTyping(false);
console.error(err);
+ setMessages((prev) => prev.filter((msg) => msg.id !== tempTypingId));
}
};
@@ -70,7 +61,14 @@ function ChatSection() {
{ id: tempId, userId, message, sender: 'USER', type: 'text', createdAt: tempCreatedAt },
]);
- await handleSendMessage({ message, userId });
+ const nextStep = userCurrentStep === 3 ? userCurrentStep + 1 : userCurrentStep;
+
+ const payload: stepPayload =
+ nextStep === 0
+ ? { message, userId, currentStep: nextStep }
+ : { message, userId, currentStep: nextStep, ...selectedOptions.current };
+
+ await handleSendMessage(payload);
};
// 옵션 클릭 시
@@ -103,49 +101,26 @@ function ChatSection() {
},
]);
+ // QA (질문형) 일 시 0 나머지는 +1
const nextStep = value === 'QA' ? 0 : (stepData?.currentStep ?? 0) + 1;
setUserCurrentStep(nextStep);
- switch (stepData.currentStep + 1) {
- case 2:
- selectedOptions.current.selectedAlcoholStrength = value;
- break;
- case 3:
- selectedOptions.current.selectedAlcoholBaseType = value;
- break;
+ // 0단계에서 QA 선택 시
+ if (stepData.currentStep === 0 && value === 'QA') {
+ setOption('selectedSearchType', 'QA');
}
- const payload: stepPayload = {
- message: selectedLabel,
- userId,
- currentStep: nextStep,
- ...selectedOptions.current,
- };
+ setStepOption(stepData.currentStep + 1, value);
+
+ const payload: stepPayload =
+ nextStep === 0
+ ? { message: selectedLabel, userId, currentStep: nextStep }
+ : { message: selectedLabel, userId, currentStep: nextStep, ...selectedOptions.current };
await handleSendMessage(payload);
};
- // 채팅 기록 불러오기 없으면 greeting 호출
- useEffect(() => {
- const loadChatHistory = async () => {
- const history = await fetchChatHistory();
- if (history && history.length > 0) {
- setMessages(history.sort((a, b) => Number(a.id) - Number(b.id)));
- } else {
- const greeting = await fetchGreeting('');
- if (greeting) setMessages([greeting]);
- }
- };
- loadChatHistory();
- }, []);
-
- const getRecommendations = (
- type: string | undefined,
- stepData?: StepRecommendation | null
- ): RecommendationItem[] => {
- if (type !== 'CARD_LIST' || !stepData?.recommendations) return [];
- return stepData.recommendations;
- };
+ useChatInit(setMessages);
return (
@@ -154,10 +129,8 @@ function ChatSection() {
messages={messages}
userCurrentStep={userCurrentStep}
onSelectedOption={handleSelectedOption}
- getRecommendations={getRecommendations}
- isBotTyping={isBotTyping}
/>
-
+
);
}
diff --git a/src/domains/recommend/components/bot/BotCocktailCard.tsx b/src/domains/recommend/components/bot/BotCocktailCard.tsx
index c5e6b8f..d865943 100644
--- a/src/domains/recommend/components/bot/BotCocktailCard.tsx
+++ b/src/domains/recommend/components/bot/BotCocktailCard.tsx
@@ -29,7 +29,7 @@ function BotCocktailCard({
+ 상세보기