diff --git a/webui/src/components/BackendFormField.tsx b/webui/src/components/BackendFormField.tsx index a210626..3dd7af0 100644 --- a/webui/src/components/BackendFormField.tsx +++ b/webui/src/components/BackendFormField.tsx @@ -2,11 +2,10 @@ import React from 'react' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Checkbox } from '@/components/ui/checkbox' -import type { BackendOptions } from '@/schemas/instanceOptions' import { getBackendFieldType, basicBackendFieldsConfig } from '@/lib/zodFormUtils' interface BackendFormFieldProps { - fieldKey: keyof BackendOptions + fieldKey: string value: string | number | boolean | string[] | undefined onChange: (key: string, value: string | number | boolean | string[] | undefined) => void } diff --git a/webui/src/components/InstanceDialog.tsx b/webui/src/components/InstanceDialog.tsx index cc4b7e4..919ef52 100644 --- a/webui/src/components/InstanceDialog.tsx +++ b/webui/src/components/InstanceDialog.tsx @@ -41,8 +41,8 @@ const InstanceDialog: React.FC = ({ // Get field lists dynamically from the type const basicFields = getBasicFields(); const advancedFields = getAdvancedFields(); - const basicBackendFields = getBasicBackendFields(); - const advancedBackendFields = getAdvancedBackendFields(); + const basicBackendFields = getBasicBackendFields(formData.backend_type); + const advancedBackendFields = getAdvancedBackendFields(formData.backend_type); // Reset form when dialog opens/closes or when instance changes useEffect(() => { @@ -66,10 +66,21 @@ const InstanceDialog: React.FC = ({ }, [open, instance]); const handleFieldChange = (key: keyof CreateInstanceOptions, value: any) => { - setFormData((prev) => ({ - ...prev, - [key]: value, - })); + setFormData((prev) => { + // If backend_type is changing, clear backend_options + if (key === 'backend_type' && prev.backend_type !== value) { + return { + ...prev, + [key]: value, + backend_options: {}, // Clear backend options when backend type changes + }; + } + + return { + ...prev, + [key]: value, + }; + }); }; const handleBackendFieldChange = (key: string, value: any) => { @@ -78,7 +89,7 @@ const InstanceDialog: React.FC = ({ backend_options: { ...prev.backend_options, [key]: value, - }, + } as any, })); }; @@ -260,7 +271,7 @@ const InstanceDialog: React.FC = ({ ))} @@ -345,7 +356,7 @@ const InstanceDialog: React.FC = ({ ))} diff --git a/webui/src/components/ZodFormField.tsx b/webui/src/components/ZodFormField.tsx index f1ab226..64832b0 100644 --- a/webui/src/components/ZodFormField.tsx +++ b/webui/src/components/ZodFormField.tsx @@ -2,8 +2,7 @@ import React from 'react' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Checkbox } from '@/components/ui/checkbox' -import type { CreateInstanceOptions } from '@/types/instance' -import { BackendType } from '@/types/instance' +import { BackendType, type CreateInstanceOptions } from '@/types/instance' import { getFieldType, basicFieldsConfig } from '@/lib/zodFormUtils' interface ZodFormFieldProps { @@ -39,7 +38,7 @@ const ZodFormField: React.FC = ({ fieldKey, value, onChange } className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" > - {/* Add more backend types here as they become available */} + {config.description && (

{config.description}

diff --git a/webui/src/lib/api.ts b/webui/src/lib/api.ts index 608769f..05c1f27 100644 --- a/webui/src/lib/api.ts +++ b/webui/src/lib/api.ts @@ -93,6 +93,14 @@ export const backendsApi = { body: JSON.stringify({ command }), }), }, + mlx: { + // POST /backends/mlx/parse-command + parseCommand: (command: string) => + apiCall('/backends/mlx/parse-command', { + method: 'POST', + body: JSON.stringify({ command }), + }), + }, }; // Instance API functions diff --git a/webui/src/lib/zodFormUtils.ts b/webui/src/lib/zodFormUtils.ts index 6700e74..ee1d6a1 100644 --- a/webui/src/lib/zodFormUtils.ts +++ b/webui/src/lib/zodFormUtils.ts @@ -1,4 +1,15 @@ -import { type CreateInstanceOptions, type BackendOptions, getAllFieldKeys, getAllBackendFieldKeys } from '@/schemas/instanceOptions' +import { + type CreateInstanceOptions, + type LlamaCppBackendOptions, + type MlxBackendOptions, + LlamaCppBackendOptionsSchema, + MlxBackendOptionsSchema, + getAllFieldKeys, + getAllLlamaCppFieldKeys, + getAllMlxFieldKeys, + getLlamaCppFieldType, + getMlxFieldType +} from '@/schemas/instanceOptions' // Instance-level basic fields (not backend-specific) export const basicFieldsConfig: Record = { + model: { + label: 'Model', + placeholder: 'mlx-community/Mistral-7B-Instruct-v0.3-4bit', + description: 'The path to the MLX model weights, tokenizer, and config', + required: true + }, + python_path: { + label: 'Python Virtual Environment Path', + placeholder: '/path/to/venv', + description: 'Path to Python virtual environment (optional)' + }, + temp: { + label: 'Temperature', + placeholder: '0.0', + description: 'Default sampling temperature (default: 0.0)' + }, + top_p: { + label: 'Top-P', + placeholder: '1.0', + description: 'Default nucleus sampling top-p (default: 1.0)' + }, + top_k: { + label: 'Top-K', + placeholder: '0', + description: 'Default top-k sampling (default: 0, disables top-k)' + }, + min_p: { + label: 'Min-P', + placeholder: '0.0', + description: 'Default min-p sampling (default: 0.0, disables min-p)' + }, + max_tokens: { + label: 'Max Tokens', + placeholder: '512', + description: 'Default maximum number of tokens to generate (default: 512)' + } +} + +function isBasicField(key: keyof CreateInstanceOptions): boolean { return key in basicFieldsConfig } -export function isBasicBackendField(key: keyof BackendOptions): boolean { - return key in basicBackendFieldsConfig -} export function getBasicFields(): (keyof CreateInstanceOptions)[] { return Object.keys(basicFieldsConfig) as (keyof CreateInstanceOptions)[] @@ -81,13 +135,61 @@ export function getAdvancedFields(): (keyof CreateInstanceOptions)[] { return getAllFieldKeys().filter(key => !isBasicField(key)) } -export function getBasicBackendFields(): (keyof BackendOptions)[] { - return Object.keys(basicBackendFieldsConfig) as (keyof BackendOptions)[] + +export function getBasicBackendFields(backendType?: string): string[] { + if (backendType === 'mlx_lm') { + return Object.keys(basicMlxFieldsConfig) + } else if (backendType === 'llama_cpp') { + return Object.keys(basicLlamaCppFieldsConfig) + } + // Default to LlamaCpp for backward compatibility + return Object.keys(basicLlamaCppFieldsConfig) } -export function getAdvancedBackendFields(): (keyof BackendOptions)[] { - return getAllBackendFieldKeys().filter(key => !isBasicBackendField(key)) +export function getAdvancedBackendFields(backendType?: string): string[] { + if (backendType === 'mlx_lm') { + return getAllMlxFieldKeys().filter(key => !(key in basicMlxFieldsConfig)) + } else if (backendType === 'llama_cpp') { + return getAllLlamaCppFieldKeys().filter(key => !(key in basicLlamaCppFieldsConfig)) + } + // Default to LlamaCpp for backward compatibility + return getAllLlamaCppFieldKeys().filter(key => !(key in basicLlamaCppFieldsConfig)) +} + +// Combined backend fields config for use in BackendFormField +export const basicBackendFieldsConfig: Record = { + ...basicLlamaCppFieldsConfig, + ...basicMlxFieldsConfig +} + +// Get field type for any backend option (union type) +export function getBackendFieldType(key: string): 'text' | 'number' | 'boolean' | 'array' { + // Try to get type from LlamaCpp schema first + try { + if (LlamaCppBackendOptionsSchema.shape && key in LlamaCppBackendOptionsSchema.shape) { + return getLlamaCppFieldType(key as keyof LlamaCppBackendOptions) + } + } catch { + // Schema might not be available + } + + // Try MLX schema + try { + if (MlxBackendOptionsSchema.shape && key in MlxBackendOptionsSchema.shape) { + return getMlxFieldType(key as keyof MlxBackendOptions) + } + } catch { + // Schema might not be available + } + + // Default fallback + return 'text' } // Re-export the Zod-based functions -export { getFieldType, getBackendFieldType } from '@/schemas/instanceOptions' \ No newline at end of file +export { getFieldType } from '@/schemas/instanceOptions' \ No newline at end of file diff --git a/webui/src/schemas/instanceOptions.ts b/webui/src/schemas/instanceOptions.ts index cd422c5..91a932a 100644 --- a/webui/src/schemas/instanceOptions.ts +++ b/webui/src/schemas/instanceOptions.ts @@ -1,8 +1,8 @@ import { BackendType } from '@/types/instance' import { z } from 'zod' -// Define the backend options schema (previously embedded in CreateInstanceOptionsSchema) -export const BackendOptionsSchema = z.object({ +// Define the LlamaCpp backend options schema +export const LlamaCppBackendOptionsSchema = z.object({ // Common params verbose_prompt: z.boolean().optional(), threads: z.number().optional(), @@ -170,6 +170,40 @@ export const BackendOptionsSchema = z.object({ fim_qwen_14b_spec: z.boolean().optional(), }) +// Define the MLX backend options schema +export const MlxBackendOptionsSchema = z.object({ + // Basic connection options + model: z.string().optional(), + host: z.string().optional(), + port: z.number().optional(), + python_path: z.string().optional(), + + // Model and adapter options + adapter_path: z.string().optional(), + draft_model: z.string().optional(), + num_draft_tokens: z.number().optional(), + trust_remote_code: z.boolean().optional(), + + // Logging and templates + log_level: z.enum(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']).optional(), + chat_template: z.string().optional(), + use_default_chat_template: z.boolean().optional(), + chat_template_args: z.string().optional(), // JSON string + + // Sampling defaults + temp: z.number().optional(), // Note: MLX uses "temp" not "temperature" + top_p: z.number().optional(), + top_k: z.number().optional(), + min_p: z.number().optional(), + max_tokens: z.number().optional(), +}) + +// Backend options union +export const BackendOptionsSchema = z.union([ + LlamaCppBackendOptionsSchema, + MlxBackendOptionsSchema, +]) + // Define the main create instance options schema export const CreateInstanceOptionsSchema = z.object({ // Restart options @@ -180,11 +214,13 @@ export const CreateInstanceOptionsSchema = z.object({ on_demand_start: z.boolean().optional(), // Backend configuration - backend_type: z.enum([BackendType.LLAMA_CPP]).optional(), + backend_type: z.enum([BackendType.LLAMA_CPP, BackendType.MLX_LM]).optional(), backend_options: BackendOptionsSchema.optional(), }) // Infer the TypeScript types from the schemas +export type LlamaCppBackendOptions = z.infer +export type MlxBackendOptions = z.infer export type BackendOptions = z.infer export type CreateInstanceOptions = z.infer @@ -193,9 +229,14 @@ export function getAllFieldKeys(): (keyof CreateInstanceOptions)[] { return Object.keys(CreateInstanceOptionsSchema.shape) as (keyof CreateInstanceOptions)[] } -// Helper to get all backend option field keys -export function getAllBackendFieldKeys(): (keyof BackendOptions)[] { - return Object.keys(BackendOptionsSchema.shape) as (keyof BackendOptions)[] +// Helper to get all LlamaCpp backend option field keys +export function getAllLlamaCppFieldKeys(): (keyof LlamaCppBackendOptions)[] { + return Object.keys(LlamaCppBackendOptionsSchema.shape) as (keyof LlamaCppBackendOptions)[] +} + +// Helper to get all MLX backend option field keys +export function getAllMlxFieldKeys(): (keyof MlxBackendOptions)[] { + return Object.keys(MlxBackendOptionsSchema.shape) as (keyof MlxBackendOptions)[] } // Get field type from Zod schema @@ -213,9 +254,9 @@ export function getFieldType(key: keyof CreateInstanceOptions): 'text' | 'number return 'text' // ZodString and others default to text } -// Get field type for backend options -export function getBackendFieldType(key: keyof BackendOptions): 'text' | 'number' | 'boolean' | 'array' { - const fieldSchema = BackendOptionsSchema.shape[key] +// Get field type for LlamaCpp backend options +export function getLlamaCppFieldType(key: keyof LlamaCppBackendOptions): 'text' | 'number' | 'boolean' | 'array' { + const fieldSchema = LlamaCppBackendOptionsSchema.shape[key] if (!fieldSchema) return 'text' // Handle ZodOptional wrapper @@ -225,4 +266,19 @@ export function getBackendFieldType(key: keyof BackendOptions): 'text' | 'number if (innerSchema instanceof z.ZodNumber) return 'number' if (innerSchema instanceof z.ZodArray) return 'array' return 'text' // ZodString and others default to text +} + +// Get field type for MLX backend options +export function getMlxFieldType(key: keyof MlxBackendOptions): 'text' | 'number' | 'boolean' | 'array' { + const fieldSchema = MlxBackendOptionsSchema.shape[key] + if (!fieldSchema) return 'text' + + // Handle ZodOptional wrapper + const innerSchema = fieldSchema instanceof z.ZodOptional ? fieldSchema.unwrap() : fieldSchema + + if (innerSchema instanceof z.ZodBoolean) return 'boolean' + if (innerSchema instanceof z.ZodNumber) return 'number' + if (innerSchema instanceof z.ZodArray) return 'array' + if (innerSchema instanceof z.ZodEnum) return 'text' // Enum treated as text/select + return 'text' // ZodString and others default to text } \ No newline at end of file diff --git a/webui/src/types/instance.ts b/webui/src/types/instance.ts index bffb321..869f835 100644 --- a/webui/src/types/instance.ts +++ b/webui/src/types/instance.ts @@ -3,7 +3,9 @@ import type { CreateInstanceOptions } from '@/schemas/instanceOptions' export { type CreateInstanceOptions } from '@/schemas/instanceOptions' export const BackendType = { - LLAMA_CPP: 'llama_cpp' + LLAMA_CPP: 'llama_cpp', + MLX_LM: 'mlx_lm', + // MLX_VLM: 'mlx_vlm', // Future expansion } as const export type BackendTypeValue = typeof BackendType[keyof typeof BackendType]