ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ChatBot 만들기 - 3)frontend template 만들기 (textQuery, eventQuery Function + 리덕스로 메세지 저장하고 보여주기)
    NODE.JS 2021. 5. 15. 10:00

    이제는 프론트엔드 부분을 만들어 보자. 

    순서
    만들고자 하는 템플릿

    App.js안에 대화창을 그대로 만들면 복잡해질 수 있으니, 

    src>Chatbot>Chatbot.js파일을 만들고 App.js안에 넣어주자. (컴포넌트로)

     

    import React from "react";
    import { Typography, Icon } from 'antd';
    import Chatbot from './Chatbot/Chatbot';
    const { Title } = Typography;
    
    function App() {
      return (
        <div>
          <div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
            <Title level={2} >CHAT BOT APP&nbsp;<Icon type="robot" /></Title>
          </div>
          <div style={{ display: 'flex', justifyContent: 'center' }}>
           
            <Chatbot />
    
    
          </div>
        </div>
      )
    }
    
    export default App
    

     

    chatbot.js

     

     

    대화창 템플릿을 만들어보자. 

    import React, { useEffect } from 'react';
    
    function Chatbot() {
        
    
        return (
            <div style={{
                height: 700, width: 700,
                border: '3px solid black', borderRadius: '7px'
            }}>
                <div style={{ height: 644, width: '100%', overflow: 'auto' }}>
    
    
    
                </div>
                <input
                    style={{
                        margin: 0, width: '100%', height: 50,
                        borderRadius: '4px', padding: '5px', fontSize: '1rem'
                    }}
                    placeholder="Send a message..."
                    onKeyPress={keyPressHanlder}
                    type="text"
                />
    
            </div>
        )
    }
    
    export default Chatbot;
    

     

     

    입력창에 텍스트를 입력하고 엔터 누르면 그대로 대화창에 나타나도록 설정해주자. 

        //텍스트 입력하고 엔터 입력하면 대화창에 텍스트 나오도록
        const keyPressHanlder = (e) => {
            if (e.key === "Enter") {
    
                if (!e.target.value) {
                    return alert('you need to type somthing first')
                }
    
                //we will send request to text query route 
                textQuery(e.target.value)
    
    
                e.target.value = ""; //한번 보내고 나면 비워준다.
            }
        }
    
    
    
                <input
                    style={{
                        margin: 0, width: '100%', height: 50,
                        borderRadius: '4px', padding: '5px', fontSize: '1rem'
                    }}
                    placeholder="Send a message..."
                    onKeyPress={keyPressHanlder}
                    type="text"
                />

     

     

    이제 textQuery 함수를 만들어주자 .

     

    순서
    텍스트 쿼리를 보면 text안에 text가 존재하는 형식이므로 이것을 따라서 만들어줘야한다. 

     

    import React, { useEffect } from 'react';
    import Axios from 'axios';
    
    function Chatbot() {
    
    
        const textQuery = async (text) => {
        
    
            //  First  Need to  take care of the message I sent 내가 보낸 메세지를 보여준다.  
            let conversation = {
                who: 'user', //누가 보냈는지
                content: { //보낸 내용(텍스트 안에 텍스트 그다음 진짜 텍스트)
                    text: {
                        text: text
                    }
                }
            }
    
    
    
            // We need to take care of the message Chatbot sent 내가 받은 메세지를 보여준다.
            const textQueryVariables = {
                text
            }
            try {
                //I will send request to the textQuery ROUTE 여기서 async 이기 때문에 await를 해준다.
                const response = await Axios.post('/api/dialogflow/textQuery', textQueryVariables)
    
                for (let content of response.data.fulfillmentMessages) {//결과로 들어오는 정보들이 많기 때문에 for문 돌린다.
    
                    conversation = {
                        who: 'bot',
                        content: content
                    }
    
    
    
            } catch (error) {
                conversation = {
                    who: 'bot',
                    content: {
                        text: {
                            text: " Error just occured, please check the problem"
                        }
                    }
                }
    
    
    
            }
    
        }
        
        
        
        
        
    
    

     

     

    event query 기능도 만들자.

     

     

     

        const eventQuery = async (event) => {
    
            // We need to take care of the message Chatbot sent 
            const eventQueryVariables = {
                event
            }
            try {
                //I will send request to the eventQuery ROUTE 
                const response = await Axios.post('/api/dialogflow/eventQuery', eventQueryVariables)
                for (let content of response.data.fulfillmentMessages) {
    
                    let conversation = {
                        who: 'bot',
                        content: content
                    }
    
                    dispatch(saveMessage(conversation))
                }
    
    
            } catch (error) {
                let conversation = {
                    who: 'bot',
                    content: {
                        text: {
                            text: " Error just occured, please check the problem"
                        }
                    }
                }
            }
    
        }

     

     

     

    여기서 textquery는 우리가 엔터를 누를때 실행이 되는데, 

    eventQuery는 언제 실행되는지 아직 설정이 되어있지않다. 

    우리가 웹사이트에 들어갈때마다 실행이 되도록 설정해주자.

    (event설정)

    function Chatbot() {
    
        useEffect(() => {//웹이 시작될때 trigger할 것이다. 이벤트 이름을 넣어준다.
    
            eventQuery('welcomeToMyWebsite')
    
        }, [])
    

     

     

    아직 화면창에 메세지를 보여주는 것이 없는데,

    이제 메세지를 보여줄 때 리덕스를 사용헤서 메세지를 저장하고 보여주도록 설정하자. 

     

    (페이지를 리프레시 할 때마다 대화는 새로 시작되므로 db에 넣지는 않을 것이다)

    (메세지를 db에 저장하지않고 저장하는 방법은 state에 저장하는 방법과 redux로 저장하는 방법 두가지가 있다)

    순서

     

    client>index.js 파일안에 redux를 포함시켜준다. 

     

     

    import React from "react";
    import ReactDOM from "react-dom";
    import './index.css';
    import "antd/dist/antd.css";
    
    import App from "./App";
    
    import Reducer from './_reducers';
    import { Provider } from 'react-redux';
    import { createStore, applyMiddleware } from 'redux';
    import promiseMiddleware from 'redux-promise';
    import ReduxThunk from 'redux-thunk';
    import { BrowserRouter } from "react-router-dom";
    
    import * as serviceWorker from "./serviceWorker";
    const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
    
    ReactDOM.render(
      <Provider
        store={createStoreWithMiddleware(
          Reducer,
          window.__REDUX_DEVTOOLS_EXTENSION__ &&
          window.__REDUX_DEVTOOLS_EXTENSION__()
        )}
      >
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Provider>
    
      ,
      document.getElementById("root")
    );
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister();

     

    또 리덕스를 사용할때 쓰는 메소드를 actions>message_actions.js파일안에 넣어준다. 

    import {
        SAVE_MESSAGE,
    } from './types';
    
    export function saveMessage(dataToSubmit) {
       
        return {
            type: SAVE_MESSAGE,
            payload: dataToSubmit
        }
    }
    

     

     

    actions>types.js도 다음과 같이 변경해준다.

    export const SAVE_MESSAGE = 'save_message';

     

    reducers>message_reducer.js파일을 만들고 다음과 같이 추가해준다.

    import {
        SAVE_MESSAGE,
    } from '../_actions/types';
    
    //원래는 빈 배열이었던것 안에 text 내용들(action.payload)를 더해준다.
    
    export default function (state = {messages:[]}, action) {
        switch (action.type) {
            case SAVE_MESSAGE:
                return {
                    ...state,
                    messages: state.messages.concat(action.payload)
                }
            default:
                return state;
        }
    }

     

    다시 chatbot.js는 이를 이용해서 리덕스를 사용한다. (dispatch)

    내가 작성한 text뿐만아니라 챗봇이 입력한 내용도 text에 concat되도록 모두 입력해준다.

    import { useDispatch, useSelector } from 'react-redux';
    import { saveMessage } from '../_actions/message_actions';
    
    
    
    unction Chatbot() {
        const dispatch = useDispatch();
        const messagesFromRedux = useSelector(state => state.message.messages)
    
        const textQuery = async (text) => {
    
            //  First  Need to  take care of the message I sent 내가 보낸 메세지를 보여준다.  
            let conversation = {
                who: 'user', //누가 보냈는지
                content: { //보낸 내용(텍스트 안에 텍스트 그다음 진짜 텍스트)
                    text: {
                        text: text
                    }
                }
            }
    
            dispatch(saveMessage(conversation))
            // console.log('text I sent', conversation)
    
            // We need to take care of the message Chatbot sent 내가 받은 메세지를 보여준다.
            const textQueryVariables = {
                text
            }
            try {
                //I will send request to the textQuery ROUTE 여기서 async 이기 때문에 await를 해준다.
                const response = await Axios.post('/api/dialogflow/textQuery', textQueryVariables)
    
                for (let content of response.data.fulfillmentMessages) {//결과로 들어오는 정보들이 많기 때문에 for문 돌린다.
    
                    conversation = {
                        who: 'bot',
                        content: content
                    }
    
                    dispatch(saveMessage(conversation))
                }
    
    
            } catch (error) {
                conversation = {
                    who: 'bot',
                    content: {
                        text: {
                            text: " Error just occured, please check the problem"
                        }
                    }
                }
    
                dispatch(saveMessage(conversation))
    
    
            }
    
        }
    
    
        const eventQuery = async (event) => {
    
            // We need to take care of the message Chatbot sent 
            const eventQueryVariables = {
                event
            }
            try {
                //I will send request to the eventtQuery ROUTE 
                const response = await Axios.post('/api/dialogflow/eventQuery', eventQueryVariables)
                for (let content of response.data.fulfillmentMessages) {
    
                    let conversation = {
                        who: 'bot',
                        content: content
                    }
    
                    dispatch(saveMessage(conversation))
                }
    
    
            } catch (error) {
                let conversation = {
                    who: 'bot',
                    content: {
                        text: {
                            text: " Error just occured, please check the problem"
                        }
                    }
                }
                dispatch(saveMessage(conversation))
            }
    
        }
    

     

    내가 입력하면 빈 array가 text를 가지게 된 것을 확인할 수 있다. 
    하나 더 입력하면 배열에 추가가 된다.

     

     

    이제는 저장된 내용들을 chat panel에 나오도록 지정해본다. 

     

    순서

    메세지를 보여주는 컴포넌트를 챗봇.js안에 넣으면 복잡해질 수 있어서 

    따로 빼서 작성해준다. 

     

    챗봇.js를 다음과 같이 수정해준다. 

    import React, { useEffect } from 'react';
    import Axios from 'axios';
    import { useDispatch, useSelector } from 'react-redux';
    import { saveMessage } from '../_actions/message_actions';
    import Message from './Sections/Message';
    
    function Chatbot() {
        const dispatch = useDispatch();
        //모든 메세지를 리덕스로부터 가져오기 위해서 useSelector를 사용한다. 
        const messagesFromRedux = useSelector(state => state.message.messages)
    
    
    
        const renderMessage = (returnedMessages) => {
    
    		//하나씩 다 드러내기 위해서 map 을 이용한다. 
            if (returnedMessages) {
                return returnedMessages.map((message, i) => {
                    return renderOneMessage(message, i);
                })
            } else {
                return null;
            }
        }
     
      return (
    
                    {renderMessage(messagesFromRedux)}
    
    
    )
    
    export default Chatbot;
    

     

    state.message.messages인 이유는 리덕스 스토어의 모습때문이다.

    여기서 renderOneMessage를 나타내주도록 하자. 

    디자인은 antd를 사용할 것이다. 

     

    import { List, Icon, Avatar } from 'antd';
        
        const renderOneMessage = (message, i) => {
            console.log('message', message)
    
                return <div>
                    <List.Item key = {i} style={{ padding: '1rem' }}>
                        <List.Item.Meta
                            avatar={<Avatar />}
                            title={message.who}
                            description={message.content.text.text}
                        />
                    </List.Item>
                </div>
            }
    
    
    
    
    
    
            // template for card message 
    
    
    
    
        }

    여기서 채팅창에 드러나게 되는 메세지 구조는 계속 사용되므로 

    sections>message.js파일을 만들어서 옮겨주자.

     

    import React from 'react'
    import { List, Icon, Avatar } from 'antd';
    
    function Message(props) {
    
        //채팅창의 프로필을 나타내기 위해서 말하는 사람이 아바타냐 아니냐에 따라서 icon다르게 보이게 설정
        const AvatarSrc = props.who ==='bot' ? <Icon type="robot" /> : <Icon type="smile" />  
    
        return (
            <List.Item style={{ padding: '1rem' }}>
                <List.Item.Meta
                    avatar={<Avatar icon={AvatarSrc} />}
                    title={props.who}
                    description={props.text}
                />
            </List.Item>
        )
    }
    
    export default Message
    

     

    chatbot.js도 다음과 같이 변경해준다.

    import Message from './Sections/Message';
    
    
                return <Message key={i} who={message.who} text={message.content.text.text} />
    

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

Designed by Tistory.