Skip to content

Commit

Permalink
챗봇 UI작업 (#59)
Browse files Browse the repository at this point in the history
* feat: 챗봇 UI작업
#48

* feat: 챗봇 채팅창 disable
#48

* fix: 렌더링 불일치 오류 해결

#48

* fix: 빌드 오류 해결

#48

* refactor: console.log 삭제

#48

* design: CommentCard sx prop 수정

#48

---------

Co-authored-by: yeonddori <[email protected]>
  • Loading branch information
stopmin and yeonddori authored Jul 20, 2024
1 parent 72bbdad commit 15a7492
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 23 deletions.
175 changes: 175 additions & 0 deletions src/app/chatbot/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
'use client';

import React, { useState, useEffect } from 'react';

import { Container, Box, Typography, Card } from '@mui/material';

import ChatContainer from '@/components/ChatbotContainer';
import ChatInput from '@/components/ChatInput';
import CommentCard from '@/components/CommentCard';
import GradientBox from '@/components/GradientBox';
import color from '@/constants/color';
import chatbotIntro from '@/mocks/chatIntro';

const initialMessage =
'안녕 나는 AI 산지니야🤖\n최근 네가 읽었던 글에 대한 질문이나, 경제 단어에 대해서 더 알려줄게😆\n경단에 대해서 더 알려주는 것도 가능가능!!';

interface Message {
id: number;
content?: string;
isUser?: boolean;
buttons?: Array<{ id: number; text: string; onClick: () => void }>;
}

const Page = () => {
const [messages, setMessages] = useState<Message[]>([]);

const handleButtonClick = async (type: string) => {
let response;
try {
if (type === 'question') {
response = await fetch('/api/question');
} else if (type === 'term') {
response = await fetch('/api/term');
} else if (type === 'gyeongdan') {
response = await fetch('/api/gyeongdan');
}
const data = await (response as Response).json();
setMessages((prevMessages) => [...prevMessages, { id: 0, content: data.message, isUser: false }]);
} catch (error) {
console.error('Error fetching data:', error);
}
};

useEffect(() => {
setMessages([
{ id: 0, content: initialMessage, isUser: false },
{
id: 0,
buttons: [
{ id: 0, text: '질문하기', onClick: () => handleButtonClick('question') },
{ id: 0, text: '용어 찾기', onClick: () => handleButtonClick('term') },
{ id: 0, text: '경단 설명', onClick: () => handleButtonClick('gyeongdan') },
],
isUser: false,
},
]);
}, []);

const handleSendMessage = (message: string) => {
const newMessage: Message = { id: 0, content: message, isUser: true };
setMessages((prevMessages) => [...prevMessages, newMessage]);

setTimeout(() => {
setMessages((prevMessages) => [...prevMessages, { id: 0, content: message, isUser: false }]);
}, 500);
};

return (
<GradientBox
sx={{
minHeight: 'calc(100vh - 100px)',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem',
}}
>
<Box
sx={{
flex: '1 1 40%',
maxWidth: '40%',
display: 'flex',
flexDirection: 'column',
alignItems: 'left',
justifyContent: 'center',
height: 'calc(100vh - 100px)',
overflowY: 'auto',
}}
>
<Typography color={color.blue} marginLeft="3rem" mb={3} variant="h4">
AI 산지니에 대해서 궁금해?
</Typography>
<Card
sx={{
width: '100%',
padding: '1.5rem',
boxShadow: 0,
borderRadius: 10,
overflowY: 'auto',
}}
>
<Typography
sx={{
textAlign: 'left',
whiteSpace: 'pre-line',
lineHeight: '1.6',
'& h5': {
fontWeight: 'bold',
marginBottom: '0.5rem',
},
'& span': {
fontWeight: 'bold',
},
}}
variant="body2"
>
{chatbotIntro}
</Typography>
</Card>
</Box>
<Box
sx={{
flex: '2 1 60%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: 'calc(100vh - 100px)',
}}
>
<Box
sx={{
width: '100%',
padding: '0 2rem',
marginBottom: '1rem',
}}
>
<Typography color={color.blue} mb={3} variant="h4">
AI 산지니야 반가워~
</Typography>
<CommentCard
isStroke
content={'AI 산지니에게 바르고 고운말을 해주세요~!\n산지니는 구글에서 다른 기사를 찾아보는 일을 좋아해요🧐'}
isChat={false}
/>
</Box>
<Container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%',
height: '100%',
overflowY: 'auto',
padding: '0 2rem',
}}
>
<ChatContainer messages={messages} />
</Container>
<ChatInput onSendMessage={handleSendMessage} />
<Box
sx={{
width: '100%',
padding: '0 2rem',
marginTop: '1rem',
}}
/>
</Box>
</GradientBox>
);
};

export default Page;
55 changes: 33 additions & 22 deletions src/app/insight/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import dynamic from 'next/dynamic';
import React from 'react';
import React, { useEffect, useState } from 'react';

import { Box, Stack, Typography, Grid } from '@mui/material';

