import { useMemo, useRef } from "preact/hooks"; import { useQuery } from "@common/hooks/useAsyncState"; import { useInputState } from "@common/hooks/useInputState"; import { useUpdate } from "@common/hooks/useUpdate"; import { useAppState } from "../contexts/state"; import LLM from "../utils/llm"; import styles from "../assets/settings-modal.module.css"; interface Props { onClose: () => void; } export const SettingsModal = ({ onClose }: Props) => { const { connection, model, dispatch } = useAppState(); const [url, setUrl] = useInputState(connection?.url ?? ""); const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? ""); const [selectedModel, setSelectedModel] = useInputState(model ?? ""); const [update, triggerFetch] = useUpdate(); const urlRef = useRef(url); const apiKeyRef = useRef(apiKey); urlRef.current = url; apiKeyRef.current = apiKey; const connectionToFetch = useMemo(() => { const currentUrl = urlRef.current; const currentApiKey = apiKeyRef.current; if (!currentUrl || !currentApiKey) return null; return { url: currentUrl, apiKey: currentApiKey }; }, [update]); const fetchModels = useMemo(() => async (conn: LLM.Connection | null) => { if (!conn) return []; const r = await LLM.getModels(conn); return r.data; }, []); const modelsData = useQuery(fetchModels, connectionToFetch); const isLoadingModels = connectionToFetch != null && modelsData == undefined; const groupedModels = useMemo(() => { const sorted = (modelsData ?? []).sort((a, b) => { // Sort by tool support first (true before false) if (a.support_tools !== b.support_tools) { return a.support_tools ? -1 : 1; } // Then by max context (bigger first, undefined treated as 0) const aContext = a.max_context ?? 0; const bContext = b.max_context ?? 0; if (aContext !== bContext) { return bContext - aContext; } // Then by name (alphabetically) return a.id.localeCompare(b.id); }); // Group by context size const groups = Map.groupBy(sorted, m => m.max_context ?? 0); // Convert to array sorted by context size (bigger first) return Array.from(groups.entries()) .sort((a, b) => b[0] - a[0]) .map(([context, models]) => ({ context, models })); }, [modelsData]); const handleBlur = () => { if (url && apiKey) { triggerFetch(); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter' && url && apiKey) { triggerFetch(); } }; const handleConfirm = () => { dispatch({ type: 'SET_CONNECTION', connection: connectionToFetch, }); dispatch({ type: 'SET_MODEL', model: selectedModel || null, }); onClose(); }; const connectionToTest = url && apiKey ? { url, apiKey } : null; return (
e.stopPropagation()}>

Settings

{connectionToTest ? ( isLoadingModels ? (

Loading models...

) : groupedModels.length > 0 ? ( ) : (

No models available

) ) : (

Enter connection details to load models

)}
); };