/** * @file Box AI sidebar component * @author Box */ import * as React from 'react'; import flow from 'lodash/flow'; import { useIntl } from 'react-intl'; import classNames from 'classnames'; import { BoxAiAgentSelectorWithApi, useAgents } from '@box/box-ai-agent-selector'; import { IconButton, Tooltip } from '@box/blueprint-web'; import { ArrowsExpand } from '@box/blueprint-web-assets/icons/Fill'; import { BoxAiContentAnswers, ClearConversationButton, IntelligenceModal, withApiWrapper, type ApiWrapperWithInjectedProps, } from '@box/box-ai-content-answers'; import SidebarContent from './SidebarContent'; import { withAPIContext } from '../common/api-context'; import { withErrorBoundary } from '../common/error-boundary'; import { withLogger } from '../common/logger'; import { ORIGIN_BOXAI_SIDEBAR, SIDEBAR_VIEW_BOXAI } from '../../constants'; import { EVENT_JS_READY } from '../common/logger/constants'; import { mark } from '../../utils/performance'; import { BoxAISidebarContext } from './context/BoxAISidebarContext'; import BoxAISidebarTitle from './BoxAISidebarTitle'; import messages from '../common/messages'; import './BoxAISidebar.scss'; const MARK_NAME_JS_READY: string = `${ORIGIN_BOXAI_SIDEBAR}_${EVENT_JS_READY}`; mark(MARK_NAME_JS_READY); function BoxAISidebarContent(props: ApiWrapperWithInjectedProps & { shouldShowLandingPage: boolean }) { const { createSession, encodedSession, onClearAction, getAIStudioAgents, hasRequestInProgress, hostAppName, isAIStudioAgentSelectorEnabled, isLoading, isResetChatEnabled, onSelectAgent, questions, shouldShowLandingPage, sendQuestion, stopQuestion, ...rest } = props; const { formatMessage } = useIntl(); const isSessionInitiated = React.useRef(false); const [isModalOpen, setIsModalOpen] = React.useState(false); const { cache, contentName, elementId, fileExtension, isFeedbackEnabled, isFeedbackFormEnabled, isStopResponseEnabled, items, itemSize, onFeedbackFormSubmit, onUserInteraction, recordAction, setCacheValue, shouldFeedbackFormIncludeFeedbackText, shouldPreinitSession, } = React.useContext(BoxAISidebarContext); const { agents, requestState, selectedAgent } = useAgents(); const { questions: cacheQuestions } = cache; if (cache.shouldShowLandingPage !== shouldShowLandingPage) { setCacheValue('shouldShowLandingPage', shouldShowLandingPage); } if (cache.encodedSession !== encodedSession) { setCacheValue('encodedSession', encodedSession); } if (cache.questions !== questions) { setCacheValue('questions', questions); } if (cache.agents.selectedAgent !== selectedAgent) { setCacheValue('agents', { agents, requestState, selectedAgent }); } const handleUserIntentToUseAI = (userHasInteracted: boolean = false) => { // Create session if not already created or loading if (!shouldPreinitSession && !encodedSession && !isLoading && createSession) { createSession(true, false); } if (userHasInteracted && onUserInteraction) { onUserInteraction(); } }; const handleModalClose = () => { setIsModalOpen(false); }; const handleSwitchToModalClick = () => { handleUserIntentToUseAI(); setIsModalOpen(true); }; React.useEffect(() => { if (shouldPreinitSession && !encodedSession && createSession) { createSession(true, true); } if ( encodedSession && cacheQuestions.length > 0 && cacheQuestions[cacheQuestions.length - 1].isCompleted === false ) { // if we have cache with question that is not completed resend it to trigger an API sendQuestion({ prompt: cacheQuestions[cacheQuestions.length - 1].prompt }); } if (recordAction) { recordAction({ action: 'programmatic', component: 'sidebar', feature: 'answers', target: 'loaded', data: { items: items.map(item => { return { status: item.status, fileType: item.fileType }; }), }, }); } return () => { // stop API request on unmount (e.g. during switching to another tab) stopQuestion(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Resend the last question (if it was sent before session loaded) after (re-)initializing session React.useEffect(() => { const lastQuestion = cacheQuestions[cacheQuestions.length - 1]; if (!shouldPreinitSession && !isSessionInitiated.current && encodedSession && lastQuestion?.isLoading) { sendQuestion(lastQuestion, selectedAgent, false); isSessionInitiated.current = true; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [encodedSession]); const renderBoxAISidebarTitle = () => { return (
{isAIStudioAgentSelectorEnabled && (
)}
); }; const renderActions = () => ( <> {renderBoxAISidebarTitle()} {isResetChatEnabled && } ); return ( <>
); } export { BoxAISidebarContent as BoxAISidebarComponent }; const BoxAISidebarContentDefaultExport: typeof withAPIContext = flow([ withLogger(ORIGIN_BOXAI_SIDEBAR), withErrorBoundary(ORIGIN_BOXAI_SIDEBAR), withAPIContext, withApiWrapper, // returns only props for Box AI, keep it at the end ])(BoxAISidebarContent); export default BoxAISidebarContentDefaultExport;