diff --git a/src/api/chatbot/system-message.ts b/src/api/chatbot/system-message.ts new file mode 100644 index 0000000..9c12af0 --- /dev/null +++ b/src/api/chatbot/system-message.ts @@ -0,0 +1 @@ +export const BOT_SYSTEM_MESSAGE = `` diff --git a/src/commons/enums/Chatbot.ts b/src/commons/enums/Chatbot.ts new file mode 100644 index 0000000..4594b5f --- /dev/null +++ b/src/commons/enums/Chatbot.ts @@ -0,0 +1,5 @@ +export enum CHATBOT_ROLE { + USER = 'user', + ASSISTANT = 'assistant', + SYSTEM = 'system', +} diff --git a/src/commons/types/chatbot-types.ts b/src/commons/types/chatbot-types.ts new file mode 100644 index 0000000..7b76aa8 --- /dev/null +++ b/src/commons/types/chatbot-types.ts @@ -0,0 +1,13 @@ +export interface ChatCommandContext { + addIntent(intent: string): void +} + +export interface ChatCommandArgs { + context?: ChatCommandContext +} + +export const NullChatCommandContext: ChatCommandContext = { + addIntent(intent: string) { + // do nothing + }, +} diff --git a/src/components/dso-chat-bot/DsoChatbot.tsx b/src/components/dso-chat-bot/DsoChatbot.tsx new file mode 100644 index 0000000..0682d23 --- /dev/null +++ b/src/components/dso-chat-bot/DsoChatbot.tsx @@ -0,0 +1,208 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import classNames from 'classnames' +import TrashBin from 'img/icons/trashbin.svg' +import UserAvatar from 'img/icons/user_avatar.svg' +import AssistantAvatar from 'img/icons/assisstant_avatar.svg' +import { CHATBOT_ROLE } from 'commons/enums/Chatbot' +import { BOT_SYSTEM_MESSAGE } from 'api/chatbot/system-message' +import { Input } from '@grafana/ui' +import { Button } from 'components/button/Button' + +import './dso-chat-bot.scss' + +interface ChatBotMessage { + role: CHATBOT_ROLE + message: string + id: string + includeInContextHistory: boolean + includeInChatPanel: boolean +} + +export const DsoChatBot = () => { + /** States and Refs */ + const [text, setText] = useState('') + const [chatContent, setChatContent] = useState(undefined) + const chatContentRef = useRef(null) + const textInputRef = useRef(null) + + const addMessageToChatContent = useCallback( + (text: string, role: CHATBOT_ROLE, includeInContextHistory: boolean, includeInChatPanel: boolean) => { + if (text) { + setChatContent((prev) => { + return [ + ...(prev || []), + { + // TODO not a good key + id: text, + message: text, + role: role, + includeInContextHistory: includeInContextHistory, + includeInChatPanel: includeInChatPanel, + }, + ] + }) + } + setText('') + }, + [] + ) + + const handleNewUserMessage = useCallback(async () => { + addMessageToChatContent(text, CHATBOT_ROLE.USER, true, true) + + const newContent = [ + ...(chatContent || []), + { + message: text, + role: CHATBOT_ROLE.USER, + includeInContextHistory: true, + }, + ] + const messages = newContent + .filter(({ includeInContextHistory }) => includeInContextHistory) + .map(({ message, role }) => ({ + role: role, + content: message, + })) + + console.log('messages: ', messages) + + const url = `https://dso.dev.meeraspace.com/chatbot-api/v1/generate` + + let options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ messages: [], functions: [] }), + } + const response = await fetch(url, options) + console.log('response:::::::::', response) + }, [addMessageToChatContent, chatContent, text]) + + /** Callbacks */ + + // + const initializeChatContext = useCallback(() => { + setChatContent(undefined) + addMessageToChatContent(BOT_SYSTEM_MESSAGE, CHATBOT_ROLE.SYSTEM, true, false) + addMessageToChatContent(`How can I help you?`, CHATBOT_ROLE.ASSISTANT, false, true) + }, [addMessageToChatContent]) + // + useEffect(() => { + if (chatContent === undefined) { + initializeChatContext() + } + }, [chatContent, initializeChatContext]) + // + useEffect(() => { + if (chatContentRef && chatContentRef.current) { + // @ts-ignore + chatContentRef.current.scrollTo(0, chatContentRef.current.scrollHeight * 100) + } + }, [chatContent]) + // + + useEffect(() => { + if (textInputRef && textInputRef.current) { + // @ts-ignore + textInputRef.current.focus() + } + }, [textInputRef, chatContent]) + + /** Renderer */ + return ( +
+ { +
+ Talk to New Oil Management +
+
+
+ } +
+ {chatContent && + chatContent + .filter(({ includeInChatPanel }) => includeInChatPanel) + .map(({ message, id, role }) => ( +
+
+ {role === CHATBOT_ROLE.ASSISTANT ? ( + Bot + ) : ( + User + )} +
+
+ +
+
+ ))} +
+ +
+ { + const value = e.currentTarget.value + setText(value) + }} + onKeyDown={(event) => { + if (!event.shiftKey && event.key === 'Enter') { + event.preventDefault() + handleNewUserMessage() + } + }} + style={{ + marginBottom: '8px', + }} + className={classNames('searchInput')} + /> +
+
+ ) +} diff --git a/src/components/dso-chat-bot/dso-chat-bot.scss b/src/components/dso-chat-bot/dso-chat-bot.scss new file mode 100644 index 0000000..ebafa8d --- /dev/null +++ b/src/components/dso-chat-bot/dso-chat-bot.scss @@ -0,0 +1,176 @@ +$input-height: 54px; +$header-height: 36px; +$footer-height: 36px; +$chat-panel-max-height: 400px; +$chat-panel-min-height: 72px; +$input-container-max-height: 144px; +$input-container-min-height: 72px; +$input-vertical-margin: 8px; + +.dsoChartBot { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + width: 100%; + height: 100%; + overflow: auto; + + &-header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: $header-height; + padding: 0 10px; + border-radius: 10px 10px 0 0; + background-color: #669; + + .buttonWrapper-button { + $button-size: 18px; + + min-width: $button-size; + max-width: $button-size; + min-height: $button-size; + max-height: $button-size; + } + + &-actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + column-gap: 5px; + } + + &-text { + color: #fff; + font-size: 16px; + font-weight: 700; + } + } + + &-footer { + display: flex; + align-items: center; + justify-content: flex-end; + width: 100%; + height: fit-content; + margin-top: 10px; + } + + &-inputContainer { + display: flex; + align-items: center; + + justify-content: center; + width: 100%; + height: $input-height; + padding: 0 8px; + overflow: auto; + border-right: solid 1px #669; + border-bottom: solid 1px #669; + border-left: solid 1px #669; + border-radius: 0 0 10px 10px; + background-color: #fff; + + .md-text-field-container { + padding-left: 5px; + border: solid 1px #669; + border-radius: 4px; + } + + .md-text { + font-size: 16px; + } + + .md-divider--text-field { + display: none; + } + } + + &-chatPanel { + display: flex; + flex-wrap: wrap; + height: calc(100% - $header-height - $input-height - $input-vertical-margin * 2); + width: 100%; + padding: 16px; + overflow-x: hidden; + overflow-y: auto; + border-right: solid 1px #669; + border-left: solid 1px #669; + background-color: #fff; + + &-commandButtonWrapper { + width: fit-content; + margin-right: 5px; + margin-bottom: 5px; + } + + &-messageContainer { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + height: fit-content; + padding: 2px 5px; + + &-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + min-width: 36px; + height: 36px; + + &-image { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + } + } + + &-avatar.user { + margin-left: 12px; + } + + &-avatar.assistant { + margin-right: 12px; + } + + &-message { + width: fit-content; + min-width: 64px; + height: fit-content; + padding: 5px 12px; + border-radius: 18px; + + &-messageText { + font-size: 16px; + white-space: pre-line; + } + + &-messageText.user { + color: #fff; + } + + &-messageText.assistant { + color: #000; + } + } + + &-message.assistant { + background-color: rgba(0, 0, 0, 0.05); + } + + &-message.user { + background-color: #1e90ff; + } + } + + &-messageContainer.user { + flex-direction: row-reverse; + } + } +}