diff --git a/CHANGELOG.md b/CHANGELOG.md index a511663b..c71fb81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Features - introduces Pipeline to execute asynchronous operations ([#376](https://github.com/opensearch-project/dashboards-assistant/pull/376)) +- Chatbot entry UI redesign ([#396](https://github.com/opensearch-project/dashboards-assistant/pull/396)) ### Enhancements diff --git a/public/chat_header_button.test.tsx b/public/chat_header_button.test.tsx index b4aac2b6..82cf0361 100644 --- a/public/chat_header_button.test.tsx +++ b/public/chat_header_button.test.tsx @@ -4,12 +4,13 @@ */ import React from 'react'; -import { act, render, fireEvent, screen } from '@testing-library/react'; +import { act, render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { BehaviorSubject } from 'rxjs'; import { HeaderChatButton } from './chat_header_button'; -import { applicationServiceMock } from '../../../src/core/public/mocks'; +import { applicationServiceMock, chromeServiceMock } from '../../../src/core/public/mocks'; +import { HeaderVariant } from '../../../src/core/public'; import { AssistantActions } from './types'; -import { BehaviorSubject } from 'rxjs'; import * as coreContextExports from './contexts/core_context'; import { MountWrapper } from '../../../src/core/public/utils'; @@ -51,6 +52,7 @@ jest.mock('./services', () => { }; }); +const chromeStartMock = chromeServiceMock.createStartContract(); const sideCarHideMock = jest.fn(() => { const element = document.getElementById('sidecar-mock-div'); if (element) { @@ -64,6 +66,9 @@ const sideCarRefMock = { // mock sidecar open,hide and show jest.spyOn(coreContextExports, 'useCore').mockReturnValue({ + services: { + chrome: chromeStartMock, + }, overlays: { // @ts-ignore sidecar: () => { @@ -236,4 +241,21 @@ describe('', () => { expect(sideCarHideMock).toHaveBeenCalled(); expect(sideCarRefMock.close).toHaveBeenCalled(); }); + + it('should render toggle chat flyout button icon', () => { + chromeStartMock.getHeaderVariant$.mockReturnValue( + new BehaviorSubject(HeaderVariant.APPLICATION) + ); + render( + + ); + expect(screen.getByLabelText('toggle chat flyout button icon')).toBeInTheDocument(); + }); }); diff --git a/public/chat_header_button.tsx b/public/chat_header_button.tsx index c4a48b4e..73ab081d 100644 --- a/public/chat_header_button.tsx +++ b/public/chat_header_button.tsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiBadge, EuiFieldText, EuiIcon } from '@elastic/eui'; +import { EuiBadge, EuiFieldText, EuiIcon, EuiButtonIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useEffectOnce } from 'react-use'; +import { useEffectOnce, useObservable } from 'react-use'; -import { ApplicationStart, SIDECAR_DOCKED_MODE } from '../../../src/core/public'; +import { ApplicationStart, HeaderVariant, SIDECAR_DOCKED_MODE } from '../../../src/core/public'; import { getIncontextInsightRegistry } from './services'; import { ChatFlyout } from './chat_flyout'; import { ChatContext, IChatContext } from './contexts/chat_context'; @@ -32,9 +32,12 @@ interface HeaderChatButtonProps { actionExecutors: Record; assistantActions: AssistantActions; currentAccount: UserAccount; + inLegacyHeader?: boolean; } export const HeaderChatButton = (props: HeaderChatButtonProps) => { + const core = useCore(); + const { inLegacyHeader } = props; const sideCarRef = useRef<{ close: Function }>(); const [appId, setAppId] = useState(); const [conversationId, setConversationId] = useState(); @@ -50,8 +53,10 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => { const flyoutVisibleRef = useRef(flyoutVisible); flyoutVisibleRef.current = flyoutVisible; const registry = getIncontextInsightRegistry(); + const headerVariant = useObservable(core.services.chrome.getHeaderVariant$()); + const isSingleLineHeader = headerVariant === HeaderVariant.APPLICATION; + const [sidecarDockedMode, setSidecarDockedMode] = useState(DEFAULT_SIDECAR_DOCKED_MODE); - const core = useCore(); const flyoutFullScreen = sidecarDockedMode === SIDECAR_DOCKED_MODE.TAKEOVER; const flyoutMountPoint = useRef(null); usePatchFixedStyle(); @@ -226,46 +231,63 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => { return ( <> -
+ {!inLegacyHeader && isSingleLineHeader && ( + setFlyoutVisible(!flyoutVisible)} + display="base" + size="s" + aria-label="toggle chat flyout button icon" + /> + )} +
setInputFocus(true)} onBlur={() => setInputFocus(false)} - placeholder="Ask question" + placeholder="Ask a question" onKeyPress={onKeyPress} onKeyUp={onKeyUp} - prepend={ - setFlyoutVisible(!flyoutVisible)} - /> - } - append={ - - {inputFocus ? ( - - ⏎ - - ) : ( - - Ctrl + / - - )} - - } + className="llm-chat-header-text-input" + /> + setFlyoutVisible(!flyoutVisible)} + className="llm-chat-toggle-icon" /> + + {inputFocus ? ( + + ⏎ + + ) : ( + + Ctrl + / + + )} + diff --git a/public/index.scss b/public/index.scss index be693d9f..cbc51a49 100644 --- a/public/index.scss +++ b/public/index.scss @@ -4,25 +4,14 @@ */ .llm-chat-header-icon-wrapper { - margin: 0 8px; - height: 48px; display: flex; align-items: center; + position: relative; + width: 200px; - .euiFormControlLayout__prepend { - background-color: transparent !important; - width: 36px !important; - } - - .euiFieldText { - width: 96px; - transition: width 0.3s ease-in-out; - - &:focus { - width: 250px; - background-image: none; - background-color: transparent; - } + &.in-legacy-header{ + margin: 0 $euiSizeS; + height: $euiSizeL * 2; } .euiIcon { @@ -31,27 +20,19 @@ } } - .llm-chat-header-shortcut { - display: flex; - align-items: center; - padding-right: 6px; + .llm-chat-header-text-input{ + padding-left: $euiSizeXL; + padding-right: $euiSizeL * 2; } - &::after { - content: ''; - display: block; + .llm-chat-toggle-icon{ position: absolute; - top: 4px; - bottom: 4px; - left: 4px; - right: 4px; - border-radius: 9px; - z-index: -1; + left: $euiSizeS; } -} -.llm-chat-header-icon-wrapper-selected { - &::after { - background-color: $ouiColorPrimary; + + .llm-chat-header-shortcut{ + position: absolute; + right: $euiSizeS; } } diff --git a/public/plugin.tsx b/public/plugin.tsx index 954a4704..8d0d8a1c 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -246,20 +246,38 @@ export class AssistantPlugin }); } - coreStart.chrome.navControls.registerRight({ - order: 10000, - mount: toMountPoint( - - - - ), - }); + if (coreStart.chrome.navGroup.getNavGroupEnabled()) { + coreStart.chrome.navControls.registerPrimaryHeaderRight({ + order: 10000, + mount: toMountPoint( + + + + ), + }); + } else { + coreStart.chrome.navControls.registerRight({ + order: 10000, + mount: toMountPoint( + + + + ), + }); + } }; setupChat(); }