Skip to content

Commit

Permalink
(#79) ๐Ÿ”„ refactor: ์‹œ๊ฐ„ ์ˆœ๋Œ€๋กœ ์ฑ— ์ปดํฌ๋„ŒํŠธ ์ถœ๋ ฅ๋˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง
Browse files Browse the repository at this point in the history
  • Loading branch information
inaemon committed Nov 28, 2024
1 parent daaf77b commit 568f1d7
Show file tree
Hide file tree
Showing 5 changed files with 433 additions and 170 deletions.
11 changes: 1 addition & 10 deletions src/constants/ChatbotData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,4 @@ export const getStadiumChatApiData = (frontendStadiumValue: string): string => {

// backendParameters์—์„œ ๋งค์นญ๋˜๋Š” ๊ฐ’ ๋ฐ˜ํ™˜
return backendParameters[index];
};

export interface GuideResponseData {
answer: string; // ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ฑ—๋ด‡ ๊ฐ€์ด๋“œ ๋‹ต๋ณ€ ๋ฐ์ดํ„ฐ
imgUrl: string; // ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ฑ—๋ด‡ ๊ฐ€์ด๋“œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ
categoryNumber: number; // ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ํฐ ์นดํ…Œ๊ณ ๋ฆฌ
categoryName: string; // ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ํฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ช…
subcategoryNumber: number; // ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์„ธ๋ถ€ ์นดํ…Œ๊ณ ๋ฆฌ
subCategoryName: string; // ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์„ธ๋ถ€ ์นดํ…Œ๊ณ ๋ฆฌ๋ช…
}
};
287 changes: 287 additions & 0 deletions src/pages/Chatbot/Chatbot copy ๊ธฐ์กด ๋ฐฑ์—….tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import React, { useState, useEffect, useRef } from "react";
import BackLogoBar from "../../components/layout/BackLogoBar";
import { stadiumList } from "../../constants/ZoneData";
import StadiumSelection from "./components/stadiumcategory/StadiumSelection";
import ChatbotInputField from "./components/input/ChatbotInputField";

import DateBanner from "./components/DateBanner";

import { questionCategories, GuideResponseData } from "@/src/constants/ChatbotData";
import { GuideGetResponseType } from "@/src/api/ChatbotApiType";

import RookieChat from "./components/chat/RookieChat";
import UserChat from "./components/chat/UserChat";
import CategoryChat from "./components/chat/CategoryChat";

import RookieImageMessage from "./components/message/custom/RookieImageMessage";
import chatbotManualIcon from "@/src/assets/webp/chatbot_manual.webp";

import Chatting from "@/src/pages/Chatbot/components/Chatting";

