diff --git a/workspaces/intelligent-assistant/.changeset/notebook-overlay-docked-modes.md b/workspaces/intelligent-assistant/.changeset/notebook-overlay-docked-modes.md new file mode 100644 index 00000000000..d8366e1ec0c --- /dev/null +++ b/workspaces/intelligent-assistant/.changeset/notebook-overlay-docked-modes.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-intelligent-assistant': minor +--- + +implement docked and overlay display modes for Notebook diff --git a/workspaces/intelligent-assistant/e2e-tests/utils/lightspeedE2eSetup.ts b/workspaces/intelligent-assistant/e2e-tests/utils/lightspeedE2eSetup.ts index dbe3c07f1aa..c64e10d4ae3 100644 --- a/workspaces/intelligent-assistant/e2e-tests/utils/lightspeedE2eSetup.ts +++ b/workspaces/intelligent-assistant/e2e-tests/utils/lightspeedE2eSetup.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +/// import type { Browser, Page } from '@playwright/test'; import { models, conversations, mockedShields } from '../fixtures/responses'; import { openLightspeed, switchToLocale } from './testHelper'; @@ -39,17 +40,28 @@ export type LightspeedE2eBootstrap = { }; async function loginAsGuest(page: Page) { - const enter = page.getByRole('button', { name: 'Enter' }); - await enter.click(); - await page.waitForTimeout(2000); + const maxAttempts = 3; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const enter = page.getByRole('button', { name: 'Enter' }); + await enter.click(); + await page.waitForTimeout(2000); - if (process.env.APP_MODE !== 'nfs') { - await page - .getByRole('heading', { name: 'Red Hat Catalog' }) - .waitFor({ state: 'visible', timeout: 5_000 }); + if (process.env.APP_MODE !== 'nfs') { + try { + await page + .getByRole('heading', { name: 'Red Hat Catalog' }) + .waitFor({ state: 'visible', timeout: 10_000 }); + return; + } catch { + if (attempt === maxAttempts) throw new Error('loginAsGuest failed'); + await page.reload(); + await page.waitForTimeout(2000); + } + } else { + return; + } } } - /** * One logged-in Lightspeed session with the same dev-mode mocks as the legacy * monolithic suite. Each Playwright test file should call this from `beforeAll`. diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx index 5ef597e663c..907a620e7b6 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx @@ -62,6 +62,7 @@ import { Label, MenuToggle, MenuToggleElement, + Button as PFButton, Select, SelectList, SelectOption, @@ -71,11 +72,13 @@ import { } from '@patternfly/react-core'; import { PenIcon, + PlusCircleIcon, PlusIcon, SearchIcon, SortAmountDownAltIcon, SortAmountDownIcon, ThumbtackIcon, + TimesIcon, TrashIcon, } from '@patternfly/react-icons'; import { RhUiAiExperienceIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-experience-icon'; @@ -127,8 +130,15 @@ import { McpServersSettings } from './McpServersSettings'; import { MessageBarModelSelector } from './MessageBarModelSelector'; import { DeleteNotebookModal } from './notebooks/DeleteNotebookModal'; import { NotebooksTab } from './notebooks/NotebooksTab'; -import { NotebookView } from './notebooks/NotebookView'; +import { + NotebookView, + type NotebookViewHandle, +} from './notebooks/NotebookView'; import { RenameNotebookModal } from './notebooks/RenameNotebookModal'; +import { + SidebarCollapseIcon, + SidebarExpandIcon, +} from './notebooks/SidebarCollapseIcon'; import PermissionRequiredState from './PermissionRequiredState'; import { RenameConversationModal } from './RenameConversationModal'; @@ -146,8 +156,6 @@ const ConditionalWrapper = ({ const useStyles = makeStyles(theme => ({ body: { - // remove default margin and padding from common elements - // lists excluded for proper formatting '& h1, & h2, & h3, & h4, & h5, & h6, & p, & li': { margin: 0, padding: 0, @@ -163,6 +171,16 @@ const useStyles = makeStyles(theme => ({ overflow: 'hidden', }, }, + bodyCompact: { + height: '100% !important', + minHeight: '0 !important', + overflow: 'hidden', + '& .pf-chatbot-container': { + minHeight: '0 !important', + display: 'flex', + flexDirection: 'column', + }, + }, header: { padding: `${theme.spacing(3)}px ${theme.spacing(3)}px 0 ${theme.spacing( 3, @@ -191,6 +209,11 @@ const useStyles = makeStyles(theme => ({ alignItems: 'center', }, }, + notebookHeaderActions: { + display: 'flex', + alignItems: 'center', + gap: theme.spacing(0.5), + }, headerLogo: { width: 48, height: 48, @@ -221,10 +244,13 @@ const useStyles = makeStyles(theme => ({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', + flexWrap: 'wrap', + gap: theme.spacing(1), marginBottom: theme.spacing(4), }, notebooksHeading: { marginBottom: 0, + whiteSpace: 'nowrap', }, notebooksHeadingEmpty: { '&&': { @@ -280,6 +306,14 @@ const useStyles = makeStyles(theme => ({ gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', }, }, + notebooksGridCompact: { + display: 'grid', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + gap: theme.spacing(2), + width: '100%', + maxWidth: '100%', + paddingBottom: theme.spacing(3), + }, notebookCard: { borderRadius: theme.spacing(1.5), display: 'flex', @@ -666,6 +700,8 @@ export const LightspeedChat = ({ consumePendingOverlayThreadHandoff, shellViewTab, setShellViewTab, + activeNotebookId, + setActiveNotebookId, } = useLightspeedDrawerContext(); const isFullscreenMode = displayMode === ChatbotDisplayMode.embedded; const location = useLocation(); @@ -676,9 +712,6 @@ export const LightspeedChat = ({ const [filterValue, setFilterValue] = useState(''); const [announcement, setAnnouncement] = useState(''); const [activeTab, setActiveTab] = useState(() => { - if (!isFullscreenMode) { - return 0; - } if (notebooksRouteMatch || notebookViewRouteMatch) { return 1; } @@ -709,35 +742,51 @@ export const LightspeedChat = ({ const [activeNotebook, setActiveNotebook] = useState( null, ); + const effectiveNotebookId = + routeNotebookId || (!isFullscreenMode ? activeNotebookId : undefined); const { data: routeNotebook, isLoading: routeNotebookLoading, isError: routeNotebookError, - } = useNotebookSession(routeNotebookId); + } = useNotebookSession(effectiveNotebookId); useEffect(() => { - if (routeNotebookId && routeNotebook && !routeNotebookLoading) { + if (effectiveNotebookId && routeNotebook && !routeNotebookLoading) { setActiveNotebook(routeNotebook); - } else if (routeNotebookId && routeNotebookError) { - navigate(`${LIGHTSPEED_PATH}/notebooks`, { replace: true }); - } else if (!routeNotebookId && notebooksRouteMatch) { + setActiveNotebookId(routeNotebook.session_id); + } else if (effectiveNotebookId && routeNotebookError) { + if (isFullscreenMode) { + navigate(`${LIGHTSPEED_PATH}/notebooks`, { replace: true }); + } else { + setActiveNotebook(null); + setActiveNotebookId(undefined); + } + } else if (!effectiveNotebookId && notebooksRouteMatch) { setActiveNotebook(null); } }, [ - routeNotebookId, + effectiveNotebookId, routeNotebook, routeNotebookLoading, routeNotebookError, notebooksRouteMatch, + isFullscreenMode, navigate, + setActiveNotebookId, ]); const [notebookAlerts, setNotebookAlerts] = useState[]>( [], ); const createNotebookMutation = useCreateNotebook(); + const notebookViewRef = useRef(null); const { data: notebookDocuments = [], isFetching: isDocumentsFetching } = useNotebookDocuments(activeNotebook?.session_id); + const [notebookUploadsInProgress, setNotebookUploadsInProgress] = + useState(false); + const [notebookSidebarCollapsed, setNotebookSidebarCollapsed] = + useState(true); + const [notebookUploadModalOpen, setNotebookUploadModalOpen] = useState(false); const [conversationId, setConversationId] = useState(''); const [requestId, setRequestId] = useState(''); const [newChatCreated, setNewChatCreated] = useState(false); @@ -756,7 +805,7 @@ export const LightspeedChat = ({ const wasStoppedByUserRef = useRef(false); const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } = useLastOpenedConversation(user); - const showChatPanel = !isFullscreenMode || activeTab === 0; + const showChatPanel = activeTab === 0; const showNotebooksPanel = (notebooksEnabled || isOnNotebookRoute) && activeTab !== 0; const [isChatHistoryDrawerOpen, setIsChatHistoryDrawerOpen] = @@ -794,17 +843,19 @@ export const LightspeedChat = ({ const handleNotebookTabSelect = (_event: SyntheticEvent, nextTab: number) => { setActiveTab(nextTab); setShellViewTab(nextTab); - if (nextTab === 1) { - navigate(`${LIGHTSPEED_PATH}/notebooks`); - if (notebooksPermissionResolved) { - refetchNotebooks(); + if (isFullscreenMode) { + if (nextTab === 1) { + navigate(`${LIGHTSPEED_PATH}/notebooks`); + } else { + navigate( + routeConversationId + ? `${LIGHTSPEED_PATH}/conversation/${routeConversationId}` + : LIGHTSPEED_PATH, + ); } - } else { - navigate( - routeConversationId - ? `${LIGHTSPEED_PATH}/conversation/${routeConversationId}` - : LIGHTSPEED_PATH, - ); + } + if (nextTab === 1 && notebooksPermissionResolved) { + refetchNotebooks(); } }; @@ -833,16 +884,26 @@ export const LightspeedChat = ({ { name: UNTITLED_NOTEBOOK_NAME }, { onSuccess: (session: NotebookSession) => { - navigate(`${LIGHTSPEED_PATH}/notebooks/${session.session_id}`); + if (isFullscreenMode) { + navigate(`${LIGHTSPEED_PATH}/notebooks/${session.session_id}`); + } else { + setActiveNotebook(session); + setActiveNotebookId(session.session_id); + } }, }, ); - }, [createNotebookMutation, navigate]); + }, [createNotebookMutation, isFullscreenMode, navigate, setActiveNotebookId]); const handleCloseNotebook = useCallback(() => { - navigate(`${LIGHTSPEED_PATH}/notebooks`); + if (isFullscreenMode) { + navigate(`${LIGHTSPEED_PATH}/notebooks`); + } else { + setActiveNotebook(null); + setActiveNotebookId(undefined); + } refetchNotebooks(); - }, [navigate, refetchNotebooks]); + }, [isFullscreenMode, navigate, refetchNotebooks, setActiveNotebookId]); const handleRemoveNotebookAlert = (key: React.Key) => { setNotebookAlerts(prevAlerts => @@ -1909,22 +1970,12 @@ export const LightspeedChat = ({ currentName={ notebooks.find(n => n.session_id === renameNotebookId)?.name ?? '' } - /> - )} - {deleteNotebookId && ( - setDeleteNotebookId(null)} - onDeleted={handleNotebookDeleted} - sessionId={deleteNotebookId} - name={ - notebooks.find(n => n.session_id === deleteNotebookId)?.name ?? '' - } + isCompact={!isFullscreenMode} /> )} )} + {!isFullscreenMode && showNotebooksPanel && activeNotebook && ( +
+ + + + + + + notebookViewRef.current?.openUploadModal()} + aria-label={t('notebook.view.documents.add')} + size="sm" + isDisabled={notebookUploadsInProgress} + > + + + + + notebookViewRef.current?.toggleSidebar()} + aria-label={ + notebookSidebarCollapsed + ? t('notebook.view.sidebar.expand') + : t('notebook.view.sidebar.collapse') + } + size="sm" + > + {notebookSidebarCollapsed ? ( + + ) : ( + + )} + + +
+ )} {isFullscreenMode && ( <> setIsMcpSettingsOpen(true)} /> - {isFullscreenMode &&
} - {isFullscreenMode && shouldShowTabs && ( + {(isFullscreenMode || shouldShowTabs) && ( +
+ )} + {shouldShowTabs && ( )} {showNotebooksPanel && !notebooksPermissionLoading && hasNotebooksAccess && !activeNotebook && ( - { - navigate(`${LIGHTSPEED_PATH}/notebooks/${notebook.session_id}`); +
+ > + { + if (isFullscreenMode) { + navigate( + `${LIGHTSPEED_PATH}/notebooks/${notebook.session_id}`, + ); + } else { + setActiveNotebook(notebook); + setActiveNotebookId(notebook.session_id); + } + }} + onRename={setRenameNotebookId} + onDelete={setDeleteNotebookId} + onCreateNotebook={handleCreateNotebook} + t={t} + /> + {deleteNotebookId && ( + setDeleteNotebookId(null)} + onDeleted={handleNotebookDeleted} + sessionId={deleteNotebookId} + name={ + notebooks.find(n => n.session_id === deleteNotebookId) + ?.name ?? '' + } + isCompact={!isFullscreenMode} + /> + )} +
)} {showNotebooksPanel && !notebooksPermissionLoading && diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerContext.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerContext.tsx index 0f54b922799..601d31bc1d0 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerContext.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerContext.tsx @@ -50,8 +50,8 @@ export interface LightspeedDrawerContextType { * Set the display mode (overlay, docked, or fullscreen/embedded). * When entering embedded mode, optional `embeddedNotebooks` navigates to * `/lightspeed/notebooks` (or a session URL) instead of the chat route. - * Leaving embedded for overlay or docked resets the shell tab to Chat - * (Notebooks is only available in fullscreen). + * Notebooks are available in all display modes; the shell tab and active + * notebook are preserved across mode switches. */ setDisplayMode: ( mode: ChatbotDisplayMode, @@ -105,6 +105,13 @@ export interface LightspeedDrawerContextType { */ shellViewTab: number; setShellViewTab: (tab: number) => void; + /** + * ID of the currently active notebook session, persisted across + * overlay/docked/fullscreen remounts so display-mode switches preserve + * the open notebook. + */ + activeNotebookId: string | undefined; + setActiveNotebookId: (id: string | undefined) => void; } const CONTEXT_KEY = '__lightspeed_drawer_context__' as keyof typeof globalThis; diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerProvider.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerProvider.tsx index 560c490f541..3e7eb5f1db2 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerProvider.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightspeedDrawerProvider.tsx @@ -31,7 +31,7 @@ const useStyles = makeStyles(theme => ({ bottom: `calc(${theme?.spacing?.(2) ?? '16px'} + 5em)`, right: `calc(${theme?.spacing?.(2) ?? '16px'} + 1.5em)`, maxWidth: 'min(30rem, calc(100vw - 32px)) !important', - overflowX: 'hidden' as const, + overflow: 'hidden' as const, transition: 'margin-right 0.3s ease', 'body.docked-drawer-open &': { marginRight: DOCKED_CONTENT_OFFSET, diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedChat.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedChat.test.tsx index 78e3d9b6486..d210386d104 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedChat.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedChat.test.tsx @@ -258,6 +258,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); localStorage.clear(); @@ -677,6 +679,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 1, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat('/intelligent-assistant/notebooks')); @@ -701,7 +705,7 @@ describe('LightspeedChat', () => { ); }); - it('should not render Chat/Notebooks tabs in overlay mode', async () => { + it('should render Chat/Notebooks tabs in overlay mode', async () => { mockUseLightspeedDrawerContext.mockReturnValue({ isChatbotActive: true, toggleChatbot: jest.fn(), @@ -718,6 +722,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat()); @@ -726,15 +732,13 @@ describe('LightspeedChat', () => { expect(screen.getByLabelText('Options')).toBeInTheDocument(); }); - expect( - screen.queryByRole('tab', { name: 'Chat' }), - ).not.toBeInTheDocument(); + expect(screen.queryByRole('tab', { name: 'Chat' })).toBeInTheDocument(); expect( screen.queryByRole('tab', { name: 'Notebooks' }), - ).not.toBeInTheDocument(); + ).toBeInTheDocument(); }); - it('should not render Chat/Notebooks tabs in docked mode', async () => { + it('should render Chat/Notebooks tabs in docked mode', async () => { mockUseLightspeedDrawerContext.mockReturnValue({ isChatbotActive: true, toggleChatbot: jest.fn(), @@ -751,6 +755,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat()); @@ -759,12 +765,10 @@ describe('LightspeedChat', () => { expect(screen.getByLabelText('Options')).toBeInTheDocument(); }); - expect( - screen.queryByRole('tab', { name: 'Chat' }), - ).not.toBeInTheDocument(); + expect(screen.queryByRole('tab', { name: 'Chat' })).toBeInTheDocument(); expect( screen.queryByRole('tab', { name: 'Notebooks' }), - ).not.toBeInTheDocument(); + ).toBeInTheDocument(); }); it('should show current display mode as selected in full-screen mode', async () => { @@ -784,6 +788,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat()); @@ -818,6 +824,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat()); @@ -852,6 +860,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat()); @@ -946,6 +956,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); }); @@ -982,6 +994,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 1, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat('/intelligent-assistant')); @@ -1057,6 +1071,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); render(setupLightspeedChat('/intelligent-assistant/notebooks')); @@ -1092,6 +1108,8 @@ describe('LightspeedChat', () => { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }); }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerProvider.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerProvider.test.tsx index 363edffefb3..911e7aff0c3 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerProvider.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerProvider.test.tsx @@ -99,6 +99,8 @@ function baseContextValue(): LightspeedDrawerContextType { consumePendingOverlayThreadHandoff: jest.fn(() => false), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), }; } diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx index 73995ebf7c7..0b94799cc70 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx @@ -43,6 +43,8 @@ describe('LightspeedDrawerStateExposer', () => { setDraftFileContents: jest.fn(), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), ...overrides, }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedFAB.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedFAB.test.tsx index 67af1716dd3..3027100a9b4 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedFAB.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/LightspeedFAB.test.tsx @@ -43,6 +43,8 @@ describe('LightspeedFAB', () => { setDraftFileContents: jest.fn(), shellViewTab: 0, setShellViewTab: jest.fn(), + activeNotebookId: undefined, + setActiveNotebookId: jest.fn(), ...overrides, }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/AddDocumentModal.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/AddDocumentModal.tsx index 7b85fbb5946..004473e2718 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/AddDocumentModal.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/AddDocumentModal.tsx @@ -41,6 +41,7 @@ import { getNotebookAcceptedFileTypes, validateFiles, } from '../../utils/notebook-upload-utils'; +import { getScopedDialogProps } from '../../utils/scoped-dialog-utils'; import { FileListItem } from './FileListItem'; const useStyles = makeStyles(theme => ({ @@ -48,24 +49,43 @@ const useStyles = makeStyles(theme => ({ borderRadius: 24, maxWidth: 578, }, + dialogPaperCompact: { + borderRadius: 12, + maxWidth: '100%', + }, dialogTitle: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '24px 24px 16px', }, + dialogTitleCompact: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: '16px 16px 12px !important', + }, titleText: { fontWeight: 500, fontSize: '1.25rem', lineHeight: '1.625rem', letterSpacing: '-0.25px', }, + titleTextCompact: { + fontWeight: 600, + fontSize: '1.125rem', + lineHeight: '1.5rem', + letterSpacing: '-0.25px', + }, closeButton: { color: theme.palette.text.primary, }, dialogContent: { padding: '0 24px 24px', }, + dialogContentCompact: { + padding: '0 16px 16px !important', + }, errorAlert: { marginBottom: theme.spacing(2), }, @@ -100,6 +120,11 @@ const useStyles = makeStyles(theme => ({ justifyContent: 'flex-end', gap: theme.spacing(1), }, + dialogActionsCompact: { + padding: '12px 16px !important', + justifyContent: 'flex-end', + gap: theme.spacing(1), + }, addButton: { textTransform: 'none', }, @@ -120,6 +145,7 @@ type AddDocumentModalProps = { onDuplicatesFound?: (files: File[]) => void; filesToAdd?: File[]; onFilesAdded?: () => void; + isCompact?: boolean; }; export const AddDocumentModal = ({ @@ -134,13 +160,13 @@ export const AddDocumentModal = ({ onDuplicatesFound, filesToAdd, onFilesAdded, + isCompact = false, }: AddDocumentModalProps) => { const classes = useStyles(); const { t } = useTranslation(); const uploadMutation = useUploadDocument(); const [validationErrors, setValidationErrors] = useState([]); const [selectedFiles, setSelectedFiles] = useState([]); - const totalExistingAndSelected = existingDocumentNames.length + selectedFiles.length; const remainingSlots = NOTEBOOK_MAX_FILES - totalExistingAndSelected; @@ -224,17 +250,26 @@ export const AddDocumentModal = ({ onClose(); }; + const scopedProps = getScopedDialogProps(isCompact); + return ( - - + + {t('notebook.upload.modal.title')} {selectedFiles.length > 0 && ` (${selectedFiles.length}/${NOTEBOOK_MAX_FILES - existingDocumentNames.length})`} @@ -249,7 +284,11 @@ export const AddDocumentModal = ({ - + {validationErrors.length > 0 && ( {validationErrors @@ -315,7 +354,11 @@ export const AddDocumentModal = ({ )} - + + ))} +
)}
- {welcomePrompts.length > 0 && ( -
- {welcomePrompts.map(prompt => ( - + ); + }; + + return ( +
+ {toastAlerts.length > 0 && ( + + {toastAlerts.map(({ key, title, variant }) => ( + handleRemoveToastAlert(key as React.Key)} + actionClose={ + handleRemoveToastAlert(key as React.Key)} + /> + } + /> ))} -
+ )} -
- ); - }; - - return ( -
- {toastAlerts.length > 0 && ( - - {toastAlerts.map(({ key, title, variant }) => ( - handleRemoveToastAlert(key as React.Key)} - actionClose={ - handleRemoveToastAlert(key as React.Key)} - /> - } - /> - ))} - - )} - - - -
- {sidebarCollapsed && ( -
- - - - { - if (hasUploadsInProgress) - return t('notebook.view.documents.uploadsInProgress'); - if (isAddDisabled) - return t('notebook.view.documents.maxReached'); - return t('notebook.view.documents.add'); - })()} - position="right" - > - - - -
- )} - -
-
- -
- -
{renderMainContent()}
- - {hasNoDocuments && - messages.length === 0 && - renderNotebookDisclaimerAlert()} - - - {hasNoDocuments ? ( + { + if (hasUploadsInProgress) + return t('notebook.view.documents.uploadsInProgress'); + if (isAddDisabled) + return t('notebook.view.documents.maxReached'); + return t('notebook.view.documents.add'); + })()} + position="right" > -
- -
+ + +
- ) : ( - +
+ )} + +
+ {!isCompact && ( +
+ +
)} - - + +
+ {renderMainContent()} +
+ + {hasNoDocuments && + messages.length === 0 && + renderNotebookDisclaimerAlert()} + + + {hasNoDocuments ? ( + +
+ +
+
+ ) : ( + + )} + +
+
-
- - - - - d.title)} - hasUploadsInProgress={hasUploadsInProgress} - onFilesUploading={handleFilesUploading} - onUploadStarted={handleUploadStarted} - onUploadFailed={handleUploadFailed} - onDuplicatesFound={handleDuplicatesFound} - filesToAdd={filesToAddToModal} - onFilesAdded={handleFilesAddedToModal} - /> - - f.name)} - /> - - setDeleteDocumentTarget(null)} - onConfirm={confirmDeleteDocument} - documentName={deleteDocumentTarget?.name ?? ''} - /> - - ); -}; + + + + + d.title)} + hasUploadsInProgress={hasUploadsInProgress} + onFilesUploading={handleFilesUploading} + onUploadStarted={handleUploadStarted} + onUploadFailed={handleUploadFailed} + onDuplicatesFound={handleDuplicatesFound} + filesToAdd={filesToAddToModal} + onFilesAdded={handleFilesAddedToModal} + isCompact={isCompact} + /> + + f.name)} + isCompact={isCompact} + /> + + setDeleteDocumentTarget(null)} + onConfirm={confirmDeleteDocument} + documentName={deleteDocumentTarget?.name ?? ''} + isCompact={isCompact} + /> + + ); + }, +); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/OverwriteConfirmModal.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/OverwriteConfirmModal.tsx index 61327087ef8..ebce6cde55d 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/OverwriteConfirmModal.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/OverwriteConfirmModal.tsx @@ -26,6 +26,7 @@ import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import { useTranslation } from '../../hooks/useTranslation'; +import { getScopedDialogProps } from '../../utils/scoped-dialog-utils'; import { FileTypeIcon } from './FileTypeIcon'; const useStyles = makeStyles(theme => ({ @@ -97,6 +98,7 @@ type OverwriteConfirmModalProps = { onClose: () => void; onConfirm: () => void; fileNames: string[]; + isCompact?: boolean; }; export const OverwriteConfirmModal = ({ @@ -104,6 +106,7 @@ export const OverwriteConfirmModal = ({ onClose, onConfirm, fileNames, + isCompact = false, }: OverwriteConfirmModalProps) => { const classes = useStyles(); const { t } = useTranslation(); @@ -116,6 +119,7 @@ export const OverwriteConfirmModal = ({ PaperProps={{ className: classes.dialogPaper, }} + {...getScopedDialogProps(isCompact)} > diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/RenameNotebookModal.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/RenameNotebookModal.tsx index 0bc79b88526..181c17c2585 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/RenameNotebookModal.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/RenameNotebookModal.tsx @@ -31,15 +31,24 @@ import Typography from '@mui/material/Typography'; import { useRenameNotebook } from '../../hooks/notebooks/useRenameNotebook'; import { useTranslation } from '../../hooks/useTranslation'; +import { getScopedDialogProps } from '../../utils/scoped-dialog-utils'; const useStyles = makeStyles(theme => ({ dialogPaper: { borderRadius: 16, }, + dialogPaperCompact: { + borderRadius: 12, + padding: theme.spacing(1), + }, dialogTitle: { padding: '16px 20px', fontStyle: 'inherit', }, + dialogTitleCompact: { + padding: '12px 16px', + fontStyle: 'inherit', + }, titleRow: { display: 'flex', alignItems: 'center', @@ -58,9 +67,17 @@ const useStyles = makeStyles(theme => ({ paddingTop: 0, paddingLeft: theme.spacing(2.5), }, + dialogContentCompact: { + paddingTop: 0, + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + }, description: { marginBottom: theme.spacing(3), }, + descriptionCompact: { + marginBottom: theme.spacing(2), + }, textField: { marginTop: 0, }, @@ -69,11 +86,21 @@ const useStyles = makeStyles(theme => ({ marginLeft: theme.spacing(2.5), marginRight: theme.spacing(2.5), }, + errorBoxCompact: { + maxWidth: '100%', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + }, dialogActions: { justifyContent: 'left', padding: theme.spacing(2.5), gap: theme.spacing(1), }, + dialogActionsCompact: { + justifyContent: 'left', + padding: theme.spacing(1.5), + gap: theme.spacing(1), + }, submitButton: { textTransform: 'none', borderRadius: 999, @@ -93,11 +120,13 @@ export const RenameNotebookModal = ({ onClose, sessionId, currentName, + isCompact = false, }: { isOpen: boolean; onClose: () => void; sessionId: string; currentName: string; + isCompact?: boolean; }) => { const classes = useStyles(); const { t } = useTranslation(); @@ -122,6 +151,8 @@ export const RenameNotebookModal = ({ } }; + const scopedProps = getScopedDialogProps(isCompact); + return ( - + {t('notebooks.rename.title').replace('{{name}}', currentName)} @@ -142,7 +177,7 @@ export const RenameNotebookModal = ({ aria-label="close" onClick={onClose} title={t('common.close')} - size="large" + size={isCompact ? 'small' : 'large'} className={classes.closeButton} > @@ -151,12 +186,16 @@ export const RenameNotebookModal = ({ {t('notebooks.rename.description')} @@ -175,11 +214,15 @@ export const RenameNotebookModal = ({ /> {isError && ( - + {String(error)} )} - +