Skip to content

Commit

Permalink
Merge pull request #39 from MARU-EGG/refactor-maru-egg-page-component
Browse files Browse the repository at this point in the history
[USER] refactor: Chatform components
  • Loading branch information
swgvenghy authored Aug 22, 2024
2 parents 3ed6d34 + 4c7f88c commit bc219e4
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 157 deletions.
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@types/node": "^16.18.101",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vercel/analytics": "^1.3.1",
"antd": "^5.19.0",
"axios": "^1.7.2",
"class-variance-authority": "^0.7.0",
Expand Down
6 changes: 5 additions & 1 deletion src/api/post-question.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { server_axiosInstance } from '../utils/axios';

export async function postQuestion(category: string | undefined, type: string, content: string): Promise<any> {
export async function postQuestion(
category: string | undefined,
type: string | undefined,
content: string,
): Promise<any> {
const data = {
type,
category,
Expand Down
71 changes: 71 additions & 0 deletions src/hooks/use-chat-form.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// src/hooks/useChatForm.ts
import { useState, useCallback } from 'react';
import { useAutoComplete } from './use-auto-complete.hooks';
import useChatStore from '../store/chat-store';
import { postQuestion } from '../api/post-question';
import { SearchById } from '../api/admin-search-by-id';
import useTypeStore from '../store/type-category-store';

const useChatForm = () => {
const [content, setContent] = useState<string>('');
const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
const [disabled, setDisabled] = useState(false);
const [autoOpen, setAutoOpen] = useState(false);
const { results } = useAutoComplete({ content });
const { type, category } = useTypeStore();

const handleChange = useCallback((value: string) => {
setContent(value);
setAutoOpen(true);
setSelectedId(undefined);
}, []);

const handleSelect = useCallback((value: string, id: number) => {
setSelectedId(id);
setContent(value);
setDisabled(value.trim() === '');
setAutoOpen(false);
}, []);

const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
useChatStore.getState().addMessage({ content, role: 'user' });
useChatStore.getState().addMessage({ content: '답변을 생성중입니다...', role: 'system' });
useChatStore.getState().setLoading(true);
setContent('');
setAutoOpen(false);
setDisabled(true);
if (selectedId === undefined) {
const response = await postQuestion(category, type, content);
useChatStore.getState().updateLastMessage(response.answer.content);
useChatStore.getState().setLoading(false);
setDisabled(false);
} else {
const response = await SearchById(selectedId);
useChatStore.getState().updateLastMessage(response.answer.content);
useChatStore.getState().setLoading(false);
setSelectedId(undefined);
setDisabled(false);
}
} catch (error) {
useChatStore.getState().setLoading(false);
useChatStore.getState().updateLastMessage('답변 생성에 실패했습니다. 새로고침해주세요');
}
},
[content, selectedId, category, type],
);

return {
content,
autoOpen,
disabled,
results,
handleChange,
handleSelect,
handleSubmit,
};
};

export default useChatForm;
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import './index.css';
import { CookiesProvider } from 'react-cookie';
import { Analytics } from '@vercel/analytics/react';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<CookiesProvider>
<App />
<Analytics />
</CookiesProvider>,
);

Expand Down
2 changes: 1 addition & 1 deletion src/store/type-category-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { create } from 'zustand';

