diff --git a/workspaces/lightspeed/.changeset/migrate-mui-v5.md b/workspaces/lightspeed/.changeset/migrate-mui-v5.md
new file mode 100644
index 0000000000..d8404239d7
--- /dev/null
+++ b/workspaces/lightspeed/.changeset/migrate-mui-v5.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-lightspeed': patch
+---
+
+Migrated from Material UI v4 (`@material-ui/*`) to MUI v5 (`@mui/material`, `@mui/styles`). Replaced all `makeStyles`/`createStyles` usage with `styled()` and `sx` prop. Added `StylesProvider` with seeded `createGenerateClassName` for JSS collision prevention. Added ESLint restrictions to prevent `@material-ui/*` imports from being reintroduced.
diff --git a/workspaces/lightspeed/eslint.frontend-shared.cjs b/workspaces/lightspeed/eslint.frontend-shared.cjs
new file mode 100644
index 0000000000..02cb00ae23
--- /dev/null
+++ b/workspaces/lightspeed/eslint.frontend-shared.cjs
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+const materialUiMigrationEslintConfig = {
+ restrictedImports: [
+ {
+ name: '@material-ui/core',
+ message: 'Use @mui/material instead of Material UI v4.',
+ },
+ {
+ name: '@material-ui/lab',
+ message: 'Use @mui/material instead of Material UI v4.',
+ },
+ {
+ name: '@material-ui/styles',
+ message:
+ 'Use @mui/styles, @mui/material (sx/styled), or Backstage UI instead of Material UI v4.',
+ },
+ ],
+ restrictedImportPatterns: ['@material-ui/*'],
+};
+
+/**
+ * ESLint config for frontend packages in this workspace (MUI v4 migration guards).
+ */
+module.exports = packageDir =>
+ require('@backstage/cli/config/eslint-factory')(
+ packageDir,
+ materialUiMigrationEslintConfig,
+ );
diff --git a/workspaces/lightspeed/packages/app/.eslintrc.js b/workspaces/lightspeed/packages/app/.eslintrc.js
index 9184408ae4..493ce7565d 100644
--- a/workspaces/lightspeed/packages/app/.eslintrc.js
+++ b/workspaces/lightspeed/packages/app/.eslintrc.js
@@ -13,4 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports -- workspace ESLint shared config
+module.exports = require('../../eslint.frontend-shared.cjs')(__dirname);
diff --git a/workspaces/lightspeed/packages/app/package.json b/workspaces/lightspeed/packages/app/package.json
index 9599f7c802..c60a197d16 100644
--- a/workspaces/lightspeed/packages/app/package.json
+++ b/workspaces/lightspeed/packages/app/package.json
@@ -32,8 +32,8 @@
"@backstage/plugin-search": "^1.7.0",
"@backstage/plugin-user-settings": "^0.9.1",
"@backstage/ui": "^0.13.1",
- "@material-ui/core": "^4.12.2",
- "@material-ui/icons": "^4.9.1",
+ "@mui/icons-material": "^6.1.8",
+ "@mui/material": "^5.12.2",
"@red-hat-developer-hub/backstage-plugin-app-react": "^0.0.5",
"@red-hat-developer-hub/backstage-plugin-lightspeed": "workspace:^",
"react": "^18.0.2",
diff --git a/workspaces/lightspeed/packages/app/src/modules/nav/LogoFull.tsx b/workspaces/lightspeed/packages/app/src/modules/nav/LogoFull.tsx
index 6b6a1bd8c1..9e835665ae 100644
--- a/workspaces/lightspeed/packages/app/src/modules/nav/LogoFull.tsx
+++ b/workspaces/lightspeed/packages/app/src/modules/nav/LogoFull.tsx
@@ -13,31 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { makeStyles } from '@material-ui/core';
+import { styled } from '@mui/material/styles';
-const useStyles = makeStyles({
- svg: {
- width: 'auto',
- height: 30,
- },
- path: {
- fill: '#7df3e1',
- },
+const StyledSvg = styled('svg')({
+ width: 'auto',
+ height: 30,
});
-export const LogoFull = () => {
- const classes = useStyles();
+const StyledPath = styled('path')({
+ fill: '#7df3e1',
+});
+export const LogoFull = () => {
return (
-
+
+
+
);
};
diff --git a/workspaces/lightspeed/packages/app/src/modules/nav/LogoIcon.tsx b/workspaces/lightspeed/packages/app/src/modules/nav/LogoIcon.tsx
index 87024b08c2..fc7c64f81a 100644
--- a/workspaces/lightspeed/packages/app/src/modules/nav/LogoIcon.tsx
+++ b/workspaces/lightspeed/packages/app/src/modules/nav/LogoIcon.tsx
@@ -13,31 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { makeStyles } from '@material-ui/core';
+import { styled } from '@mui/material/styles';
-const useStyles = makeStyles({
- svg: {
- width: 'auto',
- height: 28,
- },
- path: {
- fill: '#7df3e1',
- },
+const StyledSvg = styled('svg')({
+ width: 'auto',
+ height: 28,
});
-export const LogoIcon = () => {
- const classes = useStyles();
+const StyledPath = styled('path')({
+ fill: '#7df3e1',
+});
+export const LogoIcon = () => {
return (
-
+
+
+
);
};
diff --git a/workspaces/lightspeed/packages/app/src/modules/nav/Sidebar.tsx b/workspaces/lightspeed/packages/app/src/modules/nav/Sidebar.tsx
index 606c5f2147..7e9f9cd30c 100644
--- a/workspaces/lightspeed/packages/app/src/modules/nav/Sidebar.tsx
+++ b/workspaces/lightspeed/packages/app/src/modules/nav/Sidebar.tsx
@@ -24,7 +24,7 @@ import {
} from '@backstage/core-components';
import { NavContentBlueprint } from '@backstage/plugin-app-react';
import { SidebarLogo } from './SidebarLogo';
-import MenuIcon from '@material-ui/icons/Menu';
+import MenuIcon from '@mui/icons-material/Menu';
export const SidebarContent = NavContentBlueprint.make({
params: {
diff --git a/workspaces/lightspeed/packages/app/src/modules/nav/SidebarLogo.tsx b/workspaces/lightspeed/packages/app/src/modules/nav/SidebarLogo.tsx
index 0085b75fce..04da2b85a6 100644
--- a/workspaces/lightspeed/packages/app/src/modules/nav/SidebarLogo.tsx
+++ b/workspaces/lightspeed/packages/app/src/modules/nav/SidebarLogo.tsx
@@ -18,34 +18,37 @@ import {
sidebarConfig,
useSidebarOpenState,
} from '@backstage/core-components';
-import { makeStyles } from '@material-ui/core';
+
+import Box from '@mui/material/Box';
+
import { LogoFull } from './LogoFull';
import { LogoIcon } from './LogoIcon';
-const useSidebarLogoStyles = makeStyles({
- root: {
- width: sidebarConfig.drawerWidthClosed,
- height: 3 * sidebarConfig.logoHeight,
- display: 'flex',
- flexFlow: 'row nowrap',
- alignItems: 'center',
- marginBottom: -14,
- },
- link: {
- width: sidebarConfig.drawerWidthClosed,
- marginLeft: 24,
- },
-});
-
export const SidebarLogo = () => {
- const classes = useSidebarLogoStyles();
const { isOpen } = useSidebarOpenState();
return (
-
-
+
+
{isOpen ? : }
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/.eslintrc.js b/workspaces/lightspeed/plugins/lightspeed/.eslintrc.js
index e2c39f94df..493ce7565d 100644
--- a/workspaces/lightspeed/plugins/lightspeed/.eslintrc.js
+++ b/workspaces/lightspeed/plugins/lightspeed/.eslintrc.js
@@ -14,4 +14,5 @@
* limitations under the License.
*/
-module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports -- workspace ESLint shared config
+module.exports = require('../../eslint.frontend-shared.cjs')(__dirname);
diff --git a/workspaces/lightspeed/plugins/lightspeed/package.json b/workspaces/lightspeed/plugins/lightspeed/package.json
index 9f7af121a8..c125fbb456 100644
--- a/workspaces/lightspeed/plugins/lightspeed/package.json
+++ b/workspaces/lightspeed/plugins/lightspeed/package.json
@@ -59,8 +59,6 @@
"@backstage/plugin-app-react": "^0.2.1",
"@backstage/plugin-permission-react": "^0.4.41",
"@backstage/theme": "^0.7.2",
- "@material-ui/core": "^4.9.13",
- "@material-ui/lab": "^4.0.0-alpha.61",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^6.1.8",
"@mui/material": "^5.12.2",
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/Attachment.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/Attachment.tsx
index 00502f5409..89ebe97ffc 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/Attachment.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/Attachment.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { makeStyles } from '@material-ui/core';
+import GlobalStyles from '@mui/material/GlobalStyles';
import {
AttachmentEdit,
ChatbotDisplayMode,
@@ -24,13 +24,8 @@ import {
import { useTranslation } from '../hooks/useTranslation';
import { useFileAttachmentContext } from './AttachmentContext';
-const useStyles = makeStyles(() => ({
- modalFooter: {
- '&>button': {
- width: '12% !important',
- },
- },
-}));
+const MODAL_FOOTER_CLASS = 'lightspeed-modal-footer';
+
const Attachment = () => {
const {
currentFileContent,
@@ -38,7 +33,6 @@ const Attachment = () => {
modalState,
setCurrentFileContent,
} = useFileAttachmentContext();
- const classes = useStyles();
const { t } = useTranslation();
if (!currentFileContent) {
@@ -55,6 +49,13 @@ const Attachment = () => {
return (
<>
+ button`]: {
+ width: '12% !important',
+ },
+ }}
+ />
{
secondaryActionButtonText={t('modal.close')}
primaryActionButtonText={t('modal.edit')}
title={t('modal.title.preview')}
- modalFooterClassName={classes.modalFooter}
+ modalFooterClassName={MODAL_FOOTER_CLASS}
onEdit={() => {
setIsPreviewModalOpen(false);
setIsEditModalOpen(true);
@@ -83,7 +84,7 @@ const Attachment = () => {
title={t('modal.title.edit')}
secondaryActionButtonText={t('modal.cancel')}
primaryActionButtonText={t('modal.save')}
- modalFooterClassName={classes.modalFooter}
+ modalFooterClassName={MODAL_FOOTER_CLASS}
onSave={(_, content) => {
setCurrentFileContent({
...currentFileContent,
@@ -95,7 +96,6 @@ const Attachment = () => {
);
if (existingIndex !== -1) {
- // Update the existing file's content
const updated = [...prev];
updated[existingIndex] = {
...updated[existingIndex],
@@ -104,7 +104,6 @@ const Attachment = () => {
return updated;
}
- // File doesn't exist, add a new one
return [
...prev,
{
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/CollapsedHistoryStrip.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/CollapsedHistoryStrip.tsx
index 8052ff5b17..bcfd695b07 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/CollapsedHistoryStrip.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/CollapsedHistoryStrip.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { makeStyles } from '@material-ui/core';
+import { styled } from '@mui/material/styles';
import { Button, Tooltip } from '@patternfly/react-core';
import { useTranslation } from '../hooks/useTranslation';
@@ -39,51 +39,51 @@ export const PencilIcon = ({ className }: IconProps) => (
);
-const useStyles = makeStyles(theme => ({
- strip: {
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- paddingTop: theme.spacing(1.5),
- gap: theme.spacing(1.5),
- borderRight: '1px solid var(--pf-t--global--border--color--default)',
- width: 48,
- minWidth: 48,
- flexShrink: 0,
- backgroundColor: 'var(--pf-t--global--background--color--primary--default)',
- height: '100%',
+const StripRoot = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ paddingTop: theme.spacing(1.5),
+ gap: theme.spacing(1.5),
+ borderRight: '1px solid var(--pf-t--global--border--color--default)',
+ width: 48,
+ minWidth: 48,
+ flexShrink: 0,
+ backgroundColor: 'var(--pf-t--global--background--color--primary--default)',
+ height: '100%',
+}));
+
+const StyledIconButton = styled(Button)({
+ padding: '8px !important',
+ minWidth: 0,
+ lineHeight: 1,
+ borderRadius: '8px !important',
+ border: '1px solid var(--pf-t--global--border--color--default) !important',
+ color: 'var(--pf-t--global--icon--color--regular)',
+ '& svg': {
+ width: 18,
+ height: 18,
},
- iconButton: {
- padding: '8px !important',
- minWidth: 0,
- lineHeight: 1,
- borderRadius: '8px !important',
- border: '1px solid var(--pf-t--global--border--color--default) !important',
- color: 'var(--pf-t--global--icon--color--regular)',
- '& svg': {
- width: 18,
- height: 18,
- },
- '&:hover': {
- color: 'var(--pf-t--global--icon--color--hover) !important',
- backgroundColor:
- 'var(--pf-t--global--background--color--action--plain--hover) !important',
- },
+ '&:hover': {
+ color: 'var(--pf-t--global--icon--color--hover) !important',
+ backgroundColor:
+ 'var(--pf-t--global--background--color--action--plain--hover) !important',
},
- newChatIconButton: {
- padding: '8px !important',
- minWidth: 0,
- lineHeight: 1,
- borderRadius: '8px !important',
- border: '1px solid var(--pf-t--global--border--color--default) !important',
- color: 'var(--pf-t--global--color--brand--default)',
- '&:hover': {
- color: 'var(--pf-t--global--color--brand--hover) !important',
- backgroundColor:
- 'var(--pf-t--global--background--color--action--plain--hover) !important',
- },
+});
+
+const StyledNewChatButton = styled(Button)({
+ padding: '8px !important',
+ minWidth: 0,
+ lineHeight: 1,
+ borderRadius: '8px !important',
+ border: '1px solid var(--pf-t--global--border--color--default) !important',
+ color: 'var(--pf-t--global--color--brand--default)',
+ '&:hover': {
+ color: 'var(--pf-t--global--color--brand--hover) !important',
+ backgroundColor:
+ 'var(--pf-t--global--background--color--action--plain--hover) !important',
},
-}));
+});
type CollapsedHistoryStripProps = {
onExpand: () => void;
@@ -96,32 +96,29 @@ export const CollapsedHistoryStrip = ({
onNewChat,
newChatDisabled = false,
}: CollapsedHistoryStripProps) => {
- const classes = useStyles();
const { t } = useTranslation();
return (
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/FilePreview.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/FilePreview.tsx
index fb9e588363..448c58c98f 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/FilePreview.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/FilePreview.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Divider } from '@material-ui/core';
+import Divider from '@mui/material/Divider';
import { FileDetailsLabel } from '@patternfly/chatbot';
import { useFileAttachmentContext } from './AttachmentContext';
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
index dbf0ae45d4..7d493b8b48 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
@@ -34,7 +34,8 @@ import { useLocation, useMatch, useNavigate } from 'react-router-dom';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
-import { Button, makeStyles } from '@material-ui/core';
+import Button from '@mui/material/Button';
+import { styled } from '@mui/material/styles';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import {
@@ -140,10 +141,61 @@ const ConditionalWrapper = ({
children: React.ReactNode;
}) => (condition ? wrapper(children) : children);
-const useStyles = makeStyles(theme => ({
- body: {
- // remove default margin and padding from common elements
- // lists excluded for proper formatting
+const PREFIX = 'LightspeedChat';
+const classes = {
+ body: `${PREFIX}-body`,
+ header: `${PREFIX}-header`,
+ errorContainer: `${PREFIX}-errorContainer`,
+ drawerFileDropZone: `${PREFIX}-drawerFileDropZone`,
+ headerMenu: `${PREFIX}-headerMenu`,
+ headerLogo: `${PREFIX}-headerLogo`,
+ headerTitle: `${PREFIX}-headerTitle`,
+ headerDivider: `${PREFIX}-headerDivider`,
+ notebooksContainer: `${PREFIX}-notebooksContainer`,
+ notebooksHeader: `${PREFIX}-notebooksHeader`,
+ notebooksHeading: `${PREFIX}-notebooksHeading`,
+ notebooksHeadingEmpty: `${PREFIX}-notebooksHeadingEmpty`,
+ notebooksEmptyState: `${PREFIX}-notebooksEmptyState`,
+ notebooksIcon: `${PREFIX}-notebooksIcon`,
+ notebooksDescription: `${PREFIX}-notebooksDescription`,
+ notebooksAction: `${PREFIX}-notebooksAction`,
+ notebooksActionEmpty: `${PREFIX}-notebooksActionEmpty`,
+ notebooksGrid: `${PREFIX}-notebooksGrid`,
+ notebookCard: `${PREFIX}-notebookCard`,
+ notebookCardHeader: `${PREFIX}-notebookCardHeader`,
+ notebookCardDivider: `${PREFIX}-notebookCardDivider`,
+ notebookCardBody: `${PREFIX}-notebookCardBody`,
+ notebookDocuments: `${PREFIX}-notebookDocuments`,
+ notebookUpdated: `${PREFIX}-notebookUpdated`,
+ notebookTitle: `${PREFIX}-notebookTitle`,
+ notebookCardHeaderActions: `${PREFIX}-notebookCardHeaderActions`,
+ notebookTitleText: `${PREFIX}-notebookTitleText`,
+ notebookMenuButton: `${PREFIX}-notebookMenuButton`,
+ notebookDropdownList: `${PREFIX}-notebookDropdownList`,
+ notebookDropdownMenu: `${PREFIX}-notebookDropdownMenu`,
+ notebookDropdownItem: `${PREFIX}-notebookDropdownItem`,
+ footer: `${PREFIX}-footer`,
+ fullscreenFooter: `${PREFIX}-fullscreenFooter`,
+ messageBar: `${PREFIX}-messageBar`,
+ sortDropdown: `${PREFIX}-sortDropdown`,
+ chatbotContent: `${PREFIX}-chatbotContent`,
+ chatbotContentHasOverflow: `${PREFIX}-chatbotContentHasOverflow`,
+ chatbotContentScroll: `${PREFIX}-chatbotContentScroll`,
+ chatbotContentScrollNewChat: `${PREFIX}-chatbotContentScrollNewChat`,
+ toastAlertGroup: `${PREFIX}-toastAlertGroup`,
+ toastAlert: `${PREFIX}-toastAlert`,
+ chatbotContentSpacer: `${PREFIX}-chatbotContentSpacer`,
+ settingsFlat: `${PREFIX}-settingsFlat`,
+ mcpFullscreenLayout: `${PREFIX}-mcpFullscreenLayout`,
+ mcpChatPane: `${PREFIX}-mcpChatPane`,
+ mcpSettingsPane: `${PREFIX}-mcpSettingsPane`,
+ mcpCollapsedDrawerOrderFix: `${PREFIX}-mcpCollapsedDrawerOrderFix`,
+ fullscreenChatLayout: `${PREFIX}-fullscreenChatLayout`,
+ fullscreenMainContent: `${PREFIX}-fullscreenMainContent`,
+};
+
+const StyledChatRoot = styled('div')(({ theme }) => ({
+ [`& .${classes.body}`]: {
'& h1, & h2, & h3, & h4, & h5, & h6, & p, & li': {
margin: 0,
padding: 0,
@@ -153,17 +205,15 @@ const useStyles = makeStyles(theme => ({
'var(--pf-t--global--background--color--floating--default)',
},
},
- header: {
- padding: `${theme.spacing(3)}px ${theme.spacing(3)}px 0 ${theme.spacing(
- 3,
- )}px !important`,
+ [`& .${classes.header}`]: {
+ padding: `${theme.spacing(3)} ${theme.spacing(3)} 0 ${theme.spacing(3)} !important`,
backgroundColor:
'var(--pf-t--global--background--color--floating--default) !important',
},
- errorContainer: {
+ [`& .${classes.errorContainer}`]: {
padding: theme.spacing(3),
},
- drawerFileDropZone: {
+ [`& .${classes.drawerFileDropZone}`]: {
gap: 0,
rowGap: 0,
columnGap: 0,
@@ -172,20 +222,19 @@ const useStyles = makeStyles(theme => ({
flex: 1,
minWidth: 0,
},
- headerMenu: {
- // align hamburger icon with title
+ [`& .${classes.headerMenu}`]: {
'& .pf-v6-c-button': {
display: 'flex',
alignItems: 'center',
},
},
- headerLogo: {
+ [`& .${classes.headerLogo}`]: {
width: 48,
height: 48,
marginRight: theme.spacing(1.5),
flexShrink: 0,
},
- headerTitle: {
+ [`& .${classes.headerTitle}`]: {
justifyContent: 'left !important',
'& h1': {
fontSize: '32px !important',
@@ -194,13 +243,13 @@ const useStyles = makeStyles(theme => ({
fontFamily: '"Red Hat Display", sans-serif !important',
},
},
- headerDivider: {
+ [`& .${classes.headerDivider}`]: {
paddingTop: 8,
borderBottom: '1px solid var(--pf-t--global--border--color--default)',
backgroundColor:
'var(--pf-t--global--background--color--floating--default)',
},
- notebooksContainer: {
+ [`& .${classes.notebooksContainer}`]: {
padding: theme.spacing(3),
height: '100%',
display: 'flex',
@@ -211,22 +260,22 @@ const useStyles = makeStyles(theme => ({
backgroundColor:
'var(--pf-t--global--background--color--floating--default)',
},
- notebooksHeader: {
+ [`& .${classes.notebooksHeader}`]: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: theme.spacing(4),
},
- notebooksHeading: {
+ [`& .${classes.notebooksHeading}`]: {
marginBottom: 0,
},
- notebooksHeadingEmpty: {
+ [`& .${classes.notebooksHeadingEmpty}`]: {
'&&': {
marginBottom: theme.spacing(1),
paddingBottom: theme.spacing(1),
},
},
- notebooksEmptyState: {
+ [`& .${classes.notebooksEmptyState}`]: {
flex: 1,
display: 'flex',
flexDirection: 'column',
@@ -234,30 +283,30 @@ const useStyles = makeStyles(theme => ({
justifyContent: 'center',
textAlign: 'center',
},
- notebooksIcon: {
+ [`& .${classes.notebooksIcon}`]: {
fontSize: 48,
color: 'var(--pf-t--global--icon--color--subtle)',
marginBottom: theme.spacing(1.5),
},
- notebooksDescription: {
+ [`& .${classes.notebooksDescription}`]: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(3),
maxWidth: 420,
},
- notebooksAction: {
+ [`& .${classes.notebooksAction}`]: {
textTransform: 'none',
borderRadius: 999,
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
},
- notebooksActionEmpty: {
+ [`& .${classes.notebooksActionEmpty}`]: {
textTransform: 'none',
borderRadius: 999,
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
marginTop: theme.spacing(3),
},
- notebooksGrid: {
+ [`& .${classes.notebooksGrid}`]: {
display: 'grid',
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
gap: theme.spacing(2),
@@ -271,7 +320,7 @@ const useStyles = makeStyles(theme => ({
gridTemplateColumns: 'repeat(1, minmax(0, 1fr))',
},
},
- notebookCard: {
+ [`& .${classes.notebookCard}`]: {
borderRadius: theme.spacing(1.5),
display: 'flex',
flexDirection: 'column',
@@ -282,62 +331,62 @@ const useStyles = makeStyles(theme => ({
cursor: 'pointer',
},
},
- notebookCardHeader: {
+ [`& .${classes.notebookCardHeader}`]: {
padding: theme.spacing(2),
paddingBottom: 0,
alignItems: 'center',
},
- notebookCardDivider: {
+ [`& .${classes.notebookCardDivider}`]: {
borderTop: '1px solid var(--pf-t--global--border--color--default)',
marginTop: theme.spacing(1),
},
- notebookCardBody: {
+ [`& .${classes.notebookCardBody}`]: {
padding: theme.spacing(2),
paddingTop: theme.spacing(1.5),
},
- notebookDocuments: {
+ [`& .${classes.notebookDocuments}`]: {
paddingTop: theme.spacing(1),
paddingLeft: theme.spacing(2),
},
- notebookUpdated: {
+ [`& .${classes.notebookUpdated}`]: {
paddingBottom: theme.spacing(5),
paddingLeft: theme.spacing(2),
paddingTop: theme.spacing(2),
},
- notebookTitle: {
+ [`& .${classes.notebookTitle}`]: {
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
minWidth: 0,
flex: 1,
},
- notebookCardHeaderActions: {
+ [`& .${classes.notebookCardHeaderActions}`]: {
marginLeft: theme.spacing(1),
},
- notebookTitleText: {
+ [`& .${classes.notebookTitleText}`]: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
- notebookMenuButton: {
+ [`& .${classes.notebookMenuButton}`]: {
color: theme.palette.text.secondary,
},
- notebookDropdownList: {
+ [`& .${classes.notebookDropdownList}`]: {
paddingTop: 0,
paddingBottom: 0,
paddingInlineStart: 0,
},
- notebookDropdownMenu: {
+ [`& .${classes.notebookDropdownMenu}`]: {
'--pf-v6-c-menu--PaddingBlockStart': '0',
'--pf-v6-c-menu--PaddingBlockEnd': '0',
},
- notebookDropdownItem: {
+ [`& .${classes.notebookDropdownItem}`]: {
justifyContent: 'flex-start',
textAlign: 'left',
paddingLeft: theme.spacing(1.5),
paddingRight: theme.spacing(1.5),
},
- footer: {
+ [`& .${classes.footer}`]: {
backgroundColor:
'var(--pf-t--global--background--color--floating--default)',
'&>.pf-chatbot__footer-container': {
@@ -346,12 +395,12 @@ const useStyles = makeStyles(theme => ({
},
'& .pf-chatbot__message-bar': {
backgroundColor:
- theme.palette.type === 'light'
+ theme.palette.mode === 'light'
? theme.palette.grey[100]
: 'var(--pf-t--global--background--color--secondary--default)',
},
},
- fullscreenFooter: {
+ [`& .${classes.fullscreenFooter}`]: {
'&>.pf-chatbot__footer-container': {
width: '100% !important',
padding: theme.spacing(1.5),
@@ -359,7 +408,7 @@ const useStyles = makeStyles(theme => ({
margin: '0 auto',
},
},
- messageBar: {
+ [`& .${classes.messageBar}`]: {
border: '1px solid var(--pf-t--global--border--color--default)',
borderRadius: 24,
padding: theme.spacing(0.5),
@@ -367,12 +416,11 @@ const useStyles = makeStyles(theme => ({
display: 'none',
},
},
- sortDropdown: {
+ [`& .${classes.sortDropdown}`]: {
padding: 0,
margin: 0,
},
- // Outer content wrapper (library may override overflow; we rely on inner scroll wrapper).
- chatbotContent: {
+ [`& .${classes.chatbotContent}`]: {
minHeight: 0,
display: 'flex',
flexDirection: 'column',
@@ -390,14 +438,13 @@ const useStyles = makeStyles(theme => ({
wordBreak: 'break-word',
},
},
- chatbotContentHasOverflow: {
+ [`& .${classes.chatbotContentHasOverflow}`]: {
'& .pf-chatbot__jump': {
visibility: 'visible',
pointerEvents: 'auto',
},
},
- // Inner scroll container we control: always scrollable so zoomed-in users see full content.
- chatbotContentScroll: {
+ [`& .${classes.chatbotContentScroll}`]: {
minHeight: 0,
flex: 1,
display: 'flex',
@@ -405,28 +452,27 @@ const useStyles = makeStyles(theme => ({
overflowY: 'auto',
WebkitOverflowScrolling: 'touch',
},
- chatbotContentScrollNewChat: {
+ [`& .${classes.chatbotContentScrollNewChat}`]: {
backgroundColor:
'var(--pf-t--global--background--color--floating--default)',
},
- toastAlertGroup: {
- '--pf-v6-c-alert-group--m-toast--InsetInlineEnd': `${theme.spacing(2.5)}px`,
- '--pf-v6-c-alert-group--m-toast--InsetBlockStart': `${theme.spacing(2.5)}px`,
+ [`& .${classes.toastAlertGroup}`]: {
+ '--pf-v6-c-alert-group--m-toast--InsetInlineEnd': `${theme.spacing(2.5)}`,
+ '--pf-v6-c-alert-group--m-toast--InsetBlockStart': `${theme.spacing(2.5)}`,
'--pf-v6-c-alert-group--m-toast--MaxWidth': '350px',
'--pf-v6-c-alert-group--m-toast--ZIndex': '9999',
},
- toastAlert: {
+ [`& .${classes.toastAlert}`]: {
maxWidth: '350px',
'& .pf-v6-c-alert__title': {
margin: 0,
},
},
- // When present, pushes welcome content to bottom (zoom out). Scroll up to see important box (zoom in).
- chatbotContentSpacer: {
+ [`& .${classes.chatbotContentSpacer}`]: {
flex: 1,
minHeight: 0,
},
- settingsFlat: {
+ [`& .${classes.settingsFlat}`]: {
height: '100%',
width: '100%',
'&.pf-chatbot__settings-form-container': {
@@ -467,7 +513,7 @@ const useStyles = makeStyles(theme => ({
display: 'none',
},
},
- mcpFullscreenLayout: {
+ [`& .${classes.mcpFullscreenLayout}`]: {
display: 'grid',
gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)',
minHeight: 0,
@@ -477,7 +523,7 @@ const useStyles = makeStyles(theme => ({
minWidth: 0,
overflow: 'hidden',
},
- mcpChatPane: {
+ [`& .${classes.mcpChatPane}`]: {
display: 'flex',
flexDirection: 'column',
minHeight: 0,
@@ -487,7 +533,7 @@ const useStyles = makeStyles(theme => ({
wordBreak: 'break-word',
overflowWrap: 'break-word',
},
- mcpSettingsPane: {
+ [`& .${classes.mcpSettingsPane}`]: {
width: '100%',
minWidth: 0,
borderLeft: `1px solid ${theme.palette.divider}`,
@@ -498,7 +544,7 @@ const useStyles = makeStyles(theme => ({
minHeight: 0,
overflow: 'auto',
},
- mcpCollapsedDrawerOrderFix: {
+ [`& .${classes.mcpCollapsedDrawerOrderFix}`]: {
'& .pf-v6-c-drawer.pf-m-panel-left > .pf-v6-c-drawer__main > .pf-v6-c-drawer__content, & .pf-v5-c-drawer.pf-m-panel-left > .pf-v5-c-drawer__main > .pf-v5-c-drawer__content':
{
order: 'unset',
@@ -510,11 +556,7 @@ const useStyles = makeStyles(theme => ({
transition: 'none !important',
},
},
- // TODO: These PatternFly drawer overrides are needed because PF Chatbot doesn't
- // provide clean APIs for custom expand/collapse icons and positioning.
- // Remove once PatternFly supports these features.
- // See: https://github.com/patternfly/chatbot/issues/834
- fullscreenChatLayout: {
+ [`& .${classes.fullscreenChatLayout}`]: {
display: 'flex',
flexDirection: 'row',
flex: 1,
@@ -569,7 +611,7 @@ const useStyles = makeStyles(theme => ({
opacity: 1,
},
},
- fullscreenMainContent: {
+ [`& .${classes.fullscreenMainContent}`]: {
display: 'flex',
flexDirection: 'column',
flex: 1,
@@ -601,7 +643,6 @@ export const LightspeedChat = ({
models,
}: LightspeedChatProps) => {
const isMobile = useIsMobile();
- const classes = useStyles();
const { t } = useTranslation();
const navigate = useNavigate();
const configApi = useApi(configApiRef);
@@ -1594,14 +1635,7 @@ export const LightspeedChat = ({
),
- [
- isSortSelectOpen,
- selectedSort,
- onSortSelect,
- sortToggle,
- t,
- classes.sortDropdown,
- ],
+ [isSortSelectOpen, selectedSort, onSortSelect, sortToggle, t],
);
const handleAttach = (data: File[], event: ReactDropzoneDropEvent) => {
@@ -1795,7 +1829,7 @@ export const LightspeedChat = ({
}
return (
- <>
+
{notebookAlerts.length > 0 && (
- >
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBox.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBox.tsx
index c162197f66..36d0993273 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBox.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBox.tsx
@@ -22,7 +22,8 @@ import {
useRef,
} from 'react';
-import { makeStyles } from '@material-ui/core';
+import GlobalStyles from '@mui/material/GlobalStyles';
+import { styled } from '@mui/material/styles';
import {
ChatbotDisplayMode,
ChatbotWelcomePrompt,
@@ -45,23 +46,14 @@ import { ToolCall } from '../types';
import { parseReasoning } from '../utils/reasoningParser';
import { mapToPatternFlyToolCall } from '../utils/toolCallMapper';
-const useStyles = makeStyles(theme => ({
- prompt: {
- 'justify-content': 'flex-end',
- },
- container: {
- maxWidth: 'unset !important',
- },
- alert: {
- background: 'unset !important',
- },
- promptSuggestions: {
- '& div.pf-chatbot__prompt-suggestions': {
- 'flex-direction': 'column !important',
- },
- },
+const DEEP_THINKING_CLASS = 'lightspeed-deep-thinking';
- userMessageText: {
+const StyledMessageBox = styled(MessageBox, {
+ shouldForwardProp: (prop: string) =>
+ !['isNewChat', 'hasPrompts', 'isEmbeddedMode'].includes(prop),
+})<{ isNewChat?: boolean; hasPrompts?: boolean; isEmbeddedMode?: boolean }>(
+ ({ theme, isNewChat, hasPrompts, isEmbeddedMode }) => ({
+ maxWidth: 'unset !important',
'& div.pf-chatbot__message--user': {
'& div.pf-chatbot__message-text': {
'& p': {
@@ -69,37 +61,32 @@ const useStyles = makeStyles(theme => ({
},
},
},
- },
- deepThinking: {
- animation: '$deepThinking 1.6s ease-in-out infinite',
- },
+ ...(isNewChat
+ ? {
+ flex: 'none',
+ height: 'auto',
+ overflow: 'visible',
+ }
+ : {
+ flex: 1,
+ minHeight: 0,
+ }),
+ ...(hasPrompts && {
+ justifyContent: 'flex-end',
+ }),
+ ...(hasPrompts &&
+ !isEmbeddedMode && {
+ '& div.pf-chatbot__prompt-suggestions': {
+ flexDirection: 'column !important',
+ },
+ }),
+ }),
+);
- '@keyframes deepThinking': {
- '0%': {
- opacity: 0.65,
- },
- '50%': {
- opacity: 1,
- },
- '100%': {
- opacity: 0.65,
- },
- },
- // Message box fills remaining height and scrolls when there are messages.
- messageBoxFlex: {
- flex: 1,
- minHeight: 0,
- },
- // New chat: message box sizes to content so ChatbotContent is the scroll container.
- // overflow: visible so full height is included in parent's scrollHeight (no clipping).
- messageBoxAutoHeight: {
- flex: 'none',
- height: 'auto',
- overflow: 'visible',
- },
-}));
+const StyledAlert = styled(Alert)({
+ background: 'unset !important',
+});
-// Extended message type that includes tool calls
interface ExtendedMessageProps extends MessageProps {
toolCalls?: ToolCall[];
}
@@ -135,7 +122,6 @@ export const LightspeedChatBox = forwardRef(
}: LightspeedChatBoxProps,
ref: ForwardedRef,
) => {
- const classes = useStyles();
const scrollQueued = useRef(false);
const containerRef = useRef(null);
const { t } = useTranslation();
@@ -189,27 +175,14 @@ export const LightspeedChatBox = forwardRef(
// eslint-disable-next-line
}, [autoScroll, cmessages, containerRef]);
- const messageBoxClasses = `${classes.container} ${classes.userMessageText}`;
- const isEmbeddedMode = displayMode === ChatbotDisplayMode.embedded;
-
const isNewChat = welcomePrompts.length > 0 && messages.length === 0;
- const getMessageBoxClassName = () => {
- const base = isNewChat
- ? `${messageBoxClasses} ${classes.messageBoxAutoHeight}`
- : `${messageBoxClasses} ${classes.messageBoxFlex}`;
- if (!welcomePrompts.length) {
- return base;
- }
- const withPrompt = `${base} ${classes.prompt}`;
- if (isEmbeddedMode) {
- return withPrompt;
- }
- return `${withPrompt} ${classes.promptSuggestions}`;
- };
+ const isEmbeddedMode = displayMode === ChatbotDisplayMode.embedded;
return (
- 0}
+ isEmbeddedMode={isEmbeddedMode}
announcement={announcement}
ref={containerRef}
onScrollToTopClick={scrollToTop}
@@ -220,16 +193,23 @@ export const LightspeedChatBox = forwardRef(
jumpButtonTopTooltipProps={{ content: t('tooltip.backToTop') }}
>
-
+
+
{topicRestrictionEnabled
? t('disclaimer.withValidation')
: t('disclaimer.withoutValidation')}
-
+
{welcomePrompts.length ? (
@@ -250,13 +230,11 @@ export const LightspeedChatBox = forwardRef(
const messageContent = message.content as string;
const parsedReasoning = parseReasoning(messageContent || '');
- // Map first tool call to PatternFly's toolCall prop
const firstToolCall = message.toolCalls?.[0];
const toolCallProp = firstToolCall
? mapToPatternFlyToolCall(firstToolCall, t, message.role)
: undefined;
- // Handle additional tool calls (if any) via extraContent
const additionalToolCalls = message.toolCalls?.slice(1);
const extraContentParts: {
@@ -278,7 +256,7 @@ export const LightspeedChatBox = forwardRef(
id: `deep-thinking-${index}`,
style: { whiteSpace: 'pre-line' },
className: parsedReasoning.isReasoningInProgress
- ? classes.deepThinking
+ ? DEEP_THINKING_CLASS
: undefined,
},
toggleContent: t('reasoning.thinking'),
@@ -290,7 +268,6 @@ export const LightspeedChatBox = forwardRef(
const allToolCalls: React.ReactNode[] = [];
- // Add first tool call if it exists
if (toolCallProp && firstToolCall) {
allToolCalls.push(
0) {
additionalToolCalls.forEach(tc => {
const tcProps = mapToPatternFlyToolCall(tc, t, message.role);
@@ -317,7 +293,6 @@ export const LightspeedChatBox = forwardRef(
});
}
- // Show Thinking first, then tool calls
if (deepThinking || allToolCalls.length > 0) {
extraContentParts.beforeMainContent = (
<>
@@ -351,7 +326,7 @@ export const LightspeedChatBox = forwardRef(
/>
);
})}
-
+
);
},
);
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx
index 9ef30eff01..24f0609257 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx
@@ -16,10 +16,10 @@
import { Ref, useState } from 'react';
-import { createStyles, makeStyles } from '@material-ui/core';
import ToggleOffOutlinedIcon from '@mui/icons-material/ToggleOffOutlined';
import ToggleOnOutlinedIcon from '@mui/icons-material/ToggleOnOutlined';
import Divider from '@mui/material/Divider';
+import { styled } from '@mui/material/styles';
import {
ChatbotDisplayMode,
ChatbotHeaderActions,
@@ -58,27 +58,33 @@ type LightspeedChatBoxHeaderProps = {
setDisplayMode: (mode: ChatbotDisplayMode) => void;
};
-const useStyles = makeStyles(theme =>
- createStyles({
- dropdown: {
- '& ul, & li': {
- padding: 0,
- margin: 0,
- },
- },
- header: {
- backgroundColor: theme.palette.action.disabled,
- },
- optionsToggle: {
- '& svg': {
- transform: 'none !important',
- },
- },
- groupTitle: {
- fontWeight: 'bold',
- },
+const dropdownOverrideStyles = {
+ '& ul, & li': {
+ padding: 0,
+ margin: 0,
+ },
+} as const;
+
+const StyledDropdown = styled(Dropdown)(dropdownOverrideStyles);
+
+const StyledOptionsDropdown = styled(ChatbotHeaderOptionsDropdown)({
+ ...dropdownOverrideStyles,
+ '& .pf-v6-c-menu-toggle svg, & .pf-v5-c-menu-toggle svg': {
+ transform: 'none !important',
+ },
+});
+
+const StyledMenuToggle = styled(MenuToggle, {
+ shouldForwardProp: (prop: string) => prop !== 'isDisabledStyle',
+})<{ isDisabledStyle?: boolean }>(({ theme, isDisabledStyle }) => ({
+ ...(isDisabledStyle && {
+ backgroundColor: theme.palette.action.disabled,
}),
-);
+}));
+
+const StyledDropdownGroup = styled(DropdownGroup)({
+ fontWeight: 'bold',
+});
export const LightspeedChatBoxHeader = ({
selectedModel,
@@ -96,8 +102,6 @@ export const LightspeedChatBoxHeader = ({
const [isOptionsMenuOpen, setIsOptionsMenuOpen] = useState(false);
const { t } = useTranslation();
- const styles = useStyles();
-
const maxLabelLength = Math.max(
...models.map(m => m.label.length),
selectedModel.length,
@@ -106,8 +110,8 @@ export const LightspeedChatBoxHeader = ({
const toggleMinWidth = `${maxLabelLength + 4}ch`;
const toggle = (toggleRef: Ref
) => (
-
{selectedModel}
-
+
);
const handlePinningChatsToggle = (state: boolean) => {
@@ -143,8 +147,7 @@ export const LightspeedChatBoxHeader = ({
return (
{!hideModelSelector && (
- {
handleSelectedModel(value as string);
@@ -160,7 +163,7 @@ export const LightspeedChatBoxHeader = ({
>
{models.map(model => (
-
+
{model.label}
-
+
))}
-
+
)}
-
>
)}
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatContainer.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatContainer.tsx
index aeece211b3..2086b070f4 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatContainer.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatContainer.tsx
@@ -19,12 +19,9 @@ import { useAsync } from 'react-use';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
-import { Button } from '@material-ui/core';
-import {
- StylesProvider as StylesProviderV4,
- useTheme,
-} from '@material-ui/core/styles';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
+import Button from '@mui/material/Button';
+import { useTheme } from '@mui/material/styles';
import { StylesProvider } from '@mui/styles';
import { QueryClientProvider } from '@tanstack/react-query';
@@ -32,10 +29,7 @@ import { useAllModels } from '../hooks/useAllModels';
import { useLightspeedViewPermission } from '../hooks/useLightspeedViewPermission';
import { useTopicRestrictionStatus } from '../hooks/useQuestionValidation';
import { useTranslation } from '../hooks/useTranslation';
-import {
- generateClassName,
- generateClassNameV4,
-} from '../utils/generateClassName';
+import { generateClassName } from '../utils/generateClassName';
import queryClient from '../utils/queryClient';
import FileAttachmentContextProvider from './AttachmentContext';
import { LightspeedChat } from './LightSpeedChat';
@@ -55,7 +49,7 @@ const LAST_SELECTED_MODEL_KEY = 'lastSelectedModel';
*/
const LightspeedChatContainerInner = () => {
const {
- palette: { type },
+ palette: { mode: type },
} = useTheme();
const { t } = useTranslation();
@@ -222,11 +216,9 @@ const LightspeedChatContainerInner = () => {
export const LightspeedChatContainer = () => {
return (
-
-
-
-
-
+
+
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatModelsState.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatModelsState.tsx
index 6e015a03b3..c724417c39 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatModelsState.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatModelsState.tsx
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-import {
- Box,
- Button,
- CircularProgress,
- Link,
- Typography,
-} from '@material-ui/core';
-import { createStyles, makeStyles } from '@material-ui/core/styles';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import CircularProgress from '@mui/material/CircularProgress';
+import Link from '@mui/material/Link';
+import { styled } from '@mui/material/styles';
+import Typography from '@mui/material/Typography';
import { useTranslation } from '../hooks/useTranslation';
@@ -33,77 +31,46 @@ const LLAMA_STACK_CONFIGURE_DOCS_URL =
const LIGHTSPEED_BACKEND_README_URL =
'https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/lightspeed/plugins/lightspeed-backend/README.md';
-const useStyles = makeStyles(theme =>
- createStyles({
- root: {
- display: 'flex',
- flexDirection: 'column',
- boxSizing: 'border-box',
- width: '100%',
- maxWidth: '100%',
- minWidth: 0,
- minHeight: '100%',
- height: '100%',
- flex: '1 1 auto',
- alignItems: 'center',
- justifyContent: 'center',
- padding: theme.spacing(4, 2),
- backgroundColor: theme.palette.background.default,
- },
- panel: {
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- textAlign: 'center',
- width: '100%',
- maxWidth: 440,
- gap: theme.spacing(2),
- },
- emptyStateIcon: {
- fontSize: 64,
- color: theme.palette.text.secondary,
- },
- errorIcon: {
- fontSize: 64,
- color: theme.palette.warning.main,
- },
- description: {
- lineHeight: 1.5,
- color: theme.palette.text.secondary,
- },
- actions: {
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- gap: theme.spacing(1.5),
- marginTop: theme.spacing(1),
- },
- backendLink: {
- display: 'inline-flex',
- alignItems: 'center',
- gap: theme.spacing(0.5),
- fontSize: theme.typography.body1.fontSize,
- fontWeight: 500,
- },
- }),
-);
+const Root = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ boxSizing: 'border-box',
+ width: '100%',
+ maxWidth: '100%',
+ minWidth: 0,
+ minHeight: '100%',
+ height: '100%',
+ flex: '1 1 auto',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: theme.spacing(4, 2),
+ backgroundColor: theme.palette.background.default,
+}));
+
+const Panel = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ textAlign: 'center',
+ width: '100%',
+ maxWidth: 440,
+ gap: theme.spacing(2),
+}));
/**
* Shown while the models list is loading for an authorized user.
*/
export const LightspeedChatModelsLoading = () => {
- const classes = useStyles();
const { t } = useTranslation();
return (
-
-
+
);
};
@@ -111,18 +78,13 @@ export const LightspeedChatModelsLoading = () => {
* Shown when LCORE / Llama Stack is up but no LLM models are registered.
*/
export const LcoreNotConfiguredEmptyState = () => {
- const classes = useStyles();
const { t } = useTranslation();
return (
-
-
+
+
@@ -136,11 +98,19 @@ export const LcoreNotConfiguredEmptyState = () => {
{t('lcore.notConfigured.description')}
-
+
({
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: theme.spacing(0.5),
+ fontSize: theme.typography.body1.fontSize,
+ fontWeight: 500,
+ })}
component="a"
color="primary"
href={LIGHTSPEED_BACKEND_README_URL}
@@ -163,8 +139,8 @@ export const LcoreNotConfiguredEmptyState = () => {
-
-
+
+
);
};
@@ -173,23 +149,21 @@ type ModelsLoadErrorEmptyStateProps = {
};
/**
- * Shown when the models API fails (distinct from “no models configured”).
+ * Shown when the models API fails (distinct from "no models configured").
*/
export const ModelsLoadErrorEmptyState = ({
onRetry,
}: ModelsLoadErrorEmptyStateProps) => {
- const classes = useStyles();
const { t } = useTranslation();
return (
-
-
+
@@ -203,16 +177,24 @@ export const ModelsLoadErrorEmptyState = ({
{t('lcore.loadError.description')}
-
+
-
-
+
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedDrawerProvider.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedDrawerProvider.tsx
index 560c490f54..0a8e670144 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedDrawerProvider.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedDrawerProvider.tsx
@@ -16,7 +16,7 @@
import { PropsWithChildren } from 'react';
-import { makeStyles } from '@mui/styles';
+import { styled } from '@mui/material/styles';
import { ChatbotModal } from '@patternfly/chatbot';
import { DOCKED_CONTENT_OFFSET } from '../const';
@@ -24,18 +24,16 @@ import { useLightspeedProviderState } from '../hooks/useLightspeedProviderState'
import { LightspeedChatContainer } from './LightspeedChatContainer';
import { LightspeedDrawerContext } from './LightspeedDrawerContext';
-const useStyles = makeStyles(theme => ({
- chatbotModal: {
- boxShadow:
- '0 14px 20px -7px rgba(0, 0, 0, 0.22), 0 32px 50px 6px rgba(0, 0, 0, 0.16), 0 12px 60px 12px rgba(0, 0, 0, 0.14) !important',
- bottom: `calc(${theme?.spacing?.(2) ?? '16px'} + 5em)`,
- right: `calc(${theme?.spacing?.(2) ?? '16px'} + 1.5em)`,
- maxWidth: 'min(30rem, calc(100vw - 32px)) !important',
- overflowX: 'hidden' as const,
- transition: 'margin-right 0.3s ease',
- 'body.docked-drawer-open &': {
- marginRight: DOCKED_CONTENT_OFFSET,
- },
+const StyledChatbotModal = styled(ChatbotModal)(({ theme }) => ({
+ boxShadow:
+ '0 14px 20px -7px rgba(0, 0, 0, 0.22), 0 32px 50px 6px rgba(0, 0, 0, 0.16), 0 12px 60px 12px rgba(0, 0, 0, 0.14) !important',
+ bottom: `calc(${theme.spacing(2)} + 5em)`,
+ right: `calc(${theme.spacing(2)} + 1.5em)`,
+ maxWidth: 'min(30rem, calc(100vw - 32px)) !important',
+ overflowX: 'hidden',
+ transition: 'margin-right 0.3s ease',
+ 'body.docked-drawer-open &': {
+ marginRight: DOCKED_CONTENT_OFFSET,
},
}));
@@ -43,7 +41,6 @@ const useStyles = makeStyles(theme => ({
* @public
*/
export const LightspeedDrawerProvider = ({ children }: PropsWithChildren) => {
- const classes = useStyles();
const { contextValue, shouldRenderOverlayModal, closeChatbot } =
useLightspeedProviderState();
@@ -51,17 +48,16 @@ export const LightspeedDrawerProvider = ({ children }: PropsWithChildren) => {
{children}
{shouldRenderOverlayModal && (
- closeChatbot()}
ouiaId="LightspeedChatbotModal"
aria-labelledby="lightspeed-chatpopup-modal"
- className={classes.chatbotModal}
>
-
+
)}
);
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedPage.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedPage.tsx
index aec6f2cde6..3de5adfe58 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedPage.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedPage.tsx
@@ -16,25 +16,20 @@
import { Content, Header, Page } from '@backstage/core-components';
-import { createStyles, makeStyles } from '@material-ui/core/styles';
+import { styled } from '@mui/material/styles';
import { useTranslation } from '../hooks/useTranslation';
import { LightspeedChatContainer } from './LightspeedChatContainer';
-const useStyles = makeStyles(() =>
- createStyles({
- container: {
- padding: '0px',
- },
- }),
-);
+const NoPaddingContent = styled(Content)({
+ padding: 0,
+});
/**
* Lightspeed Page - Routable fullscreen/embedded mode
* @public
*/
export const LightspeedPage = () => {
- const classes = useStyles();
const { t } = useTranslation();
return (
@@ -44,9 +39,9 @@ export const LightspeedPage = () => {
style={{ display: 'none' }}
pageTitleOverride={t('page.title')}
/>
-
+
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/McpServersSettings.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/McpServersSettings.tsx
index 91d65d7987..21714beb03 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/McpServersSettings.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/McpServersSettings.tsx
@@ -19,12 +19,13 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { configApiRef, fetchApiRef, useApi } from '@backstage/core-plugin-api';
import { usePermission } from '@backstage/plugin-permission-react';
-import { makeStyles } from '@material-ui/core';
import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined';
+import GlobalStyles from '@mui/material/GlobalStyles';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
+import { styled } from '@mui/material/styles';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import {
@@ -74,23 +75,56 @@ type TokenValidationState = 'idle' | 'validating' | 'success' | 'error';
const SAVED_TOKEN_MASK = '********************';
-const useStyles = makeStyles(theme => ({
- '@global': {
- '.pf-v6-c-backdrop': {
- zIndex: '1400 !important',
- },
- '.pf-v5-c-backdrop': {
- zIndex: '1400 !important',
- },
- },
- root: {
+const PREFIX = 'McpServersSettings';
+const classes = {
+ root: `${PREFIX}-root`,
+ headerRow: `${PREFIX}-headerRow`,
+ selectedCount: `${PREFIX}-selectedCount`,
+ title: `${PREFIX}-title`,
+ closeButton: `${PREFIX}-closeButton`,
+ nameHeaderButton: `${PREFIX}-nameHeaderButton`,
+ nameHeaderText: `${PREFIX}-nameHeaderText`,
+ nameCell: `${PREFIX}-nameCell`,
+ statusHeader: `${PREFIX}-statusHeader`,
+ statusColumnCell: `${PREFIX}-statusColumnCell`,
+ rowName: `${PREFIX}-rowName`,
+ nameValue: `${PREFIX}-nameValue`,
+ statusCell: `${PREFIX}-statusCell`,
+ statusValue: `${PREFIX}-statusValue`,
+ statusOk: `${PREFIX}-statusOk`,
+ statusToken: `${PREFIX}-statusToken`,
+ statusWarn: `${PREFIX}-statusWarn`,
+ statusDisabled: `${PREFIX}-statusDisabled`,
+ actionButton: `${PREFIX}-actionButton`,
+ modalDescription: `${PREFIX}-modalDescription`,
+ modalContent: `${PREFIX}-modalContent`,
+ modalCustomCloseButton: `${PREFIX}-modalCustomCloseButton`,
+ modalHeading: `${PREFIX}-modalHeading`,
+ tokenRow: `${PREFIX}-tokenRow`,
+ tokenClearButton: `${PREFIX}-tokenClearButton`,
+ tokenHelper: `${PREFIX}-tokenHelper`,
+ tokenInput: `${PREFIX}-tokenInput`,
+ tokenInputSuccess: `${PREFIX}-tokenInputSuccess`,
+ tokenInputError: `${PREFIX}-tokenInputError`,
+ modalActions: `${PREFIX}-modalActions`,
+ modalActionButton: `${PREFIX}-modalActionButton`,
+ modalCancelButton: `${PREFIX}-modalCancelButton`,
+ forgetTokenButton: `${PREFIX}-forgetTokenButton`,
+ configureModal: `${PREFIX}-configureModal`,
+ toggleCell: `${PREFIX}-toggleCell`,
+ table: `${PREFIX}-table`,
+ alert: `${PREFIX}-alert`,
+};
+
+const StyledRoot = styled('div')(({ theme }) => ({
+ [`&.${classes.root}`]: {
padding: 0,
height: '100%',
minHeight: '100%',
width: '100%',
overflow: 'auto',
},
- headerRow: {
+ [`& .${classes.headerRow}`]: {
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'space-between',
@@ -99,20 +133,20 @@ const useStyles = makeStyles(theme => ({
marginLeft: theme.spacing(3),
marginRight: theme.spacing(2),
},
- selectedCount: {
+ [`& .${classes.selectedCount}`]: {
color: theme.palette.text.secondary,
marginTop: theme.spacing(0.5),
fontSize: '0.75rem',
},
- title: {
+ [`& .${classes.title}`]: {
fontSize: '1.125rem',
},
- closeButton: {
+ [`& .${classes.closeButton}`]: {
marginTop: -theme.spacing(1),
marginRight: -theme.spacing(1),
color: theme.palette.text.primary,
},
- nameHeaderButton: {
+ [`& .${classes.nameHeaderButton}`]: {
paddingLeft: 0,
paddingTop: 0,
paddingBottom: 0,
@@ -126,72 +160,72 @@ const useStyles = makeStyles(theme => ({
display: 'inline-flex',
alignItems: 'center',
},
- nameHeaderText: {
+ [`& .${classes.nameHeaderText}`]: {
paddingLeft: '7px',
fontSize: '0.75rem',
lineHeight: '1.25rem',
fontWeight: 600,
},
- nameCell: {
+ [`& .${classes.nameCell}`]: {
paddingLeft: '8px !important',
},
- statusHeader: {
+ [`& .${classes.statusHeader}`]: {
paddingLeft: '0 !important',
},
- statusColumnCell: {
+ [`& .${classes.statusColumnCell}`]: {
paddingLeft: '0 !important',
},
- rowName: {
+ [`& .${classes.rowName}`]: {
fontSize: '1rem',
fontWeight: 500,
whiteSpace: 'nowrap',
},
- nameValue: {
+ [`& .${classes.nameValue}`]: {
fontSize: '0.875rem',
fontWeight: 500,
},
- statusCell: {
+ [`& .${classes.statusCell}`]: {
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
whiteSpace: 'nowrap',
},
- statusValue: {
+ [`& .${classes.statusValue}`]: {
fontSize: '0.875rem',
},
- statusOk: {
+ [`& .${classes.statusOk}`]: {
color: '#147878',
},
- statusToken: {
+ [`& .${classes.statusToken}`]: {
color: '#147878',
},
- statusWarn: {
+ [`& .${classes.statusWarn}`]: {
color: '#B1380B',
},
- statusDisabled: {
+ [`& .${classes.statusDisabled}`]: {
color: theme.palette.text.secondary,
},
- actionButton: {
+ [`& .${classes.actionButton}`]: {
color: theme.palette.text.secondary,
},
- modalDescription: {
+ [`& .${classes.modalDescription}`]: {
color: theme.palette.text.secondary,
fontSize: '0.875rem',
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
- modalContent: {
+ [`& .${classes.modalContent}`]: {
position: 'relative',
padding: theme.spacing(3, 0, 3, 3),
marginRight: theme.spacing(3),
},
- modalCustomCloseButton: {
+ [`& .${classes.modalCustomCloseButton}`]: {
position: 'absolute',
top: theme.spacing(2),
right: theme.spacing(-0.5),
color: theme.palette.text.primary,
},
- modalHeading: {
+ [`& .${classes.modalHeading}`]: {
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.75),
@@ -205,10 +239,10 @@ const useStyles = makeStyles(theme => ({
fontWeight: 500,
},
},
- tokenRow: {
+ [`& .${classes.tokenRow}`]: {
position: 'relative',
},
- tokenClearButton: {
+ [`& .${classes.tokenClearButton}`]: {
position: 'absolute',
right: theme.spacing(1),
top: '50%',
@@ -216,12 +250,12 @@ const useStyles = makeStyles(theme => ({
zIndex: 1,
color: theme.palette.action.active,
},
- tokenHelper: {
+ [`& .${classes.tokenHelper}`]: {
color: theme.palette.text.secondary,
fontSize: '0.75rem',
marginTop: theme.spacing(0.5),
},
- tokenInput: {
+ [`& .${classes.tokenInput}`]: {
marginTop: '1rem !important',
'& .MuiOutlinedInput-root': {
height: '3.5rem',
@@ -233,7 +267,7 @@ const useStyles = makeStyles(theme => ({
fontSize: '0.875rem',
},
},
- tokenInputSuccess: {
+ [`& .${classes.tokenInputSuccess}`]: {
'& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline': {
borderColor: '#3E8635',
borderWidth: 1,
@@ -248,7 +282,7 @@ const useStyles = makeStyles(theme => ({
color: '#3E8635',
},
},
- tokenInputError: {
+ [`& .${classes.tokenInputError}`]: {
'& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline': {
borderColor: '#C9190B',
borderWidth: 1,
@@ -263,18 +297,18 @@ const useStyles = makeStyles(theme => ({
color: '#C9190B',
},
},
- modalActions: {
+ [`& .${classes.modalActions}`]: {
marginTop: theme.spacing(3),
display: 'flex',
gap: theme.spacing(1),
},
- modalActionButton: {
+ [`& .${classes.modalActionButton}`]: {
fontSize: '1rem',
},
- modalCancelButton: {
+ [`& .${classes.modalCancelButton}`]: {
fontSize: '1rem',
},
- forgetTokenButton: {
+ [`& .${classes.forgetTokenButton}`]: {
fontSize: '1rem',
border: '1px solid #B1380B',
borderRadius: '1.25rem',
@@ -288,7 +322,7 @@ const useStyles = makeStyles(theme => ({
backgroundColor: 'rgba(201, 25, 11, 0.08)',
},
},
- configureModal: {
+ [`& .${classes.configureModal}`]: {
'& .pf-v6-c-modal-box': {
width: '608px',
maxWidth: '608px',
@@ -311,10 +345,10 @@ const useStyles = makeStyles(theme => ({
fontSize: '1.25rem !important',
},
},
- toggleCell: {
+ [`& .${classes.toggleCell}`]: {
paddingRight: '0 !important',
},
- table: {
+ [`& .${classes.table}`]: {
width: '100%',
'& th': {
borderBottom: 0,
@@ -331,7 +365,7 @@ const useStyles = makeStyles(theme => ({
verticalAlign: 'middle',
},
},
- alert: {
+ [`& .${classes.alert}`]: {
marginLeft: theme.spacing(3),
marginRight: theme.spacing(3),
marginBottom: theme.spacing(2),
@@ -408,7 +442,6 @@ export const McpServersSettings = ({
onClose,
backgroundColor,
}: McpServersSettingsProps) => {
- const classes = useStyles();
const { t } = useTranslation();
const configApi = useApi(configApiRef);
const fetchApi = useApi(fetchApiRef);
@@ -821,10 +854,16 @@ export const McpServersSettings = ({
};
return (
-
+
@@ -1114,6 +1153,6 @@ export const McpServersSettings = ({
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/MessageBarModelSelector.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/MessageBarModelSelector.tsx
index 00f96c26ed..4dcd76c786 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/MessageBarModelSelector.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/MessageBarModelSelector.tsx
@@ -16,7 +16,7 @@
import { Ref, useState } from 'react';
-import { makeStyles } from '@material-ui/core';
+import { styled } from '@mui/material/styles';
import {
Dropdown,
DropdownItem,
@@ -35,35 +35,34 @@ type MessageBarModelSelectorProps = {
disabled?: boolean;
};
-const useStyles = makeStyles(theme => ({
- selectorToggle: {
- display: 'flex',
- alignItems: 'center',
- gap: 4,
- color: theme.palette.text.secondary,
- fontSize: 14,
- fontWeight: 500,
- cursor: 'pointer',
- padding: '4px 8px',
- borderRadius: 8,
- border: 'none',
- background: 'transparent',
- '&:hover': {
- backgroundColor: theme.palette.action.hover,
- },
- '&:disabled': {
- cursor: 'not-allowed',
- opacity: 0.5,
- },
+const StyledMenuToggle = styled(MenuToggle)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: 4,
+ color: theme.palette.text.secondary,
+ fontSize: 14,
+ fontWeight: 500,
+ cursor: 'pointer',
+ padding: '4px 8px',
+ borderRadius: 8,
+ border: 'none',
+ background: 'transparent',
+ '&:hover': {
+ backgroundColor: theme.palette.action.hover,
},
- dropdown: {
- '& ul, & li': {
- padding: 0,
- margin: 0,
- },
+ '&:disabled': {
+ cursor: 'not-allowed',
+ opacity: 0.5,
},
}));
+const StyledDropdown = styled(Dropdown)({
+ '& ul, & li': {
+ padding: 0,
+ margin: 0,
+ },
+});
+
export const MessageBarModelSelector = ({
selectedModel,
models,
@@ -71,30 +70,27 @@ export const MessageBarModelSelector = ({
disabled = false,
}: MessageBarModelSelectorProps) => {
const [isOpen, setIsOpen] = useState(false);
- const classes = useStyles();
const { t } = useTranslation();
const selectedModelLabel =
models.find(m => m.value === selectedModel)?.label ?? selectedModel;
const toggle = (toggleRef: Ref) => (
- setIsOpen(!isOpen)}
isExpanded={isOpen}
isDisabled={disabled}
variant="plain"
- className={classes.selectorToggle}
aria-label={t('aria.chatbotSelector')}
>
{selectedModelLabel}
-
+
);
return (
- {
onSelect(value as string);
@@ -119,6 +115,6 @@ export const MessageBarModelSelector = ({
))}
-
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/PermissionRequiredState.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/PermissionRequiredState.tsx
index 38ad489f9e..163b09fbff 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/PermissionRequiredState.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/PermissionRequiredState.tsx
@@ -18,59 +18,55 @@ import { Fragment } from 'react';
import { EmptyState } from '@backstage/core-components';
-import { createStyles, makeStyles } from '@material-ui/core/styles';
+import { styled } from '@mui/material/styles';
import { useTranslation } from '../hooks/useTranslation';
import { PermissionRequiredIcon } from './PermissionRequiredIcon';
import { Trans } from './Trans';
-const useStyles = makeStyles(theme =>
- createStyles({
- root: {
+const Root = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ height: '100%',
+ minHeight: '100%',
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: theme.palette.background.default,
+ containerType: 'inline-size',
+ '& [class*="BackstageEmptyState-root"]': {
+ alignItems: 'center',
+ padding: theme.spacing(4),
+ },
+ '& [class*="MuiTypography-h5"]': {
+ fontSize: 'clamp(1.875rem, 3.75cqi, 3.125rem)',
+ fontWeight: 400,
+ },
+ '& [class*="MuiTypography-body1"]': {
+ fontSize: '1em',
+ color: theme.palette.text.secondary,
+ '& b': {
+ fontWeight: 500,
+ color: theme.palette.text.primary,
+ },
+ },
+ '@container (max-width: 899px)': {
+ '& [class*="BackstageEmptyState-root"]': {
+ textAlign: 'center',
+ },
+ '& [class*="MuiGrid-grid-md-6"]': {
+ maxWidth: '100%',
+ flexBasis: '100%',
+ },
+ '& [class*="BackstageEmptyState-imageContainer"]': {
+ order: -1,
display: 'flex',
- flexDirection: 'column',
- width: '100%',
- height: '100%',
- minHeight: '100%',
- flex: 1,
- alignItems: 'center',
justifyContent: 'center',
- backgroundColor: theme.palette.background.default,
- containerType: 'inline-size',
- '& [class*="BackstageEmptyState-root"]': {
- alignItems: 'center',
- padding: theme.spacing(4),
- },
- '& [class*="MuiTypography-h5"]': {
- fontSize: 'clamp(1.875rem, 3.75cqi, 3.125rem)',
- fontWeight: 400,
- },
- '& [class*="MuiTypography-body1"]': {
- fontSize: '1em',
- color: theme.palette.text.secondary,
- '& b': {
- fontWeight: 500,
- color: theme.palette.text.primary,
- },
- },
- '@container (max-width: 899px)': {
- '& [class*="BackstageEmptyState-root"]': {
- textAlign: 'center',
- },
- '& [class*="MuiGrid-grid-md-6"]': {
- maxWidth: '100%',
- flexBasis: '100%',
- },
- '& [class*="BackstageEmptyState-imageContainer"]': {
- order: -1,
- display: 'flex',
- justifyContent: 'center',
- marginBottom: theme.spacing(-4),
- },
- },
+ marginBottom: theme.spacing(-4),
},
- }),
-);
+ },
+}));
interface PermissionRequiredStateProps {
subject: string;
@@ -83,7 +79,6 @@ const PermissionRequiredState = ({
permissions,
action,
}: PermissionRequiredStateProps) => {
- const classes = useStyles();
const { t } = useTranslation();
const permissionsList = (
@@ -98,7 +93,7 @@ const PermissionRequiredState = ({
);
return (
-
+
}}
action={action}
/>
-
+
);
};
export default PermissionRequiredState;
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/RenameConversationModal.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/RenameConversationModal.tsx
index c77be838d2..360fcf3ba7 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/RenameConversationModal.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/RenameConversationModal.tsx
@@ -16,7 +16,6 @@
import { useEffect, useState } from 'react';
-import { TextField } from '@material-ui/core';
import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
import CloseIcon from '@mui/icons-material/Close';
import Alert from '@mui/material/Alert';
@@ -27,6 +26,7 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
+import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { useConversations } from '../hooks/useConversations';
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/Router.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/Router.tsx
index ddc2b957be..bc57f21744 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/Router.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/Router.tsx
@@ -18,13 +18,9 @@ import { Route, Routes } from 'react-router-dom';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
-import { StylesProvider as StylesProviderV4 } from '@material-ui/core/styles';
import { StylesProvider } from '@mui/styles';
-import {
- generateClassName,
- generateClassNameV4,
-} from '../utils/generateClassName';
+import { generateClassName } from '../utils/generateClassName';
import { LightspeedPage } from './LightspeedPage';
/**
@@ -37,24 +33,19 @@ export const Router = () => {
return (
-
-
- } />
- }
- />
- {notebooksEnabled && (
- <>
- } />
- }
- />
- >
- )}
-
-
+
+ } />
+ }
+ />
+ {notebooksEnabled && (
+ <>
+ } />
+ } />
+ >
+ )}
+
);
};
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/ToolCallContent.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/ToolCallContent.tsx
index 8effe8cbdb..c252fcf4ee 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/ToolCallContent.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/ToolCallContent.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { makeStyles } from '@material-ui/core';
+import { styled } from '@mui/material/styles';
import { Message } from '@patternfly/chatbot';
import {
Content,
@@ -38,14 +38,12 @@ interface ToolCallContentProps {
role?: 'user' | 'bot';
}
-const useStyles = makeStyles(() => ({
- codeBlock: {
- '& .pf-chatbot__message-code-block': {
- border: '1px solid var(--pf-t--global--border--color--default)',
- borderRadius: 'var(--pf-t--global--border--radius--small)',
- },
+const CodeBlockWrapper = styled(FlexItem)({
+ '& .pf-chatbot__message-code-block': {
+ border: '1px solid var(--pf-t--global--border--color--default)',
+ borderRadius: 'var(--pf-t--global--border--radius--small)',
},
-}));
+});
/**
* Lightweight component for rendering tool call expandable content.
@@ -55,7 +53,6 @@ export const ToolCallContent = ({
toolCall,
role = 'bot',
}: ToolCallContentProps) => {
- const classes = useStyles();
const { t } = useTranslation();
const formatExecutionTime = (seconds?: number): string => {
@@ -236,7 +233,7 @@ export const ToolCallContent = ({
direction={{ default: 'column' }}
spaceItems={{ default: 'spaceItemsXs' }}
>
-
+
-
+
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedPage.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedPage.test.tsx
index 676acd6749..005e8e7041 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedPage.test.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedPage.test.tsx
@@ -58,15 +58,6 @@ const mockLightspeedApi = {
isTopicRestrictionEnabled: jest.fn().mockResolvedValue(false),
};
-jest.mock('@mui/material', () => ({
- ...jest.requireActual('@mui/material'),
- makeStyles: () => () => {
- return {
- container: 'container',
- };
- },
-}));
-
jest.mock('../../hooks/useAllModels', () => ({
useAllModels: jest.fn(),
}));
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx
index 7b85fbb594..71676cd7d6 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx
@@ -17,7 +17,6 @@
import { useEffect, useState } from 'react';
import { FileRejection } from 'react-dropzone';
-import { makeStyles } from '@material-ui/core/styles';
import CloseIcon from '@mui/icons-material/Close';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
@@ -27,6 +26,7 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
+import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import {
MultipleFileUpload,
@@ -43,69 +43,22 @@ import {
} from '../../utils/notebook-upload-utils';
import { FileListItem } from './FileListItem';
-const useStyles = makeStyles(theme => ({
- dialogPaper: {
- borderRadius: 24,
- maxWidth: 578,
+const StyledDropzone = styled('div')({
+ '& .pf-v6-c-multiple-file-upload__main': {
+ borderColor: 'var(--pf-t--global--border--color--brand--default)',
+ transition: 'background-color 0.2s ease',
+ cursor: 'pointer',
},
- dialogTitle: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- padding: '24px 24px 16px',
- },
- titleText: {
- fontWeight: 500,
- fontSize: '1.25rem',
- lineHeight: '1.625rem',
- letterSpacing: '-0.25px',
- },
- closeButton: {
- color: theme.palette.text.primary,
- },
- dialogContent: {
- padding: '0 24px 24px',
- },
- errorAlert: {
- 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': {
- backgroundColor:
- 'color-mix(in srgb, var(--pf-t--global--color--brand--default) 10%, transparent)',
- },
- },
- fileListContainer: {
- marginTop: theme.spacing(2),
- maxHeight: 200,
- overflowY: 'auto',
- },
- fileListHeader: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- marginBottom: theme.spacing(1),
- },
- fileCount: {
- fontSize: '0.875rem',
- color: theme.palette.text.secondary,
- },
- dialogActions: {
- padding: '16px 24px',
- justifyContent: 'flex-end',
- gap: theme.spacing(1),
- },
- addButton: {
- textTransform: 'none',
- },
- cancelButton: {
- textTransform: 'none',
+ '& .pf-v6-c-multiple-file-upload__main:hover': {
+ backgroundColor:
+ 'color-mix(in srgb, var(--pf-t--global--color--brand--default) 10%, transparent)',
},
+});
+
+const FileListContainer = styled(Box)(({ theme }) => ({
+ marginTop: theme.spacing(2),
+ maxHeight: 200,
+ overflowY: 'auto',
}));
type AddDocumentModalProps = {
@@ -135,7 +88,6 @@ export const AddDocumentModal = ({
filesToAdd,
onFilesAdded,
}: AddDocumentModalProps) => {
- const classes = useStyles();
const { t } = useTranslation();
const uploadMutation = useUploadDocument();
const [validationErrors, setValidationErrors] = useState([]);
@@ -230,11 +182,26 @@ export const AddDocumentModal = ({
onClose={handleClose}
aria-labelledby="add-document-modal-title"
PaperProps={{
- className: classes.dialogPaper,
+ sx: { borderRadius: '24px', maxWidth: 578 },
}}
>
-
-
+
+
{t('notebook.upload.modal.title')}
{selectedFiles.length > 0 &&
` (${selectedFiles.length}/${NOTEBOOK_MAX_FILES - existingDocumentNames.length})`}
@@ -242,16 +209,16 @@ export const AddDocumentModal = ({
-
+
{validationErrors.length > 0 && (
-
+
{validationErrors
.map(errorKey => {
const message = (t as Function)(errorKey) as string;
@@ -264,34 +231,44 @@ export const AddDocumentModal = ({
)}
{hasUploadsInProgress && (
-
+
{t('notebook.view.documents.uploadsInProgress')}
)}
{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')}
- />
-
+
+
+ }
+ titleText={t('notebook.upload.modal.dragDropTitle')}
+ titleTextSeparator={t('notebook.upload.modal.separator')}
+ infoText={t('notebook.upload.modal.infoText')}
+ browseButtonText={t('notebook.upload.modal.browseButton')}
+ />
+
+
)}
{selectedFiles.length > 0 && (
-
-
-
+
+
+
{(t as Function)('notebook.upload.modal.selectedFiles', {
count: selectedFiles.length,
max: NOTEBOOK_MAX_FILES - existingDocumentNames.length,
@@ -311,21 +288,23 @@ export const AddDocumentModal = ({
)}
/>
))}
-
+
)}
-
+