From 492ffb3ccc756bbea18b23bb1f7ba84864d3e075 Mon Sep 17 00:00:00 2001 From: Kimd0ng Date: Fri, 10 Jan 2025 15:08:01 +0900 Subject: [PATCH 1/7] =?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/7] =?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/7] =?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/7] =?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/7] =?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/7] =?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/7] =?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 (