char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Stores split refactor (#27240)521a64cd0

char/aliceai-t5-moe
6.3 KiB266 linesraw
1import { filterModelOptions, groupModelOptions } from '$lib/components/app/models/utils';
2import { CHAT_INPUT_FOCUS_SELECTOR } from '$lib/constants';
3import { modelsStore, serverStore } from '$lib/stores';
4import type { ModelOption } from '$lib/types/models';
5import { onMount } from 'svelte';
6
7export interface UseModelsSelectorOptions {
8	currentModel: () => string | null;
9	useGlobalSelection?: () => boolean;
10	onModelChange?: () =>
11		| ((modelId: string, modelName: string) => Promise<boolean> | boolean | void)
12		| undefined;
13	onOpenChange?: (open: boolean) => void;
14}
15
16export interface UseModelsSelectorReturn {
17	readonly options: ModelOption[];
18	readonly loading: boolean;
19	readonly updating: boolean;
20	readonly activeId: string | null;
21	readonly isRouter: boolean;
22	readonly serverModel: string | null;
23	readonly isHighlightedCurrentModelActive: boolean;
24	readonly isCurrentModelInCache: boolean;
25	readonly filteredOptions: ModelOption[];
26	readonly groupedFilteredOptions: ReturnType<typeof groupModelOptions>;
27	readonly isLoadingModel: boolean;
28	readonly searchTerm: string;
29	readonly showModelDialog: boolean;
30	readonly infoModelId: string | null;
31	setSearchTerm(value: string): void;
32	setShowModelDialog(value: boolean): void;
33	handleInfoClick(modelName: string): void;
34	handleSelect(modelId: string): Promise<void>;
35	handleOpenChange(open: boolean): void;
36	isFavorite(model: string): boolean;
37	getDisplayOption(): ModelOption | undefined;
38}
39
40/**
41 * Shared reactive state and logic for model selection.
42 *
43 * Used by both the desktop dropdown (`ModelsSelectorDropdown`)
44 * and the mobile sheet (`ModelsSelectorSheet`) to avoid
45 * duplicating store derivations, selection handling, and model loading.
46 */
47export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSelectorReturn {
48	const options = $derived(
49		modelsStore.models.filter((option) => {
50			const modelProps = modelsStore.props.getModelProps(option.model);
51
52			return modelProps?.ui !== false;
53		})
54	);
55	const loading = $derived(modelsStore.loading);
56	const updating = $derived(modelsStore.updating);
57	const activeId = $derived(modelsStore.selectedModelId);
58	const isRouter = $derived(serverStore.isRouterMode);
59	const serverModel = $derived(modelsStore.singleModelName);
60	const currentModel = $derived(opts.currentModel());
61	const onModelChange = $derived(opts.onModelChange?.());
62	const isHighlightedCurrentModelActive = $derived.by(() => {
63		if (!isRouter || !currentModel) return false;
64
65		const currentOption = options.find((option) => option.model === currentModel);
66
67		return currentOption ? currentOption.id === activeId : false;
68	});
69	const isCurrentModelInCache = $derived.by(() => {
70		if (!isRouter || !currentModel) return true;
71
72		return options.some((option) => option.model === currentModel);
73	});
74
75	let isLoadingModel = $state(false);
76	let searchTerm = $state('');
77	let showModelDialog = $state(false);
78	let infoModelId = $state<string | null>(null);
79
80	const filteredOptions = $derived(filterModelOptions(options, searchTerm));
81	const groupedFilteredOptions = $derived(
82		groupModelOptions(filteredOptions, modelsStore.favoriteModelIds, (m) =>
83			modelsStore.isModelLoaded(m)
84		)
85	);
86
87	function handleInfoClick(modelName: string) {
88		infoModelId = modelName;
89		showModelDialog = true;
90	}
91
92	onMount(() => {
93		modelsStore.fetch().catch((error) => {
94			console.error('Unable to load models:', error);
95		});
96	});
97
98	function handleOpenChange(open: boolean) {
99		if (loading || updating) return;
100
101		if (isRouter) {
102			searchTerm = '';
103
104			if (open) {
105				modelsStore.fetchRouterModels().then(() => {
106					modelsStore.props.fetchModalitiesForLoadedModels();
107				});
108			}
109
110			opts.onOpenChange?.(open);
111		} else {
112			showModelDialog = open;
113		}
114	}
115
116	async function handleSelect(modelId: string) {
117		const option = options.find((opt) => opt.id === modelId);
118
119		if (!option) return;
120
121		let shouldCloseMenu = true;
122
123		if (onModelChange) {
124			const result = await onModelChange(option.id, option.model);
125
126			if (result === false) {
127				shouldCloseMenu = false;
128			}
129		} else {
130			await modelsStore.selectModelById(option.id);
131		}
132
133		if (shouldCloseMenu) {
134			handleOpenChange(false);
135
136			requestAnimationFrame(() => {
137				const input = document.querySelector<HTMLElement>(CHAT_INPUT_FOCUS_SELECTOR);
138
139				input?.focus({ preventScroll: true });
140			});
141		}
142
143		if (!onModelChange && isRouter && !modelsStore.isModelLoaded(option.model)) {
144			isLoadingModel = true;
145
146			modelsStore.status
147				.load(option.model)
148				.catch((error) => console.error('Failed to load model:', error))
149				.finally(() => (isLoadingModel = false));
150		}
151	}
152
153	function getDisplayOption(): ModelOption | undefined {
154		if (!isRouter) {
155			const displayModel = serverModel || currentModel;
156
157			if (displayModel) {
158				return {
159					capabilities: [],
160					id: serverModel ? 'current' : 'offline-current',
161					model: displayModel,
162					name: displayModel.split('/').pop() || displayModel
163				};
164			}
165
166			return undefined;
167		}
168
169		if (currentModel) {
170			if (!isCurrentModelInCache) {
171				return {
172					capabilities: [],
173					id: 'not-in-cache',
174					model: currentModel,
175					name: currentModel.split('/').pop() || currentModel
176				};
177			}
178
179			return options.find((option) => option.model === currentModel);
180		}
181
182		if (activeId) {
183			return options.find((option) => option.id === activeId);
184		}
185
186		return undefined;
187	}
188
189	return {
190		get activeId() {
191			return activeId;
192		},
193
194		get filteredOptions() {
195			return filteredOptions;
196		},
197
198		getDisplayOption,
199
200		get groupedFilteredOptions() {
201			return groupedFilteredOptions;
202		},
203
204		handleInfoClick,
205
206		handleOpenChange,
207
208		handleSelect,
209
210		get infoModelId() {
211			return infoModelId;
212		},
213
214		get isCurrentModelInCache() {
215			return isCurrentModelInCache;
216		},
217
218		isFavorite(model: string) {
219			return modelsStore.favoriteModelIds.has(model);
220		},
221
222		get isHighlightedCurrentModelActive() {
223			return isHighlightedCurrentModelActive;
224		},
225
226		get isLoadingModel() {
227			return isLoadingModel;
228		},
229
230		get isRouter() {
231			return isRouter;
232		},
233
234		get loading() {
235			return loading;
236		},
237
238		get options() {
239			return options;
240		},
241
242		get searchTerm() {
243			return searchTerm;
244		},
245
246		get serverModel() {
247			return serverModel;
248		},
249
250		setSearchTerm(value: string) {
251			searchTerm = value;
252		},
253
254		setShowModelDialog(value: boolean) {
255			showModelDialog = value;
256		},
257
258		get showModelDialog() {
259			return showModelDialog;
260		},
261
262		get updating() {
263			return updating;
264		}
265	};
266}