-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from MARU-EGG/feature-#3
[FEAT-3] Funnel 패턴 도입, 각 단계에 해당하는 컴포넌트 개발, 챗봇 질문응답 api 연동
- Loading branch information
Showing
43 changed files
with
1,477 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,4 @@ dist-ssr | |
*.njsproj | ||
*.sln | ||
*.sw? | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { server_axiosInstance } from '@/api'; | ||
import { AdmissionType, ResponseDetailAdmissionType } from '@/types/admission-type'; | ||
import { DefaultPostQuestionParams, PostQuestionResponse } from '@/types/questions'; | ||
|
||
export const getAdmissionDetail = async (type: AdmissionType): Promise<ResponseDetailAdmissionType[]> => { | ||
const response = await server_axiosInstance.get(`/api/admissions/details/${type}`); | ||
return response.data; | ||
}; | ||
|
||
export const postQuestion = async ({ | ||
category, | ||
type, | ||
content, | ||
}: DefaultPostQuestionParams): Promise<PostQuestionResponse> => { | ||
const response = await server_axiosInstance.post('/api/questions', JSON.stringify({ category, type, content }), { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
return response.data; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import axios from 'axios'; | ||
|
||
export const server_axiosInstance = axios.create({ | ||
baseURL: import.meta.env.VITE_SPRING_SERVER_API_ADDRESS, | ||
timeout: 200000, | ||
withCredentials: true, | ||
}); | ||
|
||
export const llm_axiosInstance = axios.create({ | ||
baseURL: import.meta.env.VITE_LLM_SERVER_API_ADDRESS, | ||
timeout: 200000, | ||
withCredentials: true, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import React from 'react'; | ||
|
||
function SelectorHeader({ children }: { children: React.ReactNode }) { | ||
return <div className="bg-primary bg-image-tree-right text-title p-4 text-white">{children}</div>; | ||
return <div className="bg-image-tree-right rounded-t-lg bg-primary p-4 text-title text-white">{children}</div>; | ||
} | ||
|
||
export default SelectorHeader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const systemMessage = { | ||
introduction: | ||
'안녕하세요,\n명지대학교 입시 정보를 똑똑하게\n찾아주는 마루에그입니다!\n\n명지대학교 입시에 대해 궁금하신가요?\n아래 전형 중 하나를 선택해주세요!', | ||
admissionGuide: (type: string) => | ||
`${type} 전형이 궁금하시군요!\n\n어떤 세부 전형이 궁금하신가요?\n아래에서 세부 전형을 선택해주세요!`, | ||
additionalInfo: | ||
'아래 버튼을 눌러 더 자세한 정보를 확인\n하거나 직접 질문해보세요!\n\n더욱 자세한 상담을 원하시면\n 명지대학교 입학처 02-300-1799,1800으로 전화주시길 바랍니다.', | ||
referenceGuide: '💡답변 출처를 알려드릴게요!\n출처를 클릭하면 모집요강으로\n확인할 수 있어요!', | ||
errorMessage: '서버가 답변을 불러오는데 실패했어요.', | ||
campusGuide: | ||
'학과별로 보고싶으신가요?\n명지대학교는 총 2개의 캠퍼스,12개의\n단과대와 63개의 학과(부)로 구성되어\n있습니다.\n아래 질문들을 통해서 궁금하신 학과(부)를\n찾아주세요!', | ||
} as const; | ||
|
||
export default systemMessage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
export interface AdmissionPresetType { | ||
label: string; | ||
category: 'ADMISSION_GUIDELINE' | 'PASSING_RESULT'; | ||
question: string | ((type: string) => string); | ||
} | ||
|
||
const PRESET_BUTTON: AdmissionPresetType[] = [ | ||
{ | ||
label: '전형일정', | ||
category: 'ADMISSION_GUIDELINE', | ||
question: '전형일정', | ||
}, | ||
|
||
{ | ||
label: '제출 서류', | ||
category: 'ADMISSION_GUIDELINE', | ||
question: '제출 서류 유의사항', | ||
}, | ||
{ | ||
label: '입시 결과', | ||
category: 'PASSING_RESULT', | ||
question: (type: string) => `${type}의 모든 학과에 대한 입시결과 알려줘`, | ||
}, | ||
{ | ||
label: '면접 유의사항', | ||
category: 'ADMISSION_GUIDELINE', | ||
question: '블라인드 면접 유의사항', | ||
}, | ||
{ | ||
label: '실기고사', | ||
category: 'ADMISSION_GUIDELINE', | ||
question: '실기고사', | ||
}, | ||
] as const; | ||
|
||
export default PRESET_BUTTON; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { getAdmissionDetail } from '@/api/api'; | ||
import { AdmissionType } from '@/types/admission-type'; | ||
import formatAdmissionDetail from '@/utils/format-admission-detail'; | ||
import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|
||
export default function useAdmissionDetail(admissionType: AdmissionType | null) { | ||
const { data } = useSuspenseQuery({ | ||
queryKey: [admissionType, 'admission'], | ||
queryFn: async () => { | ||
if (!admissionType) { | ||
throw new Error('Admission type is required'); | ||
} | ||
return getAdmissionDetail(admissionType); | ||
}, | ||
select: (data) => formatAdmissionDetail(data), | ||
}); | ||
|
||
return data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { postQuestion } from '@/api/api'; | ||
import { DefaultPostQuestionParams } from '@/types/questions'; | ||
import { useMutation } from '@tanstack/react-query'; | ||
|
||
export default function usePostQuestion() { | ||
const questionMutation = useMutation({ | ||
mutationFn: (params: DefaultPostQuestionParams) => postQuestion(params), | ||
mutationKey: ['POST_QUESTION'], | ||
}); | ||
return { questionMutation }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { useEffect, useState } from 'react'; | ||
import systemMessage from '@/constants/message'; | ||
import usePostQuestion from '@/hooks/querys/usePostQuestion'; | ||
import useMessagesStore from '@/stores/store/message-store'; | ||
import useQuestionReferencesStore from '@/stores/store/question-references-store'; | ||
import { AdmissionType } from '@/types/admission-type'; | ||
import { PostQuestionResponse } from '@/types/questions'; | ||
import makePrompt from '@/utils/make-prompt'; | ||
import { useQueryClient } from '@tanstack/react-query'; | ||
|
||
interface UseAdmissionType { | ||
admissionType: AdmissionType; | ||
category: 'ADMISSION_GUIDELINE' | 'PASSING_RESULT'; | ||
question: string; | ||
questionType: 'general' | 'detail'; | ||
} | ||
|
||
export default function useAdmissionQuestionResult({ | ||
admissionType, | ||
category, | ||
question, | ||
questionType, | ||
}: UseAdmissionType) { | ||
const [result, setResult] = useState<PostQuestionResponse | null>(null); | ||
const [timestamp, setTimestamp] = useState(Date.now()); | ||
const { questionMutation } = usePostQuestion(); | ||
const { setMessages } = useMessagesStore(); | ||
const { setReferences } = useQuestionReferencesStore(); | ||
const queryClient = useQueryClient(); | ||
|
||
const checkAndSetCacheData = (QUERY_KEY: unknown[]) => { | ||
const cachedData = queryClient.getQueryData<string>(QUERY_KEY); | ||
if (cachedData) { | ||
setMessages([ | ||
{ | ||
role: 'system', | ||
message: cachedData, | ||
markdown: true, | ||
}, | ||
]); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
useEffect(() => { | ||
if (!admissionType || !question) return; | ||
|
||
const QUERY_KEY = [admissionType, category, question, questionType]; | ||
if (checkAndSetCacheData(QUERY_KEY)) return; | ||
|
||
const content = questionType === 'general' ? makePrompt(admissionType, question) : question; | ||
|
||
questionMutation.mutate( | ||
{ | ||
type: admissionType, | ||
category: category, | ||
content: content, | ||
}, | ||
{ | ||
onSuccess: (data) => { | ||
setResult(data); | ||
setMessages([ | ||
{ | ||
role: 'system', | ||
message: data.answer.content, | ||
markdown: true, | ||
}, | ||
]); | ||
setReferences(data.references); | ||
queryClient.setQueryData(QUERY_KEY, data.answer.content); | ||
}, | ||
onError: () => { | ||
setMessages([{ role: 'system', message: systemMessage.errorMessage }]); | ||
}, | ||
}, | ||
); | ||
}, [question, timestamp]); | ||
|
||
const refetch = () => { | ||
setTimestamp(Date.now()); | ||
}; | ||
|
||
return { result, isLoading: questionMutation.isPending, refetch }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,10 @@ | ||
import { StrictMode } from 'react'; | ||
import { createRoot } from 'react-dom/client'; | ||
import { BrowserRouter } from 'react-router-dom'; | ||
import App from './App.tsx'; | ||
import './index.css'; | ||
|
||
createRoot(document.getElementById('root')!).render( | ||
<StrictMode> | ||
<BrowserRouter> | ||
<App /> | ||
</BrowserRouter> | ||
</StrictMode>, | ||
<BrowserRouter> | ||
<App /> | ||
</BrowserRouter>, | ||
); |
62 changes: 62 additions & 0 deletions
62
src/page/components/chat-step/admission-category-result/admission-category-result.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import PresetButton from '@/components/preset-button/preset-button'; | ||
import PRESET_BUTTON, { AdmissionPresetType } from '@/constants/preset-buttons'; | ||
import useAdmissionQuestionResult from '@/hooks/use-admission-question-result'; | ||
import useAdmissionStore from '@/stores/store/admission-store'; | ||
import useMessagesStore from '@/stores/store/message-store'; | ||
import { AdmissionType } from '@/types/admission-type'; | ||
import { ChatSteps } from '@/types/chat'; | ||
|
||
interface Props { | ||
changeStep: (step: ChatSteps) => void; | ||
admissionType: AdmissionType; | ||
admissionCategory: string; | ||
} | ||
|
||
function AdmissionCategoryResult({ admissionType, changeStep, admissionCategory }: Props) { | ||
const { setQuestion } = useAdmissionStore(); | ||
const { isLoading } = useAdmissionQuestionResult({ | ||
admissionType: admissionType, | ||
category: 'ADMISSION_GUIDELINE', | ||
question: admissionCategory, | ||
questionType: 'general', | ||
}); | ||
const { setMessages } = useMessagesStore(); | ||
|
||
const selectQuestion = (question: AdmissionPresetType) => { | ||
changeStep('상세전형 질문 결과'); | ||
setMessages([{ role: 'user', message: question.label }]); | ||
setQuestion(question); | ||
}; | ||
|
||
if (!isLoading) | ||
return ( | ||
<div> | ||
<div className="mt-2 flex w-full justify-end"> | ||
<div className="flex w-72 flex-wrap justify-end gap-2"> | ||
<PresetButton | ||
onClick={() => { | ||
changeStep('질문 출처 결과'); | ||
setMessages([{ role: 'user', message: '🙋♂️ 어디에서 볼 수 있나요?' }]); | ||
}} | ||
> | ||
🙋♂️ 어디에서 볼 수 있나요? | ||
</PresetButton> | ||
{PRESET_BUTTON.map((question) => ( | ||
<PresetButton | ||
onClick={() => { | ||
selectQuestion(question); | ||
}} | ||
key={question.label} | ||
> | ||
{question.label} | ||
</PresetButton> | ||
))} | ||
<PresetButton onClick={() => changeStep('상세전형 학과별 입시')}>학과별 입시</PresetButton> | ||
<PresetButton onClick={() => window.location.reload()}>조건 재설정</PresetButton> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default AdmissionCategoryResult; |
Oops, something went wrong.