Skip to content

Commit 550ec7b

Browse files
committed
feat(lightspeed): add MCP servers settings panel
1 parent 32a88b0 commit 550ec7b

5 files changed

Lines changed: 592 additions & 243 deletions

File tree

workspaces/lightspeed/plugins/lightspeed/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@patternfly/chatbot": "6.5.0",
6969
"@patternfly/react-core": "6.4.1",
7070
"@patternfly/react-icons": "^6.3.1",
71+
"@patternfly/react-table": "^6.4.1",
7172
"@red-hat-developer-hub/backstage-plugin-lightspeed-common": "workspace:^",
7273
"@red-hat-developer-hub/backstage-plugin-theme": "^0.12.0",
7374
"@tanstack/react-query": "^5.59.15",

workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx

Lines changed: 175 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
FileDropZone,
4646
MessageBar,
4747
MessageProps,
48+
Settings,
4849
} from '@patternfly/chatbot';
4950
import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
5051
import {
@@ -106,6 +107,7 @@ import { DeleteNotebookModal } from './notebooks/DeleteNotebookModal';
106107
import { NotebooksTab } from './notebooks/NotebooksTab';
107108
import { RenameNotebookModal } from './notebooks/RenameNotebookModal';
108109
import PermissionRequiredState from './PermissionRequiredState';
110+
import { McpServersSettings } from './McpServersSettings';
109111
import { RenameConversationModal } from './RenameConversationModal';
110112

111113
const useStyles = makeStyles(theme => ({
@@ -330,6 +332,56 @@ const useStyles = makeStyles(theme => ({
330332
flex: 1,
331333
minHeight: 0,
332334
},
335+
settingsFlat: {
336+
height: '100%',
337+
width: '100%',
338+
'&.pf-chatbot__settings-form-container': {
339+
background: 'transparent',
340+
padding: 0,
341+
margin: 0,
342+
width: '100%',
343+
maxWidth: 'none',
344+
},
345+
'& .pf-chatbot__settings-form': {
346+
margin: 0,
347+
padding: 0,
348+
width: '100%',
349+
maxWidth: 'none',
350+
},
351+
'& .pf-chatbot__settings-form-row': {
352+
background: 'transparent',
353+
border: 0,
354+
margin: 0,
355+
padding: 0,
356+
width: '100%',
357+
maxWidth: 'none',
358+
},
359+
'& .pf-chatbot__settings-label': {
360+
display: 'none',
361+
},
362+
},
363+
mcpFullscreenLayout: {
364+
display: 'flex',
365+
minHeight: 0,
366+
height: '100%',
367+
flex: 1,
368+
width: '100%',
369+
},
370+
mcpChatPane: {
371+
display: 'flex',
372+
flexDirection: 'column',
373+
minHeight: 0,
374+
flex: 1,
375+
minWidth: 0,
376+
},
377+
mcpSettingsPane: {
378+
flex: 1,
379+
minWidth: 0,
380+
borderLeft: `1px solid ${theme.palette.divider}`,
381+
display: 'flex',
382+
flexDirection: 'column',
383+
minHeight: 0,
384+
},
333385
}));
334386

335387
type LightspeedChatProps = {
@@ -383,6 +435,8 @@ export const LightspeedChat = ({
383435
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
384436
const [isRenameModalOpen, setIsRenameModalOpen] = useState<boolean>(false);
385437
const [isSortSelectOpen, setIsSortSelectOpen] = useState<boolean>(false);
438+
const [isMcpSettingsOpen, setIsMcpSettingsOpen] = useState<boolean>(false);
439+
const [chatHeaderBgColor, setChatHeaderBgColor] = useState<string>();
386440
const contentScrollRef = useRef<HTMLDivElement>(null);
387441
const bottomSentinelRef = useRef<HTMLDivElement>(null);
388442
const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } =
@@ -432,6 +486,16 @@ export const LightspeedChat = ({
432486
}
433487
}, [isMobile, isFullscreenMode]);
434488

489+
useEffect(() => {
490+
if (typeof window === 'undefined') return;
491+
const headerElement = document.querySelector('.pf-chatbot__header');
492+
if (!headerElement) return;
493+
const computedBg = window.getComputedStyle(headerElement).backgroundColor;
494+
if (computedBg) {
495+
setChatHeaderBgColor(computedBg);
496+
}
497+
}, [displayMode, isMcpSettingsOpen]);
498+
435499
const {
436500
isPinningChatsEnabled,
437501
pinnedChats,
@@ -577,6 +641,7 @@ export const LightspeedChat = ({
577641

578642
const onNewChat = useCallback(() => {
579643
(async () => {
644+
setIsMcpSettingsOpen(false);
580645
if (conversationId !== TEMP_CONVERSATION_ID) {
581646
setMessages([]);
582647
setFileContents([]);
@@ -768,6 +833,7 @@ export const LightspeedChat = ({
768833

769834
const onSelectActiveItem = useCallback(
770835
(_: MouseEvent | undefined, selectedItem: string | number | undefined) => {
836+
setIsMcpSettingsOpen(false);
771837
setNewChatCreated(false);
772838
const newConvId = String(selectedItem);
773839
setConversationId((c_id: string) => {
@@ -789,6 +855,7 @@ export const LightspeedChat = ({
789855
setDraftMessage,
790856
scrollToBottomRef,
791857
setCurrentConversationId,
858+
setIsMcpSettingsOpen,
792859
],
793860
);
794861

@@ -977,6 +1044,109 @@ export const LightspeedChat = ({
9771044
});
9781045
};
9791046

1047+
const chatMainContent = (
1048+
<>
1049+
<ChatbotContent className={classes.chatbotContent}>
1050+
<div ref={contentScrollRef} className={classes.chatbotContentScroll}>
1051+
{welcomePrompts.length > 0 && (
1052+
<div className={classes.chatbotContentSpacer} aria-hidden />
1053+
)}
1054+
<LightspeedChatBox
1055+
userName={userName}
1056+
messages={messages}
1057+
profileLoading={profileLoading}
1058+
announcement={announcement}
1059+
ref={scrollToBottomRef}
1060+
welcomePrompts={welcomePrompts}
1061+
conversationId={conversationId}
1062+
isStreaming={isSendButtonDisabled}
1063+
topicRestrictionEnabled={topicRestrictionEnabled}
1064+
displayMode={displayMode}
1065+
/>
1066+
{welcomePrompts.length > 0 && (
1067+
<div
1068+
ref={bottomSentinelRef}
1069+
aria-hidden
1070+
style={{ height: 0, flexShrink: 0 }}
1071+
/>
1072+
)}
1073+
</div>
1074+
</ChatbotContent>
1075+
<ChatbotFooter className={classes.footer}>
1076+
<FilePreview />
1077+
<MessageBar
1078+
onSendMessage={sendMessage}
1079+
isSendButtonDisabled={isSendButtonDisabled}
1080+
hasAttachButton
1081+
handleAttach={handleAttach}
1082+
hasMicrophoneButton
1083+
value={draftMessage}
1084+
onChange={handleDraftMessage}
1085+
buttonProps={{
1086+
attach: {
1087+
inputTestId: 'attachment-input',
1088+
tooltipContent: t('tooltip.attach'),
1089+
},
1090+
microphone: {
1091+
tooltipContent: {
1092+
active: t('tooltip.microphone.active'),
1093+
inactive: t('tooltip.microphone.inactive'),
1094+
},
1095+
},
1096+
send: {
1097+
tooltipContent: t('tooltip.send'),
1098+
},
1099+
}}
1100+
allowedFileTypes={supportedFileTypes}
1101+
onAttachRejected={onAttachRejected}
1102+
placeholder={t('chatbox.message.placeholder')}
1103+
/>
1104+
<ChatbotFootnote {...getFootnoteProps(t)} />
1105+
</ChatbotFooter>
1106+
</>
1107+
);
1108+
1109+
let mainPanelContent = <>{chatMainContent}</>;
1110+
1111+
if (isMcpSettingsOpen && isFullscreenMode) {
1112+
mainPanelContent = (
1113+
<div className={classes.mcpFullscreenLayout}>
1114+
<div className={classes.mcpChatPane}>{chatMainContent}</div>
1115+
<div className={classes.mcpSettingsPane}>
1116+
<McpServersSettings
1117+
onClose={() => setIsMcpSettingsOpen(false)}
1118+
backgroundColor={chatHeaderBgColor}
1119+
/>
1120+
</div>
1121+
</div>
1122+
);
1123+
} else if (isMcpSettingsOpen) {
1124+
mainPanelContent = (
1125+
<Settings
1126+
className={classes.settingsFlat}
1127+
fields={[
1128+
{
1129+
id: 'mcp-servers-settings',
1130+
label: '',
1131+
field: (
1132+
<McpServersSettings
1133+
onClose={() => setIsMcpSettingsOpen(false)}
1134+
backgroundColor={chatHeaderBgColor}
1135+
/>
1136+
),
1137+
},
1138+
]}
1139+
/>
1140+
);
1141+
}
1142+
1143+
let drawerPanelStyle: { [key: string]: string | number } | undefined;
1144+
if (!isFullscreenMode) {
1145+
drawerPanelStyle = { zIndex: 1300 };
1146+
} else if (isMcpSettingsOpen) {
1147+
drawerPanelStyle = { width: 320, minWidth: 320, maxWidth: 320 };
1148+
}
1149+
9801150
return (
9811151
<>
9821152
{notebookAlerts.length > 0 && (
@@ -1066,6 +1236,7 @@ export const LightspeedChat = ({
10661236
<LightspeedChatBoxHeader
10671237
selectedModel={selectedModel}
10681238
handleSelectedModel={item => {
1239+
setIsMcpSettingsOpen(false);
10691240
onNewChat();
10701241
handleSelectedModel(item);
10711242
}}
@@ -1075,6 +1246,7 @@ export const LightspeedChat = ({
10751246
setDisplayMode={setDisplayMode}
10761247
displayMode={displayMode}
10771248
onPinnedChatsToggle={handlePinningChatsToggle}
1249+
onMcpSettingsClick={() => setIsMcpSettingsOpen(true)}
10781250
/>
10791251
</ChatbotHeader>
10801252
{isFullscreenMode && (
@@ -1096,7 +1268,7 @@ export const LightspeedChat = ({
10961268
drawerPanelContentProps={{
10971269
isResizable: isFullscreenMode,
10981270
hasNoBorder: !isFullscreenMode,
1099-
style: isFullscreenMode ? undefined : { zIndex: 1300 },
1271+
style: drawerPanelStyle,
11001272
}}
11011273
reverseButtonOrder
11021274
displayMode={ChatbotDisplayMode.embedded}
@@ -1157,69 +1329,8 @@ export const LightspeedChat = ({
11571329
</ChatbotAlert>
11581330
</div>
11591331
)}
1160-
<ChatbotContent className={classes.chatbotContent}>
1161-
<div
1162-
ref={contentScrollRef}
1163-
className={classes.chatbotContentScroll}
1164-
>
1165-
{welcomePrompts.length > 0 && (
1166-
<div
1167-
className={classes.chatbotContentSpacer}
1168-
aria-hidden
1169-
/>
1170-
)}
1171-
<LightspeedChatBox
1172-
userName={userName}
1173-
messages={messages}
1174-
profileLoading={profileLoading}
1175-
announcement={announcement}
1176-
ref={scrollToBottomRef}
1177-
welcomePrompts={welcomePrompts}
1178-
conversationId={conversationId}
1179-
isStreaming={isSendButtonDisabled}
1180-
topicRestrictionEnabled={topicRestrictionEnabled}
1181-
displayMode={displayMode}
1182-
/>
1183-
{welcomePrompts.length > 0 && (
1184-
<div
1185-
ref={bottomSentinelRef}
1186-
aria-hidden
1187-
style={{ height: 0, flexShrink: 0 }}
1188-
/>
1189-
)}
1190-
</div>
1191-
</ChatbotContent>
1192-
<ChatbotFooter className={classes.footer}>
1193-
<FilePreview />
1194-
<MessageBar
1195-
onSendMessage={sendMessage}
1196-
isSendButtonDisabled={isSendButtonDisabled}
1197-
hasAttachButton
1198-
handleAttach={handleAttach}
1199-
hasMicrophoneButton
1200-
value={draftMessage}
1201-
onChange={handleDraftMessage}
1202-
buttonProps={{
1203-
attach: {
1204-
inputTestId: 'attachment-input',
1205-
tooltipContent: t('tooltip.attach'),
1206-
},
1207-
microphone: {
1208-
tooltipContent: {
1209-
active: t('tooltip.microphone.active'),
1210-
inactive: t('tooltip.microphone.inactive'),
1211-
},
1212-
},
1213-
send: {
1214-
tooltipContent: t('tooltip.send'),
1215-
},
1216-
}}
1217-
allowedFileTypes={supportedFileTypes}
1218-
onAttachRejected={onAttachRejected}
1219-
placeholder={t('chatbox.message.placeholder')}
1220-
/>
1221-
<ChatbotFootnote {...getFootnoteProps(t)} />
1222-
</ChatbotFooter>
1332+
1333+
{mainPanelContent}
12231334
</FileDropZone>
12241335
}
12251336
/>

workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBoxHeader.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { createStyles, makeStyles } from '@material-ui/core';
2020
import ToggleOffOutlinedIcon from '@mui/icons-material/ToggleOffOutlined';
2121
import ToggleOnOutlinedIcon from '@mui/icons-material/ToggleOnOutlined';
2222
import Divider from '@mui/material/Divider';
23+
import SvgIcon from '@mui/material/SvgIcon';
2324
import {
2425
ChatbotDisplayMode,
2526
ChatbotHeaderActions,
@@ -48,6 +49,7 @@ type LightspeedChatBoxHeaderProps = {
4849
models: { label: string; value: string; provider: string }[];
4950
isPinningChatsEnabled: boolean;
5051
onPinnedChatsToggle: (state: boolean) => void;
52+
onMcpSettingsClick: () => void;
5153
isModelSelectorDisabled?: boolean;
5254
setDisplayMode: (mode: ChatbotDisplayMode) => void;
5355
};
@@ -81,6 +83,7 @@ export const LightspeedChatBoxHeader = ({
8183
models,
8284
isPinningChatsEnabled,
8385
onPinnedChatsToggle,
86+
onMcpSettingsClick,
8487
isModelSelectorDisabled = false,
8588
setDisplayMode,
8689
}: LightspeedChatBoxHeaderProps) => {
@@ -230,6 +233,33 @@ export const LightspeedChatBoxHeader = ({
230233
{t('settings.pinned.enable')}
231234
</DropdownItem>
232235
)}
236+
<DropdownItem
237+
value="mcpSettings"
238+
key="mcpSettings"
239+
icon={
240+
<SvgIcon
241+
sx={{ marginTop: '8px' }}
242+
viewBox="0 0 12 12"
243+
fontSize="small"
244+
>
245+
<path
246+
fillRule="evenodd"
247+
clipRule="evenodd"
248+
d="M7.84399 1.17149C7.6024 0.936371 7.2786 0.804813 6.94149 0.804813C6.60437 0.804813 6.28057 0.936371 6.03899 1.17149L1.22599 5.89149C1.14544 5.96977 1.03755 6.01357 0.925236 6.01357C0.812918 6.01357 0.705027 5.96977 0.624485 5.89149C0.585054 5.85314 0.55371 5.80728 0.532308 5.75661C0.510905 5.70594 0.499878 5.65149 0.499878 5.59649C0.499878 5.54149 0.510905 5.48704 0.532308 5.43637C0.55371 5.3857 0.585054 5.33984 0.624485 5.30149L5.43749 0.581489C5.84009 0.189686 6.3797 -0.029541 6.94149 -0.029541C7.50327 -0.029541 8.04288 0.189686 8.44549 0.581489C8.67844 0.808041 8.85444 1.08654 8.95908 1.39418C9.06371 1.70183 9.09401 2.02988 9.04749 2.35149C9.37336 2.30514 9.70553 2.33423 10.0184 2.43653C10.3312 2.53882 10.6164 2.71158 10.852 2.94149L10.877 2.96649C11.0741 3.15823 11.2309 3.38755 11.3379 3.6409C11.4449 3.89424 11.5 4.16647 11.5 4.44149C11.5 4.71651 11.4449 4.98874 11.3379 5.24208C11.2309 5.49543 11.0741 5.72475 10.877 5.91649L6.52399 10.185C6.51085 10.1978 6.5004 10.213 6.49327 10.2299C6.48614 10.2468 6.48246 10.2649 6.48246 10.2832C6.48246 10.3016 6.48614 10.3197 6.49327 10.3366C6.5004 10.3534 6.51085 10.3687 6.52399 10.3815L7.41799 11.2585C7.45742 11.2968 7.48876 11.3427 7.51016 11.3934C7.53157 11.444 7.54259 11.4985 7.54259 11.5535C7.54259 11.6085 7.53157 11.6629 7.51016 11.7136C7.48876 11.7643 7.45742 11.8101 7.41799 11.8485C7.33744 11.9268 7.22955 11.9706 7.11724 11.9706C7.00492 11.9706 6.89703 11.9268 6.81649 11.8485L5.92249 10.972C5.83041 10.8825 5.75721 10.7755 5.70723 10.6572C5.65724 10.539 5.63149 10.4119 5.63149 10.2835C5.63149 10.1551 5.65724 10.028 5.70723 9.90975C5.75721 9.79149 5.83041 9.68446 5.92249 9.59499L10.2755 5.32599C10.3937 5.21091 10.4877 5.07331 10.5519 4.9213C10.616 4.7693 10.6491 4.60598 10.6491 4.44099C10.6491 4.276 10.616 4.11268 10.5519 3.96068C10.4877 3.80867 10.3937 3.67107 10.2755 3.55599L10.2505 3.53149C10.0092 3.29662 9.68579 3.16507 9.34904 3.16479C9.01229 3.16451 8.6887 3.29552 8.44699 3.52999L4.86099 7.04699L4.85999 7.04799L4.81099 7.09649C4.73042 7.17492 4.62242 7.21881 4.50999 7.21881C4.39755 7.21881 4.28955 7.17492 4.20899 7.09649C4.16955 7.05814 4.13821 7.01228 4.11681 6.96161C4.09541 6.91094 4.08438 6.85649 4.08438 6.80149C4.08438 6.74649 4.09541 6.69204 4.11681 6.64137C4.13821 6.5907 4.16955 6.54484 4.20899 6.50649L7.84549 2.93999C7.96339 2.82483 8.05706 2.68724 8.12096 2.53532C8.18486 2.38341 8.21771 2.22023 8.21757 2.05542C8.21743 1.89061 8.18431 1.7275 8.12014 1.57569C8.05598 1.42388 7.96209 1.28645 7.84399 1.17149Z"
249+
fill="currentColor"
250+
/>
251+
<path
252+
fillRule="evenodd"
253+
clipRule="evenodd"
254+
d="M7.24248 2.35148C7.28192 2.31313 7.31326 2.26727 7.33466 2.2166C7.35606 2.16593 7.36709 2.11149 7.36709 2.05648C7.36709 2.00148 7.35606 1.94703 7.33466 1.89636C7.31326 1.84569 7.28192 1.79983 7.24248 1.76148C7.16192 1.68305 7.05392 1.63916 6.94148 1.63916C6.82905 1.63916 6.72105 1.68305 6.64048 1.76148L3.08098 5.25248C2.88383 5.44422 2.72711 5.67354 2.6201 5.92689C2.51308 6.18023 2.45795 6.45246 2.45795 6.72748C2.45795 7.0025 2.51308 7.27473 2.6201 7.52808C2.72711 7.78142 2.88383 8.01074 3.08098 8.20248C3.48365 8.59417 4.02324 8.81331 4.58498 8.81331C5.14673 8.81331 5.68632 8.59417 6.08898 8.20248L9.64898 4.71148C9.68842 4.67313 9.71976 4.62727 9.74116 4.5766C9.76256 4.52593 9.77359 4.47149 9.77359 4.41648C9.77359 4.36148 9.76256 4.30703 9.74116 4.25636C9.71976 4.20569 9.68842 4.15983 9.64898 4.12148C9.56842 4.04305 9.46042 3.99916 9.34798 3.99916C9.23555 3.99916 9.12755 4.04305 9.04698 4.12148L5.48748 7.61248C5.2459 7.8476 4.9221 7.97916 4.58498 7.97916C4.24787 7.97916 3.92407 7.8476 3.68248 7.61248C3.56425 7.4974 3.47028 7.3598 3.40611 7.2078C3.34194 7.05579 3.30888 6.89247 3.30888 6.72748C3.30888 6.56249 3.34194 6.39917 3.40611 6.24717C3.47028 6.09517 3.56425 5.95756 3.68248 5.84248L7.24248 2.35148Z"
255+
fill="currentColor"
256+
/>
257+
</SvgIcon>
258+
}
259+
onClick={onMcpSettingsClick}
260+
>
261+
MCP settings
262+
</DropdownItem>
233263
</DropdownList>
234264
</DropdownGroup>
235265
</ChatbotHeaderOptionsDropdown>

0 commit comments

Comments
 (0)