diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 26716150..13105e84 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -61,7 +61,14 @@ import Locale from "../locales"; import { IconButton } from "./button"; import styles from "./chat.module.scss"; -import { ListItem, Modal, showConfirm, showPrompt, showToast } from "./ui-lib"; +import { + ListItem, + Modal, + Selector, + showConfirm, + showPrompt, + showToast, +} from "./ui-lib"; import { useLocation, useNavigate } from "react-router-dom"; import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant"; import { Avatar } from "./emoji"; @@ -404,16 +411,11 @@ export function ChatActions(props: { // switch model const currentModel = chatStore.currentSession().mask.modelConfig.model; - function nextModel() { - const models = config.models.filter((m) => m.available).map((m) => m.name); - const modelIndex = models.indexOf(currentModel); - const nextIndex = (modelIndex + 1) % models.length; - const nextModel = models[nextIndex]; - chatStore.updateCurrentSession((session) => { - session.mask.modelConfig.model = nextModel as ModelType; - session.mask.syncGlobalConfig = false; - }); - } + const models = useMemo( + () => config.models.filter((m) => m.available).map((m) => m.name), + [config.models], + ); + const [showModelSelector, setShowModelSelector] = useState(false); return (
@@ -485,10 +487,28 @@ export function ChatActions(props: { /> setShowModelSelector(true)} text={currentModel} icon={} /> + + {showModelSelector && ( + ({ + title: m, + value: m, + }))} + onClose={() => setShowModelSelector(false)} + onSelection={(s) => { + if (s.length === 0) return; + chatStore.updateCurrentSession((session) => { + session.mask.modelConfig.model = s[0] as ModelType; + session.mask.syncGlobalConfig = false; + }); + showToast(s[0]); + }} + /> + )}
); } diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss index 86b467e5..6e8b64e8 100644 --- a/app/components/ui-lib.module.scss +++ b/app/components/ui-lib.module.scss @@ -62,6 +62,7 @@ box-shadow: var(--card-shadow); margin-bottom: 20px; animation: slide-in ease 0.3s; + background: var(--white); } .list .list-item:last-child { @@ -270,3 +271,34 @@ border: 1px solid var(--primary); } } + +.selector { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + + &-content { + .list { + overflow: hidden; + + .list-item { + cursor: pointer; + background-color: var(--white); + + &:hover { + filter: brightness(0.95); + } + + &:active { + filter: brightness(0.9); + } + } + } + } +} diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx index 512044dc..814c0dd1 100644 --- a/app/components/ui-lib.tsx +++ b/app/components/ui-lib.tsx @@ -47,9 +47,13 @@ export function ListItem(props: { children?: JSX.Element | JSX.Element[]; icon?: JSX.Element; className?: string; + onClick?: () => void; }) { return ( -
+
{props.icon &&
{props.icon}
}
@@ -432,3 +436,37 @@ export function showImageModal(img: string) { ), }); } + +export function Selector(props: { + items: Array<{ + title: string; + subTitle?: string; + value: T; + }>; + onSelection?: (selection: T[]) => void; + onClose?: () => void; + multiple?: boolean; +}) { + return ( +
+
+ + {props.items.map((item, i) => { + return ( + { + props.onSelection?.([item.value]); + props.onClose?.(); + }} + > + ); + })} + +
+
+ ); +}