diff --git a/webui/src/components/InstanceDialog.tsx b/webui/src/components/InstanceDialog.tsx index 8111348..d9b731c 100644 --- a/webui/src/components/InstanceDialog.tsx +++ b/webui/src/components/InstanceDialog.tsx @@ -1,7 +1,5 @@ import React, { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, @@ -11,13 +9,9 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { BackendType, type CreateInstanceOptions, type Instance } from "@/types/instance"; -import { getAdvancedFields, getAdvancedBackendFields } from "@/lib/zodFormUtils"; -import { ChevronDown, ChevronRight, Terminal } from "lucide-react"; import ParseCommandDialog from "@/components/ParseCommandDialog"; -import AutoRestartConfiguration from "@/components/instance/AutoRestartConfiguration"; -import BasicInstanceFields from "@/components/instance/BasicInstanceFields"; -import BackendConfiguration from "@/components/instance/BackendConfiguration"; -import AdvancedInstanceFields from "@/components/instance/AdvancedInstanceFields"; +import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard"; +import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard"; interface InstanceDialogProps { open: boolean; @@ -36,13 +30,9 @@ const InstanceDialog: React.FC = ({ const [instanceName, setInstanceName] = useState(""); const [formData, setFormData] = useState({}); - const [showAdvanced, setShowAdvanced] = useState(false); const [nameError, setNameError] = useState(""); const [showParseDialog, setShowParseDialog] = useState(false); - // Get field lists dynamically from the type - const advancedFields = getAdvancedFields(); - const advancedBackendFields = getAdvancedBackendFields(formData.backend_type); // Reset form when dialog opens/closes or when instance changes useEffect(() => { @@ -60,7 +50,6 @@ const InstanceDialog: React.FC = ({ backend_options: {}, }); } - setShowAdvanced(false); // Always start with basic view setNameError(""); // Reset any name errors } }, [open, instance]); @@ -151,9 +140,6 @@ const InstanceDialog: React.FC = ({ onOpenChange(false); }; - const toggleAdvanced = () => { - setShowAdvanced(!showAdvanced); - }; const handleCommandParsed = (parsedOptions: CreateInstanceOptions) => { setFormData(prev => ({ @@ -189,91 +175,25 @@ const InstanceDialog: React.FC = ({
-
- {/* Instance Name - Special handling since it's not in CreateInstanceOptions */} -
- - handleNameChange(e.target.value)} - placeholder="my-instance" - disabled={isEditing} // Don't allow name changes when editing - className={nameError ? "border-red-500" : ""} - /> - {nameError &&

{nameError}

} -

- Unique identifier for the instance -

-
- - {/* Auto Restart Configuration Section */} - + {/* Instance Settings Card */} + - {/* Basic Fields */} - - - {/* Backend Configuration Section */} - setShowParseDialog(true)} /> - {/* Advanced Fields Toggle */} -
-
- - - -
-
- - {/* Advanced Fields */} - {showAdvanced && ( -
- -
- )}
@@ -299,6 +219,7 @@ const InstanceDialog: React.FC = ({ open={showParseDialog} onOpenChange={setShowParseDialog} onParsed={handleCommandParsed} + backendType={formData.backend_type || BackendType.LLAMA_CPP} /> ); diff --git a/webui/src/components/ParseCommandDialog.tsx b/webui/src/components/ParseCommandDialog.tsx index 593c664..5043a57 100644 --- a/webui/src/components/ParseCommandDialog.tsx +++ b/webui/src/components/ParseCommandDialog.tsx @@ -17,15 +17,16 @@ interface ParseCommandDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onParsed: (options: CreateInstanceOptions) => void; + backendType: BackendTypeValue; } const ParseCommandDialog: React.FC = ({ open, onOpenChange, onParsed, + backendType, }) => { const [command, setCommand] = useState(''); - const [backendType, setBackendType] = useState(BackendType.LLAMA_CPP); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -75,7 +76,6 @@ const ParseCommandDialog: React.FC = ({ const handleOpenChange = (open: boolean) => { if (!open) { setCommand(''); - setBackendType(BackendType.LLAMA_CPP); setError(null); } onOpenChange(open); @@ -103,17 +103,13 @@ const ParseCommandDialog: React.FC = ({
- - +
diff --git a/webui/src/components/__tests__/InstanceModal.test.tsx b/webui/src/components/__tests__/InstanceModal.test.tsx index 0644c3c..a931ae9 100644 --- a/webui/src/components/__tests__/InstanceModal.test.tsx +++ b/webui/src/components/__tests__/InstanceModal.test.tsx @@ -280,29 +280,6 @@ afterEach(() => { }) }) - describe('Advanced Fields Toggle', () => { - it('shows advanced fields when toggle clicked', async () => { - const user = userEvent.setup() - - render( - - ) - - // Advanced fields should be hidden initially - expect(screen.queryByText(/Advanced Configuration/)).toBeInTheDocument() - - // Click to expand - await user.click(screen.getByText(/Advanced Configuration/)) - - // Should show more configuration options - // Note: Specific fields depend on zodFormUtils configuration - // We're testing the toggle behavior, not specific fields - }) - }) describe('Form Data Handling', () => { it('cleans up undefined values before submission', async () => { diff --git a/webui/src/components/instance/AdvancedInstanceFields.tsx b/webui/src/components/instance/AdvancedInstanceFields.tsx deleted file mode 100644 index 16ea9af..0000000 --- a/webui/src/components/instance/AdvancedInstanceFields.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import type { CreateInstanceOptions } from '@/types/instance' -import { getAdvancedFields, basicFieldsConfig } from '@/lib/zodFormUtils' -import { getFieldType } from '@/schemas/instanceOptions' -import TextInput from '@/components/form/TextInput' -import NumberInput from '@/components/form/NumberInput' -import CheckboxInput from '@/components/form/CheckboxInput' -import ArrayInput from '@/components/form/ArrayInput' - -interface AdvancedInstanceFieldsProps { - formData: CreateInstanceOptions - onChange: (key: keyof CreateInstanceOptions, value: any) => void -} - -const AdvancedInstanceFields: React.FC = ({ - formData, - onChange -}) => { - const advancedFields = getAdvancedFields() - - const renderField = (fieldKey: keyof CreateInstanceOptions) => { - const config = basicFieldsConfig[fieldKey as string] || { label: fieldKey } - const fieldType = getFieldType(fieldKey) - - switch (fieldType) { - case 'boolean': - return ( - onChange(fieldKey, value)} - description={config.description} - /> - ) - - case 'number': - return ( - onChange(fieldKey, value)} - placeholder={config.placeholder} - description={config.description} - /> - ) - - case 'array': - return ( - onChange(fieldKey, value)} - placeholder={config.placeholder} - description={config.description} - /> - ) - - default: - return ( - onChange(fieldKey, value)} - placeholder={config.placeholder} - description={config.description} - /> - ) - } - } - - // Filter out restart options and backend_options (handled separately) - const fieldsToRender = advancedFields.filter( - fieldKey => !['max_restarts', 'restart_delay', 'backend_options'].includes(fieldKey as string) - ) - - if (fieldsToRender.length === 0) { - return null - } - - return ( -
-

Advanced Instance Configuration

- {fieldsToRender - .sort() - .map(renderField)} -
- ) -} - -export default AdvancedInstanceFields \ No newline at end of file diff --git a/webui/src/components/instance/BackendConfigurationCard.tsx b/webui/src/components/instance/BackendConfigurationCard.tsx new file mode 100644 index 0000000..3e5e43f --- /dev/null +++ b/webui/src/components/instance/BackendConfigurationCard.tsx @@ -0,0 +1,117 @@ +import React, { useState } from 'react' +import { BackendType, type CreateInstanceOptions } from '@/types/instance' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Terminal, ChevronDown, ChevronRight } from 'lucide-react' +import { getBasicBackendFields, getAdvancedBackendFields } from '@/lib/zodFormUtils' +import BackendFormField from '@/components/BackendFormField' +import SelectInput from '@/components/form/SelectInput' + +interface BackendConfigurationCardProps { + formData: CreateInstanceOptions + onBackendFieldChange: (key: string, value: unknown) => void + onChange: (key: keyof CreateInstanceOptions, value: unknown) => void + onParseCommand: () => void +} + +const BackendConfigurationCard: React.FC = ({ + formData, + onBackendFieldChange, + onChange, + onParseCommand +}) => { + const [showAdvanced, setShowAdvanced] = useState(false) + const basicBackendFields = getBasicBackendFields(formData.backend_type) + const advancedBackendFields = getAdvancedBackendFields(formData.backend_type) + + return ( + + + Backend Configuration + + + {/* Backend Type Selection */} + onChange('backend_type', value)} + options={[ + { value: BackendType.LLAMA_CPP, label: 'Llama Server (llama_cpp)' }, + { value: BackendType.MLX_LM, label: 'MLX LM (mlx_lm)' }, + { value: BackendType.VLLM, label: 'vLLM (vllm)' } + ]} + description="Select the backend server type" + /> + + {/* Parse Command Section */} +
+ +

+ Import settings from your backend command +

+
+ + {/* Basic Backend Options */} + {basicBackendFields.length > 0 && ( +
+

Basic Backend Options

+ {basicBackendFields.map((fieldKey) => ( + )?.[fieldKey] as string | number | boolean | string[] | undefined} + onChange={onBackendFieldChange} + /> + ))} +
+ )} + + {/* Advanced Backend Options */} + {advancedBackendFields.length > 0 && ( +
+ + + {showAdvanced && ( +
+ {advancedBackendFields + .sort() + .map((fieldKey) => ( + )?.[fieldKey] as string | number | boolean | string[] | undefined} + onChange={onBackendFieldChange} + /> + ))} +
+ )} +
+ )} +
+
+ ) +} + +export default BackendConfigurationCard \ No newline at end of file diff --git a/webui/src/components/instance/InstanceSettingsCard.tsx b/webui/src/components/instance/InstanceSettingsCard.tsx new file mode 100644 index 0000000..d997a8c --- /dev/null +++ b/webui/src/components/instance/InstanceSettingsCard.tsx @@ -0,0 +1,84 @@ +import React from 'react' +import type { CreateInstanceOptions } from '@/types/instance' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' +import AutoRestartConfiguration from '@/components/instance/AutoRestartConfiguration' +import NumberInput from '@/components/form/NumberInput' +import CheckboxInput from '@/components/form/CheckboxInput' + +interface InstanceSettingsCardProps { + instanceName: string + nameError: string + isEditing: boolean + formData: CreateInstanceOptions + onNameChange: (name: string) => void + onChange: (key: keyof CreateInstanceOptions, value: unknown) => void +} + +const InstanceSettingsCard: React.FC = ({ + instanceName, + nameError, + isEditing, + formData, + onNameChange, + onChange +}) => { + return ( + + + Instance Settings + + + {/* Instance Name */} +
+ + onNameChange(e.target.value)} + placeholder="my-instance" + disabled={isEditing} + className={nameError ? "border-red-500" : ""} + /> + {nameError &&

{nameError}

} +

+ Unique identifier for the instance +

+
+ + {/* Auto Restart Configuration */} + + + {/* Basic Instance Options */} +
+

Basic Instance Options

+ + onChange('idle_timeout', value)} + placeholder="30" + description="Minutes before stopping an idle instance" + /> + + onChange('on_demand_start', value)} + description="Start instance only when needed" + /> +
+
+
+ ) +} + +export default InstanceSettingsCard \ No newline at end of file