diff --git a/src/games/storywriter/assets/settings-modal.module.css b/src/games/storywriter/assets/settings-modal.module.css index 4de3018..b76986d 100644 --- a/src/games/storywriter/assets/settings-modal.module.css +++ b/src/games/storywriter/assets/settings-modal.module.css @@ -116,6 +116,7 @@ .formGroup { display: flex; flex-direction: column; + gap: 4px; } .formGroupFill { @@ -128,6 +129,20 @@ font-weight: bold; } +.selectMultiline { + flex: 1; + overflow-y: auto; +} + +.selectMultiline optgroup { + padding-block: 4px; +} + +.selectMultiline option { + padding-block: 4px; + padding-inline-start: 16px; +} + .input, .select, .textarea { diff --git a/src/games/storywriter/components/settings/connection.tsx b/src/games/storywriter/components/settings/connection.tsx index 6f50a01..792169a 100644 --- a/src/games/storywriter/components/settings/connection.tsx +++ b/src/games/storywriter/components/settings/connection.tsx @@ -2,6 +2,7 @@ import { useBool } from "@common/hooks/useBool"; import { useQuery } from "@common/hooks/useAsyncState"; import { useInputState } from "@common/hooks/useInputState"; import { useUpdate } from "@common/hooks/useUpdate"; +import clsx from "clsx"; import { useMemo, useRef } from "preact/hooks"; import styles from "../../assets/settings-modal.module.css"; import { useAppState } from "../../contexts/state"; @@ -36,6 +37,8 @@ export const ConnectionSettings = () => { const modelsData = useQuery(fetchModels, connectionToFetch); const isLoadingModels = connectionToFetch != null && modelsData == undefined; + const [modelFilter, setModelFilter] = useInputState(""); + const groupedModels = useMemo(() => { const sorted = (modelsData ?? []).sort((a, b) => { const aWeight = Number(a.support_tools) * 2 + Number(a.support_thinking); @@ -52,6 +55,25 @@ export const ConnectionSettings = () => { .map(([context, models]) => ({ context, models })); }, [modelsData]); + const filteredGroupedModels = useMemo(() => { + if (!modelFilter) return groupedModels; + const query = modelFilter.toLowerCase(); + const fuzzyMatch = (target: string) => { + const t = target.toLowerCase(); + let qi = 0; + for (let ti = 0; ti < t.length && qi < query.length; ti++) { + if (t[ti] === query[qi]) qi++; + } + return qi === query.length; + }; + return groupedModels + .map(({ context, models }) => ({ + context, + models: models.filter(m => m.id === selectedModel || fuzzyMatch(m.id)), + })) + .filter(({ models }) => models.length > 0); + }, [groupedModels, modelFilter, selectedModel]); + const handleBlur = () => { if (url && apiKey) { dispatch({ type: "SET_CONNECTION", connection: { url, apiKey } }); @@ -115,28 +137,37 @@ export const ConnectionSettings = () => { -
Loading models...
) : groupedModels.length > 0 ? ( - + <> + + + > ) : (No models available
)