Skip to content

Commit e838cc8

Browse files
author
Bogdan Tsechoev
committedMar 17, 2025·
Merge branch 'llm_model_selector' into 'master'
feat (ui): Add LLM model selector, remove model selection from chat settings & small UI improvements on small screens See merge request postgres-ai/database-lab!980
2 parents 9fb8476 + 4a7e6b0 commit e838cc8

File tree

5 files changed

+165
-103
lines changed

5 files changed

+165
-103
lines changed
 

‎ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
3131
[theme.breakpoints.down('sm')]: {
3232
height: '100vh!important',
3333
marginTop: '0!important',
34-
width: 320,
34+
width: 'min(100%, 360px)',
3535
zIndex: 9999
3636
},
3737
'& > ul': {
@@ -57,6 +57,11 @@ const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
5757
background: 'white',
5858
[theme.breakpoints.down('sm')]: {
5959
padding: 0
60+
},
61+
"@media (max-width: 960px)": {
62+
"& .MuiFormControl-root": {
63+
display: "none" // Hide model selector in chats list
64+
}
6065
}
6166
},
6267
listItemLink: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react';
2+
import { FormControl, Select, MenuItem, Typography, InputLabel, useMediaQuery } from "@mui/material";
3+
import { SelectChangeEvent } from "@mui/material/Select";
4+
5+
import { useAiBot } from "../hooks";
6+
7+
export const ModelSelector = () => {
8+
const { aiModel, aiModels, setAiModel } = useAiBot();
9+
const isSmallScreen = useMediaQuery("(max-width: 960px)");
10+
11+
const handleChange = (event: SelectChangeEvent<string | null>) => {
12+
const [vendor, name] = (event.target.value as string).split("/");
13+
const model = aiModels?.find(
14+
(model) => model.vendor === vendor && model.name === name
15+
);
16+
if (model) setAiModel(model);
17+
};
18+
19+
const truncateText = (text: string, maxLength: number) => {
20+
return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
21+
};
22+
23+
return (
24+
<FormControl
25+
variant="outlined"
26+
size="small"
27+
sx={{ minWidth: isSmallScreen ? 120 : 200 }}
28+
>
29+
<Select
30+
labelId="model-select-label"
31+
id="model-select"
32+
value={aiModel ? `${aiModel.vendor}/${aiModel.name}` : ""}
33+
onChange={handleChange}
34+
displayEmpty
35+
inputProps={{
36+
"aria-describedby": "Select the AI model to be used for generating responses. Different models may vary in performance. Choose the one that best suits your needs.",
37+
sx: {
38+
height: "32px",
39+
fontSize: "0.875rem",
40+
padding: isSmallScreen ? "8px 24px 8px 8px!important" : "8px 14px",
41+
},
42+
}}
43+
sx={{ height: "32px" }}
44+
renderValue={(selected) => {
45+
if (!selected) return "Select Model";
46+
const [vendor, name] = selected.split("/");
47+
return truncateText(`${vendor}/${name}`, isSmallScreen ? 20 : 30);
48+
}}
49+
>
50+
{aiModels &&
51+
aiModels.map((model) => (
52+
<MenuItem
53+
key={`${model.vendor}/${model.name}`}
54+
value={`${model.vendor}/${model.name}`}
55+
title={`${model.vendor}/${model.name}`}
56+
sx={{
57+
display: "flex",
58+
flexDirection: "column",
59+
alignItems: "flex-start"
60+
}}
61+
>
62+
<span>{truncateText(`${model.vendor}/${model.name}`, isSmallScreen ? 33 : 40)}</span>
63+
{model.comment && (
64+
<Typography
65+
variant="body2"
66+
color="textSecondary"
67+
sx={{
68+
fontSize: "0.7rem",
69+
color: "rgba(0, 0, 0, 0.6)",
70+
}}
71+
aria-hidden="true"
72+
>
73+
{model.comment}
74+
</Typography>
75+
)}
76+
</MenuItem>
77+
))}
78+
</Select>
79+
</FormControl>
80+
);
81+
};

‎ui/packages/platform/src/pages/Bot/SettingsDialog/SettingsDialog.tsx

+42-48
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
makeStyles,
1616
Radio,
1717
RadioGroup,
18-
TextField,
18+
TextField, Theme,
1919
Typography,
2020
} from '@material-ui/core'
2121
import MuiDialogTitle from '@material-ui/core/DialogTitle'
@@ -30,6 +30,8 @@ import { AiModel } from "../../../types/api/entities/bot";
3030
import settings from "../../../utils/settings";
3131
import { Link } from "@postgres.ai/shared/components/Link2";
3232
import { ExternalIcon } from "@postgres.ai/shared/icons/External";
33+
import Divider from "@material-ui/core/Divider";
34+
import cn from "classnames";
3335

3436
type DialogTitleProps = {
3537
id: string
@@ -123,35 +125,30 @@ const DialogActions = (props: { children: React.ReactNode }) => {
123125
)
124126
}
125127