interface TypeCategoryState {
export interface TypeCategoryState {
type: undefined | 'SUSI' | 'PYEONIP' | 'JEONGSI';
category: undefined | 'ADMISSION_GUIDELINE' | 'PASSING_RESULT' | 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST';
setSelectedType: (button: TypeCategoryState['type']) => void;
Expand Down
65 changes: 7 additions & 58 deletions src/ui/components/molecule/chat-form/chat-form.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,18 @@
import React, { useState } from 'react';
// src/ui/molecule/chat-form.tsx
import React from 'react';
import TextInput from '../../atom/text-input/text-input';
import IconButton from '../../atom/icon/icon-button';
import { ReactComponent as SendIcon } from '../../../../assets/Send.svg';
import { postQuestion } from '../../../../api/post-question';
import { cn } from '../../../../utils/style';
import useChatStore from '../../../../store/chat-store';
import { useAutoComplete } from '../../../../hooks/use-auto-complete.hooks';
import AutoCompleteList from '../../atom/auto-complete/auto-complete';
import { SearchById } from '../../../../api/admin-search-by-id';

interface ChatFormProps {
type: 'SUSI' | 'PYEONIP' | 'JEONGSI';
category?: 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST' | 'PASSING_RESULT' | 'ADMISSION_GUIDELINE';
}

const ChatForm = ({ type, category }: ChatFormProps) => {
const [content, setContent] = useState<string>('');
const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
const [disabled, setDisabled] = useState(false);
const [autoOpen, setAutoOpen] = useState(false);
const { results } = useAutoComplete({ content });

const handleChange = (value: string) => {
setContent(value);
setAutoOpen(true);
setSelectedId(undefined);
};

const handleSelect = (value: string, id: number) => {
setSelectedId(id);
setContent(value);
setDisabled(value.trim() === '');
setAutoOpen(false);
};
import { cn } from '../../../../utils/style';
import useChatForm from '../../../../hooks/use-chat-form.hooks';

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
useChatStore.getState().addMessage({ content, role: 'user' });
useChatStore.getState().addMessage({ content: '답변을 생성중입니다...', role: 'system' });
useChatStore.getState().setLoading(true);
setContent('');
setAutoOpen(false);
setDisabled(true);
if (selectedId === undefined) {
const response = await postQuestion(category, type, content);
useChatStore.getState().updateLastMessage(response.answer.content);
useChatStore.getState().setLoading(false);
setDisabled(false);
} else {
const response = await SearchById(selectedId);
useChatStore.getState().updateLastMessage(response.answer.content);
useChatStore.getState().setLoading(false);
setSelectedId(undefined);
setDisabled(false);
}
} catch (error) {
useChatStore.getState().setLoading(false);
useChatStore.getState().updateLastMessage('답변 생성에 실패했습니다. 새로고침해주세요');
}
};
const ChatForm = () => {
const { content, autoOpen, disabled, results, handleChange, handleSelect, handleSubmit } = useChatForm();

return (
<>
{autoOpen ? <AutoCompleteList results={results} onSelect={handleSelect} /> : null}
{autoOpen && <AutoCompleteList results={results} onSelect={handleSelect} />}
<form
className={cn('flex flex-nowrap rounded-2xl border py-2 pr-1', 'bg-background-default')}
onSubmit={handleSubmit}
Expand Down
99 changes: 99 additions & 0 deletions src/ui/components/molecule/chat-section/chat-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// src/ui/components/organism/chat-section/chat-section.tsx
import React from 'react';
import ChatCard from '../../atom/chat-card/chat-card';
import PresetButton from '../../atom/preset/preset-button';
import useTypeStore, { TypeCategoryState } from '../../../../store/type-category-store';
import useChatStore from '../../../../store/chat-store';

const ChatSection: React.FC = () => {
const { setSelectedType, type, setSelectedCategory, category } = useTypeStore();
const { messages } = useChatStore();
const [selectedTypeButton, setSelectedTypeButton] = React.useState<TypeCategoryState['type']>(undefined);
const [selectedCategoryButton, setSelectedCategoryButton] = React.useState<TypeCategoryState['category']>(undefined);

const handleTypeButtonClick = (selectedType: TypeCategoryState['type']) => {
setSelectedType(selectedType);
setSelectedTypeButton(selectedType);
};

const handleCategoryButtonClick = (selectedCategory: TypeCategoryState['category']) => {
setSelectedCategory(selectedCategory);
setSelectedCategoryButton(selectedCategory);
};

return (
<div className="max-h-screen-minus-header w-full overflow-y-auto px-4 pb-24 pt-16">
<ChatCard
content={`안녕하세요 입학처 챗봇 MARU-EGG입니다!
궁금하신 내용 안내 도와드리겠습니다.
알아보고 싶은 전형을 선택해주세요!`}
role="system"
/>
<div className="flex space-x-2">
<PresetButton onClick={() => handleTypeButtonClick('SUSI')} isSelected={selectedTypeButton === 'SUSI'}>
수시
</PresetButton>
<PresetButton onClick={() => handleTypeButtonClick('PYEONIP')} isSelected={selectedTypeButton === 'PYEONIP'}>
편입
</PresetButton>
<PresetButton onClick={() => handleTypeButtonClick('JEONGSI')} isSelected={selectedTypeButton === 'JEONGSI'}>
정시
</PresetButton>
</div>
{type !== undefined && (
<ChatCard role="user" content={type === 'SUSI' ? '수시' : type === 'JEONGSI' ? '정시' : '편입'} />
)}
{type !== undefined && (
<>
<ChatCard content={`알고싶은 내용을 선택해주세요`} role="system" />
<div className="flex w-2/5 flex-wrap space-y-2">
<PresetButton
onClick={() => handleCategoryButtonClick('ADMISSION_GUIDELINE')}
isSelected={selectedCategoryButton === 'ADMISSION_GUIDELINE'}
>
모집관련내용
</PresetButton>
<PresetButton
onClick={() => handleCategoryButtonClick('PASSING_RESULT')}
isSelected={selectedCategoryButton === 'PASSING_RESULT'}
>
전년도 입시결과
</PresetButton>
<PresetButton
onClick={() => handleCategoryButtonClick('PAST_QUESTIONS')}
isSelected={selectedCategoryButton === 'PAST_QUESTIONS'}
>
면접등 기출문제
</PresetButton>
</div>
</>
)}

{category !== undefined && (
<ChatCard
role="user"
content={
category === 'ADMISSION_GUIDELINE'
? '모집관련내용'
: category === 'PASSING_RESULT'
? '전년도 입시결과'
: '면접 등 기출문제'
}
/>
)}
{type !== undefined && category !== undefined && (
<ChatCard
role="system"
content={`안녕하세요 입학처 챗봇 MARU-EGG입니다!
궁금하신 내용 안내 도와드리겠습니다.
`}
/>
)}
{messages.map((msg, index) => {
return <ChatCard key={index} content={msg.content} role={msg.role} />;
})}
</div>
);
};

export default ChatSection;
Loading

0 comments on commit bc219e4

Please sign in to comment.