const Chatbot = () => {
// ์Šคํƒ€๋””์›€ ์„ ํƒ ๊ด€๋ จ
const [selectedStadium, setSelectedStadium] = useState<string | null>(null); // ์„ ํƒํ•œ ์Šคํƒ€๋””์›€ ์ €์žฅ
const isStadiumSelected = selectedStadium !== null && selectedStadium !== ""; // ์Šคํƒ€๋””์›€ ์„ ํƒ ์—ฌ๋ถ€
const [showInitialMessages, setShowInitialMessages] = useState(false); // ์ดˆ๊ธฐ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ์—ฌ๋ถ€
const [isLoading, setIsLoading] = useState(true); // ๋กœ๋”ฉ ์ƒํƒœ ์ถ”๊ฐ€ // ์ดˆ๊ธฐ์—๋Š” ์ž๋™ ์Šคํฌ๋กค ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ

const handleStadiumSelect = (stadium: string) => {
setSelectedStadium(stadium);
setIsLoading(false); // ๋กœ๋”ฉ์ด ๋๋‚˜๋ฉด isLoading์„ false๋กœ ์„ค์ •
};


// ์ฑ—๋ด‡ ์ฒซ ์ธ์‚ฌ ๋ Œ๋”๋ง ๊ด€๋ จ
useEffect(() => {
// ์ฑ—๋ด‡ ํŽ˜์ด์ง€ ๋“ค์–ด์˜จ ํ›„ ์ดˆ๊ธฐ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
setShowInitialMessages(true);
}, []);


// ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ ๊ด€๋ จ (๋ฐฐ์—ด๋กœ ์ €์žฅํ•ด์•ผ ํ”„๋ก ํŠธ์—์„œ ๊ด€๋ฆฌ ๋ฐ ๊ณ„์† ๋Œ€ํ™” ์ƒ์„ฑ ๊ฐ€๋Šฅ)
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
const MAX_CATEGORIES = 15; // ์นดํ…Œ๊ณ ๋ฆฌ ์ตœ๋Œ€ ๊ฐœ์ˆ˜ ์ง€์ •
const handleCategorySelect = (category: string) => {
setSelectedCategories((prevCategories) => {
const updatedCategories = [...prevCategories, category];

// ๋ฐฐ์—ด์ด ์ตœ๋Œ€ ๊ฐœ์ˆ˜๋ฅผ ๋„˜์œผ๋ฉด ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ ์ œ๊ฑฐ ํ›„ ์ƒˆ๋กœ์šด ํ•ญ๋ชฉ ์ถ”๊ฐ€
if (updatedCategories.length > MAX_CATEGORIES) {
updatedCategories.shift(); // ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ ์ œ๊ฑฐ
}
return updatedCategories;
});
};

// ๊ฐ€์ด๋“œ ์ฑ—๋ด‡ ๋‹ต๋ณ€ ๊ด€๋ จ
const [responseGuideDataList, setResponseGuideDataList] = useState<GuideResponseData[]>([]); // ์„ธ๋ถ€ ์นดํ…Œ๊ณ ๋ฆฌ index์™€ ๋งคํ•‘ํ•˜์—ฌ API ์‘๋‹ต ์ €์žฅ

// Guide API ์‘๋‹ต์„ category index์— ๋งคํ•‘ํ•˜์—ฌ ์ €์žฅ
// ์ฑ„ํŒ…์ฐฝ์— ๋ Œ๋”๋งํ•˜๊ฒŒ ์œ„ํ•œ ์šฉ๋„
const handleGuideResponseUpdate = (answer: string, imgUrl: string, linkName: string, link: string, categoryKey: number, categoryName: string, subCategoryKey: number, subCategoryName: string) => {
setResponseGuideDataList((prev) => [
...prev,

{
answer: answer,
imgUrl: imgUrl,
linkName: linkName,
link: link,
categoryNumber: categoryKey,
categoryName: categoryName,
subcategoryNumber: subCategoryKey,
subCategoryName: subCategoryName,
},
]);
};


// ํด๋กœ๋ฐ” ์ฑ—๋ด‡ ๋‹ต๋ณ€ ๊ด€๋ จ
const [responseClovaDataList, setResponseClovaDataList] = useState<string[]>([]); // ์„ธ๋ถ€ ์นดํ…Œ๊ณ ๋ฆฌ index์™€ ๋งคํ•‘ํ•˜์—ฌ API ์‘๋‹ต ์ €์žฅ

// Clova API ์‘๋‹ต์„ category index์— ๋งคํ•‘ํ•˜์—ฌ ์ €์žฅ
// ์ฑ„ํŒ…์ฐฝ์— ๋ Œ๋”๋งํ•˜๊ฒŒ ์œ„ํ•œ ์šฉ๋„
const handleClovaResponseUpdate = (answer: string) => {
setResponseClovaDataList((prev) => [
...prev,
{
answer: answer,
},
]);
};


// ๊ฐ€์ด๋“œ ๋‹ต๋ณ€ ๋ Œ๋”๋ง
const renderGuideAnswerData = (response: GuideGetResponseType) => {
const answerImageUrl = response.imgUrl;
const answerString = response.answer;
const answerLinkName = response.linkName;
const answerLink = response.link;

const answerListWithImg = [
{ type: "imgUrl", content: answerImageUrl },
{ type: "preformattedText", content: answerString }
];
const answerList = [
{ type: "preformattedTextWithTail", content: answerString },
];
const answerListWithBtn = [
{ type: "preformattedTextButtonWithTail", content: answerString, buttonContent: answerLinkName, url: answerLink },
];

return (
<>
{answerImageUrl ?
// ์ด๋ฏธ์ง€, ๋‹ต๋ณ€ ์ถœ๋ ฅ
<RookieChat
contentList={answerListWithImg}
/>
: answerLink ?
// ์ด๋ฏธ์ง€, ๋‹ต๋ณ€, ๋งํฌ๋กœ ์ด๋™ํ•˜๋Š” ๋ฒ„ํŠผ ์ถœ๋ ฅ
<RookieChat
contentList={answerListWithBtn}
/>
:
// ๋‹ต๋ณ€ ์ถœ๋ ฅ
<RookieChat
contentList={answerList}
/>
}
</>
);
}


// ์Šคํฌ๋กค์„ ์กฐ์ž‘ํ•  ์˜์—ญ(์ฑ„ํŒ…์ฐฝ div) ์ง€์ •
const chatContainerRef = useRef<HTMLDivElement>(null);

// ์ž๋™ ์Šคํฌ๋กค ๊ธฐ๋Šฅ
// ์ฑ„ํŒ…์ด ์ถ”๊ฐ€๋  ๋•Œ ์Šคํฌ๋กค ๋งจ ์•„๋ž˜๋กœ ์ด๋™
const scrollToBottom = () => {
// ์ฑ„ํŒ… ์˜์—ญ์„ ๋„˜์–ด์„œ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋งจ ์•„๋ž˜๋กœ ์Šคํฌ๋กค
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior: 'smooth', // ๋ถ€๋“œ๋Ÿฌ์šด ์Šคํฌ๋กค
});

// ์ฑ„ํŒ… ์˜์—ญ ๋งจ ์•„๋ž˜๋กœ ์Šคํฌ๋กค
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
};
useEffect(() => {
if (!isLoading) {
scrollToBottom();
}
}, [selectedStadium, selectedCategories, responseGuideDataList]);