Expand All @@ -16,6 +16,16 @@ import insightsDataList from '@/mocks/insights';
const ChartRenderer = dynamic(() => import('@/components/ChartRenderer'), { ssr: false });

const Page = () => {
const [isClient, setIsClient] = useState(false);

useEffect(() => {
setIsClient(true);
}, []);

if (!isClient) {
return null;
}

return (
<GradientBox sx={{ height: '100vh', width: '100vw' }}>
<Stack alignItems="center">
Expand All @@ -33,30 +43,31 @@ const Page = () => {
justifyContent: 'space-between',
}}
>
<Box sx={{ flex: 1, marginRight: { md: '1rem', xs: 0 }, marginBottom: { xs: '1.5rem', md: 0 } }}>
<CommentCard isCharacter isStroke content={insightData.comment} sx={{ marginBottom: '1.5rem' }} />
<CommentCard
content={
<Box>
<Typography color={color.gradient_blue_dark} variant="h4">
{insightData.title}
</Typography>
{'\n'}
<Typography color={color.gray_dark} variant="body1">
{insightData.content}
</Typography>
</Box>
}
sx={{ marginBottom: '1rem', width: '650px' }}
/>
<Box sx={{ flex: 1, mr: { md: '1rem', xs: 0 }, mb: { xs: '1.5rem', md: 0 } }}>
<Box mb="1.5rem">
<CommentCard isCharacter isStroke content={insightData.comment} />
</Box>
<Box mb="1rem" width="650px">
<CommentCard
content={
<Box>
<Typography color={color.gradient_blue_dark} variant="h4">
{insightData.title}
</Typography>
{'\n'}
<Typography color={color.gray_dark} variant="body1">
{insightData.content}
</Typography>
</Box>
}
/>
</Box>
</Box>
<Box sx={{ flex: 1 }}>
<ChartRenderer data={insightData.chart.data} height={320} layout={insightData.chart.layout} width={480} />
<CommentCard
isFilled
content="나만의 인사이트 만들어보기(공사중 🚧)"
sx={{ width: '480px', marginTop: '1rem', textAlign: 'center' }}
/>
<Box mt="1rem" textAlign="center" width="480px">
<CommentCard isFilled content="나만의 인사이트 만들어보기(공사중 🚧)" />
</Box>
</Box>
</Box>
<Box sx={{ maxWidth: '1500px', marginTop: '2rem' }}>
Expand Down
80 changes: 80 additions & 0 deletions src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client';

import React, { useState } from 'react';

import { Box, TextField, Button } from '@mui/material';

import color from '@/constants/color';

interface ChatInputProps {
onSendMessage: (message: string) => void;
}

const ChatInput = ({ onSendMessage }: ChatInputProps) => {
const [userMessage, setUserMessage] = useState<string>('');

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUserMessage(event.target.value);
};

const handleSendMessage = () => {
if (userMessage.trim() === '') return;
onSendMessage(userMessage);
setUserMessage('');
};

const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
handleSendMessage();
}
};

return (
<Box
sx={{
display: 'flex',
width: '100%',
maxWidth: '600px',
borderRadius: '20px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
}}
>
<TextField
fullWidth
disabled={userMessage.trim() === ''}
placeholder="AI 산지니에게 대화를 요청해보세요!"
sx={{
marginRight: '1rem',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderRadius: '20px',
'& .MuiOutlinedInput-root': {
'& fieldset': {
border: 'none',
},
},
}}
value={userMessage}
variant="outlined"
onChange={handleInputChange}
onKeyPress={handleKeyPress}
/>
<Button
color="primary"
sx={{
background: `linear-gradient(45deg, ${color.gradient_blue_dark}, ${color.gradient_blue_light})`,
color: 'white',
borderRadius: '20px',
'&:hover': {
background: `linear-gradient(45deg, ${color.gradient_blue_light}, ${color.gradient_blue_dark})`,
},
}}
variant="contained"
onClick={handleSendMessage}
>
전송
</Button>
</Box>
);
};

export default ChatInput;
40 changes: 40 additions & 0 deletions src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';

import { Box, Button } from '@mui/material';

import CommentCard from '@/components/CommentCard';

interface ButtonProps {
id: number;
text: string;
onClick: () => void;
}

interface ChatMessageProps {
content?: string;
isUser?: boolean;
delay: number;
buttons?: ButtonProps[];
}

const ChatMessage = ({ content, isUser, delay, buttons }: ChatMessageProps) => {
return (
<Box
style={{ animationDelay: `${delay}s`, animationName: 'fadeIn', animationDuration: '0.5s' }}
sx={{ display: 'flex', justifyContent: isUser ? 'flex-end' : 'flex-start', marginY: '0.5rem' }}
>
{content && <CommentCard isChat isStroke content={content} isCharacter={!isUser} isFilled={isUser} />}
{buttons && (
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
{buttons.map((button) => (
<Button key={button.id} color="primary" variant="contained" onClick={button.onClick}>
{button.text}
</Button>
))}
</Box>
)}
</Box>
);
};

export default ChatMessage;
Loading

0 comments on commit 15a7492

Please sign in to comment.