From ad013979821bf8911f7d51eb90ae58a82582545d Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Mon, 20 Jul 2026 04:11:38 +0530 Subject: [PATCH 01/13] feat(intelligent-assistant): improve notebook UI components and modals Enhance AddDocumentModal, OverwriteConfirmModal, NotebookCard, and NotebookView with improved UX text, document count formatting, file list styling, and fix translation type errors by removing unnecessary `as any` casts. Signed-off-by: its-mitesh-kumar --- .../.changeset/bright-tigers-dance.md | 5 + .../intelligent-assistant/report-alpha.api.md | 9 + .../src/components/LightSpeedChat.tsx | 2 - .../__tests__/AddDocumentModal.test.tsx | 42 ++-- .../__tests__/NotebookCard.test.tsx | 36 ++- .../__tests__/OverwriteConfirmModal.test.tsx | 132 +++++------ .../components/notebooks/AddDocumentModal.tsx | 208 ++++++++++++++---- .../components/notebooks/DocumentSidebar.tsx | 6 +- .../src/components/notebooks/FileListItem.tsx | 25 ++- .../src/components/notebooks/NotebookCard.tsx | 26 ++- .../src/components/notebooks/NotebookView.tsx | 151 +++++++++++-- .../notebooks/OverwriteConfirmModal.tsx | 157 +++++++++---- .../notebooks/UploadResourceScreen.tsx | 1 - .../src/translations/ref.ts | 20 +- 14 files changed, 595 insertions(+), 225 deletions(-) create mode 100644 workspaces/intelligent-assistant/.changeset/bright-tigers-dance.md diff --git a/workspaces/intelligent-assistant/.changeset/bright-tigers-dance.md b/workspaces/intelligent-assistant/.changeset/bright-tigers-dance.md new file mode 100644 index 00000000000..be494c67173 --- /dev/null +++ b/workspaces/intelligent-assistant/.changeset/bright-tigers-dance.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-intelligent-assistant': patch +--- + +Improve notebook UI: enhance AddDocumentModal, OverwriteConfirmModal, NotebookCard, and NotebookView components with better UX text, document count formatting, and fix translation type errors diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md index 3b1aeb164dd..e1269c1c85f 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md @@ -51,6 +51,9 @@ export const intelligentAssistantTranslationRef: TranslationRef< readonly 'notebooks.empty.description': string; readonly 'notebooks.empty.action': string; readonly 'notebooks.documents': string; + readonly 'notebooks.documents.none': string; + readonly 'notebooks.documents.one': string; + readonly 'notebooks.documents.other': string; readonly 'notebooks.actions.rename': string; readonly 'notebooks.actions.delete': string; readonly 'notebooks.rename.title': string; @@ -89,15 +92,21 @@ export const intelligentAssistantTranslationRef: TranslationRef< readonly 'notebook.upload.modal.browseButton': string; readonly 'notebook.upload.modal.separator': string; readonly 'notebook.upload.modal.infoText': string; + readonly 'notebook.upload.modal.supportedFormats': string; + readonly 'notebook.upload.modal.maxFileSize': string; readonly 'notebook.upload.modal.selectedFiles': string; readonly 'notebook.upload.modal.addButton': string; + readonly 'notebook.upload.modal.addButtonEmpty': string; readonly 'notebook.upload.modal.removeFile': string; readonly 'notebook.upload.error.unsupportedType': string; readonly 'notebook.upload.error.fileTooLarge': string; readonly 'notebook.upload.error.tooManyFiles': string; readonly 'notebook.overwrite.modal.title': string; readonly 'notebook.overwrite.modal.description': string; + readonly 'notebook.overwrite.modal.replace': string; + readonly 'notebook.overwrite.modal.ignore': string; readonly 'notebook.overwrite.modal.action': string; + readonly 'notebook.overwrite.modal.back': string; readonly 'notebook.document.delete': string; readonly 'notebook.document.delete.title': string; readonly 'notebook.document.delete.description': string; 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 9184eddbe84..5871a4258a4 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/LightSpeedChat.tsx @@ -306,11 +306,9 @@ const useStyles = makeStyles(theme => ({ }, notebookDocuments: { paddingTop: theme.spacing(1), - paddingLeft: theme.spacing(2), }, notebookUpdated: { paddingBottom: theme.spacing(5), - paddingLeft: theme.spacing(2), paddingTop: theme.spacing(2), }, notebookTitle: { diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx index a2e0e29bf1f..0476a378a8b 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx @@ -50,8 +50,10 @@ describe('AddDocumentModal', () => { it('should render the modal when open', () => { render(); - expect(screen.getByText('Add a document to Notebook')).toBeInTheDocument(); - expect(screen.getByText('Drag and drop files here')).toBeInTheDocument(); + expect(screen.getByText('Add resources')).toBeInTheDocument(); + expect( + screen.getByText('Drag and drop files here, or click to browse'), + ).toBeInTheDocument(); }); it('should not render when isOpen is false', () => { @@ -64,13 +66,13 @@ describe('AddDocumentModal', () => { render(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Add (0)' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument(); }); it('should have Add button disabled when no files selected', () => { render(); - const addButton = screen.getByRole('button', { name: 'Add (0)' }); + const addButton = screen.getByRole('button', { name: 'Add' }); expect(addButton).toBeDisabled(); }); @@ -96,7 +98,7 @@ describe('AddDocumentModal', () => { render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -116,7 +118,7 @@ describe('AddDocumentModal', () => { render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -138,7 +140,7 @@ describe('AddDocumentModal', () => { render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -160,7 +162,7 @@ describe('AddDocumentModal', () => { render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -193,7 +195,7 @@ describe('AddDocumentModal', () => { render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -215,7 +217,7 @@ describe('AddDocumentModal', () => { await waitFor(() => { expect(screen.queryByText('test-file.txt')).not.toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Add (0)' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Add' })).toBeDisabled(); }); }); @@ -223,7 +225,7 @@ describe('AddDocumentModal', () => { const { rerender } = render(); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const file = new File(['content'], 'test-file.txt', { type: 'text/plain' }); @@ -245,7 +247,7 @@ describe('AddDocumentModal', () => { expect(screen.queryByText('test-file.txt')).not.toBeInTheDocument(); }); - it('should call onDuplicatesFound for files that already exist', async () => { + it('should call onDuplicatesFound when Add is clicked with duplicate files', async () => { render( { ); const dropzone = screen - .getByText('Drag and drop files here') + .getByText('Drag and drop files here, or click to browse') .closest('div'); const existingFile = new File(['content'], 'existing-file.txt', { type: 'text/plain', @@ -271,11 +273,17 @@ describe('AddDocumentModal', () => { }); await waitFor(() => { - expect(defaultProps.onDuplicatesFound).toHaveBeenCalledWith([ - existingFile, - ]); expect(screen.getByText('new-file.txt')).toBeInTheDocument(); - expect(screen.queryByText('existing-file.txt')).not.toBeInTheDocument(); + expect(screen.getByText('existing-file.txt')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole('button', { name: 'Add (2)' })); + + await waitFor(() => { + expect(defaultProps.onDuplicatesFound).toHaveBeenCalledWith( + [existingFile], + [existingFile, newFile], + ); }); }); }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx index 705be6236a8..701f426f0d0 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx @@ -75,9 +75,39 @@ describe('NotebookCard', () => { expect(screen.getByText('My Notebook')).toBeInTheDocument(); }); - it('should render the document count', () => { + it('should render the document count for multiple documents', () => { render(); - expect(screen.getByText(/2/)).toBeInTheDocument(); + expect(screen.getByText('2 documents')).toBeInTheDocument(); + }); + + it('should render "No documents" when document_count is 0', () => { + render( + , + ); + expect(screen.getByText('No documents')).toBeInTheDocument(); + }); + + it('should render "1 document" when document_count is 1', () => { + render( + , + ); + expect(screen.getByText('1 document')).toBeInTheDocument(); + }); + + it('should render "No documents" when document_count is undefined', () => { + render( + , + ); + expect(screen.getByText('No documents')).toBeInTheDocument(); }); it('should call onClick with notebook when card is clicked', () => { @@ -143,6 +173,6 @@ describe('NotebookCard', () => { it('should render document_count from the notebook session', () => { render(); - expect(screen.getByText(/2/)).toBeInTheDocument(); + expect(screen.getByText('2 documents')).toBeInTheDocument(); }); }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx index 076cdd46065..ec7fa934c2e 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx @@ -23,131 +23,115 @@ jest.mock('../../hooks/useTranslation', () => ({ useTranslation: jest.fn(() => mockUseTranslation()), })); +const createFile = (name: string) => + new File(['content'], name, { type: 'text/plain' }); + describe('OverwriteConfirmModal', () => { const onClose = jest.fn(); const onConfirm = jest.fn(); - const fileNames = ['report.pdf', 'data.yaml', 'notes.txt']; + const onBack = jest.fn(); + + const allFiles = [ + createFile('report.pdf'), + createFile('data.yaml'), + createFile('notes.txt'), + ]; + const duplicateFileNames = ['report.pdf', 'data.yaml']; beforeEach(() => { jest.clearAllMocks(); }); - it('should render the modal with file list when open', () => { + const renderModal = (props = {}) => render( , ); + it('should render the modal with all files when open', () => { + renderModal(); + expect(screen.getByText('report.pdf')).toBeInTheDocument(); expect(screen.getByText('data.yaml')).toBeInTheDocument(); expect(screen.getByText('notes.txt')).toBeInTheDocument(); }); it('should not render when isOpen is false', () => { - render( - , - ); + renderModal({ isOpen: false }); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); it('should render a warning alert', () => { - render( - , - ); + renderModal(); - expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByText(/2 files already exist/)).toBeInTheDocument(); + }); + + it('should render radio options for replace and ignore', () => { + renderModal(); + + expect(screen.getByLabelText('Replace existing files')).toBeInTheDocument(); + expect( + screen.getByLabelText('Ignore duplicated files'), + ).toBeInTheDocument(); }); it('should render FileTypeIcon badges for each file', () => { - render( - , - ); + renderModal(); expect(screen.getByText('pdf')).toBeInTheDocument(); expect(screen.getByText('yaml')).toBeInTheDocument(); expect(screen.getByText('txt')).toBeInTheDocument(); }); - it('should call onConfirm when overwrite button is clicked', () => { - render( - , - ); + it('should call onConfirm with all files when replace is selected', () => { + renderModal(); - const overwriteButton = screen.getByRole('button', { - name: 'Overwrite', + const uploadButton = screen.getByRole('button', { + name: /Upload \(3\)/, }); - fireEvent.click(overwriteButton); + fireEvent.click(uploadButton); - expect(onConfirm).toHaveBeenCalledTimes(1); + expect(onConfirm).toHaveBeenCalledWith(allFiles); }); - it('should call onClose when cancel button is clicked', () => { - render( - , - ); + it('should call onConfirm with only new files when ignore is selected', () => { + renderModal(); - const cancelButton = screen.getByRole('button', { name: 'Cancel' }); - fireEvent.click(cancelButton); + const ignoreRadio = screen.getByLabelText('Ignore duplicated files'); + fireEvent.click(ignoreRadio); - expect(onClose).toHaveBeenCalledTimes(1); + const uploadButton = screen.getByRole('button', { + name: /Upload \(1\)/, + }); + fireEvent.click(uploadButton); + + expect(onConfirm).toHaveBeenCalledWith([allFiles[2]]); + }); + + it('should call onBack when Back button is clicked', () => { + renderModal(); + + const backButton = screen.getByRole('button', { name: 'Back' }); + fireEvent.click(backButton); + + expect(onBack).toHaveBeenCalledTimes(1); }); it('should call onClose when close icon button is clicked', () => { - render( - , - ); + renderModal(); const closeButton = screen.getByRole('button', { name: 'Close' }); fireEvent.click(closeButton); expect(onClose).toHaveBeenCalledTimes(1); }); - - it('should render an empty list for no files', () => { - render( - , - ); - - expect(screen.queryByRole('listitem')).not.toBeInTheDocument(); - }); }); 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..4505491ceea 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { FileRejection } from 'react-dropzone'; import { makeStyles } from '@material-ui/core/styles'; @@ -30,11 +30,16 @@ import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import { MultipleFileUpload, + MultipleFileUploadContext, MultipleFileUploadMain, + Tooltip, } from '@patternfly/react-core'; import { UploadIcon } from '@patternfly/react-icons'; -import { NOTEBOOK_MAX_FILES } from '../../const'; +import { + NOTEBOOK_EXTENSION_TO_FILE_TYPE, + NOTEBOOK_MAX_FILES, +} from '../../const'; import { useUploadDocument } from '../../hooks/notebooks/useUploadDocument'; import { useTranslation } from '../../hooks/useTranslation'; import { @@ -70,15 +75,24 @@ const useStyles = makeStyles(theme => ({ marginBottom: theme.spacing(2), }, dropzone: { - '& .pf-v6-c-multiple-file-upload__main': { - borderColor: 'var(--pf-t--global--border--color--brand--default)', - transition: 'background-color 0.2s ease', - cursor: 'pointer', - }, - '& .pf-v6-c-multiple-file-upload__main:hover': { + borderColor: 'var(--pf-t--global--border--color--brand--default)', + borderWidth: 2, + borderStyle: 'dashed', + borderRadius: theme.spacing(1), + padding: theme.spacing(2), + transition: 'background-color 0.2s ease', + cursor: 'pointer', + '&:hover': { backgroundColor: 'color-mix(in srgb, var(--pf-t--global--color--brand--default) 10%, transparent)', }, + '& .pf-v6-c-multiple-file-upload__main': { + border: 'none', + paddingBottom: 0, + }, + '& .pf-v6-c-multiple-file-upload__title-icon': { + fontSize: '2rem', + }, }, fileListContainer: { marginTop: theme.spacing(2), @@ -106,8 +120,80 @@ const useStyles = makeStyles(theme => ({ cancelButton: { textTransform: 'none', }, + uploadIcon: { + color: 'var(--pf-t--global--icon--color--brand--default)', + }, + supportedFormatsLabel: { + fontSize: '0.875rem', + color: theme.palette.text.secondary, + textAlign: 'center', + marginTop: theme.spacing(1), + }, + chipContainer: { + display: 'flex', + flexWrap: 'wrap', + gap: theme.spacing(0.75), + justifyContent: 'center', + marginTop: theme.spacing(0.5), + }, + fileTypeChip: { + display: 'inline-block', + padding: '2px 10px', + borderRadius: 12, + fontSize: '0.75rem', + fontWeight: 500, + backgroundColor: + theme.palette.type === 'dark' + ? 'rgba(255, 255, 255, 0.1)' + : 'rgba(0, 0, 0, 0.08)', + color: theme.palette.text.secondary, + }, + maxFileSizeText: { + fontSize: '0.875rem', + color: theme.palette.text.secondary, + textAlign: 'center', + marginTop: theme.spacing(1), + }, + dropzoneDisabled: { + opacity: 0.5, + pointerEvents: 'none', + cursor: 'default', + '&:hover': { + backgroundColor: 'transparent', + }, + }, })); +const DropzoneClickArea = ({ + children, + isDisabled, +}: { + children: React.ReactNode; + isDisabled?: boolean; +}) => { + const { open } = useContext(MultipleFileUploadContext); + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + open(); + } + } + } + style={{ cursor: isDisabled ? 'default' : 'pointer' }} + > + {children} +
+ ); +}; + type AddDocumentModalProps = { isOpen: boolean; onClose: () => void; @@ -117,7 +203,7 @@ type AddDocumentModalProps = { onFilesUploading?: (files: File[]) => void; onUploadStarted?: (info: { fileName: string; documentId: string }) => void; onUploadFailed?: (fileName: string) => void; - onDuplicatesFound?: (files: File[]) => void; + onDuplicatesFound?: (duplicateFiles: File[], allFiles: File[]) => void; filesToAdd?: File[]; onFilesAdded?: () => void; }; @@ -145,6 +231,13 @@ export const AddDocumentModal = ({ existingDocumentNames.length + selectedFiles.length; const remainingSlots = NOTEBOOK_MAX_FILES - totalExistingAndSelected; + useEffect(() => { + if (!isOpen) { + setSelectedFiles([]); + setValidationErrors([]); + } + }, [isOpen]); + useEffect(() => { if (filesToAdd && filesToAdd.length > 0) { setSelectedFiles(prev => [...prev, ...filesToAdd]); @@ -164,18 +257,8 @@ export const AddDocumentModal = ({ if (valid.length === 0) return; - const existingNamesSet = new Set([ - ...existingDocumentNames, - ...selectedFiles.map(f => f.name), - ]); - const newFiles = valid.filter(f => !existingNamesSet.has(f.name)); - const duplicateFiles = valid.filter(f => - existingDocumentNames.includes(f.name), - ); - - if (duplicateFiles.length > 0) { - onDuplicatesFound?.(duplicateFiles); - } + const alreadySelectedNames = new Set(selectedFiles.map(f => f.name)); + const newFiles = valid.filter(f => !alreadySelectedNames.has(f.name)); if (newFiles.length > 0) { setSelectedFiles(prev => [...prev, ...newFiles]); @@ -189,6 +272,15 @@ export const AddDocumentModal = ({ const handleAddFiles = () => { if (selectedFiles.length === 0) return; + const duplicateFiles = selectedFiles.filter(f => + existingDocumentNames.includes(f.name), + ); + + if (duplicateFiles.length > 0) { + onDuplicatesFound?.(duplicateFiles, selectedFiles); + return; + } + onFilesUploading?.(selectedFiles); for (const file of selectedFiles) { uploadMutation @@ -269,24 +361,54 @@ export const AddDocumentModal = ({ )} - {remainingSlots > 0 && ( - - } - titleText={t('notebook.upload.modal.dragDropTitle')} - titleTextSeparator={t('notebook.upload.modal.separator')} - infoText={t('notebook.upload.modal.infoText')} - browseButtonText={t('notebook.upload.modal.browseButton')} - /> - - )} + {(() => { + const isDropzoneDisabled = remainingSlots <= 0; + const dropzoneContent = ( + + + } + titleText={t('notebook.upload.modal.dragDropTitle')} + isUploadButtonHidden + /> + + {t('notebook.upload.modal.supportedFormats')} + +
+ {[ + ...new Set(Object.values(NOTEBOOK_EXTENSION_TO_FILE_TYPE)), + ].map(label => ( + + {label.toUpperCase()} + + ))} +
+ + {t('notebook.upload.modal.maxFileSize')} + +
+
+ ); + + return isDropzoneDisabled ? ( + +
{dropzoneContent}
+
+ ) : ( + dropzoneContent + ); + })()} {selectedFiles.length > 0 && ( @@ -330,9 +452,11 @@ export const AddDocumentModal = ({ color="primary" disabled={selectedFiles.length === 0 || hasUploadsInProgress} > - {(t as Function)('notebook.upload.modal.addButton', { - count: selectedFiles.length, - })} + {selectedFiles.length > 0 + ? (t as Function)('notebook.upload.modal.addButton', { + count: selectedFiles.length, + }) + : t('notebook.upload.modal.addButtonEmpty')} diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx index 34111003bb8..99f8b7fd547 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx @@ -26,7 +26,7 @@ import { Spinner, Tooltip, } from '@patternfly/react-core'; -import { EllipsisVIcon, PlusCircleIcon } from '@patternfly/react-icons'; +import { AddCircleOIcon, EllipsisVIcon } from '@patternfly/react-icons'; import { NOTEBOOK_MAX_FILES } from '../../const'; import { useTranslation } from '../../hooks/useTranslation'; @@ -197,7 +197,7 @@ export const DocumentSidebar = ({ ); }; diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookCard.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookCard.tsx index 2f3187cc983..3a4432da805 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookCard.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookCard.tsx @@ -27,8 +27,12 @@ import { DropdownList, MenuToggle, } from '@patternfly/react-core'; -import { EllipsisVIcon } from '@patternfly/react-icons'; -import { CatalogIcon } from '@patternfly/react-icons/dist/esm/icons'; +import { + CatalogIcon, + EllipsisVIcon, + PenIcon, + TrashIcon, +} from '@patternfly/react-icons'; import { intelligentAssistantTranslationRef } from '../../translations/ref'; import { NotebookSession } from '../../types'; @@ -64,12 +68,22 @@ export const NotebookCard = ({ } }; + const count = notebook.document_count ?? 0; + const getDocumentCountText = () => { + if (count === 0) return t('notebooks.documents.none'); + if (count === 1) return t('notebooks.documents.one'); + return (t as Function)('notebooks.documents.other', { count }); + }; + const documentCountText = getDocumentCountText(); + return ( onClick(notebook)} onKeyDown={handleCardKeyDown} > @@ -110,6 +124,7 @@ export const NotebookCard = ({ } onClick={event => { event.stopPropagation(); onRename(notebook.session_id); @@ -120,6 +135,7 @@ export const NotebookCard = ({ } onClick={event => { event.stopPropagation(); onDelete(notebook.session_id); @@ -145,9 +161,7 @@ export const NotebookCard = ({
- - {notebook.document_count ?? 0} {t('notebooks.documents')} - + {documentCountText}
diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx index b07d0bd4688..52b940ab831 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx @@ -38,7 +38,7 @@ import { Tooltip, type AlertProps, } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; +import { PlusIcon, TimesIcon } from '@patternfly/react-icons'; import { useQueryClient } from '@tanstack/react-query'; import { notebooksApiRef } from '../../api/notebooksApi'; @@ -52,9 +52,11 @@ import { useDocumentStatusPolling, type PendingUpload, } from '../../hooks/notebooks/useDocumentStatusPolling'; +import { useUploadDocument } from '../../hooks/notebooks/useUploadDocument'; import { useConversationMessages } from '../../hooks/useConversationMessages'; import { CreateMessageVariables } from '../../hooks/useCreateCoversationMessage'; import { useNotebookWelcomePrompts } from '../../hooks/useNotebookWelcomePrompts'; +import { useStopConversation } from '../../hooks/useStopConversation'; import { useTranslation } from '../../hooks/useTranslation'; import { NotebookSessionMetadata, SessionDocument } from '../../types'; import { ChatbotFootnoteWithIcon } from '../../utils/lightspeed-chatbox-utils'; @@ -256,12 +258,31 @@ const useStyles = makeStyles(theme => ({ ? theme.palette.grey[100] : 'var(--pf-t--global--background--color--secondary--default)', }, - '& .pf-chatbot__button--send, & .pf-chatbot__button--microphone': { - '--pf-v6-c-button--BorderRadius': - 'var(--pf-t--global--border--radius--pill)', - borderRadius: 'var(--pf-t--global--border--radius--pill) !important', + '& .pf-chatbot__button--stop, & .pf-chatbot__button--attach, & .pf-chatbot__button--send, & .pf-chatbot__button--microphone': + { + '--pf-v6-c-button--BorderRadius': + 'var(--pf-t--global--border--radius--pill)', + borderRadius: 'var(--pf-t--global--border--radius--pill) !important', + }, + }, + messageBar: { + border: '1px solid var(--pf-t--global--border--color--default)', + borderRadius: 24, + padding: theme.spacing(0.5), + '&::after': { + display: 'none', }, }, + addResourceButton: { + background: 'none', + border: 'none', + cursor: 'pointer', + padding: 4, + display: 'inline-flex', + alignItems: 'center', + color: 'inherit', + marginLeft: theme.spacing(2), + }, chatContent: { minHeight: 0, display: 'flex', @@ -315,6 +336,8 @@ export const NotebookView = ({ metadata?.conversation_id ?? TEMP_CONVERSATION_ID, ); const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false); + const [requestId, setRequestId] = useState(''); + const { mutate: stopConversation } = useStopConversation(); const [announcement, setAnnouncement] = useState( undefined, ); @@ -355,6 +378,10 @@ export const NotebookView = ({ [notebookCreateMessage, sessionId], ); + const onRequestIdReady = useCallback((rid: string) => { + setRequestId(rid); + }, []); + const { conversationMessages, handleInputPrompt, scrollToBottomRef } = useConversationMessages( conversationId, @@ -365,6 +392,7 @@ export const NotebookView = ({ onComplete, onStart, createMessageAdapter, + onRequestIdReady, ); const [messages, setMessages] = @@ -387,12 +415,23 @@ export const NotebookView = ({ [handleInputPrompt, t], ); + const handleStopButton = useCallback(() => { + if (requestId) { + stopConversation(requestId); + setRequestId(''); + } + setIsSendButtonDisabled(false); + setAnnouncement(t('conversation.announcement.responseStopped')); + }, [requestId, stopConversation, t]); + const notebookPrompts = useNotebookWelcomePrompts(); const welcomePrompts = notebookPrompts.map(title => ({ title, onClick: () => sendMessage(title), })); + const uploadMutation = useUploadDocument(); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); const [uploadingFileNames, setUploadingFileNames] = useState([]); @@ -403,6 +442,7 @@ export const NotebookView = ({ new Set(), ); const [filesToOverwrite, setFilesToOverwrite] = useState([]); + const [allFilesForOverwrite, setAllFilesForOverwrite] = useState([]); const [isOverwriteModalOpen, setIsOverwriteModalOpen] = useState(false); const [filesToAddToModal, setFilesToAddToModal] = useState([]); @@ -438,6 +478,18 @@ export const NotebookView = ({ const handleOpenUploadModal = () => setIsUploadModalOpen(true); const handleCloseUploadModal = () => setIsUploadModalOpen(false); + const handleCloseNotebook = async () => { + const isUntitled = notebookName === UNTITLED_NOTEBOOK_NAME; + const hasNoDocuments = documents.length === 0; + const hasNoPendingUploads = !pendingUploads.length; + const hasNoChat = conversationId === TEMP_CONVERSATION_ID; + + if (isUntitled && hasNoDocuments && hasNoPendingUploads && hasNoChat) { + await notebooksApi.deleteSession(sessionId).catch(() => {}); + } + onClose(); + }; + const handleFilesUploading = (files: File[]) => { setUploadingFileNames(prev => { const newNames = files @@ -472,28 +524,52 @@ export const NotebookView = ({ ]); }; - const handleDuplicatesFound = (files: File[]) => { - setFilesToOverwrite(files); + const handleDuplicatesFound = (duplicateFiles: File[], allFiles: File[]) => { + setFilesToOverwrite(duplicateFiles); + setAllFilesForOverwrite(allFiles); + setIsUploadModalOpen(false); setIsOverwriteModalOpen(true); }; - const handleOverwriteConfirm = () => { - const files = filesToOverwrite; + const handleOverwriteConfirm = (filesToUpload: File[]) => { setIsOverwriteModalOpen(false); setFilesToOverwrite([]); - - if (files.length === 0) return; - - setFilesToAddToModal(files); + setAllFilesForOverwrite([]); + + if (filesToUpload.length === 0) return; + + handleFilesUploading(filesToUpload); + for (const file of filesToUpload) { + uploadMutation + .mutateAsync({ sessionId, file }) + .then(data => { + handleUploadStarted({ + fileName: file.name, + documentId: data.document_id, + }); + }) + .catch(() => { + handleUploadFailed(file.name); + }); + } }; const handleFilesAddedToModal = () => { setFilesToAddToModal([]); }; + const handleOverwriteBack = () => { + setIsOverwriteModalOpen(false); + setFilesToAddToModal(allFilesForOverwrite); + setFilesToOverwrite([]); + setAllFilesForOverwrite([]); + setIsUploadModalOpen(true); + }; + const handleOverwriteCancel = () => { setIsOverwriteModalOpen(false); setFilesToOverwrite([]); + setAllFilesForOverwrite([]); }; const pollingResults = useDocumentStatusPolling(sessionId, pendingUploads); @@ -740,7 +816,7 @@ export const NotebookView = ({ + } + buttonProps={{ + send: { + tooltipContent: t('tooltip.send'), + }, + }} />
) : ( handleOpenUploadModal()} + aria-label={t('notebook.view.documents.add')} + className={classes.addResourceButton} + > + + + } + buttonProps={{ + microphone: { + tooltipContent: { + active: t('tooltip.microphone.active'), + inactive: t('tooltip.microphone.inactive'), + }, + }, + send: { + tooltipContent: t('tooltip.send'), + }, + }} /> )} @@ -808,7 +925,9 @@ export const NotebookView = ({ isOpen={isOverwriteModalOpen} onClose={handleOverwriteCancel} onConfirm={handleOverwriteConfirm} - fileNames={filesToOverwrite.map(f => f.name)} + onBack={handleOverwriteBack} + allFiles={allFilesForOverwrite} + duplicateFileNames={filesToOverwrite.map(f => f.name)} /> ({ dialogContent: { padding: '0 24px 24px', }, + warningAlert: { + marginBottom: theme.spacing(2), + '& .pf-v6-c-alert__title': { + marginTop: 0, + }, + }, + radioGroup: { + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(0.5), + marginBottom: theme.spacing(2), + '& label': { + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), + cursor: 'pointer', + fontSize: '0.875rem', + }, + }, fileList: { margin: 0, padding: 0, listStyle: 'none', + maxHeight: 300, + overflowY: 'auto', }, fileItem: { display: 'flex', alignItems: 'center', gap: theme.spacing(1), - padding: `${theme.spacing(2)}px 0`, - borderBottom: - '1px solid var(--pf-t--global--border--color--default, #c7c7c7)', - cursor: 'pointer', + padding: `${theme.spacing(1.5)}px ${theme.spacing(1.5)}px`, + border: '1px solid var(--pf-t--global--border--color--default)', + borderRadius: 8, + marginBottom: theme.spacing(1), }, fileName: { flex: 1, @@ -74,44 +96,62 @@ const useStyles = makeStyles(theme => ({ fontSize: '0.875rem', lineHeight: '1.25rem', }, + warningIcon: { + color: 'var(--pf-t--global--color--status--warning--default)', + fontSize: '1rem', + flexShrink: 0, + }, dialogActions: { - justifyContent: 'left', - padding: theme.spacing(2.5), + display: 'flex', + justifyContent: 'flex-start', + padding: '16px 24px', gap: theme.spacing(1), }, - overwriteButton: { - textTransform: 'none', - borderRadius: 999, - }, - cancelButton: { - textTransform: 'none', - borderRadius: 999, - }, - warningAlert: { - borderRadius: '6px', - }, })); type OverwriteConfirmModalProps = { isOpen: boolean; onClose: () => void; - onConfirm: () => void; - fileNames: string[]; + onConfirm: (filesToUpload: File[]) => void; + onBack: () => void; + allFiles: File[]; + duplicateFileNames: string[]; }; export const OverwriteConfirmModal = ({ isOpen, onClose, onConfirm, - fileNames, + onBack, + allFiles, + duplicateFileNames, }: OverwriteConfirmModalProps) => { const classes = useStyles(); const { t } = useTranslation(); + const [duplicateAction, setDuplicateAction] = useState<'replace' | 'ignore'>( + 'replace', + ); + + const duplicateSet = new Set(duplicateFileNames); + const newFiles = allFiles.filter(f => !duplicateSet.has(f.name)); + const duplicateFiles = allFiles.filter(f => duplicateSet.has(f.name)); + + const filesToUpload = duplicateAction === 'replace' ? allFiles : newFiles; + + const handleConfirm = () => { + onConfirm(filesToUpload); + setDuplicateAction('replace'); + }; + + const handleClose = () => { + setDuplicateAction('replace'); + onClose(); + }; return ( @@ -132,37 +172,60 @@ export const OverwriteConfirmModal = ({ - - {t('notebook.overwrite.modal.description')} - + + +
+ + +
    - {fileNames.map(name => ( -
  • - - {name} + {allFiles.map(file => ( +
  • + + {file.name} + {duplicateSet.has(file.name) && ( + + )}
  • ))}
- - - - +
); }; diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/UploadResourceScreen.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/UploadResourceScreen.tsx index 2653946517a..c3dca669e0a 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/UploadResourceScreen.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/UploadResourceScreen.tsx @@ -97,7 +97,6 @@ export const UploadResourceScreen = ({ variant="secondary" className={classes.uploadButton} icon={} - iconPosition="end" onClick={onUploadClick} > {t('notebook.view.upload.action')} diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts index c4fb6cf6e9d..3768dcac053 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts @@ -36,6 +36,9 @@ export const intelligentAssistantMessages = { 'Start a new notebook to organize your sources and generate AI-powered insights.', 'notebooks.empty.action': 'Create a new notebook', 'notebooks.documents': 'Documents', + 'notebooks.documents.none': 'No documents', + 'notebooks.documents.one': '1 document', + 'notebooks.documents.other': '{{count}} documents', 'notebooks.actions.rename': 'Rename', 'notebooks.actions.delete': 'Delete', 'notebooks.rename.title': 'Rename {{name}}?', @@ -84,14 +87,18 @@ export const intelligentAssistantMessages = { 'notebook.upload.failed': '"{{fileName}}" upload failed.', // Notebook upload modal - 'notebook.upload.modal.title': 'Add a document to Notebook', - 'notebook.upload.modal.dragDropTitle': 'Drag and drop files here', + 'notebook.upload.modal.title': 'Add resources', + 'notebook.upload.modal.dragDropTitle': + 'Drag and drop files here, or click to browse', 'notebook.upload.modal.browseButton': 'Upload', 'notebook.upload.modal.separator': 'or', 'notebook.upload.modal.infoText': 'Accepted file types: .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.supportedFormats': 'Supported formats:', + 'notebook.upload.modal.maxFileSize': 'Maximum file size is 25 MB.', 'notebook.upload.modal.selectedFiles': '{{count}} of {{max}} files selected', 'notebook.upload.modal.addButton': 'Add ({{count}})', + 'notebook.upload.modal.addButtonEmpty': 'Add', 'notebook.upload.modal.removeFile': 'Remove {{fileName}}', 'notebook.upload.error.unsupportedType': 'Upload error: Unsupported file type(s) found. Please upload only supported file types.', @@ -101,10 +108,13 @@ export const intelligentAssistantMessages = { 'Upload error: Maximum of {{max}} files allowed.', // Notebook overwrite modal - 'notebook.overwrite.modal.title': 'Overwrite Files?', + 'notebook.overwrite.modal.title': 'File already exists', 'notebook.overwrite.modal.description': - 'The following files already exist in this notebook. Do you want to overwrite them with the new versions?', - 'notebook.overwrite.modal.action': 'Overwrite', + '{{duplicateCount}} files already exist in this notebook. {{newCount}} new resources will be added regardless.', + 'notebook.overwrite.modal.replace': 'Replace existing files', + 'notebook.overwrite.modal.ignore': 'Ignore duplicated files', + 'notebook.overwrite.modal.action': 'Upload ({{count}})', + 'notebook.overwrite.modal.back': 'Back', 'notebook.document.delete': 'Delete', 'notebook.document.delete.title': 'Remove resource?', 'notebook.document.delete.description': From da3844ae9b6e015182f2abaf1e16e414816e8576 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Mon, 3 Aug 2026 07:18:41 +0530 Subject: [PATCH 02/13] updating e2e test Signed-off-by: its-mitesh-kumar --- .../pages/NotebookAddDocumentModalPage.ts | 41 ++++++++----------- .../src/translations/de.ts | 20 ++++++--- .../src/translations/es.ts | 20 ++++++--- .../src/translations/fr.ts | 21 +++++++--- .../src/translations/it.ts | 21 +++++++--- .../src/translations/ja.ts | 20 ++++++--- 6 files changed, 93 insertions(+), 50 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index 789a58fcce5..5cc7872cd64 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -44,32 +44,24 @@ export class NotebookAddDocumentModalPage { ); } - separatorBetweenDragZoneAndBrowse(): Locator { - return this.dialog().getByText(this.t['notebook.upload.modal.separator'], { - exact: true, - }); - } - - browseFilesButton(): Locator { - return this.dialog().getByRole('button', { - name: this.t['notebook.upload.modal.browseButton'], - exact: true, - }); + supportedFormatsLabel(): Locator { + return this.dialog().getByText( + this.t['notebook.upload.modal.supportedFormats'], + ); } - acceptedFileTypesParagraph(): Locator { - return this.dialog().getByText(this.t['notebook.upload.modal.infoText'], { - exact: true, - }); + maxFileSizeText(): Locator { + return this.dialog().getByText(this.t['notebook.upload.modal.maxFileSize']); } addFilesButton(stagedCount: number): Locator { - const label = substituteNotebookTemplate( - this.t['notebook.upload.modal.addButton'], - { - count: stagedCount, - }, - ); + const label = + stagedCount > 0 + ? substituteNotebookTemplate( + this.t['notebook.upload.modal.addButton'], + { count: stagedCount }, + ) + : this.t['notebook.upload.modal.addButtonEmpty']; return this.dialog().getByRole('button', { name: label }); } @@ -82,9 +74,8 @@ export class NotebookAddDocumentModalPage { /** Drop-zone copy, “or”, browse button, accepted file types paragraph. */ async expectUploadAreaFullyDescribed(): Promise { await expect(this.dragAndDropInstructions()).toBeVisible(); - await expect(this.separatorBetweenDragZoneAndBrowse()).toBeVisible(); - await expect(this.browseFilesButton()).toBeVisible(); - await expect(this.acceptedFileTypesParagraph()).toBeVisible(); + await expect(this.supportedFormatsLabel()).toBeVisible(); + await expect(this.maxFileSizeText()).toBeVisible(); } async expectModalTitleBarMatchesAriaSnapshot(): Promise { @@ -102,7 +93,7 @@ export class NotebookAddDocumentModalPage { async selectFilesViaBrowsePicker(filePaths: string[]): Promise { const [fileChooser] = await Promise.all([ this.page.waitForEvent('filechooser'), - this.browseFilesButton().click(), + this.dragAndDropInstructions().click(), ]); await fileChooser.setFiles(filePaths); } diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts index 6dd9cea222a..da48dd6481c 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts @@ -186,10 +186,13 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'notebook.document.delete.success': '„{{documentName}}" wurde erfolgreich entfernt.', 'notebook.document.delete.title': 'Ressource entfernen?', - 'notebook.overwrite.modal.action': 'Überschreiben', + 'notebook.overwrite.modal.action': 'Hochladen ({{count}})', + 'notebook.overwrite.modal.back': 'Zurück', 'notebook.overwrite.modal.description': - 'Die folgenden Dateien existieren bereits in diesem Notizbuch. Möchten Sie sie mit den neuen Versionen überschreiben?', - 'notebook.overwrite.modal.title': 'Dateien überschreiben?', + '{{duplicateCount}} Dateien existieren bereits in diesem Notizbuch. {{newCount}} neue Ressourcen werden trotzdem hinzugefügt.', + 'notebook.overwrite.modal.ignore': 'Doppelte Dateien ignorieren', + 'notebook.overwrite.modal.replace': 'Bestehende Dateien ersetzen', + 'notebook.overwrite.modal.title': 'Datei existiert bereits', 'notebook.upload.error.fileTooLarge': 'Upload-Fehler: Dateigröße überschreitet das Limit von 25 MB.', 'notebook.upload.error.tooManyFiles': @@ -198,15 +201,19 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'Upload-Fehler: Nicht unterstützte Dateitypen gefunden. Bitte laden Sie nur unterstützte Dateitypen hoch.', 'notebook.upload.failed': 'Hochladen von "{{fileName}}" fehlgeschlagen.', 'notebook.upload.modal.addButton': 'Hinzufügen ({{count}})', + 'notebook.upload.modal.addButtonEmpty': 'Hinzufügen', 'notebook.upload.modal.browseButton': 'Hochladen', - 'notebook.upload.modal.dragDropTitle': 'Dateien hierher ziehen und ablegen', + 'notebook.upload.modal.dragDropTitle': + 'Dateien hierher ziehen und ablegen oder zum Durchsuchen klicken', 'notebook.upload.modal.infoText': 'Akzeptierte Dateitypen: .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.maxFileSize': 'Maximale Dateigröße beträgt 25 MB.', + 'notebook.upload.modal.supportedFormats': 'Unterstützte Formate:', 'notebook.upload.modal.removeFile': '{{fileName}} entfernen', 'notebook.upload.modal.selectedFiles': '{{count}} von {{max}} Dateien ausgewählt', 'notebook.upload.modal.separator': 'oder', - 'notebook.upload.modal.title': 'Dokument zum Notizbuch hinzufügen', + 'notebook.upload.modal.title': 'Ressourcen hinzufügen', 'notebook.view.close': 'Notizbuch schließen', 'notebook.view.documents.add': 'Hinzufügen', 'notebook.view.documents.count': '{{count}} Dokumente', @@ -235,6 +242,9 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'notebooks.delete.title': '{{name}} löschen?', 'notebooks.delete.toast': 'Notizbuch gelöscht!', 'notebooks.documents': 'Dokumente', + 'notebooks.documents.none': 'Keine Dokumente', + 'notebooks.documents.one': '1 Dokument', + 'notebooks.documents.other': '{{count}} Dokumente', 'notebooks.empty.action': 'Neues Notizbuch erstellen', 'notebooks.empty.description': 'Erstellen Sie ein neues Notizbuch, um Ihre Quellen zu organisieren und KI-gestützte Erkenntnisse zu gewinnen.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts index bbf6b0768ee..c122d56f1ec 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts @@ -182,10 +182,13 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'notebook.document.delete.success': '«{{documentName}}» se eliminó correctamente.', 'notebook.document.delete.title': '¿Eliminar recurso?', - 'notebook.overwrite.modal.action': 'Sobrescribir', + 'notebook.overwrite.modal.action': 'Subir ({{count}})', + 'notebook.overwrite.modal.back': 'Atrás', 'notebook.overwrite.modal.description': - 'Los siguientes archivos ya existen en este cuaderno. ¿Desea sobrescribirlos con las nuevas versiones?', - 'notebook.overwrite.modal.title': '¿Sobrescribir archivos?', + '{{duplicateCount}} archivos ya existen en este cuaderno. {{newCount}} nuevos recursos se agregarán de todos modos.', + 'notebook.overwrite.modal.ignore': 'Ignorar archivos duplicados', + 'notebook.overwrite.modal.replace': 'Reemplazar archivos existentes', + 'notebook.overwrite.modal.title': 'El archivo ya existe', 'notebook.upload.error.fileTooLarge': 'Error de carga: el tamaño del archivo supera el límite de 25 MB.', 'notebook.upload.error.tooManyFiles': @@ -194,16 +197,20 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'Error de carga: se encontraron tipos de archivo no compatibles. Suba solo tipos de archivo compatibles.', 'notebook.upload.failed': 'Error al subir "{{fileName}}".', 'notebook.upload.modal.addButton': 'Agregar ({{count}})', + 'notebook.upload.modal.addButtonEmpty': 'Agregar', 'notebook.upload.modal.browseButton': 'Subir', 'notebook.upload.modal.dragDropTitle': - 'Arrastra y suelta los archivos aquí', + 'Arrastra y suelta los archivos aquí, o haz clic para explorar', 'notebook.upload.modal.infoText': 'Tipos de archivo aceptados: .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.maxFileSize': + 'El tamaño máximo del archivo es 25 MB.', + 'notebook.upload.modal.supportedFormats': 'Formatos compatibles:', 'notebook.upload.modal.removeFile': 'Eliminar {{fileName}}', 'notebook.upload.modal.selectedFiles': '{{count}} de {{max}} archivos seleccionados', 'notebook.upload.modal.separator': 'o', - 'notebook.upload.modal.title': 'Agregar un documento al cuaderno', + 'notebook.upload.modal.title': 'Agregar recursos', 'notebook.view.close': 'Cerrar cuaderno', 'notebook.view.documents.add': 'Agregar', 'notebook.view.documents.count': '{{count}} Documentos', @@ -231,6 +238,9 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'notebooks.delete.title': '¿Eliminar {{name}}?', 'notebooks.delete.toast': '¡Cuaderno eliminado!', 'notebooks.documents': 'Documentos', + 'notebooks.documents.none': 'Sin documentos', + 'notebooks.documents.one': '1 documento', + 'notebooks.documents.other': '{{count}} documentos', 'notebooks.empty.action': 'Crear un cuaderno nuevo', 'notebooks.empty.description': 'Crea un nuevo cuaderno para organizar tus fuentes y generar información con IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts index eb54f9c883e..185b41c9e17 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts @@ -186,10 +186,13 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'notebook.document.delete.success': '« {{documentName}} » supprimé avec succès.', 'notebook.document.delete.title': 'Supprimer la ressource ?', - 'notebook.overwrite.modal.action': 'Écraser', + 'notebook.overwrite.modal.action': 'Charger ({{count}})', + 'notebook.overwrite.modal.back': 'Retour', 'notebook.overwrite.modal.description': - 'Les fichiers suivants existent déjà dans ce carnet. Voulez-vous les écraser avec les nouvelles versions ?', - 'notebook.overwrite.modal.title': 'Écraser les fichiers ?', + '{{duplicateCount}} fichiers existent déjà dans ce carnet. {{newCount}} nouvelles ressources seront ajoutées quand même.', + 'notebook.overwrite.modal.ignore': 'Ignorer les fichiers en double', + 'notebook.overwrite.modal.replace': 'Remplacer les fichiers existants', + 'notebook.overwrite.modal.title': 'Le fichier existe déjà', 'notebook.upload.error.fileTooLarge': 'Erreur de chargement : la taille du fichier dépasse la limite de 25 Mo.', 'notebook.upload.error.tooManyFiles': @@ -198,15 +201,20 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'Erreur de chargement : type(s) de fichier non pris en charge. Veuillez charger uniquement des types de fichiers pris en charge.', 'notebook.upload.failed': 'Échec du chargement de "{{fileName}}".', 'notebook.upload.modal.addButton': 'Ajouter ({{count}})', + 'notebook.upload.modal.addButtonEmpty': 'Ajouter', 'notebook.upload.modal.browseButton': 'Charger', - 'notebook.upload.modal.dragDropTitle': 'Glissez-déposez les fichiers ici', + 'notebook.upload.modal.dragDropTitle': + 'Glissez-déposez les fichiers ici ou cliquez pour parcourir', 'notebook.upload.modal.infoText': 'Types de fichiers acceptés : .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.maxFileSize': + 'La taille maximale du fichier est de 25 Mo.', + 'notebook.upload.modal.supportedFormats': 'Formats pris en charge :', 'notebook.upload.modal.removeFile': 'Supprimer {{fileName}}', 'notebook.upload.modal.selectedFiles': '{{count}} sur {{max}} fichiers sélectionnés', 'notebook.upload.modal.separator': 'ou', - 'notebook.upload.modal.title': 'Ajouter un document au carnet', + 'notebook.upload.modal.title': 'Ajouter des ressources', 'notebook.view.close': 'Fermer le carnet', 'notebook.view.documents.add': 'Ajouter', 'notebook.view.documents.count': '{{count}} Documents', @@ -235,6 +243,9 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'notebooks.delete.title': 'Supprimer {{name}} ?', 'notebooks.delete.toast': 'Carnet supprimé !', 'notebooks.documents': 'Documents', + 'notebooks.documents.none': 'Aucun document', + 'notebooks.documents.one': '1 document', + 'notebooks.documents.other': '{{count}} documents', 'notebooks.empty.action': 'Créer un nouveau carnet', 'notebooks.empty.description': 'Créez un nouveau carnet pour organiser vos sources et générer des informations alimentées par l’IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts index 28aeab3f461..be3dfad6a8c 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts @@ -184,10 +184,13 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'notebook.document.delete.success': '«{{documentName}}» rimosso con successo.', 'notebook.document.delete.title': 'Rimuovere la risorsa?', - 'notebook.overwrite.modal.action': 'Sovrascrivi', + 'notebook.overwrite.modal.action': 'Carica ({{count}})', + 'notebook.overwrite.modal.back': 'Indietro', 'notebook.overwrite.modal.description': - 'I seguenti file esistono già in questo quaderno. Vuoi sovrascriverli con le nuove versioni?', - 'notebook.overwrite.modal.title': 'Sovrascrivere i file?', + '{{duplicateCount}} file esistono già in questo quaderno. {{newCount}} nuove risorse verranno aggiunte comunque.', + 'notebook.overwrite.modal.ignore': 'Ignora file duplicati', + 'notebook.overwrite.modal.replace': 'Sostituisci file esistenti', + 'notebook.overwrite.modal.title': 'Il file esiste già', 'notebook.upload.error.fileTooLarge': 'Errore di caricamento: la dimensione del file supera il limite di 25 MB.', 'notebook.upload.error.tooManyFiles': @@ -196,15 +199,20 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'Errore di caricamento: trovati tipi di file non supportati. Caricare solo tipi di file supportati.', 'notebook.upload.failed': 'Caricamento di "{{fileName}}" non riuscito.', 'notebook.upload.modal.addButton': 'Aggiungi ({{count}})', + 'notebook.upload.modal.addButtonEmpty': 'Aggiungi', 'notebook.upload.modal.browseButton': 'Carica', - 'notebook.upload.modal.dragDropTitle': 'Trascina e rilascia i file qui', + 'notebook.upload.modal.dragDropTitle': + 'Trascina e rilascia i file qui o fai clic per sfogliare', 'notebook.upload.modal.infoText': 'Tipi di file accettati: .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.maxFileSize': + 'La dimensione massima del file è 25 MB.', + 'notebook.upload.modal.supportedFormats': 'Formati supportati:', 'notebook.upload.modal.removeFile': 'Rimuovi {{fileName}}', 'notebook.upload.modal.selectedFiles': '{{count}} di {{max}} file selezionati', 'notebook.upload.modal.separator': 'o', - 'notebook.upload.modal.title': 'Aggiungi un documento al quaderno', + 'notebook.upload.modal.title': 'Aggiungi risorse', 'notebook.view.close': 'Chiudi quaderno', 'notebook.view.documents.add': 'Aggiungi', 'notebook.view.documents.count': '{{count}} Documenti', @@ -233,6 +241,9 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'notebooks.delete.title': 'Eliminare {{name}}?', 'notebooks.delete.toast': 'Quaderno eliminato!', 'notebooks.documents': 'Documenti', + 'notebooks.documents.none': 'Nessun documento', + 'notebooks.documents.one': '1 documento', + 'notebooks.documents.other': '{{count}} documenti', 'notebooks.empty.action': 'Crea un nuovo quaderno', 'notebooks.empty.description': 'Crea un nuovo quaderno per organizzare le tue fonti e generare insight basati su IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts index d53434282c5..fbe4752667a 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts @@ -181,10 +181,13 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'notebook.document.delete.success': '「{{documentName}}」が正常に削除されました。', 'notebook.document.delete.title': 'リソースを削除しますか?', - 'notebook.overwrite.modal.action': '上書き', + 'notebook.overwrite.modal.action': 'アップロード ({{count}})', + 'notebook.overwrite.modal.back': '戻る', 'notebook.overwrite.modal.description': - '以下のファイルはこのノートブックに既に存在します。新しいバージョンで上書きしますか?', - 'notebook.overwrite.modal.title': 'ファイルを上書きしますか?', + '{{duplicateCount}} 件のファイルがこのノートブックに既に存在します。{{newCount}} 件の新しいリソースはそのまま追加されます。', + 'notebook.overwrite.modal.ignore': '重複ファイルを無視', + 'notebook.overwrite.modal.replace': '既存のファイルを置換', + 'notebook.overwrite.modal.title': 'ファイルが既に存在します', 'notebook.upload.error.fileTooLarge': 'アップロードエラー: ファイルサイズが 25 MB の制限を超えています。', 'notebook.upload.error.tooManyFiles': @@ -193,15 +196,19 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'アップロードエラー: サポートされていないファイル形式が見つかりました。サポートされているファイル形式のみをアップロードしてください。', 'notebook.upload.failed': '「{{fileName}}」のアップロードに失敗しました。', 'notebook.upload.modal.addButton': '追加 ({{count}})', + 'notebook.upload.modal.addButtonEmpty': '追加', 'notebook.upload.modal.browseButton': 'アップロード', - 'notebook.upload.modal.dragDropTitle': 'ここにファイルをドラッグ&ドロップ', + 'notebook.upload.modal.dragDropTitle': + 'ここにファイルをドラッグ&ドロップ、またはクリックして参照', 'notebook.upload.modal.infoText': '対応ファイル形式: .md, .txt, .pdf, .json, .yaml, .log', + 'notebook.upload.modal.maxFileSize': '最大ファイルサイズは 25 MB です。', + 'notebook.upload.modal.supportedFormats': 'サポートされている形式:', 'notebook.upload.modal.removeFile': '{{fileName}} を削除', 'notebook.upload.modal.selectedFiles': '{{max}} 件中 {{count}} 件のファイルを選択', 'notebook.upload.modal.separator': 'または', - 'notebook.upload.modal.title': 'ノートブックにドキュメントを追加', + 'notebook.upload.modal.title': 'リソースを追加', 'notebook.view.close': 'ノートブックを閉じる', 'notebook.view.documents.add': '追加', 'notebook.view.documents.count': '{{count}} 件のドキュメント', @@ -230,6 +237,9 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'notebooks.delete.title': '{{name}} を削除しますか?', 'notebooks.delete.toast': 'ノートブックを削除しました!', 'notebooks.documents': 'ドキュメント', + 'notebooks.documents.none': 'ドキュメントなし', + 'notebooks.documents.one': '1 件のドキュメント', + 'notebooks.documents.other': '{{count}} 件のドキュメント', 'notebooks.empty.action': '新しいノートブックを作成', 'notebooks.empty.description': '新しいノートブックを作成してソースを整理し、AI による洞察を生成します。', From d7829d306065f397c2dd3d4dd9449035864ac2cf Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Wed, 5 Aug 2026 14:12:03 +0530 Subject: [PATCH 03/13] updating tests Signed-off-by: its-mitesh-kumar --- .../components/__tests__/NotebookCard.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx index 701f426f0d0..62a71853563 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx @@ -75,39 +75,39 @@ describe('NotebookCard', () => { expect(screen.getByText('My Notebook')).toBeInTheDocument(); }); - it('should render the document count for multiple documents', () => { + it('should render the resource count for multiple resources', () => { render(); - expect(screen.getByText('2 documents')).toBeInTheDocument(); + expect(screen.getByText('2 resources')).toBeInTheDocument(); }); - it('should render "No documents" when document_count is 0', () => { + it('should render "No resources" when document_count is 0', () => { render( , ); - expect(screen.getByText('No documents')).toBeInTheDocument(); + expect(screen.getByText('No resources')).toBeInTheDocument(); }); - it('should render "1 document" when document_count is 1', () => { + it('should render "1 resource" when document_count is 1', () => { render( , ); - expect(screen.getByText('1 document')).toBeInTheDocument(); + expect(screen.getByText('1 resource')).toBeInTheDocument(); }); - it('should render "No documents" when document_count is undefined', () => { + it('should render "No resources" when document_count is undefined', () => { render( , ); - expect(screen.getByText('No documents')).toBeInTheDocument(); + expect(screen.getByText('No resources')).toBeInTheDocument(); }); it('should call onClick with notebook when card is clicked', () => { @@ -173,6 +173,6 @@ describe('NotebookCard', () => { it('should render document_count from the notebook session', () => { render(); - expect(screen.getByText('2 documents')).toBeInTheDocument(); + expect(screen.getByText('2 resources')).toBeInTheDocument(); }); }); From dd34d1963dccda8ed642ab31378dcf7df50fcabe Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Wed, 5 Aug 2026 14:14:40 +0530 Subject: [PATCH 04/13] fixing prettier Signed-off-by: its-mitesh-kumar --- .../plugins/intelligent-assistant/src/translations/ref.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts index 4470f88be4c..32e91184517 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts @@ -35,10 +35,10 @@ export const intelligentAssistantMessages = { 'notebooks.empty.description': 'Start a new notebook to organize your sources and generate AI-powered insights.', 'notebooks.empty.action': 'Create a new notebook', -'notebooks.documents': 'Resources', -'notebooks.documents.none': 'No resources', -'notebooks.documents.one': '1 resource', -'notebooks.documents.other': '{{count}} resources', + 'notebooks.documents': 'Resources', + 'notebooks.documents.none': 'No resources', + 'notebooks.documents.one': '1 resource', + 'notebooks.documents.other': '{{count}} resources', 'notebooks.actions.rename': 'Rename', 'notebooks.actions.delete': 'Delete', 'notebooks.rename.title': 'Rename {{name}}?', From 00e257a61e5221dbc20baf4a3406618574b14033 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Wed, 5 Aug 2026 18:00:08 +0530 Subject: [PATCH 05/13] updating the tests Signed-off-by: its-mitesh-kumar --- .../e2e-tests/lightspeed.notebooks.test.ts | 5 ++--- .../NotebookOverwriteConfirmModalPage.ts | 20 ++++++++----------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index a46dde4d821..d2a28238da2 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -136,13 +136,12 @@ test.describe('Intelligent assistant notebooks', () => { await notebooks.clickOpenUploadDocumentModal(); uploadModal = notebooks.uploadDocumentModal(); await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); const overwriteModal = notebooks.notebookOverwriteConfirmModal(); await overwriteModal.expectDialogVisible(); await overwriteModal.expectListedOverwriteFile(fileName); - await overwriteModal.clickCancel(); - await sharedPage.waitForTimeout(200); - await uploadModal.clickCancel(); + await overwriteModal.clickBack(); await notebooks.deleteFirstListedDocumentFromSidebarOverflowMenu(); await notebooks.expectNotebookEditorUploadResourceButtonVisible(); diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts index 7ac948257e2..0ef2c87062e 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts @@ -19,7 +19,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import type { LightspeedMessages } from '../utils/translations'; /** - * “Overwrite Files?” confirmation when staging a file whose name already exists in the notebook (`OverwriteConfirmModal.tsx`). + * "File already exists" confirmation when uploading a file whose name already exists in the notebook (`OverwriteConfirmModal.tsx`). */ export class NotebookOverwriteConfirmModalPage { constructor( @@ -28,7 +28,6 @@ export class NotebookOverwriteConfirmModalPage { ) {} dialog(): Locator { - /** MUI dialogs may not use the plain title as the whole accessible name across engines. */ return this.page.getByRole('dialog').filter({ has: this.page.getByRole('heading', { name: this.t['notebook.overwrite.modal.title'], @@ -39,16 +38,14 @@ export class NotebookOverwriteConfirmModalPage { async expectDialogVisible(timeout = 15_000): Promise { await expect(this.dialog()).toBeVisible({ timeout }); - await expect( - this.dialog().getByText(this.t['notebook.overwrite.modal.description'], { - exact: true, - }), - ).toBeVisible(); } - async clickCancel(): Promise { + async clickBack(): Promise { await this.dialog() - .getByRole('button', { name: this.t['common.cancel'], exact: true }) + .getByRole('button', { + name: this.t['notebook.overwrite.modal.back'], + exact: true, + }) .click(); } @@ -58,11 +55,10 @@ export class NotebookOverwriteConfirmModalPage { ).toBeVisible(); } - async clickOverwrite(): Promise { + async clickUpload(): Promise { await this.dialog() .getByRole('button', { - name: this.t['notebook.overwrite.modal.action'], - exact: true, + name: /Upload|Hochladen|Subir|Charger|Carica|アップロード/i, }) .click(); } From 48744974002fd6b739d825fd1ce44ac0a7f95166 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Wed, 5 Aug 2026 23:46:47 +0530 Subject: [PATCH 06/13] updating the e2e tests Signed-off-by: its-mitesh-kumar --- .../e2e-tests/lightspeed.notebooks.test.ts | 56 ++++++++++++++++++- .../e2e-tests/pages/NotebookSurfacePage.ts | 11 ++-- .../__tests__/AddDocumentModal.test.tsx | 16 ++++++ .../__tests__/OverwriteConfirmModal.test.tsx | 22 ++++++++ .../src/components/notebooks/NotebookView.tsx | 9 ++- 5 files changed, 106 insertions(+), 8 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index d2a28238da2..8b49420e659 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -142,12 +142,21 @@ test.describe('Intelligent assistant notebooks', () => { await overwriteModal.expectDialogVisible(); await overwriteModal.expectListedOverwriteFile(fileName); await overwriteModal.clickBack(); + await uploadModal.clickCancel(); await notebooks.deleteFirstListedDocumentFromSidebarOverflowMenu(); await notebooks.expectNotebookEditorUploadResourceButtonVisible(); }); - test('grid: close editor, rename, delete', async () => { + test('grid: close editor, rename, delete', async ({}, testInfo) => { + const { absolutePath } = localeNotebookUpload1Path(testInfo.project.name); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await sharedPage.waitForTimeout(1000); + const untitledBefore = await notebooks.untitledNotebookCards().count(); await notebooks.clickCloseNotebookEditor(); @@ -156,7 +165,7 @@ test.describe('Intelligent assistant notebooks', () => { await expect(notebooks.newestUntitledNotebookCard()).toBeVisible(); await notebooks.expectNotebookListShowsDocumentCountSummaryAndUpdatedToday( - 0, + 1, ); await notebooks @@ -190,4 +199,47 @@ test.describe('Intelligent assistant notebooks', () => { await expect(sharedPage.getByText(RENAMED_NOTEBOOK_TITLE)).toBeHidden(); }); + + test('auto-delete: empty untitled notebook is discarded on close', async () => { + await notebooks.gotoFullscreenNotebooksTab(); + const cardsBefore = await notebooks.untitledNotebookCards().count(); + + await notebooks.clickCreateNotebookFromEmptyList(); + await expect(sharedPage).toHaveURL(NOTEBOOK_EDITOR_URL_RE); + + await notebooks.clickCloseNotebookEditor(); + + await notebooks.expectUntitledNotebookCardCount(cardsBefore); + }); + + test('auto-delete: notebook with uploaded file persists on close', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + const cardsBefore = await notebooks.untitledNotebookCards().count(); + + await notebooks.clickCreateNotebookFromEmptyList(); + await expect(sharedPage).toHaveURL(NOTEBOOK_EDITOR_URL_RE); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await sharedPage.waitForTimeout(2000); + + await notebooks.clickCloseNotebookEditor(); + + await notebooks.expectUntitledNotebookCardCount(cardsBefore + 1); + + await notebooks + .notebookCardOverflowMenuButton(notebooks.newestUntitledNotebookCard()) + .click(); + await notebooks.deleteNotebookOverflowMenuItem().click(); + const confirmDelete = notebooks.notebookDeleteConfirmationDialog( + NOTEBOOK_UNTITLED_GRID_NAME, + ); + await confirmDelete.confirmDeletion(); + await notebooks.expectUntitledNotebookCardCount(cardsBefore); + }); }); diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 8d90d961638..a381c81f64a 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -17,6 +17,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import type { LightspeedMessages } from '../utils/translations'; +import { substituteNotebookTemplate } from '../utils/notebookTranslation'; import { openLightspeed } from '../utils/testHelper'; import { NotebookAddDocumentModalPage } from './NotebookAddDocumentModalPage'; @@ -340,12 +341,12 @@ export class NotebookSurfacePage { }); } - /** - * Shown on each card as count + plural label (same pattern as NotebookCard.tsx: - * `{ document_count } { t('notebooks.documents') }`, not `notebook.view.documents.count`). - */ formatNotebookCardDocumentsSummary(documentCount: number): string { - return `${documentCount} ${this.t['notebooks.documents']}`; + if (documentCount === 0) return this.t['notebooks.documents.none']; + if (documentCount === 1) return this.t['notebooks.documents.one']; + return substituteNotebookTemplate(this.t['notebooks.documents.other'], { + count: documentCount, + }); } async expectUntitledNotebookCardCount(expected: number): Promise { diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx index 0476a378a8b..0725a64f485 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/AddDocumentModal.test.tsx @@ -247,6 +247,22 @@ describe('AddDocumentModal', () => { expect(screen.queryByText('test-file.txt')).not.toBeInTheDocument(); }); + it('should show disabled dropzone with tooltip when file limit is reached', () => { + const existingNames = Array.from({ length: 10 }, (_, i) => `file-${i}.txt`); + render( + , + ); + + expect( + screen.getByText('Drag and drop files here, or click to browse'), + ).toBeInTheDocument(); + expect(screen.getByText('Maximum file size is 25 MB.')).toBeInTheDocument(); + expect(screen.getByText('Supported formats:')).toBeInTheDocument(); + }); + it('should call onDuplicatesFound when Add is clicked with duplicate files', async () => { render( { expect(onClose).toHaveBeenCalledTimes(1); }); + + it('should update upload count when switching from ignore back to replace', () => { + renderModal(); + + const ignoreRadio = screen.getByLabelText('Ignore duplicated files'); + fireEvent.click(ignoreRadio); + expect( + screen.getByRole('button', { name: /Upload \(1\)/ }), + ).toBeInTheDocument(); + + const replaceRadio = screen.getByLabelText('Replace existing files'); + fireEvent.click(replaceRadio); + expect( + screen.getByRole('button', { name: /Upload \(3\)/ }), + ).toBeInTheDocument(); + }); + + it('should render an empty file list gracefully when no files provided', () => { + renderModal({ allFiles: [], duplicateFileNames: [] }); + + expect(screen.queryByRole('listitem')).not.toBeInTheDocument(); + }); }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx index 52b940ab831..cb0067a9696 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx @@ -482,9 +482,16 @@ export const NotebookView = ({ const isUntitled = notebookName === UNTITLED_NOTEBOOK_NAME; const hasNoDocuments = documents.length === 0; const hasNoPendingUploads = !pendingUploads.length; + const hasNoUploading = !uploadingFileNames.length; const hasNoChat = conversationId === TEMP_CONVERSATION_ID; - if (isUntitled && hasNoDocuments && hasNoPendingUploads && hasNoChat) { + if ( + isUntitled && + hasNoDocuments && + hasNoPendingUploads && + hasNoUploading && + hasNoChat + ) { await notebooksApi.deleteSession(sessionId).catch(() => {}); } onClose(); From c63d6bf37e003fb8fe26192581a77b17285caef1 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Thu, 6 Aug 2026 00:26:25 +0530 Subject: [PATCH 07/13] update tests Signed-off-by: its-mitesh-kumar --- .../__tests__/OverwriteConfirmModal.test.tsx | 58 ++++++++ .../components/notebooks/AddDocumentModal.tsx | 11 +- .../src/components/notebooks/NotebookView.tsx | 140 +++++++++--------- .../notebooks/OverwriteConfirmModal.tsx | 17 ++- 4 files changed, 152 insertions(+), 74 deletions(-) diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx index f096252db73..b6c0c491b3d 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/OverwriteConfirmModal.test.tsx @@ -156,4 +156,62 @@ describe('OverwriteConfirmModal', () => { expect(screen.queryByRole('listitem')).not.toBeInTheDocument(); }); + + it('should disable Upload button when all files are duplicates and ignore is selected', () => { + renderModal({ + allFiles: [createFile('dup1.txt'), createFile('dup2.txt')], + duplicateFileNames: ['dup1.txt', 'dup2.txt'], + }); + + const ignoreRadio = screen.getByLabelText('Ignore duplicated files'); + fireEvent.click(ignoreRadio); + + const uploadButton = screen.getByRole('button', { + name: /Upload \(0\)/, + }); + expect(uploadButton).toBeDisabled(); + }); + + it('should reset radio to replace when modal reopens', () => { + const { rerender } = render( + , + ); + + fireEvent.click(screen.getByLabelText('Ignore duplicated files')); + expect( + screen.getByRole('button', { name: /Upload \(1\)/ }), + ).toBeInTheDocument(); + + rerender( + , + ); + rerender( + , + ); + + expect( + screen.getByRole('button', { name: /Upload \(3\)/ }), + ).toBeInTheDocument(); + }); }); 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 4505491ceea..aa1281f0eeb 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 @@ -48,6 +48,10 @@ import { } from '../../utils/notebook-upload-utils'; import { FileListItem } from './FileListItem'; +const UNIQUE_FILE_TYPE_LABELS = [ + ...new Set(Object.values(NOTEBOOK_EXTENSION_TO_FILE_TYPE)), +].map(t => t.toUpperCase()); + const useStyles = makeStyles(theme => ({ dialogPaper: { borderRadius: 24, @@ -175,6 +179,7 @@ const DropzoneClickArea = ({ return (
- {[ - ...new Set(Object.values(NOTEBOOK_EXTENSION_TO_FILE_TYPE)), - ].map(label => ( + {UNIQUE_FILE_TYPE_LABELS.map(label => ( - {label.toUpperCase()} + {label} ))}
diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx index cb0067a9696..6bf5e00b121 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx @@ -281,6 +281,10 @@ const useStyles = makeStyles(theme => ({ display: 'inline-flex', alignItems: 'center', color: 'inherit', + '&:focus-visible': { + outline: '2px solid var(--pf-t--global--border--color--brand--default)', + outlineOffset: 2, + }, marginLeft: theme.spacing(2), }, chatContent: { @@ -338,6 +342,7 @@ export const NotebookView = ({ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false); const [requestId, setRequestId] = useState(''); const { mutate: stopConversation } = useStopConversation(); + const wasStoppedByUserRef = useRef(false); const [announcement, setAnnouncement] = useState( undefined, ); @@ -356,7 +361,10 @@ export const NotebookView = ({ const onComplete = useCallback( (message: string) => { setIsSendButtonDisabled(false); - setAnnouncement(`Message from Bot: ${message}`); + if (!wasStoppedByUserRef.current) { + setAnnouncement(`Message from Bot: ${message}`); + } + wasStoppedByUserRef.current = false; queryClient.invalidateQueries({ queryKey: ['conversationMessages', conversationId], }); @@ -404,6 +412,7 @@ export const NotebookView = ({ const sendMessage = useCallback( (message: string | number) => { + wasStoppedByUserRef.current = false; setAnnouncement( t('conversation.announcement.userMessage' as any, { prompt: message.toString(), @@ -416,6 +425,7 @@ export const NotebookView = ({ ); const handleStopButton = useCallback(() => { + wasStoppedByUserRef.current = true; if (requestId) { stopConversation(requestId); setRequestId(''); @@ -838,74 +848,70 @@ export const NotebookView = ({ renderNotebookDisclaimerAlert()} - {hasNoDocuments ? ( - -
- handleOpenUploadModal()} - aria-label={t('notebook.view.documents.add')} - className={classes.addResourceButton} - > - - - } - buttonProps={{ - send: { - tooltipContent: t('tooltip.send'), + {(() => { + const addResourceAction = ( + + ); + return hasNoDocuments ? ( + +
+ +
+
+ ) : ( + -
-
- ) : ( - handleOpenUploadModal()} - aria-label={t('notebook.view.documents.add')} - className={classes.addResourceButton} - > - - - } - buttonProps={{ - microphone: { - tooltipContent: { - active: t('tooltip.microphone.active'), - inactive: t('tooltip.microphone.inactive'), }, - }, - send: { - tooltipContent: t('tooltip.send'), - }, - }} - /> - )} + send: { + tooltipContent: t('tooltip.send'), + }, + }} + /> + ); + })()}
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 b4dafa0a22e..4fc50702f8d 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import CloseIcon from '@mui/icons-material/Close'; @@ -132,7 +132,14 @@ export const OverwriteConfirmModal = ({ 'replace', ); - const duplicateSet = new Set(duplicateFileNames); + useEffect(() => { + if (isOpen) setDuplicateAction('replace'); + }, [isOpen]); + + const duplicateSet = useMemo( + () => new Set(duplicateFileNames), + [duplicateFileNames], + ); const newFiles = allFiles.filter(f => !duplicateSet.has(f.name)); const duplicateFiles = allFiles.filter(f => duplicateSet.has(f.name)); @@ -217,7 +224,11 @@ export const OverwriteConfirmModal = ({
- ); 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 4fc50702f8d..be2cb08b460 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 @@ -53,6 +53,7 @@ const useStyles = makeStyles(theme => ({ padding: '0 24px 24px', }, warningAlert: { + '--pf-v6-c-alert--PaddingBlockEnd': '0', marginBottom: theme.spacing(2), '& .pf-v6-c-alert__title': { marginTop: 0, @@ -61,7 +62,7 @@ const useStyles = makeStyles(theme => ({ radioGroup: { display: 'flex', flexDirection: 'column', - gap: theme.spacing(0.5), + gap: theme.spacing(1), marginBottom: theme.spacing(2), '& label': { display: 'flex', @@ -70,6 +71,9 @@ const useStyles = makeStyles(theme => ({ cursor: 'pointer', fontSize: '0.875rem', }, + '& input[type="radio"]': { + cursor: 'pointer', + }, }, fileList: { margin: 0, diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts index 157d6135188..2c52d5da7b3 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts @@ -250,12 +250,12 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'notebook.view.sidebar.expand': 'Seitenleiste ausklappen', 'notebook.view.sidebar.resize': 'Größe der Seitenleiste ändern', 'notebook.view.title': 'Unbenanntes Notizbuch', - 'notebook.view.upload.action': 'Ressource hochladen', + 'notebook.view.upload.action': 'Ressource hinzufügen', 'notebook.view.processing.description': 'Ihre Dateien werden indexiert. Sie können Fragen stellen, sobald die Verarbeitung abgeschlossen ist.', 'notebook.view.processing.heading': 'Ressourcen werden verarbeitet...', 'notebook.view.upload.heading': - 'Laden Sie eine Ressource hoch, um zu beginnen', + 'Fügen Sie eine Ressource hinzu, um zu beginnen', 'notebooks.actions.delete': 'Löschen', 'notebooks.actions.rename': 'Umbenennen', 'notebooks.card.openAria': 'Notizbuch {{name}} öffnen', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts index 70a9a1500a0..b5725c62d1d 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts @@ -247,11 +247,11 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'notebook.view.sidebar.expand': 'Expandir barra lateral', 'notebook.view.sidebar.resize': 'Redimensionar barra lateral', 'notebook.view.title': 'Cuaderno sin título', - 'notebook.view.upload.action': 'Subir un recurso', + 'notebook.view.upload.action': 'Agregar un recurso', 'notebook.view.processing.description': 'Sus archivos están siendo indexados. Puede comenzar a hacer preguntas una vez que se complete el procesamiento.', 'notebook.view.processing.heading': 'Procesando recursos...', - 'notebook.view.upload.heading': 'Sube un recurso para empezar', + 'notebook.view.upload.heading': 'Agrega un recurso para empezar', 'notebooks.actions.delete': 'Eliminar', 'notebooks.actions.rename': 'Renombrar', 'notebooks.card.openAria': 'Abrir el cuaderno {{name}}', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts index a0dae86c80a..017e810bf0d 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts @@ -251,11 +251,11 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'notebook.view.sidebar.expand': 'Développer la barre latérale', 'notebook.view.sidebar.resize': 'Redimensionner la barre latérale', 'notebook.view.title': 'Carnet sans titre', - 'notebook.view.upload.action': 'Charger une ressource', + 'notebook.view.upload.action': 'Ajouter une ressource', 'notebook.view.processing.description': 'Vos fichiers sont en cours d’indexation. Vous pourrez poser des questions une fois le traitement terminé.', 'notebook.view.processing.heading': 'Traitement des ressources...', - 'notebook.view.upload.heading': 'Chargez une ressource pour commencer', + 'notebook.view.upload.heading': 'Ajoutez une ressource pour commencer', 'notebooks.actions.delete': 'Supprimer', 'notebooks.actions.rename': 'Renommer', 'notebooks.card.openAria': 'Ouvrir le carnet {{name}}', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts index 96fd606c826..4906cbc32b1 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts @@ -249,11 +249,11 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'notebook.view.sidebar.expand': 'Espandi barra laterale', 'notebook.view.sidebar.resize': 'Ridimensiona barra laterale', 'notebook.view.title': 'Quaderno senza titolo', - 'notebook.view.upload.action': 'Carica una risorsa', + 'notebook.view.upload.action': 'Aggiungi una risorsa', 'notebook.view.processing.description': 'I tuoi file sono in fase di indicizzazione. Potrai iniziare a fare domande una volta completata l’elaborazione.', 'notebook.view.processing.heading': 'Elaborazione delle risorse...', - 'notebook.view.upload.heading': 'Carica una risorsa per iniziare', + 'notebook.view.upload.heading': 'Aggiungi una risorsa per iniziare', 'notebooks.actions.delete': 'Elimina', 'notebooks.actions.rename': 'Rinomina', 'notebooks.card.openAria': 'Apri il taccuino {{name}}', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts index 0e551fae92f..57381be8c68 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts @@ -243,12 +243,11 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'notebook.view.sidebar.expand': 'サイドバーを展開する', 'notebook.view.sidebar.resize': 'サイドバーのサイズを変更する', 'notebook.view.title': '無題のノートブック', - 'notebook.view.upload.action': 'リソースをアップロード', + 'notebook.view.upload.action': 'リソースを追加', 'notebook.view.processing.description': 'ファイルをインデックスしています。処理が完了すると質問を開始できます。', 'notebook.view.processing.heading': 'リソースを処理中...', - 'notebook.view.upload.heading': - 'リソースをアップロードして開始してください', + 'notebook.view.upload.heading': 'リソースを追加して開始してください', 'notebooks.actions.delete': '削除', 'notebooks.actions.rename': '名前の変更', 'notebooks.card.openAria': 'ノートブック {{name}} を開く', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts index 32e91184517..793296718fe 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts @@ -68,8 +68,8 @@ export const intelligentAssistantMessages = { 'notebook.view.close': 'Close notebook', 'notebook.view.documents.count': '{{count}} Resources', 'notebook.view.documents.add': 'Add', - 'notebook.view.upload.heading': 'Upload a resource to get started', - 'notebook.view.upload.action': 'Upload a resource', + 'notebook.view.upload.heading': 'Add a resource to get started', + 'notebook.view.upload.action': 'Add a resource', 'notebook.view.processing.heading': 'Processing resources...', 'notebook.view.processing.description': 'Your files are being indexed. You can start asking questions once processing is complete.', From 3f5a6205b01a68443d2a4609b9e8c7fb6d21e5e3 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Fri, 7 Aug 2026 23:09:31 +0530 Subject: [PATCH 09/13] handling pluralization for duplicate files Signed-off-by: its-mitesh-kumar --- .../NotebookOverwriteConfirmModalPage.ts | 15 ++- .../components/notebooks/AddDocumentModal.tsx | 29 +++-- .../src/components/notebooks/NotebookView.tsx | 20 +--- .../notebooks/OverwriteConfirmModal.tsx | 19 +++- .../src/translations/de.ts | 6 + .../src/translations/es.ts | 6 + .../src/translations/fr.ts | 6 + .../src/translations/it.ts | 6 + .../src/translations/ja.ts | 6 + .../src/translations/ref.ts | 6 + .../__tests__/notebook-upload-runner.test.ts | 104 ++++++++++++++++++ .../src/utils/notebook-upload-runner.ts | 50 +++++++++ 12 files changed, 235 insertions(+), 38 deletions(-) create mode 100644 workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/__tests__/notebook-upload-runner.test.ts create mode 100644 workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/notebook-upload-runner.ts diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts index 0ef2c87062e..4a359548a75 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookOverwriteConfirmModalPage.ts @@ -17,6 +17,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import type { LightspeedMessages } from '../utils/translations'; +import { substituteNotebookTemplate } from '../utils/notebookTranslation'; /** * "File already exists" confirmation when uploading a file whose name already exists in the notebook (`OverwriteConfirmModal.tsx`). @@ -55,11 +56,19 @@ export class NotebookOverwriteConfirmModalPage { ).toBeVisible(); } + uploadButtonPattern(): RegExp { + const escaped = substituteNotebookTemplate( + this.t['notebook.overwrite.modal.action'], + { count: '\\d+' }, + ) + .replace('(', '\\(') + .replace(')', '\\)'); + return new RegExp(escaped); + } + async clickUpload(): Promise { await this.dialog() - .getByRole('button', { - name: /Upload|Hochladen|Subir|Charger|Carica|アップロード/i, - }) + .getByRole('button', { name: this.uploadButtonPattern() }) .click(); } } 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 5655ae230bd..1a3958dc49a 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 @@ -42,6 +42,7 @@ import { } from '../../const'; import { useUploadDocument } from '../../hooks/notebooks/useUploadDocument'; import { useTranslation } from '../../hooks/useTranslation'; +import { runFileUploads } from '../../utils/notebook-upload-runner'; import { getNotebookAcceptedFileTypes, validateFiles, @@ -175,15 +176,17 @@ const useStyles = makeStyles(theme => ({ const DropzoneClickArea = ({ children, isDisabled, + ariaLabel, }: { children: React.ReactNode; isDisabled?: boolean; + ariaLabel?: string; }) => { const { open } = useContext(MultipleFileUploadContext); return (
{ - onUploadStarted?.({ - fileName: file.name, - documentId: data.document_id, - }); - }) - .catch(() => { - onUploadFailed?.(file.name); - }); - } + runFileUploads(uploadMutation, sessionId, selectedFiles, { + onUploading: onFilesUploading, + onStarted: onUploadStarted, + onFailed: onUploadFailed, + }); setSelectedFiles([]); setValidationErrors([]); @@ -388,7 +382,10 @@ export const AddDocumentModal = ({ }} onFileDrop={handleFileDrop} > - + } titleText={t('notebook.upload.modal.dragDropTitle')} diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx index 5504df832d4..79fb91c02b3 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx @@ -61,6 +61,7 @@ import { useStopConversation } from '../../hooks/useStopConversation'; import { useTranslation } from '../../hooks/useTranslation'; import { NotebookSessionMetadata, SessionDocument } from '../../types'; import { ChatbotFootnoteWithIcon } from '../../utils/lightspeed-chatbox-utils'; +import { runFileUploads } from '../../utils/notebook-upload-runner'; import { LightspeedChatBox } from '../LightspeedChatBox'; import { AddDocumentModal } from './AddDocumentModal'; import { DeleteDocumentModal } from './DeleteDocumentModal'; @@ -561,20 +562,11 @@ export const NotebookView = ({ if (filesToUpload.length === 0) return; - handleFilesUploading(filesToUpload); - for (const file of filesToUpload) { - uploadMutation - .mutateAsync({ sessionId, file }) - .then(data => { - handleUploadStarted({ - fileName: file.name, - documentId: data.document_id, - }); - }) - .catch(() => { - handleUploadFailed(file.name); - }); - } + runFileUploads(uploadMutation, sessionId, filesToUpload, { + onUploading: handleFilesUploading, + onStarted: handleUploadStarted, + onFailed: handleUploadFailed, + }); }; const handleFilesAddedToModal = () => { 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 be2cb08b460..e8322152e85 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 @@ -170,7 +170,11 @@ export const OverwriteConfirmModal = ({ > - {t('notebook.overwrite.modal.title')} + {(t as Function)( + duplicateFiles.length === 1 + ? 'notebook.overwrite.modal.title.one' + : 'notebook.overwrite.modal.title.other', + )} diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts index 44ae2f65d70..3c72a8a837c 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts @@ -216,6 +216,12 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'notebook.overwrite.modal.ignore': 'Doppelte Dateien ignorieren', 'notebook.overwrite.modal.replace': 'Bestehende Dateien ersetzen', 'notebook.overwrite.modal.title': 'Datei existiert bereits', + 'notebook.overwrite.modal.title.one': 'Datei existiert bereits', + 'notebook.overwrite.modal.title.other': 'Dateien existieren bereits', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} Datei existiert bereits in diesem Notizbuch. {{newCount}} neue Ressourcen werden trotzdem hinzugefügt.', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} Dateien existieren bereits in diesem Notizbuch. {{newCount}} neue Ressourcen werden trotzdem hinzugefügt.', 'notebook.upload.error.fileTooLarge': 'Upload-Fehler: Dateigröße überschreitet das Limit von 25 MB.', 'notebook.upload.error.tooManyFiles': diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts index dc360b79077..112f6dbaec1 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts @@ -212,6 +212,12 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'notebook.overwrite.modal.ignore': 'Ignorar archivos duplicados', 'notebook.overwrite.modal.replace': 'Reemplazar archivos existentes', 'notebook.overwrite.modal.title': 'El archivo ya existe', + 'notebook.overwrite.modal.title.one': 'El archivo ya existe', + 'notebook.overwrite.modal.title.other': 'Los archivos ya existen', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} archivo ya existe en este cuaderno. {{newCount}} nuevos recursos se agregarán de todos modos.', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} archivos ya existen en este cuaderno. {{newCount}} nuevos recursos se agregarán de todos modos.', 'notebook.upload.error.fileTooLarge': 'Error de carga: el tamaño del archivo supera el límite de 25 MB.', 'notebook.upload.error.tooManyFiles': diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts index 65043983456..97f1e837c6f 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts @@ -215,6 +215,12 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'notebook.overwrite.modal.ignore': 'Ignorer les fichiers en double', 'notebook.overwrite.modal.replace': 'Remplacer les fichiers existants', 'notebook.overwrite.modal.title': 'Le fichier existe déjà', + 'notebook.overwrite.modal.title.one': 'Le fichier existe déjà', + 'notebook.overwrite.modal.title.other': 'Les fichiers existent déjà', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} fichier existe déjà dans ce carnet. {{newCount}} nouvelles ressources seront ajoutées quand même.', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} fichiers existent déjà dans ce carnet. {{newCount}} nouvelles ressources seront ajoutées quand même.', 'notebook.upload.error.fileTooLarge': 'Erreur de chargement : la taille du fichier dépasse la limite de 25 Mo.', 'notebook.upload.error.tooManyFiles': diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts index dd1b1135f4c..5b7bb31bc73 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts @@ -213,6 +213,12 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'notebook.overwrite.modal.ignore': 'Ignora file duplicati', 'notebook.overwrite.modal.replace': 'Sostituisci file esistenti', 'notebook.overwrite.modal.title': 'Il file esiste già', + 'notebook.overwrite.modal.title.one': 'Il file esiste già', + 'notebook.overwrite.modal.title.other': 'I file esistono già', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} file esiste già in questo quaderno. {{newCount}} nuove risorse verranno aggiunte comunque.', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} file esistono già in questo quaderno. {{newCount}} nuove risorse verranno aggiunte comunque.', 'notebook.upload.error.fileTooLarge': 'Errore di caricamento: la dimensione del file supera il limite di 25 MB.', 'notebook.upload.error.tooManyFiles': diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts index 444c30391b3..15c16c9198f 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts @@ -209,6 +209,12 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'notebook.overwrite.modal.ignore': '重複ファイルを無視', 'notebook.overwrite.modal.replace': '既存のファイルを置換', 'notebook.overwrite.modal.title': 'ファイルが既に存在します', + 'notebook.overwrite.modal.title.one': 'ファイルが既に存在します', + 'notebook.overwrite.modal.title.other': 'ファイルが既に存在します', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} 件のファイルがこのノートブックに既に存在します。{{newCount}} 件の新しいリソースはそのまま追加されます。', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} 件のファイルがこのノートブックに既に存在します。{{newCount}} 件の新しいリソースはそのまま追加されます。', 'notebook.upload.error.fileTooLarge': 'アップロードエラー: ファイルサイズが 25 MB の制限を超えています。', 'notebook.upload.error.tooManyFiles': diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts index 7d08e1a32ed..af46fcf13ea 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts @@ -105,8 +105,14 @@ export const intelligentAssistantMessages = { // Notebook overwrite modal 'notebook.overwrite.modal.title': 'File already exists', + 'notebook.overwrite.modal.title.one': 'File already exists', + 'notebook.overwrite.modal.title.other': 'Files already exist', 'notebook.overwrite.modal.description': '{{duplicateCount}} files already exist in this notebook. {{newCount}} new resources will be added regardless.', + 'notebook.overwrite.modal.description.one': + '{{duplicateCount}} file already exists in this notebook. {{newCount}} new resources will be added regardless.', + 'notebook.overwrite.modal.description.other': + '{{duplicateCount}} files already exist in this notebook. {{newCount}} new resources will be added regardless.', 'notebook.overwrite.modal.replace': 'Replace existing files', 'notebook.overwrite.modal.ignore': 'Ignore duplicated files', 'notebook.overwrite.modal.action': 'Upload ({{count}})', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/__tests__/notebook-upload-runner.test.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/__tests__/notebook-upload-runner.test.ts new file mode 100644 index 00000000000..2d8767f59fb --- /dev/null +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/__tests__/notebook-upload-runner.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { runFileUploads } from '../notebook-upload-runner'; + +const createFile = (name: string) => + new File(['content'], name, { type: 'text/plain' }); + +describe('runFileUploads', () => { + const sessionId = 'test-session'; + const files = [createFile('a.txt'), createFile('b.txt')]; + + it('should call onUploading with all files', () => { + const mutation = { + mutateAsync: jest.fn().mockResolvedValue({ document_id: 'doc-1' }), + }; + const onUploading = jest.fn(); + + runFileUploads(mutation, sessionId, files, { onUploading }); + + expect(onUploading).toHaveBeenCalledWith(files); + }); + + it('should call mutateAsync for each file with sessionId', () => { + const mutation = { + mutateAsync: jest.fn().mockResolvedValue({ document_id: 'doc-1' }), + }; + + runFileUploads(mutation, sessionId, files, {}); + + expect(mutation.mutateAsync).toHaveBeenCalledTimes(2); + expect(mutation.mutateAsync).toHaveBeenCalledWith({ + sessionId, + file: files[0], + }); + expect(mutation.mutateAsync).toHaveBeenCalledWith({ + sessionId, + file: files[1], + }); + }); + + it('should call onStarted with fileName and documentId on success', async () => { + const mutation = { + mutateAsync: jest + .fn() + .mockResolvedValueOnce({ document_id: 'doc-a' }) + .mockResolvedValueOnce({ document_id: 'doc-b' }), + }; + const onStarted = jest.fn(); + + runFileUploads(mutation, sessionId, files, { onStarted }); + + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(onStarted).toHaveBeenCalledTimes(2); + expect(onStarted).toHaveBeenCalledWith({ + fileName: 'a.txt', + documentId: 'doc-a', + }); + expect(onStarted).toHaveBeenCalledWith({ + fileName: 'b.txt', + documentId: 'doc-b', + }); + }); + + it('should call onFailed with fileName on error', async () => { + const mutation = { + mutateAsync: jest + .fn() + .mockResolvedValueOnce({ document_id: 'doc-a' }) + .mockRejectedValueOnce(new Error('upload failed')), + }; + const onFailed = jest.fn(); + + runFileUploads(mutation, sessionId, files, { onFailed }); + + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(onFailed).toHaveBeenCalledTimes(1); + expect(onFailed).toHaveBeenCalledWith('b.txt'); + }); + + it('should work with no callbacks provided', () => { + const mutation = { + mutateAsync: jest.fn().mockResolvedValue({ document_id: 'doc-1' }), + }; + + expect(() => runFileUploads(mutation, sessionId, files, {})).not.toThrow(); + expect(mutation.mutateAsync).toHaveBeenCalledTimes(2); + }); +}); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/notebook-upload-runner.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/notebook-upload-runner.ts new file mode 100644 index 00000000000..baca941149b --- /dev/null +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/utils/notebook-upload-runner.ts @@ -0,0 +1,50 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type UploadMutation = { + mutateAsync: (args: { + sessionId: string; + file: File; + }) => Promise<{ document_id: string }>; +}; + +type UploadCallbacks = { + onUploading?: (files: File[]) => void; + onStarted?: (info: { fileName: string; documentId: string }) => void; + onFailed?: (fileName: string) => void; +}; + +export function runFileUploads( + mutation: UploadMutation, + sessionId: string, + files: File[], + callbacks: UploadCallbacks, +): void { + callbacks.onUploading?.(files); + for (const file of files) { + mutation + .mutateAsync({ sessionId, file }) + .then(data => { + callbacks.onStarted?.({ + fileName: file.name, + documentId: data.document_id, + }); + }) + .catch(() => { + callbacks.onFailed?.(file.name); + }); + } +} From 3e725e53006eee107758351bc132d6de67b735cf Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Sat, 8 Aug 2026 00:47:54 +0530 Subject: [PATCH 10/13] updating api report Signed-off-by: its-mitesh-kumar --- .../plugins/intelligent-assistant/report-alpha.api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md index 434237d3314..33dfb93f538 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md @@ -99,7 +99,11 @@ export const intelligentAssistantTranslationRef: TranslationRef< readonly 'notebook.upload.error.fileTooLarge': string; readonly 'notebook.upload.error.tooManyFiles': string; readonly 'notebook.overwrite.modal.title': string; + readonly 'notebook.overwrite.modal.title.one': string; + readonly 'notebook.overwrite.modal.title.other': string; readonly 'notebook.overwrite.modal.description': string; + readonly 'notebook.overwrite.modal.description.one': string; + readonly 'notebook.overwrite.modal.description.other': string; readonly 'notebook.overwrite.modal.replace': string; readonly 'notebook.overwrite.modal.ignore': string; readonly 'notebook.overwrite.modal.action': string; From 18f9b41dba64b17756081abef70954896ebcfcc8 Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Mon, 10 Aug 2026 13:07:47 +0530 Subject: [PATCH 11/13] updating e2e Signed-off-by: its-mitesh-kumar --- .../e2e-tests/lightspeed.notebooks.test.ts | 17 ++++++++++++++--- .../pages/NotebookAddDocumentModalPage.ts | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index f563fb3a202..a683ef04e2f 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -100,7 +100,7 @@ test.describe('Intelligent assistant notebooks', () => { await uploadModal.selectFilesViaBrowsePicker( notebookElevenFileStagingPaths(), ); - await expect(uploadModal.dialog().getByRole('alert')).toContainText( + await expect(uploadModal.errorAlert()).toContainText( substituteNotebookTemplate( translations['notebook.upload.error.tooManyFiles'], { max: NOTEBOOK_SESSION_MAX_DOCUMENTS }, @@ -115,7 +115,7 @@ test.describe('Intelligent assistant notebooks', () => { await uploadModal.selectFilesViaBrowsePicker([ notebookUnsupportedTypeFixturePath(), ]); - await expect(uploadModal.dialog().getByRole('alert')).toContainText( + await expect(uploadModal.errorAlert()).toContainText( translations['notebook.upload.error.unsupportedType'], ); await uploadModal.clickCancel(); @@ -196,8 +196,19 @@ test.describe('Intelligent assistant notebooks', () => { await notebooks.expectUntitledNotebookCardCount(untitledBefore); }); - test('grid: click card title triggers inline rename', async () => { + test('grid: click card title triggers inline rename', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + await notebooks.clickPrimaryNotebookCreate(); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await notebooks.clickCloseNotebookEditor(); const card = notebooks.newestUntitledNotebookCard(); diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index 8c655083c25..5f6e8f8ede9 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -123,7 +123,7 @@ export class NotebookAddDocumentModalPage { } errorAlert(): Locator { - return this.dialog().getByRole('alert'); + return this.dialog().locator('[data-ouia-component-type="PF6/Alert"]'); } async expectValidationAlertsInclude(text: string): Promise { From 089c3500f5e78649b1404cb67692a6309784a11b Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Mon, 10 Aug 2026 14:27:06 +0530 Subject: [PATCH 12/13] updating the tests Signed-off-by: its-mitesh-kumar --- .../e2e-tests/lightspeed.notebooks.test.ts | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index a683ef04e2f..c8abab62f0a 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -234,8 +234,19 @@ test.describe('Intelligent assistant notebooks', () => { await notebooks.expectNotebookCardAbsent(newName); }); - test('grid: Escape cancels inline rename', async () => { + test('grid: Escape cancels inline rename', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + await notebooks.clickPrimaryNotebookCreate(); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await notebooks.clickCloseNotebookEditor(); const card = notebooks.newestUntitledNotebookCard(); @@ -264,8 +275,19 @@ test.describe('Intelligent assistant notebooks', () => { await confirmDelete.confirmDeletion(); }); - test('grid: blur saves inline rename', async () => { + test('grid: blur saves inline rename', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + await notebooks.clickPrimaryNotebookCreate(); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await notebooks.clickCloseNotebookEditor(); const card = notebooks.newestUntitledNotebookCard(); @@ -292,8 +314,19 @@ test.describe('Intelligent assistant notebooks', () => { await notebooks.expectNotebookCardAbsent(newName); }); - test('grid: empty or unchanged name cancels rename', async () => { + test('grid: empty or unchanged name cancels rename', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + await notebooks.clickPrimaryNotebookCreate(); + + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await notebooks.clickCloseNotebookEditor(); const card = notebooks.newestUntitledNotebookCard(); From b9a52995a730a19ccab00a83da21c7ebebdbedad Mon Sep 17 00:00:00 2001 From: its-mitesh-kumar Date: Mon, 10 Aug 2026 15:05:59 +0530 Subject: [PATCH 13/13] handling test failures Signed-off-by: its-mitesh-kumar --- .../src/components/notebooks/NotebookView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx index 79fb91c02b3..232ba80eb18 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/NotebookView.tsx @@ -497,7 +497,8 @@ export const NotebookView = ({ const handleCloseNotebook = async () => { const isUntitled = notebookName === UNTITLED_NOTEBOOK_NAME; - const hasNoDocuments = documents.length === 0; + const hasNoDocuments = + documents.length === 0 && completedFileNames.size === 0; const hasNoPendingUploads = !pendingUploads.length; const hasNoUploading = !uploadingFileNames.length; const hasNoChat = conversationId === TEMP_CONVERSATION_ID;