126-
const useDialogStyles = makeStyles(
127-
() => ({
128+
const useDialogStyles = makeStyles<Theme>(
129+
(theme) => ({
128130
textField: {
129131
...styles.inputField,
130132
marginTop: '0px',
131133
width: 480,
134+
[theme.breakpoints.down('sm')]: {
135+
136+
}
132137
},
133138
copyButton: {
134139
marginTop: '-3px',
135140
fontSize: '20px',
136141
},
137-
dialog: {},
138-
remark: {
139-
fontSize: 12,
140-
lineHeight: '12px',
141-
142-
paddingLeft: 20,
143-
paddingBottom: 5,
144-
},
145-
remarkIcon: {
146-
display: 'block',
147-
height: '20px',
148-
width: '22px',
149-
float: 'left',
150-
paddingTop: '5px',
151-
},
152142
urlContainer: {
153-
marginTop: 10,
154-
paddingLeft: 22,
143+
marginTop: 8,
144+
paddingLeft: 20,
145+
[theme.breakpoints.down('sm')]: {
146+
padding: 0,
147+
width: '100%',
148+
'& .MuiTextField-root': {
149+
maxWidth: 'calc(100% - 36px)'
150+
}
151+
},
155152
},
156153
radioGroup: {
157154
fontSize: 12,
@@ -170,16 +167,34 @@ const useDialogStyles = makeStyles(
170167
marginBottom: 0
171168
}
172169
},
170+
unlockNoteDemo: {
171+
paddingLeft: 20
172+
},
173173
formControlLabel: {
174174
'& .Mui-disabled > *, & .Mui-disabled': {
175175
color: 'rgba(0, 0, 0, 0.6)'
176+
},
177+
[theme.breakpoints.down('sm')]: {
178+
marginRight: 0,
179+
alignItems: 'flex-start',
180+
'&:first-child': {
181+
marginTop: 6
182+
}
183+
},
184+
},
185+
formControlLabelRadio: {
186+
[theme.breakpoints.down('sm')]: {
187+
padding: '4px 9px'
176188
}
177189
},
178190
externalIcon: {
179191
width: 14,
180192
height: 14,
181193
marginLeft: 4,
182194
transform: 'translateY(2px)',
195+
},
196+
divider: {
197+
margin: '12px 0'
183198
}
184199
}),
185200
{ index: 1 },
@@ -295,8 +310,8 @@ export const SettingsDialog = (props: PublicChatDialogProps) => {
295310
<>
296311
<FormLabel component="legend">Visibility</FormLabel>
297312
<RadioGroup
298-
aria-label="shareUrl"
299-
name="shareUrl"
313+
aria-label="Thread visibility"
314+
name="threadVisibility"
300315
value={visibility}
301316
onChange={(event) => {
302317
setVisibility(event.target.value as Visibility)
@@ -306,20 +321,22 @@ export const SettingsDialog = (props: PublicChatDialogProps) => {
306321
<FormControlLabel
307322
value={Visibility.PUBLIC}
308323
className={classes.formControlLabel}
309-
control={<Radio />}
324+
control={<Radio className={classes.formControlLabelRadio} />}
310325
label={<><b>Public:</b> anyone can view chats, but only team members can respond</>}
326+
aria-label="Public: anyone can view chats, but only team members can respond"
311327
/>
312328
{visibility === Visibility.PUBLIC && threadId && (
313329
<div className={classes.urlContainer}>{urlField}</div>
314330
)}
315331
<FormControlLabel
316332
value={Visibility.PRIVATE}
317333
className={classes.formControlLabel}
318-
control={<Radio />}
334+
control={<Radio className={classes.formControlLabelRadio} />}
319335
label={<><b>Private:</b> chats are visible only to members of your organization</>}
336+
aria-label="Private: chats are visible only to members of your organization"
320337
disabled={Boolean(isDemoOrg) || !isSubscriber}
321338
/>
322-
{Boolean(isDemoOrg) && <Typography className={classes.remark}>Private chats are not allowed in "Demo"</Typography>}
339+
{Boolean(isDemoOrg) && <Typography className={cn(classes.unlockNote, classes.unlockNoteDemo)}>Private chats are not allowed in "Demo"</Typography>}
323340
{!Boolean(isDemoOrg) && !isSubscriber && <Typography variant="body2" className={classes.unlockNote}>
324341
Unlock private conversations by either:
325342
<ol>
@@ -339,29 +356,6 @@ export const SettingsDialog = (props: PublicChatDialogProps) => {
339356
</Typography>}
340357
</RadioGroup>
341358
</>
342-
{aiModels && <>
343-
<FormLabel component="legend">Model</FormLabel>
344-
<RadioGroup
345-
aria-label="model"
346-
name="model"
347-
value={`${model?.vendor}/${model?.name}`}
348-
onChange={(event) => {
349-
const selectedModel = aiModels?.find((model) => `${model.vendor}/${model.name}` === event.target.value)
350-
setModel(selectedModel!)
351-
}}
352-
className={classes.radioGroup}
353-
>
354-
{aiModels.map((model) =>
355-
<FormControlLabel
356-
key={`${model.vendor}/${model.name}`}
357-
value={`${model.vendor}/${model.name}`}
358-
control={<Radio />}
359-
label={`${model.name} ${model.comment ? model.comment : ''}`}
360-
/>
361-
)
362-
}
363-
</RadioGroup>
364-
</>}
365359
</DialogContent>
366360

367361
<DialogActions>

‎ui/packages/platform/src/pages/Bot/SettingsPanel/SettingsPanel.tsx

+25-49
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { theme } from "@postgres.ai/shared/styles/theme";
77
import { permalinkLinkBuilder } from "../utils";
88
import { useAiBot } from "../hooks";
99
import DeveloperModeIcon from "@material-ui/icons/DeveloperMode";
10+
import { ModelSelector } from "../ModelSelector/ModelSelector";
11+
import { Skeleton } from "@mui/material";
1012

1113
export type SettingsPanelProps = {
1214
onSettingsClick: () => void;
@@ -31,25 +33,14 @@ const useStyles = makeStyles((theme) => ({
3133
}
3234
},
3335
labelVisibility: {
34-
marginLeft: '0.5rem',
36+
marginRight: '0.5rem',
3537
[theme.breakpoints.down('sm')]: {
36-
marginLeft: '0.25rem'
38+
marginRight: '0.25rem'
3739
},
3840
'&:hover': {
3941
backgroundColor: colors.secondary1.main
4042
}
4143
},
42-
labelModel: {
43-
background: colors.secondary1.main,
44-
},
45-
labelModelInvalid: {
46-
background: colors.state.error,
47-
border: "none",
48-
cursor: 'pointer',
49-
'&:hover': {
50-
backgroundColor: colors.primary.dark
51-
}
52-
},
5344
labelPrivate: {
5445
backgroundColor: colors.pgaiDarkGray,
5546
},
@@ -74,48 +65,33 @@ const useStyles = makeStyles((theme) => ({
7465

7566
export const SettingsPanel = (props: SettingsPanelProps) => {
7667
const { onSettingsClick, onConsoleClick } = props;
68+
const { loading } = useAiBot()
7769
const classes = useStyles();
7870
const matches = useMediaQuery(theme.breakpoints.down('sm'));
79-
const { messages, chatVisibility, aiModel, aiModelsLoading } = useAiBot();
71+
const { messages, chatVisibility, aiModelsLoading } = useAiBot();
8072
const permalinkId = useMemo(() => messages?.[0]?.id, [messages]);
8173

82-
let modelLabel;
83-
84-
if (aiModel) {
85-
modelLabel = (
86-
<span
87-
className={cn(classes.label, classes.labelModel)}
88-
>
89-
{aiModel.name}
90-
</span>
91-
)
92-
} else {
93-
modelLabel = (
94-
<button
95-
className={cn(classes.label, classes.labelModelInvalid)}
96-
onClick={onSettingsClick}
97-
>
98-
Model not set
99-
</button>
100-
)
101-
}
102-
10374
return (
10475
<>
105-
{!aiModelsLoading && modelLabel}
106-
{permalinkId && <a
107-
href={permalinkId && chatVisibility === 'public' ? permalinkLinkBuilder(permalinkId) : ''}
108-
className={cn(classes.label, classes.labelVisibility,
109-
{
110-
[classes.labelPrivate]: chatVisibility === 'private',
111-
[classes.disabled]: chatVisibility === 'private' || !permalinkId
112-
}
113-
)}
114-
target="_blank"
115-
aria-disabled={chatVisibility === 'private' || !permalinkId}
116-
>
117-
<span>{chatVisibility}</span> thread
118-
</a>}
76+
{permalinkId && <>
77+
{loading
78+
? <Skeleton variant="rectangular" className={cn(classes.label, classes.labelVisibility)} width={64} height={16} />
79+
: <a
80+
href={permalinkId && chatVisibility === 'public' ? permalinkLinkBuilder(permalinkId) : ''}
81+
className={cn(classes.label, classes.labelVisibility,
82+
{
83+
[classes.labelPrivate]: chatVisibility === 'private',
84+
[classes.disabled]: chatVisibility === 'private' || !permalinkId
85+
}
86+
)}
87+
target="_blank"
88+
aria-disabled={chatVisibility === 'private' || !permalinkId}
89+
>
90+
<span>{chatVisibility}</span> thread
91+
</a>
92+
}
93+
</>}
94+
{!aiModelsLoading && <ModelSelector />}
11995
<Button
12096
variant="outlined"
12197
onClick={onSettingsClick}

0 commit comments

Comments
 (0)
Please sign in to comment.