08-chat.js 8.24 KB
Newer Older
박민석's avatar
박민석 committed
1
/* eslint-disable no-undef */
minseok.park's avatar
minseok.park committed
2 3 4 5 6 7
;(function () {
  'use strict'

  const chatButton = document.querySelector('.sidebar .chat-btn')
  const chat = document.querySelector('.sidebar .chat')
  const chatIcon = document.querySelector('.sidebar .chat-btn svg')
박민석's avatar
박민석 committed
8 9 10
  const chatTextArea = document.getElementById('chat-input')
  const sendButton = document.getElementById('send-btn')
  const chatBody = document.querySelector('.sidebar .chat .chat-body')
11
  const expandButton = document.querySelector('#expand-btn svg')
박민석's avatar
박민석 committed
12 13 14

  let INIT = false
  let isComposing = false
minseok.park's avatar
minseok.park committed
15
  let isLoading = false
박민석's avatar
박민석 committed
16
  const pathname = window.location.pathname.split('/')?.[3] || ''
박민석's avatar
박민석 committed
17
  let docType = pathname !== 'swlab-docs' ? pathname : '' // 선택된 제품 유형을 저장할 변수
minseok.park's avatar
minseok.park committed
18 19 20

  const openIconPath = `
    <path stroke-linecap="round" stroke-linejoin="round"
21 22 23 24 25 26 27 28
    d="M7.188 10a.312.312 0 1 1-.625 0
    .312.312 0 0 1 .625 0Zm0 0h-.313m3.438 0a.312.312 0
    1 1-.625 0 .312.312 0 0 1 .625 0Zm0 0h-.313m3.437 0a.312.312 0
    1 1-.625 0 .312.312 0 0 1 .625 0Zm0 0h-.312M17.5 10c0 3.797-3.353
    6.875-7.5 6.875a8.735 8.735 0 0 1-2.292-.303 5.368 5.368 0 0 1-3.639
    1.576 5.364 5.364 0 0 1-.428-.059 4.025 4.025 0 0 0 .88-1.818c.081-.409
    -.12-.806-.422-1.098C3.273 13.484 2.5 11.825 2.5 10c0-3.797 3.353-6.875
    7.5-6.875s7.5 3.078 7.5 6.875Z"></path>
minseok.park's avatar
minseok.park committed
29 30
  `
  const closeIconPath = `
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    <path clip-rule="evenodd" fill-rule="evenodd"
    d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12
    14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z"></path>
  `

  const collapseIconPath = `
    <path
    d="M3.28 2.22a.75.75 0 0 0-1.06 1.06L5.44 6.5H2.75a.75.75 0 0 0 0 1.5h4.5
    A.75.75 0 0 0 8 7.25v-4.5a.75.75 0 0 0-1.5 0v2.69L3.28 2.22ZM13.5 2.75
    a.75.75 0 0 0-1.5 0v4.5c0 .414.336.75.75.75h4.5a.75.75 0 0 0 0-1.5h-2.69l3.22-3.22
    a.75.75 0 0 0-1.06-1.06L13.5 5.44V2.75ZM3.28 17.78l3.22-3.22v2.69a.75.75 0 0 0 1.5
    0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75
    0 1 0 1.06 1.06ZM13.5 14.56l3.22 3.22a.75.75 0 1 0 1.06-1.06l-3.22-3.22h2.69
    a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0v-2.69Z">
    </path>
  `

  const expandIconPath = `
    <path d="
    m13.28 7.78 3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75
    0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 0 0 1.06 1.06ZM2 17.25v-4.5a.75.75 0 0 1 1.5 0v2.69l3.22-3.22a.75.75
    0 0 1 1.06 1.06L4.56 16.5h2.69a.75.75 0 0 1 0 1.5h-4.5a.747.747 0 0 1-.75-.75ZM12.22
    13.28l3.22 3.22h-2.69a.75.75 0 0 0 0 1.5h4.5a.747.747 0 0 0 .75-.75v-4.5a.75.75
    0 0 0-1.5 0v2.69l-3.22-3.22a.75.75 0 1 0-1.06 1.06ZM3.5 4.56l3.22 3.22a.75.75
    0 0 0 1.06-1.06L4.56 3.5h2.69a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0V4.56Z">
    </path>
minseok.park's avatar
minseok.park committed
57 58
  `

minseok.park's avatar
minseok.park committed
59
  function initializeChat () {
박민석's avatar
박민석 committed
60 61
    if (!INIT) {
      const messageItem = document.createElement('pre')
박민석's avatar
박민석 committed
62 63 64
      const productList = document.createElement('ul') // ul 요소 생성
      const products = ['BXM', 'BXCM', 'BXCP', 'BXI']

박민석's avatar
박민석 committed
65
      messageItem.className = 'message ai'
박민석's avatar
박민석 committed
66 67 68
      messageItem.textContent = pathname === 'swlab-docs'
        ? '뱅크웨어글로벌 소프트웨어 연구소 챗봇입니다. 어떤 제품 가이드에 대한 질문을 하시겠습니까?'
        : `뱅크웨어글로벌 소프트웨어 연구소 챗봇입니다. ${pathname.toUpperCase()}제품 가이드에 대한 질문을 해주세요.`
박민석's avatar
박민석 committed
69
      chatBody.appendChild(messageItem)
박민석's avatar
박민석 committed
70

박민석's avatar
박민석 committed
71 72 73 74 75 76 77 78 79
      if (pathname === 'swlab-docs') {
        products.forEach((product) => {
          const productItem = document.createElement('li')
          productItem.textContent = product
          productItem.addEventListener('click', () => {
            docType = product // 클릭한 제품명을 docType에 저장
            createAiMessage(`${docType} 가이드에 대한 도움을 드리겠습니다.`)
          })
          productList.appendChild(productItem)
박민석's avatar
박민석 committed
80 81
        })

박민석's avatar
박민석 committed
82 83
        chatBody.appendChild(productList)
      }
박민석's avatar
박민석 committed
84

박민석's avatar
박민석 committed
85 86 87 88 89 90 91 92 93 94 95 96 97
      INIT = true

      // Composition events
      chatTextArea.addEventListener('compositionstart', () => {
        isComposing = true
      })

      chatTextArea.addEventListener('compositionend', () => {
        isComposing = false
      })

      // Keydown event
      chatTextArea.addEventListener('keydown', function (e) {
minseok.park's avatar
minseok.park committed
98
        if (e.key === 'Enter' && !e.shiftKey && !isComposing && !isLoading) {
박민석's avatar
박민석 committed
99 100 101
          e.preventDefault() // Prevent the default action of Enter (which is to create a new line)
          const { value } = e.target
          if (value.trim() !== '') {
minseok.park's avatar
minseok.park committed
102
            handleUserMessage(value)
박민석's avatar
박민석 committed
103 104 105 106 107
          }
        }
      })

      sendButton.addEventListener('click', () => {
minseok.park's avatar
minseok.park committed
108 109 110 111
        if (!isLoading) {
          const message = chatTextArea.value
          handleUserMessage(message)
        }
박민석's avatar
박민석 committed
112 113 114 115
      })
    }
  }

minseok.park's avatar
minseok.park committed
116 117 118 119 120 121 122
  chatButton.addEventListener('click', () => {
    chat.classList.toggle('show')

    if (chat.classList.contains('show')) {
      chatIcon.innerHTML = closeIconPath
      chatIcon.setAttribute('fill', 'none')
      chatIcon.setAttribute('stroke', '#fff')
123
      chatIcon.setAttribute('viewBox', '0 -1 24 24')
minseok.park's avatar
minseok.park committed
124 125 126 127 128 129
    } else {
      chatIcon.innerHTML = openIconPath
      chatIcon.setAttribute('fill', '#fff')
      chatIcon.setAttribute('stroke', '#0072ce')
      chatIcon.setAttribute('viewBox', '0 0 20 20')
    }
박민석's avatar
박민석 committed
130 131 132 133

    if (!INIT) {
      initializeChat()
    }
minseok.park's avatar
minseok.park committed
134
  })
박민석's avatar
박민석 committed
135

136 137 138 139 140 141 142
  expandButton.addEventListener('click', () => {
    expandButton.classList.toggle('expand')

    if (expandButton.classList.contains('expand')) {
      expandButton.innerHTML = collapseIconPath
      expandButton.setAttribute('fill', '#fff')
      expandButton.setAttribute('viewBox', '0 0 20 20')
minseok.park's avatar
minseok.park committed
143
      chat.classList.add('expand')
144 145 146 147
    } else {
      expandButton.innerHTML = expandIconPath
      expandButton.setAttribute('fill', '#fff')
      expandButton.setAttribute('viewBox', '0 0 20 20')
minseok.park's avatar
minseok.park committed
148
      chat.classList.remove('expand')
149 150 151
    }
  })

minseok.park's avatar
minseok.park committed
152 153 154 155 156 157 158
  function handleUserMessage (message) {
    if (message.trim() !== '') {
      addMessageToChatBody(message)
      chatTextArea.value = ''
    }
  }

minseok.park's avatar
minseok.park committed
159
  function addMessageToChatBody (message) {
박민석's avatar
박민석 committed
160 161 162 163 164 165 166
    if (message.trim() !== '') {
      const user = document.createElement('pre')
      user.className = 'message user'
      user.textContent = message
      chatBody.appendChild(user)
      chatBody.scrollTop = chatBody.scrollHeight

박민석's avatar
박민석 committed
167 168 169 170
      if (!docType) {
        createAiMessage('가이드 도움에 필요한 제품을 선택해주세요.')
        return
      }
박민석's avatar
박민석 committed
171

박민석's avatar
박민석 committed
172
      isLoading = true
minseok.park's avatar
minseok.park committed
173

박민석's avatar
박민석 committed
174 175 176 177 178 179
      const chatLoader = document.createElement('div')
      chatLoader.className = 'chat-loader'
      const loader = document.createElement('div')
      loader.className = 'loader'
      chatLoader.appendChild(loader)
      chatBody.appendChild(chatLoader)
박민석's avatar
박민석 committed
180 181
      chatBody.scrollTop = chatBody.scrollHeight

박민석's avatar
박민석 committed
182 183
      const payload = {
        question: message,
박민석's avatar
박민석 committed
184
        docsType: docType.toLowerCase(),
박민석's avatar
박민석 committed
185 186
      }

박민석's avatar
박민석 committed
187
      fetch('https://docs.bxi.link/ask', {
박민석's avatar
박민석 committed
188 189 190 191 192
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
박민석's avatar
박민석 committed
193 194
      })
        .then((response) => {
박민석's avatar
박민석 committed
195 196 197
          const reader = response.body?.getReader()
          const decoder = new TextDecoder('utf-8')

박민석's avatar
박민석 committed
198 199 200
          const messageItem = document.createElement('pre')
          messageItem.className = 'message ai'

박민석's avatar
박민석 committed
201 202 203
          function readStream () {
            return reader?.read().then(({ done, value }) => {
              if (done) {
박민석's avatar
박민석 committed
204
                chatBody.removeChild(chatLoader)
박민석's avatar
박민석 committed
205 206 207 208 209 210 211 212 213 214
                isLoading = false
                return
              }
              const chunk = decoder.decode(value, { stream: true })
              messageItem.textContent += chunk
              chatBody.scrollTop = chatBody?.scrollHeight
              return readStream()
            })
          }

박민석's avatar
박민석 committed
215 216 217
          chatBody.appendChild(messageItem)
          chatBody.scrollTop = chatBody.scrollHeight

박민석's avatar
박민석 committed
218
          return readStream()
박민석's avatar
박민석 committed
219 220 221
        })
        .catch((error) => {
          console.error('Chatbot Error', error)
박민석's avatar
박민석 committed
222
          chatBody.removeChild(chatLoader)
박민석's avatar
박민석 committed
223
          isLoading = false
박민석's avatar
박민석 committed
224
        })
박민석's avatar
박민석 committed
225 226
    }
  }
박민석's avatar
박민석 committed
227

박민석's avatar
박민석 committed
228
  function createAiMessage (message) {
박민석's avatar
박민석 committed
229 230 231 232 233 234
    const messageItem = document.createElement('pre')
    messageItem.className = 'message ai'
    messageItem.textContent = message
    chatBody.appendChild(messageItem)
    chatBody.scrollTop = chatBody.scrollHeight
  }
minseok.park's avatar
minseok.park committed
235
})()