From 492ffb3ccc756bbea18b23bb1f7ba84864d3e075 Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:08:01 +0900 Subject: [PATCH 1/8] =?UTF-8?q?:sparkles:=20feat:=20=ED=94=84=EB=A1=AC?= =?UTF-8?q?=ED=94=84=ED=8A=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chatting/components/ChattingMain.jsx | 14 ++++++++++++-- src/util/localStorage.js | 9 +++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/Chatting/components/ChattingMain.jsx b/src/components/Chatting/components/ChattingMain.jsx index 31178df..a13a4f8 100644 --- a/src/components/Chatting/components/ChattingMain.jsx +++ b/src/components/Chatting/components/ChattingMain.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, {useEffect, useState} from "react"; import { useNavigate } from "react-router-dom"; import { H1 } from "../../../styles/font-styles"; import styles from "./ChattingMain.module.css"; @@ -11,16 +11,22 @@ import { useSetRecoilState } from "recoil"; import { useChattingRoomHooks } from "../../../api/chatting/chatting"; import { setLocalPromptMethod } from "../../../util/localStorage"; import { t } from "i18next"; +import CreatePromptModal from "../../SideBar/components/Prompt/Modal/CreatePromptModal"; function ChattingMain() { const navigate = useNavigate(); const setPromptMethod = useSetRecoilState(promptMethodState); const { getChattingRoomList } = useChattingRoomHooks(); const userName = localStorage.getItem("userName"); + const [isPromptModalOpen, setIsPromptModalOpen] = useState(false); const handlePromptCreateClick = (type) => { + setIsPromptModalOpen(true); setPromptMethod(type); setLocalPromptMethod(type); - navigate(`/promptMaking/`); + }; + + const closePromptModal = () => { + setIsPromptModalOpen(false); }; useEffect(() => { @@ -54,6 +60,10 @@ function ChattingMain() { onClick={() => handlePromptCreateClick("Free")} /> + ); } diff --git a/src/util/localStorage.js b/src/util/localStorage.js index 680c7fd..3e8110c 100644 --- a/src/util/localStorage.js +++ b/src/util/localStorage.js @@ -33,3 +33,12 @@ export const getIsFirstVisited = () => { export const setIsFirstVisited = (isFirstVisit) => { localStorage.setItem("hasVisited", isFirstVisit.toString()); }; + +export const setLocalPromptCategory = (type) => { + localStorage.setItem("LocalPromptCategory", type); +}; + +export const getLocalPromptCategory = () => { + const LocalPromptCategory = localStorage.getItem("LocalPromptCategory"); + return LocalPromptCategory; +}; From a4b85414b8502c132a844ecc049db5fa7d4c86f8 Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:08:16 +0900 Subject: [PATCH 2/8] =?UTF-8?q?:sparkles:=20feat:=20=ED=94=84=EB=A1=AC?= =?UTF-8?q?=ED=94=84=ED=8A=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Prompt/Modal/CreatePromptModal.jsx | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx index 8c0e05e..40e0d62 100644 --- a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx +++ b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx @@ -7,30 +7,33 @@ import characterIcon from "../../../../../assets/images/characterIcon.svg"; import taskIcon from "../../../../../assets/images/taskIcon.svg"; import freeIcon from "../../../../../assets/images/freeIcon.svg"; import { useNavigate } from "react-router-dom"; -import { useSetRecoilState } from "recoil"; +import {useRecoilState} from "recoil"; import { promptMethodState } from "../../../../../recoil/prompt/promptRecoilState"; -import { setLocalPromptMethod } from "../../../../../util/localStorage"; -import { H5 } from "../../../../../styles/font-styles"; +import {setLocalPromptCategory, setLocalPromptMethod} from "../../../../../util/localStorage"; +import {B5, H5} from "../../../../../styles/font-styles"; import { t } from "i18next"; function CreatePromptModal({ isOpen, onClose }) { const navigate = useNavigate(); - const setPromptMethod = useSetRecoilState(promptMethodState); - const [selectedMethod, setSelectedMethod] = useState(null); + const [promptMethod, setPromptMethod] = useRecoilState(promptMethodState); + + const allCategories = ["IT", "게임", "글쓰기", "건강", "교육", "예술", "기타"]; + const [promptCategory, setPromptCategory] = useState("IT"); + if (!isOpen) return null; const handleCreateClick = () => { - if (selectedMethod) { - setPromptMethod(selectedMethod); - setLocalPromptMethod(selectedMethod); + if (promptMethod) { + setLocalPromptCategory(promptCategory); + setLocalPromptMethod(promptMethod); navigate(`/promptMaking/`); onClose(); } }; const handleMethodClick = (method) => { - setSelectedMethod(method); + setPromptMethod(method); }; return ( @@ -46,23 +49,51 @@ function CreatePromptModal({ isOpen, onClose }) { handleMethodClick("Character")} /> handleMethodClick("Task/Research")} /> handleMethodClick("Free")} /> +
+ 카테고리 +
+
    + {allCategories.map((category) => ( +
  • setPromptCategory(category)} + className={`${styles.option} ${ + category === promptCategory + ? styles.active + : styles.none + }`} + > + + {category} + +
  • + ))} +
+
+
Date: Fri, 10 Jan 2025 15:08:20 +0900 Subject: [PATCH 3/8] =?UTF-8?q?:sparkles:=20feat:=20=ED=94=84=EB=A1=AC?= =?UTF-8?q?=ED=94=84=ED=8A=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Prompt/Modal/CreatePromptModal.module.css | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.module.css b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.module.css index 8faffb2..faf9bc7 100644 --- a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.module.css +++ b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.module.css @@ -15,3 +15,34 @@ align-items: center; gap: 20px; } + +.select { + width: 100%; +} + +.options { + display: flex; + flex-wrap: wrap; + padding: 0; + margin: 0; + list-style: none; + gap: 10px; +} + +.option { + list-style-type: none; + padding: 6px 12px; + border-radius: 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.options .active { + background-color: var(--block-main-color); + border: 1px solid var(--block-main-color); +} + +.options .none { + background-color: var(--white); + border: 1px solid var(--color-gray5); +} From 49229ac8a82082117b815bfd9a4eff35bc1df129 Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:10:01 +0900 Subject: [PATCH 4/8] =?UTF-8?q?:sparkles:=20feat:=20ai=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=EC=B6=94=EC=B2=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/prompt/prompt.js | 121 +++++++++++++- .../PromptMakingSideBar.jsx | 137 ++------------- .../components/AiBlockSection.jsx | 141 ++++++++++++++++ .../components/AiBlockSection.module.css | 86 ++++++++++ .../components/BlockSection.jsx | 131 +++++++++++++++ .../components/BlockSection.module.css | 79 +++++++++ src/hooks/promptHook/usePromptMaking.jsx | 156 ++++++++++++++++-- src/locales/en/translation.json | 3 +- src/locales/ko/translation.json | 3 +- src/recoil/prompt/promptRecoilState.js | 22 +++ 10 files changed, 739 insertions(+), 140 deletions(-) create mode 100644 src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx create mode 100644 src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.module.css create mode 100644 src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx create mode 100644 src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.module.css diff --git a/src/api/prompt/prompt.js b/src/api/prompt/prompt.js index be07cd1..2282ba5 100644 --- a/src/api/prompt/prompt.js +++ b/src/api/prompt/prompt.js @@ -2,6 +2,7 @@ import { sendRequest } from "../request"; import { aiChatInstance, blockInstance, promptInstance } from "../instance"; import { activeBlocksState, + activeAiBlocksState, // activeCategoryState, availableCategoriesState, blockDetailsState, @@ -10,7 +11,7 @@ import { categoryBlockShapesState, activeCategoryState, promptEvaluationState, - promptEvaluationErrorState, + promptEvaluationErrorState, fetchAiBlocksState, } from "../../recoil/prompt/promptRecoilState"; import { useRecoilState, useSetRecoilState } from "recoil"; @@ -19,12 +20,14 @@ export const usePromptHook = () => { const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); const setActiveBlocks = useSetRecoilState(activeBlocksState); + const setActiveAiBlocks = useSetRecoilState(activeAiBlocksState); const setCombinations = useSetRecoilState(combinationsState); const setCategoryColors = useSetRecoilState(categoryColorsState); const setBlockDetails = useSetRecoilState(blockDetailsState); const setCategoryBlockShapes = useSetRecoilState(categoryBlockShapesState); const setEvaluation = useSetRecoilState(promptEvaluationState); const setEvaluationError = useSetRecoilState(promptEvaluationErrorState); + const setFetchAiBlocksState = useSetRecoilState(fetchAiBlocksState); // 새로운 함수: API 데이터로부터 프롬프트 구조 갱신 const updatePromptStructureFromApiData = (apiData) => { if ( @@ -128,6 +131,106 @@ export const usePromptHook = () => { await updatePromptStructureFromApiData(response.data); }; + const updateAiPromptStructureFromApiData = (apiData) => { + if ( + !apiData.responseDto || + !Array.isArray(apiData.responseDto.selectBlock) + ) { + console.error("Unexpected API response structure:", apiData); + return; + } + + const blocks = apiData.responseDto.selectBlock; + blocks.forEach((block, index) => { + if (!block.blockValue) { + console.warn("Block missing blockValue:", block); + } + if (!block.blockId) { + block.blockId = 10000+index; + console.warn(`Generated blockId for block:`, block); + } + }); + + console.log("blocks: ", blocks); + // 카테고리 추출 및 중복 제거 + const categories = [ + ...new Set(blocks.map((block) => block.blockCategory)), + ]; + console.log("categories: ", categories); + + // 블록을 카테고리별로 그룹화 + const groupedBlocks = categories.reduce((acc, category) => { + acc[category] = blocks.filter( + (block) => block.blockCategory === category, + ); + return acc; + }, {}); + console.log("groupedBlocks: ", groupedBlocks); + + // 색상 생성 (간단한 예시, 실제로는 더 복잡한 로직이 필요할 수 있습니다) + const colors = categories.reduce((acc, category, index) => { + const predefinedColors = [ + "var(--block-main-color)", + "var(--block-purple)", + "var(--block-pink)", + "var(--block-red)", + "var(--block-orange)", + "var(--block-green)", + "var(--block-blue)", + ]; + acc[category] = predefinedColors[index % predefinedColors.length]; + return acc; + }, {}); + console.log("colors: ", colors); + // 블록 모양 정의 + //const predefinedShapes = [1, 2, 3, 4, 5, 6, 7]; + + // 블록 모양 정의 + // const shapes = categories.reduce((acc, category, index) => { + // acc[category] = predefinedShapes[index % predefinedShapes.length]; + // return acc; + // }, {}); + // 상태 업데이트 + // 1. 카테고리 설정 + //setAiAvailableCategories(categories); + // 2. 카테고리 중 첫번째로 active되게끔 설정 + //TODO- 에러나면 무조건 여기임 -QA 이후 확인 + // 2. activeCategory가 없거나 categories에 없는 경우에만 새로 설정 + // if (!aiActiveCategory || !categories.includes(aiActiveCategory)) { + // setAiActiveCategory(categories[0] || null); + // } + + // 3. 모든 카테고리들에 해당하는 블록들을 설정 + const activeBlocksData = Object.fromEntries( + categories.map((category) => [ + category, + (groupedBlocks[category] || []).map((block) => block.blockId), + ]), + ); + console.log(activeBlocksData); + // 3.5 active된 block들을 setting해준다. + setActiveAiBlocks(activeBlocksData); + + // 7. 블럭들의 detail들을 추가 할당한다. + setBlockDetails((prevBlockDetails) => { + const newBlocks = Object.fromEntries(blocks.map((block) => [block.blockId, block])); + return { ...prevBlockDetails, ...newBlocks }; + }); + }; + + const fetchAiBlocks = async (promptMethod, promptCategory) => { + const params = { + promptMethod: promptMethod, + promptCategory: promptCategory, + }; + + const response = await sendRequest(aiChatInstance, "get", `/recommend`, { + params, + }); + await updateAiPromptStructureFromApiData(response.data); + setFetchAiBlocksState(true); + }; + const makeBlock = async ( blockValue, blockDescription, @@ -187,11 +290,27 @@ export const usePromptHook = () => { return response; }; + const userHistory = async (userHistory, promptMethod, promptCategory) => { + const response = await sendRequest( + promptInstance, + "post", + `/history`, + { + promptHistory: userHistory, + promptMethod: promptMethod, + promptCategory: promptCategory + } + ); + return response; + } + return { fetchBlocks, makeBlock, savePrompt, deleteBlock, evaluatePrompt, + fetchAiBlocks, + userHistory, }; }; diff --git a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx index 00c7842..1138e73 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx @@ -1,44 +1,19 @@ -import React, { useEffect, useState } from "react"; -import { useRecoilState, useRecoilValue } from "recoil"; -import { Droppable, Draggable } from "react-beautiful-dnd"; +import React, {useEffect} from "react"; import styles from "./PromptMakingSidebar.module.css"; -import { H4, B5 } from "../../../styles/font-styles"; -import PromptValueBlock from "../../common/Prompt/PromptValueBlock"; -import { - activeBlocksState, - activeCategoryState, - availableCategoriesState, - categoryColorsState, - blockDetailsState, - categoryBlockShapesState, -} from "../../../recoil/prompt/promptRecoilState"; +import { H4 } from "../../../styles/font-styles"; import logo from "../../../assets/logos/promaLogoSmall.svg"; -import CreateBlockModal from "./CreateBlockModal"; -import { usePromptHook } from "../../../api/prompt/prompt"; -import { getLocalPromptMethod } from "../../../util/localStorage"; -import { t } from "i18next"; import { Link } from "react-router-dom"; +import AiBlockSection from "./components/AiBlockSection"; +import BlockSection from "./components/BlockSection"; +import {useRecoilState, useSetRecoilState} from "recoil"; +import {userHistoryState} from "../../../recoil/prompt/promptRecoilState"; const PromptMakingSidebar = () => { - const [activeCategory, setActiveCategory] = - useRecoilState(activeCategoryState); - const activeBlocks = useRecoilValue(activeBlocksState); - const categories = useRecoilValue(availableCategoriesState); - const categoryColors = useRecoilValue(categoryColorsState); - const categoryBlockShapes = useRecoilValue(categoryBlockShapesState); - const blockDetails = useRecoilValue(blockDetailsState); - const [isModalOpen, setIsModalOpen] = useState(false); - const localPromptMethod = getLocalPromptMethod(); - const { fetchBlocks } = usePromptHook(); - - const getActiveColor = () => { - return categoryColors[activeCategory] || "purple"; - }; + const setUserHistory = useSetRecoilState(userHistoryState); + //userhistory 초기화 useEffect(() => { - fetchBlocks(localPromptMethod); - console.log("blocks 불러오기"); - // eslint-disable-next-line react-hooks/exhaustive-deps + setUserHistory({}); }, []); return ( @@ -50,95 +25,19 @@ const PromptMakingSidebar = () => { className={styles.promaLogo} /> +

PROMA prompt

-
- {categories.map((category) => ( -
setActiveCategory(category)} - style={{ - "--category-color": categoryColors[category], - "--category-active-color": `${categoryColors[category]}33`, - }} - > - {category} -
- ))} -
-
- - {(provided) => ( -
- {activeBlocks[activeCategory]?.map( - (blockId, index) => { - const block = blockDetails[blockId]; - return ( - - {(provided) => ( -
- -
- )} -
- ); - }, - )} - {provided.placeholder} -
- )} -
- -
- setIsModalOpen(false)} - categories={categories} - /> + +
+ +
+

AI recommend

+
+
+
); diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx new file mode 100644 index 0000000..7de7ef6 --- /dev/null +++ b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx @@ -0,0 +1,141 @@ +import React, {useEffect, useState} from 'react'; +import styles from "./AiBlockSection.module.css"; +import {B4, B5} from "../../../../styles/font-styles"; +import {Draggable, Droppable} from "react-beautiful-dnd"; +import PromptValueBlock from "../../../common/Prompt/PromptValueBlock"; +import {t} from "i18next"; +import {useRecoilState, useRecoilValue} from "recoil"; +import { + activeAiBlocksState, activeBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, + categoryBlockShapesState, + categoryColorsState, fetchAiBlocksState, +} from "../../../../recoil/prompt/promptRecoilState"; +import {getLocalPromptCategory, getLocalPromptMethod} from "../../../../util/localStorage"; +import {usePromptHook} from "../../../../api/prompt/prompt"; +import promaAnimation from "../../../../assets/animation/promaAnimation.json"; +import Lottie from "react-lottie"; + +function AiBlockSection() { + const categoryColors = useRecoilValue(categoryColorsState); + const categoryBlockShapes = useRecoilValue(categoryBlockShapesState); + const localPromptMethod = getLocalPromptMethod(); + const localPromptCategory = getLocalPromptCategory(); + const { fetchAiBlocks } = usePromptHook(); + const [isLoading,setFetchAiBlocksState] = useRecoilState(fetchAiBlocksState); + const activeBlocksData = useRecoilValue(activeBlocksState); + const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); + const activeBlocks = useRecoilValue(activeAiBlocksState); + const categories = useRecoilValue(availableCategoriesState); + const blockDetails = useRecoilValue(blockDetailsState); + const defaultOptions = { + loop: true, + autoplay: true, + animationData: promaAnimation, + rendererSettings: { + preserveAspectRatio: "xMidYMid slice", + }, + }; + + const getActiveColor = () => { + return categoryColors[activeCategory] || "purple"; + }; + + useEffect(() => { + setFetchAiBlocksState(false); + fetchAiBlocks(localPromptMethod, localPromptCategory); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+
+
+ {categories.map((category) => ( +
setActiveCategory(category)} + style={{ + "--category-color": categoryColors[category], + "--category-active-color": `${categoryColors[category]}33`, + }} + > + {category} +
+ ))} +
+ {(isLoading) ?
+ + {(provided) => ( +
+ {activeBlocks[activeCategory]?.map( + (blockId, index) => { + const block = blockDetails[blockId]; + return ( + + {(provided) => ( +
+ +
+ )} +
+ ); + }, + )} + {provided.placeholder} +
+ )} +
+
: +
+ + {t(`promptMaking.aiRecommend`)} + + +
+ } +
+
+ ); +} + +export default AiBlockSection; diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.module.css b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.module.css new file mode 100644 index 0000000..7103134 --- /dev/null +++ b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.module.css @@ -0,0 +1,86 @@ +.sidebar { + display: flex; + width: 100%; + height: auto; + background-color: var(--white); + border-radius: 15px; + overflow: hidden; +} + +.categories { + width: auto; + display: flex; + flex-direction: column; + padding: 25px 0; + background-color: var(--white); +} + +.category { + padding: 15px 10px; + padding-right: 10px; + border-top-right-radius: 50px; + border-bottom-right-radius: 50px; + cursor: pointer; + text-orientation: upright; + word-break: break-word; /* 긴 단어를 줄바꿈 */ + transition: all 0.3s; + font-size: 14px; + background-color: var(--category-color); + white-space: nowrap; + z-index: 10; +} + +.category.active { + margin-right: -15px; + padding-right: 25px; + padding-left: 25px; +} + +.blocksContainer { + flex-grow: 1; + display: flex; + flex-direction: column; + padding: 25px; + background-color: #fff; +} + +.blocks { + overflow-y: auto; + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 10px; +} + +.block { + cursor: move; + opacity: 0.9; + transition: opacity 0.3s; +} + +.block:hover { + opacity: 1; +} + +.addButton { + width: auto; + padding: 8px 30px; + background-color: var(--white); + border: none; + border-radius: 20px; + box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2); + cursor: pointer; + margin: 10px; + align-self: center; +} + +.addButton:hover { + opacity: 0.9; +} + +.loadingSection { + flex-grow: 1; + display: flex; + flex-direction: column; + padding: 25px; +} diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx new file mode 100644 index 0000000..688f4fc --- /dev/null +++ b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx @@ -0,0 +1,131 @@ +import React, {useEffect, useState} from 'react'; +import styles from "./BlockSection.module.css"; +import {B5} from "../../../../styles/font-styles"; +import {Draggable, Droppable} from "react-beautiful-dnd"; +import PromptValueBlock from "../../../common/Prompt/PromptValueBlock"; +import {t} from "i18next"; +import CreateBlockModal from "../CreateBlockModal"; +import {useRecoilState, useRecoilValue} from "recoil"; +import { + activeBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, + categoryBlockShapesState, + categoryColorsState, +} from "../../../../recoil/prompt/promptRecoilState"; +import {getLocalPromptMethod} from "../../../../util/localStorage"; +import {usePromptHook} from "../../../../api/prompt/prompt"; + +function BlockSection() { + const categoryColors = useRecoilValue(categoryColorsState); + const categoryBlockShapes = useRecoilValue(categoryBlockShapesState); + const [isModalOpen, setIsModalOpen] = useState(false); + const localPromptMethod = getLocalPromptMethod(); + const { fetchBlocks } = usePromptHook(); + + const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); + const activeBlocks = useRecoilValue(activeBlocksState); + const categories = useRecoilValue(availableCategoriesState); + const blockDetails = useRecoilValue(blockDetailsState); + + const getActiveColor = () => { + return categoryColors[activeCategory] || "purple"; + }; + + useEffect(() => { + fetchBlocks(localPromptMethod); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+
+
+ {categories.map((category) => ( +
setActiveCategory(category)} + style={{ + "--category-color": categoryColors[category], + "--category-active-color": `${categoryColors[category]}33`, + }} + > + {category} +
+ ))} +
+
+ + {(provided) => ( +
+ {activeBlocks[activeCategory]?.map( + (blockId, index) => { + const block = blockDetails[blockId]; + return ( + + {(provided) => ( +
+ +
+ )} +
+ ); + }, + )} + {provided.placeholder} +
+ )} +
+ +
+
+ setIsModalOpen(false)} + categories={categories} + /> +
+ ); +} + +export default BlockSection; diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.module.css b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.module.css new file mode 100644 index 0000000..ec6aa42 --- /dev/null +++ b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.module.css @@ -0,0 +1,79 @@ +.sidebar { + display: flex; + width: 100%; + height: auto; + background-color: var(--white); + border-radius: 15px; + overflow: hidden; +} + +.categories { + width: auto; + display: flex; + flex-direction: column; + padding: 25px 0; + background-color: var(--white); +} + +.category { + padding: 15px 10px; + padding-right: 10px; + border-top-right-radius: 50px; + border-bottom-right-radius: 50px; + cursor: pointer; + text-orientation: upright; + word-break: break-word; /* 긴 단어를 줄바꿈 */ + transition: all 0.3s; + font-size: 14px; + background-color: var(--category-color); + white-space: nowrap; + z-index: 10; +} + +.category.active { + margin-right: -15px; + padding-right: 25px; + padding-left: 25px; +} + +.blocksContainer { + flex-grow: 1; + display: flex; + flex-direction: column; + padding: 25px; + background-color: #fff; +} + +.blocks { + overflow-y: auto; + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 10px; +} + +.block { + cursor: move; + opacity: 0.9; + transition: opacity 0.3s; +} + +.block:hover { + opacity: 1; +} + +.addButton { + width: auto; + padding: 8px 30px; + background-color: var(--white); + border: none; + border-radius: 20px; + box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2); + cursor: pointer; + margin: 10px; + align-self: center; +} + +.addButton:hover { + opacity: 0.9; +} diff --git a/src/hooks/promptHook/usePromptMaking.jsx b/src/hooks/promptHook/usePromptMaking.jsx index afca52e..a8f0514 100644 --- a/src/hooks/promptHook/usePromptMaking.jsx +++ b/src/hooks/promptHook/usePromptMaking.jsx @@ -1,10 +1,11 @@ -import { useRecoilState, useRecoilValue } from "recoil"; +import {useRecoilState, useRecoilValue} from "recoil"; import { enqueueSnackbar } from "notistack"; import { activeBlocksState, combinationsState, blockDetailsState, - activeCategoryState, + activeCategoryState, activeAiBlocksState, + userHistoryState } from "../../recoil/prompt/promptRecoilState"; import { useEffect } from "react"; import { t } from "i18next"; @@ -13,17 +14,35 @@ import { usePromptHook } from "../../api/prompt/prompt"; export const usePromptMaking = () => { const [combinations, setCombinations] = useRecoilState(combinationsState); const [activeBlocks, setActiveBlocks] = useRecoilState(activeBlocksState); + const [activeAiBlocks, setActiveAiBlocks] = useRecoilState(activeAiBlocksState); + const [userHistory, setUserHistoryState] = useRecoilState(userHistoryState); const blockDetails = useRecoilValue(blockDetailsState); const activeCategory = useRecoilValue(activeCategoryState); const { deleteBlock } = usePromptHook(); + // useEffect(() => { + // const newActiveBlocks = { ...activeBlocks }; + // for (const category in newActiveBlocks) { + // newActiveBlocks[category] = newActiveBlocks[category]?.filter( + // (blockId) => combinations[category] !== blockId, + // ); + // } + // setActiveBlocks(newActiveBlocks); + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [combinations]); + useEffect(() => { const newActiveBlocks = { ...activeBlocks }; + const newActiveAiBlocks = { ...activeAiBlocks }; for (const category in newActiveBlocks) { newActiveBlocks[category] = newActiveBlocks[category]?.filter( (blockId) => combinations[category] !== blockId, ); + newActiveAiBlocks[category] = newActiveAiBlocks[category]?.filter( + (blockId) => combinations[category] !== blockId, + ); } setActiveBlocks(newActiveBlocks); + setActiveAiBlocks(newActiveAiBlocks); // eslint-disable-next-line react-hooks/exhaustive-deps }, [combinations]); @@ -42,30 +61,66 @@ export const usePromptMaking = () => { return; } + // if (destination.droppableId === "deleteArea") { + // console.log(draggableId); + // handleDeleteBlock( + // source.droppableId, + // numericBlockId, + // isBlockDefault, + // ); + // } else if ( + // source.droppableId === "sidebar" && + // destination.droppableId !== "sidebar" + // ) { + // handleSidebarToCombinationArea( + // destination.droppableId, + // numericBlockId, + // blockCategory, + // ); + // } else if ( + // source.droppableId !== "sidebar" && + // destination.droppableId === "sidebar" + // ) { + // handleCombinationAreaToSidebar(source.droppableId, numericBlockId); + // } else if ( + // source.droppableId !== "sidebar" && + // destination.droppableId !== "sidebar" + // ) { + // handleWithinCombinationArea( + // source.droppableId, + // destination.droppableId, + // numericBlockId, + // ); + // } + if (destination.droppableId === "deleteArea") { - console.log(draggableId); handleDeleteBlock( source.droppableId, numericBlockId, isBlockDefault, ); } else if ( - source.droppableId === "sidebar" && - destination.droppableId !== "sidebar" + (source.droppableId === "sidebar" || source.droppableId === "sidebar_ai") && + destination.droppableId !== "sidebar" && + destination.droppableId !== "sidebar_ai" ) { handleSidebarToCombinationArea( destination.droppableId, numericBlockId, blockCategory, + source.droppableId, ); } else if ( source.droppableId !== "sidebar" && - destination.droppableId === "sidebar" + source.droppableId !== "sidebar_ai" && + (destination.droppableId === "sidebar" || destination.droppableId === "sidebar_ai") ) { - handleCombinationAreaToSidebar(source.droppableId, numericBlockId); + handleCombinationAreaToSidebar(source.droppableId, numericBlockId, destination.droppableId); } else if ( source.droppableId !== "sidebar" && - destination.droppableId !== "sidebar" + source.droppableId !== "sidebar_ai" && + destination.droppableId !== "sidebar" && + destination.droppableId !== "sidebar_ai" ) { handleWithinCombinationArea( source.droppableId, @@ -75,10 +130,39 @@ export const usePromptMaking = () => { } }; + // const handleSidebarToCombinationArea = ( + // category, + // blockId, + // blockCategory, + // ) => { + // if (category !== blockCategory) { + // enqueueSnackbar( + // `${t(`promptMaking.userPromptError`)} ${blockCategory} ${t(`promptMaking.userPromptError2`)}`, + // ); + // return; + // } + // + // setCombinations((prev) => ({ + // ...prev, + // [category]: blockId, + // })); + // + // setActiveBlocks((prev) => ({ + // ...prev, + // [category]: prev[category].filter((id) => id !== blockId), + // })); + // + // handleCombinationChange({ + // ...combinations, + // [category]: blockId, + // }); + // }; + const handleSidebarToCombinationArea = ( category, blockId, blockCategory, + sourceDroppableId, ) => { if (category !== blockCategory) { enqueueSnackbar( @@ -92,10 +176,26 @@ export const usePromptMaking = () => { [category]: blockId, })); - setActiveBlocks((prev) => ({ - ...prev, - [category]: prev[category].filter((id) => id !== blockId), - })); + setUserHistoryState((prev) => { + const prevEntries = typeof prev === 'string' ? prev.split('\n') : []; + const nextNumber = prevEntries.length + 1; + const description = blockDetails[blockId]?.blockDescription || 'Description not found'; + const newEntry = `${nextNumber}. ${category}에서 ${description}을 선택했습니다`; + return prevEntries.length > 0 ? `${prevEntries.join('\n')}\n${newEntry}` : newEntry; + }); + + + if (sourceDroppableId === "sidebar") { + setActiveBlocks((prev) => ({ + ...prev, + [category]: prev[category].filter((id) => id !== blockId), + })); + } else if (sourceDroppableId === "sidebar_ai") { + setActiveAiBlocks((prev) => ({ + ...prev, + [category]: prev[category].filter((id) => id !== blockId), + })); + } handleCombinationChange({ ...combinations, @@ -103,16 +203,35 @@ export const usePromptMaking = () => { }); }; - const handleCombinationAreaToSidebar = (category, blockId) => { + // const handleCombinationAreaToSidebar = (category, blockId) => { + // setCombinations((prev) => ({ + // ...prev, + // [category]: null, + // })); + // + // setActiveBlocks((prev) => ({ + // ...prev, + // [category]: [...prev[category], blockId], + // })); + // }; + + const handleCombinationAreaToSidebar = (category, blockId, destinationDroppableId) => { setCombinations((prev) => ({ ...prev, [category]: null, })); - setActiveBlocks((prev) => ({ - ...prev, - [category]: [...prev[category], blockId], - })); + if (destinationDroppableId === "sidebar") { + setActiveBlocks((prev) => ({ + ...prev, + [category]: [...prev[category], blockId], + })); + } else if (destinationDroppableId === "sidebar_ai") { + setActiveAiBlocks((prev) => ({ + ...prev, + [category]: [...prev[category], blockId], + })); + } }; const handleWithinCombinationArea = ( @@ -134,11 +253,12 @@ export const usePromptMaking = () => { const handleCombinationChange = (newCombinations) => { console.log("새로운 조합:", newCombinations); console.log("조합 시도"); + console.log(userHistory); // 여기에 조합 변경에 따른 추가 로직을 구현할 수 있습니다. }; const handleDeleteBlock = (sourceCategory, blockId, isDefault) => { - if (sourceCategory === "sidebar") { + if (sourceCategory === "sidebar" || sourceCategory === "sidebar_ai") { if (isDefault === "true") { // isDefault는 문자열로 전달될 수 있으므로 문자열 비교 console.log("Cannot delete default block"); diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 40ae43a..2789a52 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -64,7 +64,8 @@ "userPromptError3": "🚀 The migration between category is impossible! You can't move to ", "userPromptError4": "to", "blockDeleteFailed": "⚠️ Default Block can't be deleted", - "blockDeleted": "Block is completely deleted! " + "blockDeleted": "Block is completely deleted! ", + "aiRecommend" : "Ai is recommending blocks!" }, "introduce": { "introduceOne": "Prompt Engineering for Everyone", diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index e561877..5f55545 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -63,7 +63,8 @@ "userPromptError3": "🚀 카테고리 간 이동은 불가능합니다!", "userPromptError4": "에서 다음 카테고리로 이동 불가능합니다 :", "blockDeleteFailed": "⚠️ 디폴트 블록은 삭제가 불가능합니다.", - "blockDeleted": "블록 삭제가 완료되었습니다!" + "blockDeleted": "블록 삭제가 완료되었습니다!", + "aiRecommend" : "Ai가 블록을 추천중입니다!" }, "introduce": { "introduceOne": "모두를 위한 프롬프트 엔지니어링", diff --git a/src/recoil/prompt/promptRecoilState.js b/src/recoil/prompt/promptRecoilState.js index 831e357..6097ec4 100644 --- a/src/recoil/prompt/promptRecoilState.js +++ b/src/recoil/prompt/promptRecoilState.js @@ -125,6 +125,16 @@ export const activeBlocksState = atom({ ), }); +export const activeAiBlocksState = atom({ + key: "activeAiBlocksState", + default: Object.fromEntries( + Object.keys(initialBlocks).map((category) => [ + category, + initialBlocks[category].map((block) => block.blockId), + ]), + ), +}); + // 선택된 조합 export const combinationsState = atom({ key: "combinationsState", @@ -219,6 +229,18 @@ export const promptEvaluationErrorState = atom({ key: "promptEvaluationErrorState", default: null, }); + +export const fetchAiBlocksState = atom({ + key: "fetchAiBlocksState", + default: null, +}); + +export const userHistoryState = atom({ + key: "userHistoryState", + default: "", +}); + + // // 각종 상태 초기화 함수 // export const useResetCategoriesOnTypeChange = () => { // return useRecoilCallback(({ snapshot, set }) => async () => { From 70e385415541e571b34a38242ab86521fb65e4da Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:10:13 +0900 Subject: [PATCH 5/8] =?UTF-8?q?:sparkles:=20feat:=20history=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CombinationArea/SavePromptModal.jsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx b/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx index c80fd2c..b2e1773 100644 --- a/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx +++ b/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx @@ -6,7 +6,7 @@ import ModalButton from "../../common/ModalButton"; import { promptMethodState, promptListState, - blockDetailsState, + blockDetailsState, userHistoryState, } from "../../../recoil/prompt/promptRecoilState"; import { useRecoilValue } from "recoil"; import RefinedPromptText from "../FinalPromptArea/RefinedPromptText"; @@ -14,6 +14,7 @@ import { usePromptHook } from "../../../api/prompt/prompt"; import { useChattingRoomHooks } from "../../../api/chatting/chatting"; import ModalContainer from "../../common/ModalContainer"; import { t } from "i18next"; +import {getLocalPromptCategory} from "../../../util/localStorage"; const allCategories = ["IT", "게임", "글쓰기", "건강", "교육", "예술", "기타"]; @@ -30,10 +31,12 @@ const SavePromptModal = ({ const prompt = promptList.find((p) => p.promptId === promptId); const [promptTitle, setPromptTitle] = useState(""); const [promptDescription, setPromptDescription] = useState(""); - const [promptCategory, setPromptCategory] = useState("IT"); + const localPromptCategory = getLocalPromptCategory(); + const [promptCategory, setPromptCategory] = useState(localPromptCategory); const promptMethod = useRecoilValue(promptMethodState); + const userHistoryValue = useRecoilValue(userHistoryState); - const { savePrompt } = usePromptHook(); + const { savePrompt, userHistory } = usePromptHook(); const { fetchPromptList, patchPromptBlock, patchPromptInfo } = useChattingRoomHooks(); @@ -46,7 +49,7 @@ const SavePromptModal = ({ // Reset state if no prompt is found setPromptTitle(""); setPromptDescription(""); - setPromptCategory("IT"); + setPromptCategory(localPromptCategory); } }, [prompt]); @@ -65,7 +68,7 @@ const SavePromptModal = ({ console.log("listPromptAtom:", listPromptAtom); console.log("promptPreview", promptPreview); patchPromptBlock( - promptId, + promptId, listPromptAtom, promptPreview, ); @@ -90,6 +93,18 @@ const SavePromptModal = ({ listPromptAtom, ); + userHistory( + userHistoryValue, + promptMethod, + promptCategory + ); + + console.log({ + userHistoryValue, + promptMethod, + promptCategory + }); + console.log({ promptTitle, promptDescription, @@ -155,7 +170,7 @@ const SavePromptModal = ({ {allCategories.map((category) => (
  • setPromptCategory(category)} + onClick={(e) => setPromptCategory(category)} className={`${styles.option} ${ category === promptCategory ? styles.active From 58092d482cf149e38c54160c0e9949e1955a7b50 Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:20:42 +0900 Subject: [PATCH 6/8] =?UTF-8?q?:ambulance:=20fix:=20CI/CD=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PromptMakingSideBar/components/AiBlockSection.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx index 7de7ef6..4902b04 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect} from 'react'; import styles from "./AiBlockSection.module.css"; import {B4, B5} from "../../../../styles/font-styles"; import {Draggable, Droppable} from "react-beautiful-dnd"; @@ -6,7 +6,7 @@ import PromptValueBlock from "../../../common/Prompt/PromptValueBlock"; import {t} from "i18next"; import {useRecoilState, useRecoilValue} from "recoil"; import { - activeAiBlocksState, activeBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, + activeAiBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, categoryBlockShapesState, categoryColorsState, fetchAiBlocksState, } from "../../../../recoil/prompt/promptRecoilState"; @@ -22,7 +22,6 @@ function AiBlockSection() { const localPromptCategory = getLocalPromptCategory(); const { fetchAiBlocks } = usePromptHook(); const [isLoading,setFetchAiBlocksState] = useRecoilState(fetchAiBlocksState); - const activeBlocksData = useRecoilValue(activeBlocksState); const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); const activeBlocks = useRecoilValue(activeAiBlocksState); const categories = useRecoilValue(availableCategoriesState); From 8a8b501087119b80d840609599f6b23fc6634a6e Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:33:34 +0900 Subject: [PATCH 7/8] =?UTF-8?q?:ambulance:=20fix:=20CI/CD=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx index 1138e73..f1f6839 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx @@ -5,7 +5,7 @@ import logo from "../../../assets/logos/promaLogoSmall.svg"; import { Link } from "react-router-dom"; import AiBlockSection from "./components/AiBlockSection"; import BlockSection from "./components/BlockSection"; -import {useRecoilState, useSetRecoilState} from "recoil"; +import {useSetRecoilState} from "recoil"; import {userHistoryState} from "../../../recoil/prompt/promptRecoilState"; const PromptMakingSidebar = () => { @@ -13,7 +13,8 @@ const PromptMakingSidebar = () => { //userhistory 초기화 useEffect(() => { - setUserHistory({}); + setUserHistory(""); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( From e5badec463acefb7400c5529fa85f5f3cefdcd8d Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 16:05:59 +0900 Subject: [PATCH 8/8] =?UTF-8?q?:ambulance:=20fix:=20CI/CD=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/prompt/prompt.js | 37 +++-- .../Chatting/components/ChattingMain.jsx | 4 +- .../CombinationArea/SavePromptModal.jsx | 26 ++-- .../PromptMakingSideBar.jsx | 6 +- .../components/AiBlockSection.jsx | 147 ++++++++++-------- .../components/BlockSection.jsx | 40 ++--- src/components/SideBar/SideBar.jsx | 2 +- .../Prompt/Modal/CreatePromptModal.jsx | 20 ++- src/hooks/promptHook/usePromptMaking.jsx | 40 +++-- src/recoil/prompt/promptRecoilState.js | 1 - src/util/localStorage.js | 12 +- 11 files changed, 186 insertions(+), 149 deletions(-) diff --git a/src/api/prompt/prompt.js b/src/api/prompt/prompt.js index 2282ba5..81d4e21 100644 --- a/src/api/prompt/prompt.js +++ b/src/api/prompt/prompt.js @@ -11,7 +11,8 @@ import { categoryBlockShapesState, activeCategoryState, promptEvaluationState, - promptEvaluationErrorState, fetchAiBlocksState, + promptEvaluationErrorState, + fetchAiBlocksState, } from "../../recoil/prompt/promptRecoilState"; import { useRecoilState, useSetRecoilState } from "recoil"; @@ -146,7 +147,7 @@ export const usePromptHook = () => { console.warn("Block missing blockValue:", block); } if (!block.blockId) { - block.blockId = 10000+index; + block.blockId = 10000 + index; console.warn(`Generated blockId for block:`, block); } }); @@ -213,7 +214,9 @@ export const usePromptHook = () => { // 7. 블럭들의 detail들을 추가 할당한다. setBlockDetails((prevBlockDetails) => { - const newBlocks = Object.fromEntries(blocks.map((block) => [block.blockId, block])); + const newBlocks = Object.fromEntries( + blocks.map((block) => [block.blockId, block]), + ); return { ...prevBlockDetails, ...newBlocks }; }); }; @@ -224,9 +227,14 @@ export const usePromptHook = () => { promptCategory: promptCategory, }; - const response = await sendRequest(aiChatInstance, "get", `/recommend`, { - params, - }); + const response = await sendRequest( + aiChatInstance, + "get", + `/recommend`, + { + params, + }, + ); await updateAiPromptStructureFromApiData(response.data); setFetchAiBlocksState(true); }; @@ -291,18 +299,13 @@ export const usePromptHook = () => { }; const userHistory = async (userHistory, promptMethod, promptCategory) => { - const response = await sendRequest( - promptInstance, - "post", - `/history`, - { - promptHistory: userHistory, - promptMethod: promptMethod, - promptCategory: promptCategory - } - ); + const response = await sendRequest(promptInstance, "post", `/history`, { + promptHistory: userHistory, + promptMethod: promptMethod, + promptCategory: promptCategory, + }); return response; - } + }; return { fetchBlocks, diff --git a/src/components/Chatting/components/ChattingMain.jsx b/src/components/Chatting/components/ChattingMain.jsx index a13a4f8..75cf6ae 100644 --- a/src/components/Chatting/components/ChattingMain.jsx +++ b/src/components/Chatting/components/ChattingMain.jsx @@ -1,5 +1,4 @@ -import React, {useEffect, useState} from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useEffect, useState } from "react"; import { H1 } from "../../../styles/font-styles"; import styles from "./ChattingMain.module.css"; import PromptCreateButton from "./Prompt/PromptCreateButton"; @@ -14,7 +13,6 @@ import { t } from "i18next"; import CreatePromptModal from "../../SideBar/components/Prompt/Modal/CreatePromptModal"; function ChattingMain() { - const navigate = useNavigate(); const setPromptMethod = useSetRecoilState(promptMethodState); const { getChattingRoomList } = useChattingRoomHooks(); const userName = localStorage.getItem("userName"); diff --git a/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx b/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx index b2e1773..f64a01f 100644 --- a/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx +++ b/src/components/PromptMaking/CombinationArea/SavePromptModal.jsx @@ -6,7 +6,8 @@ import ModalButton from "../../common/ModalButton"; import { promptMethodState, promptListState, - blockDetailsState, userHistoryState, + blockDetailsState, + userHistoryState, } from "../../../recoil/prompt/promptRecoilState"; import { useRecoilValue } from "recoil"; import RefinedPromptText from "../FinalPromptArea/RefinedPromptText"; @@ -14,7 +15,7 @@ import { usePromptHook } from "../../../api/prompt/prompt"; import { useChattingRoomHooks } from "../../../api/chatting/chatting"; import ModalContainer from "../../common/ModalContainer"; import { t } from "i18next"; -import {getLocalPromptCategory} from "../../../util/localStorage"; +import { getLocalPromptCategory } from "../../../util/localStorage"; const allCategories = ["IT", "게임", "글쓰기", "건강", "교육", "예술", "기타"]; @@ -51,6 +52,7 @@ const SavePromptModal = ({ setPromptDescription(""); setPromptCategory(localPromptCategory); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [prompt]); if (!isOpen) return null; @@ -67,11 +69,7 @@ const SavePromptModal = ({ const promptPreview = Object.values(refinedPromptParts).join(" "); console.log("listPromptAtom:", listPromptAtom); console.log("promptPreview", promptPreview); - patchPromptBlock( - promptId, - listPromptAtom, - promptPreview, - ); + patchPromptBlock(promptId, listPromptAtom, promptPreview); patchPromptInfo( promptId, promptTitle, @@ -93,16 +91,12 @@ const SavePromptModal = ({ listPromptAtom, ); - userHistory( - userHistoryValue, - promptMethod, - promptCategory - ); + userHistory(userHistoryValue, promptMethod, promptCategory); console.log({ userHistoryValue, promptMethod, - promptCategory + promptCategory, }); console.log({ @@ -135,9 +129,9 @@ const SavePromptModal = ({ onSubmit={handleSave} children > -
    -
    {t(`promptMaking.promptPreView`)}
    -
    +
    +
    {t(`promptMaking.promptPreView`)}
    +
    diff --git a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx index f1f6839..e9a9c5d 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/PromptMakingSideBar.jsx @@ -1,12 +1,12 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import styles from "./PromptMakingSidebar.module.css"; import { H4 } from "../../../styles/font-styles"; import logo from "../../../assets/logos/promaLogoSmall.svg"; import { Link } from "react-router-dom"; import AiBlockSection from "./components/AiBlockSection"; import BlockSection from "./components/BlockSection"; -import {useSetRecoilState} from "recoil"; -import {userHistoryState} from "../../../recoil/prompt/promptRecoilState"; +import { useSetRecoilState } from "recoil"; +import { userHistoryState } from "../../../recoil/prompt/promptRecoilState"; const PromptMakingSidebar = () => { const setUserHistory = useSetRecoilState(userHistoryState); diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx index 4902b04..457ccc7 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/components/AiBlockSection.jsx @@ -1,17 +1,24 @@ -import React, {useEffect} from 'react'; +import React, { useEffect } from "react"; import styles from "./AiBlockSection.module.css"; -import {B4, B5} from "../../../../styles/font-styles"; -import {Draggable, Droppable} from "react-beautiful-dnd"; +import { B4, B5 } from "../../../../styles/font-styles"; +import { Draggable, Droppable } from "react-beautiful-dnd"; import PromptValueBlock from "../../../common/Prompt/PromptValueBlock"; -import {t} from "i18next"; -import {useRecoilState, useRecoilValue} from "recoil"; +import { t } from "i18next"; +import { useRecoilState, useRecoilValue } from "recoil"; import { - activeAiBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, + activeAiBlocksState, + activeCategoryState, + availableCategoriesState, + blockDetailsState, categoryBlockShapesState, - categoryColorsState, fetchAiBlocksState, + categoryColorsState, + fetchAiBlocksState, } from "../../../../recoil/prompt/promptRecoilState"; -import {getLocalPromptCategory, getLocalPromptMethod} from "../../../../util/localStorage"; -import {usePromptHook} from "../../../../api/prompt/prompt"; +import { + getLocalPromptCategory, + getLocalPromptMethod, +} from "../../../../util/localStorage"; +import { usePromptHook } from "../../../../api/prompt/prompt"; import promaAnimation from "../../../../assets/animation/promaAnimation.json"; import Lottie from "react-lottie"; @@ -21,8 +28,10 @@ function AiBlockSection() { const localPromptMethod = getLocalPromptMethod(); const localPromptCategory = getLocalPromptCategory(); const { fetchAiBlocks } = usePromptHook(); - const [isLoading,setFetchAiBlocksState] = useRecoilState(fetchAiBlocksState); - const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); + const [isLoading, setFetchAiBlocksState] = + useRecoilState(fetchAiBlocksState); + const [activeCategory, setActiveCategory] = + useRecoilState(activeCategoryState); const activeBlocks = useRecoilValue(activeAiBlocksState); const categories = useRecoilValue(availableCategoriesState); const blockDetails = useRecoilValue(blockDetailsState); @@ -65,65 +74,69 @@ function AiBlockSection() {
    ))}
    - {(isLoading) ?
    - - {(provided) => ( -
    - {activeBlocks[activeCategory]?.map( - (blockId, index) => { - const block = blockDetails[blockId]; - return ( - - {(provided) => ( -
    - + + {(provided) => ( +
    + {activeBlocks[activeCategory]?.map( + (blockId, index) => { + const block = blockDetails[blockId]; + return ( + + {(provided) => ( +
    + -
    - )} -
    - ); - }, - )} - {provided.placeholder} -
    - )} -
    -
    : + } + value={ + block.blockValue + } + variant={ + categoryBlockShapes[ + activeCategory + ] + } + size="medium" + /> +
    + )} + + ); + }, + )} + {provided.placeholder} +
    + )} + + + ) : (
    - - {t(`promptMaking.aiRecommend`)} - + {t(`promptMaking.aiRecommend`)}
    - } + )} ); diff --git a/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx index 688f4fc..7df137a 100644 --- a/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx +++ b/src/components/PromptMaking/PromptMakingSideBar/components/BlockSection.jsx @@ -1,18 +1,21 @@ -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState } from "react"; import styles from "./BlockSection.module.css"; -import {B5} from "../../../../styles/font-styles"; -import {Draggable, Droppable} from "react-beautiful-dnd"; +import { B5 } from "../../../../styles/font-styles"; +import { Draggable, Droppable } from "react-beautiful-dnd"; import PromptValueBlock from "../../../common/Prompt/PromptValueBlock"; -import {t} from "i18next"; +import { t } from "i18next"; import CreateBlockModal from "../CreateBlockModal"; -import {useRecoilState, useRecoilValue} from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { - activeBlocksState, activeCategoryState, availableCategoriesState, blockDetailsState, + activeBlocksState, + activeCategoryState, + availableCategoriesState, + blockDetailsState, categoryBlockShapesState, categoryColorsState, } from "../../../../recoil/prompt/promptRecoilState"; -import {getLocalPromptMethod} from "../../../../util/localStorage"; -import {usePromptHook} from "../../../../api/prompt/prompt"; +import { getLocalPromptMethod } from "../../../../util/localStorage"; +import { usePromptHook } from "../../../../api/prompt/prompt"; function BlockSection() { const categoryColors = useRecoilValue(categoryColorsState); @@ -21,7 +24,8 @@ function BlockSection() { const localPromptMethod = getLocalPromptMethod(); const { fetchBlocks } = usePromptHook(); - const [activeCategory, setActiveCategory] = useRecoilState(activeCategoryState); + const [activeCategory, setActiveCategory] = + useRecoilState(activeCategoryState); const activeBlocks = useRecoilValue(activeBlocksState); const categories = useRecoilValue(availableCategoriesState); const blockDetails = useRecoilValue(blockDetailsState); @@ -85,25 +89,25 @@ function BlockSection() { > )} ); - }, + }, )} {provided.placeholder} diff --git a/src/components/SideBar/SideBar.jsx b/src/components/SideBar/SideBar.jsx index 7181740..2e9bb1b 100644 --- a/src/components/SideBar/SideBar.jsx +++ b/src/components/SideBar/SideBar.jsx @@ -32,7 +32,7 @@ function SideBar() {
    - +
    ) : ( diff --git a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx index 40e0d62..3326676 100644 --- a/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx +++ b/src/components/SideBar/components/Prompt/Modal/CreatePromptModal.jsx @@ -7,20 +7,30 @@ import characterIcon from "../../../../../assets/images/characterIcon.svg"; import taskIcon from "../../../../../assets/images/taskIcon.svg"; import freeIcon from "../../../../../assets/images/freeIcon.svg"; import { useNavigate } from "react-router-dom"; -import {useRecoilState} from "recoil"; +import { useRecoilState } from "recoil"; import { promptMethodState } from "../../../../../recoil/prompt/promptRecoilState"; -import {setLocalPromptCategory, setLocalPromptMethod} from "../../../../../util/localStorage"; -import {B5, H5} from "../../../../../styles/font-styles"; +import { + setLocalPromptCategory, + setLocalPromptMethod, +} from "../../../../../util/localStorage"; +import { B5, H5 } from "../../../../../styles/font-styles"; import { t } from "i18next"; function CreatePromptModal({ isOpen, onClose }) { const navigate = useNavigate(); const [promptMethod, setPromptMethod] = useRecoilState(promptMethodState); - const allCategories = ["IT", "게임", "글쓰기", "건강", "교육", "예술", "기타"]; + const allCategories = [ + "IT", + "게임", + "글쓰기", + "건강", + "교육", + "예술", + "기타", + ]; const [promptCategory, setPromptCategory] = useState("IT"); - if (!isOpen) return null; const handleCreateClick = () => { diff --git a/src/hooks/promptHook/usePromptMaking.jsx b/src/hooks/promptHook/usePromptMaking.jsx index a8f0514..5a2539b 100644 --- a/src/hooks/promptHook/usePromptMaking.jsx +++ b/src/hooks/promptHook/usePromptMaking.jsx @@ -1,11 +1,12 @@ -import {useRecoilState, useRecoilValue} from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { enqueueSnackbar } from "notistack"; import { activeBlocksState, combinationsState, blockDetailsState, - activeCategoryState, activeAiBlocksState, - userHistoryState + activeCategoryState, + activeAiBlocksState, + userHistoryState, } from "../../recoil/prompt/promptRecoilState"; import { useEffect } from "react"; import { t } from "i18next"; @@ -14,7 +15,8 @@ import { usePromptHook } from "../../api/prompt/prompt"; export const usePromptMaking = () => { const [combinations, setCombinations] = useRecoilState(combinationsState); const [activeBlocks, setActiveBlocks] = useRecoilState(activeBlocksState); - const [activeAiBlocks, setActiveAiBlocks] = useRecoilState(activeAiBlocksState); + const [activeAiBlocks, setActiveAiBlocks] = + useRecoilState(activeAiBlocksState); const [userHistory, setUserHistoryState] = useRecoilState(userHistoryState); const blockDetails = useRecoilValue(blockDetailsState); const activeCategory = useRecoilValue(activeCategoryState); @@ -100,7 +102,8 @@ export const usePromptMaking = () => { isBlockDefault, ); } else if ( - (source.droppableId === "sidebar" || source.droppableId === "sidebar_ai") && + (source.droppableId === "sidebar" || + source.droppableId === "sidebar_ai") && destination.droppableId !== "sidebar" && destination.droppableId !== "sidebar_ai" ) { @@ -113,9 +116,14 @@ export const usePromptMaking = () => { } else if ( source.droppableId !== "sidebar" && source.droppableId !== "sidebar_ai" && - (destination.droppableId === "sidebar" || destination.droppableId === "sidebar_ai") + (destination.droppableId === "sidebar" || + destination.droppableId === "sidebar_ai") ) { - handleCombinationAreaToSidebar(source.droppableId, numericBlockId, destination.droppableId); + handleCombinationAreaToSidebar( + source.droppableId, + numericBlockId, + destination.droppableId, + ); } else if ( source.droppableId !== "sidebar" && source.droppableId !== "sidebar_ai" && @@ -177,14 +185,18 @@ export const usePromptMaking = () => { })); setUserHistoryState((prev) => { - const prevEntries = typeof prev === 'string' ? prev.split('\n') : []; + const prevEntries = + typeof prev === "string" ? prev.split("\n") : []; const nextNumber = prevEntries.length + 1; - const description = blockDetails[blockId]?.blockDescription || 'Description not found'; + const description = + blockDetails[blockId]?.blockDescription || + "Description not found"; const newEntry = `${nextNumber}. ${category}에서 ${description}을 선택했습니다`; - return prevEntries.length > 0 ? `${prevEntries.join('\n')}\n${newEntry}` : newEntry; + return prevEntries.length > 0 + ? `${prevEntries.join("\n")}\n${newEntry}` + : newEntry; }); - if (sourceDroppableId === "sidebar") { setActiveBlocks((prev) => ({ ...prev, @@ -215,7 +227,11 @@ export const usePromptMaking = () => { // })); // }; - const handleCombinationAreaToSidebar = (category, blockId, destinationDroppableId) => { + const handleCombinationAreaToSidebar = ( + category, + blockId, + destinationDroppableId, + ) => { setCombinations((prev) => ({ ...prev, [category]: null, diff --git a/src/recoil/prompt/promptRecoilState.js b/src/recoil/prompt/promptRecoilState.js index 6097ec4..9033f10 100644 --- a/src/recoil/prompt/promptRecoilState.js +++ b/src/recoil/prompt/promptRecoilState.js @@ -240,7 +240,6 @@ export const userHistoryState = atom({ default: "", }); - // // 각종 상태 초기화 함수 // export const useResetCategoriesOnTypeChange = () => { // return useRecoilCallback(({ snapshot, set }) => async () => { diff --git a/src/util/localStorage.js b/src/util/localStorage.js index 3e8110c..6708314 100644 --- a/src/util/localStorage.js +++ b/src/util/localStorage.js @@ -9,23 +9,23 @@ export const getLocalPromptMethod = () => { export const getTourFinished = () => { const tourFinished = localStorage.getItem("tourFinished"); - if (tourFinished === 'false') { + if (tourFinished === "false") { return false; } else { return true; } -} +}; export const setTourFinish = (tourFinished) => { localStorage.setItem("tourFinished", tourFinished.toString()); -} +}; export const getIsFirstVisited = () => { - const hasVisited = localStorage.getItem('hasVisited'); - if (hasVisited === 'false') { + const hasVisited = localStorage.getItem("hasVisited"); + if (hasVisited === "false") { return false; } else { - localStorage.setItem("hasVisited", 'true'); + localStorage.setItem("hasVisited", "true"); return true; } };