Skip to content

Commit 28836da

Browse files
Add collapse control to settings panel and relocate model selection (opendatahub-io#6158)
* Added open/close drawer controls Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * Added more styling Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * Tests cleanup Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * Feedback Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> --------- Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>
1 parent fee0fe2 commit 28836da

File tree

6 files changed

+53
-65
lines changed

6 files changed

+53
-65
lines changed

packages/gen-ai/frontend/src/__tests__/cypress/cypress/pages/chatbotPage.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,13 @@ class ChatbotPage {
142142
}
143143

144144
// Model Selection
145+
// Model dropdown is now in the toolbar (moved from Model tab)
145146
findModelDropdown(): Cypress.Chainable<JQuery<HTMLElement>> {
146-
return cy.findByText(/Model/i).parent().find('button') as unknown as Cypress.Chainable<
147-
JQuery<HTMLElement>
148-
>;
147+
return cy.findByTestId('model-selector-toggle');
149148
}
150149

151150
findModelSelectorButton(): Cypress.Chainable<JQuery<HTMLElement>> {
152-
return cy.findByRole('button', { name: /Llama|Select a model/i });
151+
return cy.findByTestId('model-selector-toggle');
153152
}
154153

155154
verifyModelSelected(): void {

packages/gen-ai/frontend/src/app/Chatbot/ChatbotPlayground.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import * as React from 'react';
2-
import { Divider, Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core';
2+
import { Button, Divider, Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core';
33
import {
44
Chatbot,
55
ChatbotContent,
66
ChatbotDisplayMode,
77
ChatbotFooter,
88
ChatbotFootnote,
9+
ChatbotHeaderMain,
910
MessageBar,
1011
} from '@patternfly/chatbot';
12+
import { CogIcon } from '@patternfly/react-icons';
1113
import { useLocation } from 'react-router-dom';
1214
import { useUserContext } from '~/app/context/UserContext';
1315
import { ChatbotContext } from '~/app/context/ChatbotContext';
@@ -27,6 +29,7 @@ import { ChatbotConfigInstance } from './ChatbotConfigInstance';
2729
import useFileManagement from './hooks/useFileManagement';
2830
import useDarkMode from './hooks/useDarkMode';
2931
import { ChatbotSettingsPanel } from './components/ChatbotSettingsPanel';
32+
import ModelDetailsDropdown from './components/ModelDetailsDropdown';
3033
import {
3134
useChatbotConfigStore,
3235
selectSelectedModel,
@@ -82,6 +85,7 @@ const ChatbotPlayground: React.FC<ChatbotPlaygroundProps> = ({
8285
const isDarkMode = useDarkMode();
8386

8487
const location = useLocation();
88+
const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(true);
8589
const selectedAAModel = location.state?.model;
8690
const mcpServersFromRoute = React.useMemo(() => {
8791
const servers = location.state?.mcpServers;
@@ -296,6 +300,9 @@ const ChatbotPlayground: React.FC<ChatbotPlaygroundProps> = ({
296300
checkMcpServerStatus={checkMcpServerStatus}
297301
guardrailModels={guardrailModelNames}
298302
guardrailModelsLoaded={guardrailModelsLoaded}
303+
onCloseClick={() => {
304+
setIsDrawerExpanded(false);
305+
}}
299306
/>
300307
))}
301308
</>
@@ -336,11 +343,37 @@ const ChatbotPlayground: React.FC<ChatbotPlaygroundProps> = ({
336343
setIsNewChatModalOpen(false);
337344
}}
338345
/>
339-
<Drawer isExpanded isInline position="left">
346+
<Drawer
347+
// onExpand={() => drawerRef.current && drawerRef.current.focus()}
348+
isExpanded={isDrawerExpanded}
349+
isInline
350+
position="left"
351+
>
340352
<Divider />
341353
<DrawerContent panelContent={settingsPanelContent}>
342354
<DrawerContentBody>
343355
<Chatbot displayMode={ChatbotDisplayMode.embedded} data-testid="chatbot">
356+
<div
357+
style={{
358+
backgroundColor: 'var(--pf-t--global--background--color--100)',
359+
paddingLeft: '1.5rem',
360+
}}
361+
>
362+
<ChatbotHeaderMain>
363+
<ModelDetailsDropdown
364+
selectedModel={primarySelectedModel || ''}
365+
onModelChange={setSelectedModel}
366+
style={{ maxWidth: '300px' }}
367+
/>
368+
<Button
369+
variant="plain"
370+
aria-label={isDrawerExpanded ? 'Close settings panel' : 'Open settings panel'}
371+
icon={<CogIcon />}
372+
onClick={() => setIsDrawerExpanded(true)}
373+
style={{ margin: '0.7rem 0 0 0.5rem' }}
374+
/>
375+
</ChatbotHeaderMain>
376+
</div>
344377
<ChatbotContent
345378
style={{
346379
backgroundColor: isDarkMode

packages/gen-ai/frontend/src/app/Chatbot/components/ChatbotSettingsPanel.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as React from 'react';
22
import {
3+
DrawerActions,
4+
DrawerCloseButton,
5+
DrawerHead,
36
DrawerPanelContent,
47
DrawerPanelBody,
58
Badge,
@@ -9,6 +12,7 @@ import {
912
Tabs,
1013
Tab,
1114
TabTitleText,
15+
Title,
1216
Tooltip,
1317
} from '@patternfly/react-core';
1418
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
@@ -17,7 +21,6 @@ import {
1721
selectSystemInstruction,
1822
selectTemperature,
1923
selectStreamingEnabled,
20-
selectSelectedModel,
2124
selectSelectedMcpServerIds,
2225
} from '~/app/Chatbot/store';
2326
import { UseSourceManagementReturn } from '~/app/Chatbot/hooks/useSourceManagement';
@@ -52,6 +55,7 @@ interface ChatbotSettingsPanelProps {
5255
// Guardrails props
5356
guardrailModels?: string[];
5457
guardrailModelsLoaded?: boolean;
58+
onCloseClick?: () => void;
5559
}
5660

5761
const SETTINGS_PANEL_WIDTH = 'chatbot-settings-panel-width';
@@ -71,6 +75,7 @@ const ChatbotSettingsPanel: React.FunctionComponent<ChatbotSettingsPanelProps> =
7175
checkMcpServerStatus,
7276
guardrailModels = [],
7377
guardrailModelsLoaded = false,
78+
onCloseClick,
7479
}) => {
7580
const [showMcpToolsWarning, setShowMcpToolsWarning] = React.useState(false);
7681
const [activeToolsCount, setActiveToolsCount] = React.useState(0);
@@ -81,13 +86,11 @@ const ChatbotSettingsPanel: React.FunctionComponent<ChatbotSettingsPanelProps> =
8186
const temperature = useChatbotConfigStore(selectTemperature(configId));
8287
const selectedMcpServerIds = useChatbotConfigStore(selectSelectedMcpServerIds(configId));
8388
const isStreamingEnabled = useChatbotConfigStore(selectStreamingEnabled(configId));
84-
const selectedModel = useChatbotConfigStore(selectSelectedModel(configId));
8589

8690
// Get updater functions from store
8791
const updateSystemInstruction = useChatbotConfigStore((state) => state.updateSystemInstruction);
8892
const updateTemperature = useChatbotConfigStore((state) => state.updateTemperature);
8993
const updateStreamingEnabled = useChatbotConfigStore((state) => state.updateStreamingEnabled);
90-
const updateSelectedModel = useChatbotConfigStore((state) => state.updateSelectedModel);
9194

9295
// Create callback handlers that include configId
9396
const handleSystemInstructionChange = React.useCallback(
@@ -104,13 +107,6 @@ const ChatbotSettingsPanel: React.FunctionComponent<ChatbotSettingsPanelProps> =
104107
[configId, updateTemperature],
105108
);
106109

107-
const handleModelChange = React.useCallback(
108-
(value: string) => {
109-
updateSelectedModel(configId, value);
110-
},
111-
[configId, updateSelectedModel],
112-
);
113-
114110
const handleStreamingToggle = React.useCallback(
115111
(enabled: boolean) => {
116112
updateStreamingEnabled(configId, enabled);
@@ -149,6 +145,12 @@ const ChatbotSettingsPanel: React.FunctionComponent<ChatbotSettingsPanelProps> =
149145
minSize="300px"
150146
onResize={handlePanelResize}
151147
>
148+
<DrawerHead>
149+
<Title headingLevel="h2">Configure</Title>
150+
<DrawerActions>
151+
<DrawerCloseButton onClick={() => onCloseClick?.()} aria-label="Close settings panel" />
152+
</DrawerActions>
153+
</DrawerHead>
152154
<DrawerPanelBody>
153155
<Tabs
154156
activeKey={activeTabKey}
@@ -163,8 +165,6 @@ const ChatbotSettingsPanel: React.FunctionComponent<ChatbotSettingsPanelProps> =
163165
data-testid="chatbot-settings-page-tab-model"
164166
>
165167
<ModelTabContent
166-
selectedModel={selectedModel}
167-
onModelChange={handleModelChange}
168168
temperature={temperature}
169169
onTemperatureChange={handleTemperatureChange}
170170
isStreamingEnabled={isStreamingEnabled}

packages/gen-ai/frontend/src/app/Chatbot/components/ModelDetailsDropdown.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import useFetchBFFConfig from '~/app/hooks/useFetchBFFConfig';
1717
interface ModelDetailsDropdownProps {
1818
selectedModel: string;
1919
onModelChange: (value: string) => void;
20+
style?: React.CSSProperties;
2021
}
2122

2223
const ModelDetailsDropdown: React.FunctionComponent<ModelDetailsDropdownProps> = ({
2324
selectedModel,
2425
onModelChange,
26+
style,
2527
}) => {
2628
const { models, aiModels, maasModels } = React.useContext(ChatbotContext);
2729
const { data: bffConfig } = useFetchBFFConfig();
@@ -53,7 +55,7 @@ const ModelDetailsDropdown: React.FunctionComponent<ModelDetailsDropdownProps> =
5355
<MenuToggle
5456
ref={toggleRef}
5557
isDisabled={models.length === 0}
56-
isFullWidth
58+
style={style}
5759
onClick={() => setIsOpen(!isOpen)}
5860
isExpanded={isOpen}
5961
data-testid="model-selector-toggle"

packages/gen-ai/frontend/src/app/Chatbot/components/settingsPanelTabs/ModelTabContent.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,23 @@ import * as React from 'react';
22
import { Form, FormGroup, Switch } from '@patternfly/react-core';
33
import { fireMiscTrackingEvent } from '@odh-dashboard/internal/concepts/analyticsTracking/segmentIOUtils';
44
import TabContentWrapper from '~/app/Chatbot/components/settingsPanelTabs/TabContentWrapper';
5-
import ModelDetailsDropdown from '~/app/Chatbot/components/ModelDetailsDropdown';
65
import ModelParameterFormGroup from '~/app/Chatbot/components/ModelParameterFormGroup';
76

87
interface ModelTabContentProps {
9-
selectedModel: string;
10-
onModelChange: (value: string) => void;
118
temperature: number;
129
onTemperatureChange: (value: number) => void;
1310
isStreamingEnabled: boolean;
1411
onStreamingToggle: (enabled: boolean) => void;
1512
}
1613

1714
const ModelTabContent: React.FunctionComponent<ModelTabContentProps> = ({
18-
selectedModel,
19-
onModelChange,
2015
temperature,
2116
onTemperatureChange,
2217
isStreamingEnabled,
2318
onStreamingToggle,
2419
}) => (
2520
<TabContentWrapper title="Model">
2621
<Form>
27-
<FormGroup fieldId="model-details">
28-
<ModelDetailsDropdown selectedModel={selectedModel} onModelChange={onModelChange} />
29-
</FormGroup>
30-
3122
<ModelParameterFormGroup
3223
fieldId="temperature"
3324
label="Temperature: 0 - 2"

packages/gen-ai/frontend/src/app/Chatbot/components/settingsPanelTabs/__tests__/ModelTabContent.spec.tsx

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,6 @@ jest.mock('@odh-dashboard/internal/concepts/analyticsTracking/segmentIOUtils', (
1313
fireMiscTrackingEvent: jest.fn(),
1414
}));
1515

16-
jest.mock('../../ModelDetailsDropdown', () => ({
17-
__esModule: true,
18-
default: ({
19-
selectedModel,
20-
onModelChange,
21-
}: {
22-
selectedModel: string;
23-
onModelChange: (value: string) => void;
24-
}) => (
25-
<div data-testid="model-details-dropdown">
26-
<span data-testid="selected-model">{selectedModel}</span>
27-
<button data-testid="change-model-button" onClick={() => onModelChange('new-model')}>
28-
Change Model
29-
</button>
30-
</div>
31-
),
32-
}));
33-
3416
jest.mock('../../ModelParameterFormGroup', () => ({
3517
__esModule: true,
3618
default: ({
@@ -56,8 +38,6 @@ const mockFireMiscTrackingEvent = jest.mocked(fireMiscTrackingEvent);
5638

5739
describe('ModelTabContent', () => {
5840
const defaultProps = {
59-
selectedModel: 'gpt-4',
60-
onModelChange: jest.fn(),
6141
temperature: 1.0,
6242
onTemperatureChange: jest.fn(),
6343
isStreamingEnabled: true,
@@ -74,13 +54,6 @@ describe('ModelTabContent', () => {
7454
expect(screen.getByRole('heading', { name: 'Model' })).toBeInTheDocument();
7555
});
7656

77-
it('renders ModelDetailsDropdown with correct props', () => {
78-
render(<ModelTabContent {...defaultProps} />);
79-
80-
expect(screen.getByTestId('model-details-dropdown')).toBeInTheDocument();
81-
expect(screen.getByTestId('selected-model')).toHaveTextContent('gpt-4');
82-
});
83-
8457
it('renders ModelParameterFormGroup with correct temperature props', () => {
8558
render(<ModelTabContent {...defaultProps} />);
8659

@@ -104,16 +77,6 @@ describe('ModelTabContent', () => {
10477
expect(streamingSwitch).not.toBeChecked();
10578
});
10679

107-
it('calls onModelChange when model is changed', async () => {
108-
const user = userEvent.setup();
109-
const mockOnModelChange = jest.fn();
110-
render(<ModelTabContent {...defaultProps} onModelChange={mockOnModelChange} />);
111-
112-
await user.click(screen.getByTestId('change-model-button'));
113-
114-
expect(mockOnModelChange).toHaveBeenCalledWith('new-model');
115-
});
116-
11780
it('calls onTemperatureChange and fires tracking event when temperature changes', async () => {
11881
const user = userEvent.setup();
11982
const mockOnTemperatureChange = jest.fn();

0 commit comments

Comments
 (0)