1import { modelsStore } from '$lib/stores/models.svelte';
2import { isRouterMode } from '$lib/stores/server.svelte';
3import { toast } from 'svelte-sonner';
4
5interface UseModelChangeValidationOptions {
6 /**
7 * Function to get required modalities for validation.
8 * For ChatForm: () => usedModalities() - all messages
9 * For ChatMessageAssistant: () => getModalitiesUpToMessage(messageId) - messages before
10 */
11 getRequiredModalities: () => ModelModalities;
12
13 /**
14 * Optional callback to execute after successful validation.
15 * For ChatForm: undefined - just select model
16 * For ChatMessageAssistant: (modelName) => onRegenerate(modelName)
17 */
18 onSuccess?: (modelName: string) => void;
19
20 /**
21 * Optional callback for rollback on validation failure.
22 * For ChatForm: (previousId) => selectModelById(previousId)
23 * For ChatMessageAssistant: undefined - no rollback needed
24 */
25 onValidationFailure?: (previousModelId: string | null) => Promise<void>;
26}
27
28export function useModelChangeValidation(options: UseModelChangeValidationOptions) {
29 const { getRequiredModalities, onSuccess, onValidationFailure } = options;
30
31 let previousSelectedModelId: string | null = null;
32 const isRouter = $derived(isRouterMode());
33
34 async function handleModelChange(modelId: string, modelName: string): Promise<boolean> {
35 try {
36 // Store previous selection for potential rollback
37 if (onValidationFailure) {
38 previousSelectedModelId = modelsStore.selectedModelId;
39 }
40
41 // Load model if not already loaded (router mode only)
42 let hasLoadedModel = false;
43 const isModelLoadedBefore = modelsStore.isModelLoaded(modelName);
44
45 if (isRouter && !isModelLoadedBefore) {
46 try {
47 await modelsStore.loadModel(modelName);
48 hasLoadedModel = true;
49 } catch {
50 toast.error(`Failed to load model "${modelName}"`);
51 return false;
52 }
53 }
54
55 // Fetch model props to validate modalities
56 const props = await modelsStore.fetchModelProps(modelName);
57
58 if (props?.modalities) {
59 const requiredModalities = getRequiredModalities();
60
61 // Check if model supports required modalities
62 const missingModalities: string[] = [];
63 if (requiredModalities.vision && !props.modalities.vision) {
64 missingModalities.push('vision');
65 }
66 if (requiredModalities.audio && !props.modalities.audio) {
67 missingModalities.push('audio');
68 }
69
70 if (missingModalities.length > 0) {
71 toast.error(
72 `Model "${modelName}" doesn't support required modalities: ${missingModalities.join(', ')}. Please select a different model.`
73 );
74
75 // Unload the model if we just loaded it
76 if (isRouter && hasLoadedModel) {
77 try {
78 await modelsStore.unloadModel(modelName);
79 } catch (error) {
80 console.error('Failed to unload incompatible model:', error);
81 }
82 }
83
84 // Execute rollback callback if provided
85 if (onValidationFailure && previousSelectedModelId) {
86 await onValidationFailure(previousSelectedModelId);
87 }
88
89 return false;
90 }
91 }
92
93 // Select the model (validation passed)
94 await modelsStore.selectModelById(modelId);
95
96 // Execute success callback if provided
97 if (onSuccess) {
98 onSuccess(modelName);
99 }
100
101 return true;
102 } catch (error) {
103 console.error('Failed to change model:', error);
104 toast.error('Failed to validate model capabilities');
105
106 // Execute rollback callback on error if provided
107 if (onValidationFailure && previousSelectedModelId) {
108 await onValidationFailure(previousSelectedModelId);
109 }
110
111 return false;
112 }
113 }
114
115 return {
116 handleModelChange
117 };
118}