이전에 Udemy 강의를 들으면서 openAI 를 api 로 가져와서 반려동물 이름을 짓는 웹을 구현했었다. 회고하면서 이거도 추가하고 저거도 추가하고... 등등 이런 저런 계획을 세웠었는데, 그걸 통틀어서 여러 이름을 지어주는 AI 작명소를 구현해보았다.
일단 이렇게 계획을 짰었다. 모종의 이유(?)들로 인해 겁먹은것보다 막 어렵지는 않았다!
모종의 이유는 아래 과정에서 써나가겠다.
figma 를 활용해서 위와 같이 간단하게 디자인을 구성했다.
사용자는 원하는 카테고리를 고르고, 이름에 대한 설명 + 포함해야 하는 문자열을 입력한다.
마지막으로는 이름 짓기 버튼을 누르면 작명 결과가 보여지도록 했다.
1편에서 했던 것 처럼, NextJS 로 개발을 진행했다. (vercel 로 배포하기 위한 빅픽쳐이기도 함)
페이지 1개에서 state 를 조정해서 기능들을 넣을 예정이었기 때문에, 페이지나 라우터 구성은 따로 하지 않았다.
// index.js
import styleSheet from './style.jsx';
export default function Home(){
return(
<div className = 'container'>
<style jsx>{styleSheet}</style>
</div>
)
}
// style.jsx
const styleSheet = `
.container{
display: flex;
flex-direction: column;
}
`
일전에 우아한테크코스 프리코스에 참여했던 경험 + Udemy Maker Jun 님의 블랙커피 강의를 수강한 경험으로, 기능들을 즉석에서 구현했을 때에 착오를 방지하기 위해서 기능 요구사항을 정리해보았다. 더 세세하게 작성하는 습관을 들이고 싶다..!
나중에 내가 이 포스팅을 봤을 때, 아 맞아 이렇게도 했었지! 할 수 있는 주요 기능들만 정리해보겠다!
⭐ 카테고리 선택 로직
카테고리 데이터는 객체요소가 들어있는 배열 형태로 구성했다.
map 메서드를 통해 emoji,text,color 를 사용해 버튼들을 렌더링한다.
카테고리 버튼을 클릭하면, category state 는 해당 카테고리로 업데이트된다.
원래는 emoji, text, color 등등의 프로퍼티를 각각의 state 로 두기로 했었지만, 그렇게 되면 state 가 너무 많아지기 때문에 이를 최소화 시키기 위해 하나의 category 를 state 로 두고, category.emoji
이런식으로 참조하는 방식으로 진행했다.
const categoryData = [
// type : 1 = '~의 이름 짓기', 0 = '~ 짓기', -1 = 아무개
{emoji:'🐶😺', text: '반려동물', color: 'pupple', type: 1},
{emoji:'🕹️', text: '별명', color: 'mint', type: 0},
{emoji:'📦', text: '상품', color: 'yellow', type: 1},
{emoji:'📜', text: '글 제목', color: 'blue', type: 0},
{emoji:'📱', text: '서비스', color: 'pink', type:1},
{emoji:'👥', text: '팀/그룹', color: 'green', type:1},
{emoji:'❓', text: '아무개', color: 'gray', type:-1},
]
요기서 type 프로퍼티는 코드를 짜다가 하드코딩이 싫어서 만든 것이다.
예를 들어, 반려동물의 경우 반려동물 이름 짓기
라는 제출 버튼이 나와야 한다. 하지만 별명이라면 별명 짓기
, 아무개라면 이름 짓기
이다.
위와 같이, category.text 를 활용하여 UI 를 동적으로 구성한다. 동적으로 변화하는 텍스트를 만들어주는 util인 MakeText를 아래와 같이 따로 빼서 작성하였다.
버튼에 들어가는 텍스트, 설명란 위에 보여지는 텍스트, api에 전송하는 prompt에 들어가는 텍스트를 반환한다.
class MakeText{
constructor(category){
this.text = category.text;
this.type = category.type;
}
buttonText = () => { //버튼 텍스트 반환
if(this.type===-1){
return '이름 짓기'
}
if(this.type===0){
return `${this.text} 짓기`
}
return `${this.text} 이름 짓기`
}
detailExplainText = () => { //설명 입력 요청 텍스트 반환
const baseExplain = '설명을 입력해 주세요'
return(
this.type<0?baseExplain:this.text+'에 대한 '+baseExplain
);
}
commandText = (detail, include) => { //서버에 요청할 명령 텍스트 반환
let baseCommand = ''
if(this.type===0){
baseCommand = `${this.text}을 한글로 추천해주세요.\n`
}
else if(this.type===1){
baseCommand = `${this.text} 한글 이름을 추천해주세요.\n`
}
else{
baseCommand = '한글 이름을 아무거나 추천해주세요.\n'
}
return(
include?
baseCommand+`설명: ${detail}.\n포함되어야 하는 문자열:${include}\n\n`:
baseCommand+`설명: ${detail}\n`
)
}
}
그리고 아래와 같이, category 가 변할 때 마다 MakeText 를 새로 생성하도록 하였다.
useEffect(()=>{
setSubmitState(0);
makeText = new MakeText(category);
initAllStates();
}, [category]);
이렇게 카테고리 버튼을 클릭하면 입력하는 UI 부분이 카테고리 state 프로퍼티를 참조하여 변화하는 형태이다. 그 과정에서 각종 문자열 포맷을 위해 MakeText 를 거쳐서 나온다.
⭐ 로딩중 화면 띄우기 (Spinner)
이름 짓기 버튼을 누른 후, 결과가 나오기 전에 아래와 같은 화면이 나오도록 했다.
생각보다 기다리는 시간이 길어질 때가 있어서 사용자의 입장에서 최대한 완성도를 높이고자 넣은 기능이다.
submitState
라는 state 변수를 사용하여 구현하였다.
submitState = 0
: 이름 짓기 요청 들어가기 전
submitState = 1
: 이름 짓기 요청 들어온 후, 결과 받아오기 전
submitState = 2
: 결과 받아온 후
const handleSubmit = async() => {
if(submitState===0){
if(detail.trim().length<1){
alert('설명을 입력해주세요');
return;
}
if(include.trim().length<1){
if(!confirm('포함되어야 하는 문자열이 없나요?')){
return;
}
}
}
setSubmitState(1); //로딩 상태로 전환
const command = makeText.commandText(detail, include.trim());
try{
//생략
}
setSubmitState(2); //결과 상태로 전환
setResult(data.result);
}
catch(error){
//생략
setSubmitState(0); //초기 상태로 전환
}
}
⬆️ submitState 업데이트
const MainBox = () => {
if(submitState===1){
return(
<LoadingBox/>
)
}
if(submitState===2){
return(
<ResultBox
result = {result}
category = {category}
setSubmitState = {setSubmitState}
></ResultBox>
)
}
}
⬆️ submitState 에 따른 화면 구현
쓰다보니 너무 길어지기도 하고, 배포랑 openAI 관련해서 기록하고 싶은 내용들이 더 남아있어서 다음에 이어서 작성하도록 하겠다!
간단한 구현이라도 기능 요구 사항을 정리하고 시작하니까 개발하는 시간이 훨씬 줄어들었다.
또한 갈아엎을 상황이 잘 오지 않아서 좋은 것 같다.
원래는 모든 함수들을 index.jsx 에 다 때려박는 안좋은 습관이 있었는데,
이번에 그걸 고치려고 노력했다.
MakeText 를 밖으로 빼고, categoryData 도 밖으로 뺐다. 로딩화면과 결과화면 컴포넌트도 탈출시켰다. 나중에 유지보수를 생각하면 훨씬 나아졌다고 생각한다.
style jsx 도 처음 알게된 기능이다. 개인적으로는 module.css 보다 쓰기 편했다!