// ์ฒ˜์Œ์— ๋ณด์—ฌ์ค„ ์ปจํ…์ธ 
const renderInitialMessage = () => {
return (
<>
{/* ๋ฃจํ‚ค ์‚ฌ์šฉ ์„ค๋ช…์„œ */}
<RookieImageMessage imgIcon={chatbotManualIcon} />

{/* ์Šคํƒ€๋””์›€ ์„ ํƒ์ฐฝ */}
<StadiumSelection stadiums={stadiumList} onSelect={handleStadiumSelect} />
</>
);
}


return (
<div>
<div>
{/* 1. ํ—ค๋”๋ฐ” */}
<BackLogoBar />
</div>

<div className="flex justify-center items-center h-full min-h-screen bg-grayscale-10 mt-[55px] ">
<div className="flex flex-col h-full max-w-[500px] w-full bg-grayscale-10">
{/* ์ฑ„ํŒ… ์˜์—ญ */}
<div
ref={chatContainerRef}
className="flex-1 px-3 py-4 h-full overflow-y-auto mb-10"
>
<Chatting/>

{/* 2. ์˜ค๋Š˜ ๋‚ ์งœ */}
<DateBanner date={new Date()} />

{/* 3. ์ฑ„ํŒ… ๋‚ด์—ญ */}
<div className="px-1">

{/* ์ฑ„ํŒ…1: ๊ตฌ์žฅ ์„ ํƒ, ๋ฃจํ‚ค ์‹œ์ž‘ ์ธ์‚ฌ๋ง, ํ•„์ˆ˜ ์ถœ๋ ฅ */}
{showInitialMessages && (
<RookieChat
contentList={[
{
type: "textListWithTail",
content: questionCategories.greetings
},
{
type: "component",
content: renderInitialMessage()
}
]}
/>
)}



{/* ์•ผ๊ตฌ์žฅ ์„ ํƒ์‹œ */}
{selectedStadium && (
<>
{/* ์ฑ„ํŒ…2: ์‚ฌ์šฉ์ž ๋‹ต๋ณ€, ํ•„์ˆ˜ ์ถœ๋ ฅ */}
<UserChat messageList={[selectedStadium]}/>

{/* ์ฑ„ํŒ…3: */}
{/* ์ฒซ ๋ฒˆ์งธ ๋‚ด์šฉ๋ฌผ์€ ๊ผฌ๋ž‘์ง€ ๋งํ’์„ ์— ์ถœ๋ ฅ */}
{/* ๋‘ ๋ฒˆ์งธ ๋‚ด์šฉ๋ฌผ์€ ์ผ๋ฐ˜ ๋งํ’์„ ์— ์ถœ๋ ฅ */}
<RookieChat
contentList={[
{
type: "textListWithTail",
content: [`'${selectedStadium}'์„(๋ฅผ) ์„ ํƒํ•˜์…จ๊ตฐ์š”!๐Ÿ˜`]
},

{
type: "textList",
content: questionCategories.baseballCategories.userMessage
}
]}
/>
</>
)}

{/* ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์‹œ ๋ฐฐ์—ด์— ์ €์žฅ ๋ฐ ์ˆœ์ฐจ ์ถœ๋ ฅ */}
{selectedStadium && selectedCategories.map((categoryFrontName, index) => (
<div key={index}>
<>
{/* ์‚ฌ์šฉ์ž ๋‹ต๋ณ€ ์ถœ๋ ฅ */}
<UserChat messageList={[categoryFrontName]}/>

{/* ์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•œ ์ฑ—๋ด‡ ์‘๋‹ต ์ถœ๋ ฅ */}
<RookieChat
contentList={[
{
type: "component",
content: <CategoryChat stadiumName={selectedStadium} categoryKey={index} categoryFrontName={categoryFrontName} onGuideResponseUpdate={handleGuideResponseUpdate} />
}
]}
/>


{/* ์„œ๋ธŒ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์‹œ ์ˆœ์ฐจ ์ถœ๋ ฅ */}
{/* Guide API ๋‹ต๋ณ€ ์ถœ๋ ฅ: ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์—๋งŒ ๋งคํ•‘๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์ถœ๋ ฅ */}
{responseGuideDataList
.filter((responseGuideData) => responseGuideData.categoryNumber === index) // ํ˜„์žฌ ์นดํ…Œ๊ณ ๋ฆฌ์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง
.map((responseGuideData, responseIndex) => (
<div key={responseIndex}>
<UserChat messageList={[responseGuideData.categoryName + " โ–ถ๏ธŽ " + responseGuideData.subCategoryName]}/>

{/* Guide API ๋‹ต๋ณ€ ์ถœ๋ ฅ: ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์—๋งŒ ๋งคํ•‘๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์ถœ๋ ฅ */}
<div className="py-2">
{renderGuideAnswerData(responseGuideData)}
</div>
</div>
))}
</>
</div>
))}

</div>
</div>


{/* 4. ์ฑ„ํŒ… ์ž…๋ ฅ์ฐฝ */}
<ChatbotInputField isStadiumSelected={isStadiumSelected} onSelect={handleCategorySelect} onClovaResponseUpdate={handleClovaResponseUpdate}/>
</div>
</div>
</div>
);
};

export default Chatbot;
Loading

0 comments on commit 568f1d7

Please sign in to comment.