diff --git a/packages/jupyter-ai/src/components/settings/model-id-input.tsx b/packages/jupyter-ai/src/components/settings/model-id-input.tsx index 888788d0b..0fc660d5c 100644 --- a/packages/jupyter-ai/src/components/settings/model-id-input.tsx +++ b/packages/jupyter-ai/src/components/settings/model-id-input.tsx @@ -1,9 +1,38 @@ import React, { useState, useEffect } from 'react'; -import { Autocomplete, TextField, Button, Box } from '@mui/material'; +import { + TextField, + Button, + Box, + Typography, + Menu, + MenuItem +} from '@mui/material'; import { AiService } from '../../handler'; import { useStackingAlert } from '../mui-extras/stacking-alert'; import Save from '@mui/icons-material/Save'; +/** + * Highlights matched substrings in a given text by wrapping them in bold tags. + */ +const highlightMatches = (text: string, inputValue: string) => { + if (!inputValue) return text; + + const parts = text.split(new RegExp(`(${inputValue})`, 'gi')); + return ( + + {parts.map((part, index) => + part.toLowerCase() === inputValue.toLowerCase() ? ( + + {part} + + ) : ( + part + ) + )} + + ); +}; + export type ModelIdInputProps = { /** * The label of the model ID input field. @@ -51,8 +80,26 @@ export function ModelIdInput(props: ModelIdInputProps): JSX.Element { const [updating, setUpdating] = useState(false); const [input, setInput] = useState(''); + const [anchorEl, setAnchorEl] = useState(null); const alert = useStackingAlert(); + const handleMenuOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleMenuItemClick = (value: string) => { + setInput(value); + handleMenuClose(); + }; + + const filteredModels = models.filter(model => + model.toLowerCase().includes(input.toLowerCase()) + ); + /** * Effect: Fetch list of models and current model on initial render, based on * the modality. @@ -128,29 +175,95 @@ export function ModelIdInput(props: ModelIdInputProps): JSX.Element { return ( - { - // This condition prevents whitespace from being inserted in the model - // ID by accident. - if (newValue !== null && !newValue.includes(' ')) { + onChange={e => { + const newValue = e.target.value; + if (!newValue.includes(' ')) { setInput(newValue); } }} - renderInput={params => ( - - )} + InputProps={{ + endAdornment: + // input && filteredModels.length > 0 ? ( + input ? ( + + + + + + {filteredModels.length > 0 ? ( + + Click to see {filteredModels.length} match + {filteredModels.length !== 1 ? 'es' : ''} + + ) : ( + + No matches + + )} + + + ) : null + }} /> + + {filteredModels.map((model, index) => ( + handleMenuItemClick(model)}> + {highlightMatches(model, input)} + + ))} +