mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
Refactor form components and improve API error handling
This commit is contained in:
@@ -45,7 +45,6 @@ const BackendFormField: React.FC<BackendFormFieldProps> = ({ fieldKey, value, on
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
@@ -72,7 +71,6 @@ const BackendFormField: React.FC<BackendFormFieldProps> = ({ fieldKey, value, on
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
@@ -99,7 +97,6 @@ const BackendFormField: React.FC<BackendFormFieldProps> = ({ fieldKey, value, on
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { BackendType, type CreateInstanceOptions, type Instance } from "@/types/instance";
|
import { BackendType, type CreateInstanceOptions, type Instance } from "@/types/instance";
|
||||||
import { getBasicFields, getAdvancedFields, getBasicBackendFields, getAdvancedBackendFields } from "@/lib/zodFormUtils";
|
import { getAdvancedFields, getAdvancedBackendFields } from "@/lib/zodFormUtils";
|
||||||
import { ChevronDown, ChevronRight, Terminal } from "lucide-react";
|
import { ChevronDown, ChevronRight, Terminal } from "lucide-react";
|
||||||
import ZodFormField from "@/components/ZodFormField";
|
|
||||||
import BackendFormField from "@/components/BackendFormField";
|
|
||||||
import ParseCommandDialog from "@/components/ParseCommandDialog";
|
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";
|
||||||
|
|
||||||
interface InstanceDialogProps {
|
interface InstanceDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -39,9 +41,7 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
const [showParseDialog, setShowParseDialog] = useState(false);
|
const [showParseDialog, setShowParseDialog] = useState(false);
|
||||||
|
|
||||||
// Get field lists dynamically from the type
|
// Get field lists dynamically from the type
|
||||||
const basicFields = getBasicFields();
|
|
||||||
const advancedFields = getAdvancedFields();
|
const advancedFields = getAdvancedFields();
|
||||||
const basicBackendFields = getBasicBackendFields(formData.backend_type);
|
|
||||||
const advancedBackendFields = getAdvancedBackendFields(formData.backend_type);
|
const advancedBackendFields = getAdvancedBackendFields(formData.backend_type);
|
||||||
|
|
||||||
// Reset form when dialog opens/closes or when instance changes
|
// Reset form when dialog opens/closes or when instance changes
|
||||||
@@ -163,8 +163,6 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
setShowParseDialog(false);
|
setShowParseDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if auto_restart is enabled
|
|
||||||
const isAutoRestartEnabled = formData.auto_restart === true;
|
|
||||||
|
|
||||||
// Save button label logic
|
// Save button label logic
|
||||||
let saveButtonLabel = "Create Instance";
|
let saveButtonLabel = "Create Instance";
|
||||||
@@ -212,70 +210,23 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auto Restart Configuration Section */}
|
{/* Auto Restart Configuration Section */}
|
||||||
<div className="space-y-4">
|
<AutoRestartConfiguration
|
||||||
<h3 className="text-lg font-medium">
|
formData={formData}
|
||||||
Auto Restart Configuration
|
onChange={handleFieldChange}
|
||||||
</h3>
|
/>
|
||||||
|
|
||||||
{/* Auto Restart Toggle */}
|
{/* Basic Fields */}
|
||||||
<ZodFormField
|
<BasicInstanceFields
|
||||||
fieldKey="auto_restart"
|
formData={formData}
|
||||||
value={formData.auto_restart}
|
onChange={handleFieldChange}
|
||||||
onChange={handleFieldChange}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Show restart options only when auto restart is enabled */}
|
|
||||||
{isAutoRestartEnabled && (
|
|
||||||
<div className="ml-6 space-y-4 border-l-2 border-muted pl-4">
|
|
||||||
<ZodFormField
|
|
||||||
fieldKey="max_restarts"
|
|
||||||
value={formData.max_restarts}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
<ZodFormField
|
|
||||||
fieldKey="restart_delay"
|
|
||||||
value={formData.restart_delay}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Basic Fields - Automatically generated from type (excluding auto restart options) */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-lg font-medium">Basic Configuration</h3>
|
|
||||||
{basicFields
|
|
||||||
.filter(
|
|
||||||
(fieldKey) =>
|
|
||||||
fieldKey !== "auto_restart" &&
|
|
||||||
fieldKey !== "max_restarts" &&
|
|
||||||
fieldKey !== "restart_delay" &&
|
|
||||||
fieldKey !== "backend_options" // backend_options is handled separately
|
|
||||||
)
|
|
||||||
.map((fieldKey) => (
|
|
||||||
<ZodFormField
|
|
||||||
key={fieldKey}
|
|
||||||
fieldKey={fieldKey}
|
|
||||||
value={formData[fieldKey]}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Backend Configuration Section */}
|
{/* Backend Configuration Section */}
|
||||||
<div className="space-y-4">
|
<BackendConfiguration
|
||||||
<h3 className="text-lg font-medium">Backend Configuration</h3>
|
formData={formData}
|
||||||
|
onBackendFieldChange={handleBackendFieldChange}
|
||||||
{/* Basic backend fields */}
|
showAdvanced={showAdvanced}
|
||||||
{basicBackendFields.map((fieldKey) => (
|
/>
|
||||||
<BackendFormField
|
|
||||||
key={fieldKey}
|
|
||||||
fieldKey={fieldKey}
|
|
||||||
value={(formData.backend_options as any)?.[fieldKey]}
|
|
||||||
onChange={handleBackendFieldChange}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Advanced Fields Toggle */}
|
{/* Advanced Fields Toggle */}
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
@@ -314,54 +265,13 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Advanced Fields - Automatically generated from type (excluding restart options) */}
|
{/* Advanced Fields */}
|
||||||
{showAdvanced && (
|
{showAdvanced && (
|
||||||
<div className="space-y-4 pl-6 border-l-2 border-muted">
|
<div className="space-y-4 pl-6 border-l-2 border-muted">
|
||||||
{/* Advanced instance fields */}
|
<AdvancedInstanceFields
|
||||||
{advancedFields
|
formData={formData}
|
||||||
.filter(
|
onChange={handleFieldChange}
|
||||||
(fieldKey) =>
|
/>
|
||||||
!["max_restarts", "restart_delay", "backend_options"].includes(
|
|
||||||
fieldKey as string
|
|
||||||
)
|
|
||||||
).length > 0 && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="text-md font-medium">Advanced Instance Configuration</h4>
|
|
||||||
{advancedFields
|
|
||||||
.filter(
|
|
||||||
(fieldKey) =>
|
|
||||||
!["max_restarts", "restart_delay", "backend_options"].includes(
|
|
||||||
fieldKey as string
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.sort()
|
|
||||||
.map((fieldKey) => (
|
|
||||||
<ZodFormField
|
|
||||||
key={fieldKey}
|
|
||||||
fieldKey={fieldKey}
|
|
||||||
value={fieldKey === 'backend_options' ? undefined : formData[fieldKey]}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Advanced backend fields */}
|
|
||||||
{advancedBackendFields.length > 0 && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="text-md font-medium">Advanced Backend Configuration</h4>
|
|
||||||
{advancedBackendFields
|
|
||||||
.sort()
|
|
||||||
.map((fieldKey) => (
|
|
||||||
<BackendFormField
|
|
||||||
key={fieldKey}
|
|
||||||
fieldKey={fieldKey}
|
|
||||||
value={(formData.backend_options as any)?.[fieldKey]}
|
|
||||||
onChange={handleBackendFieldChange}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,17 +81,14 @@ const ParseCommandDialog: React.FC<ParseCommandDialogProps> = ({
|
|||||||
onOpenChange(open);
|
onOpenChange(open);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const backendPlaceholders: Record<BackendTypeValue, string> = {
|
||||||
|
[BackendType.LLAMA_CPP]: "llama-server --model /path/to/model.gguf --gpu-layers 32 --ctx-size 4096",
|
||||||
|
[BackendType.MLX_LM]: "mlx_lm.server --model mlx-community/Mistral-7B-Instruct-v0.3-4bit --host 0.0.0.0 --port 8080",
|
||||||
|
[BackendType.VLLM]: "vllm serve --model microsoft/DialoGPT-medium --tensor-parallel-size 2 --gpu-memory-utilization 0.9",
|
||||||
|
};
|
||||||
|
|
||||||
const getPlaceholderForBackend = (backendType: BackendTypeValue): string => {
|
const getPlaceholderForBackend = (backendType: BackendTypeValue): string => {
|
||||||
switch (backendType) {
|
return backendPlaceholders[backendType] || "Enter your command here...";
|
||||||
case BackendType.LLAMA_CPP:
|
|
||||||
return "llama-server --model /path/to/model.gguf --gpu-layers 32 --ctx-size 4096";
|
|
||||||
case BackendType.MLX_LM:
|
|
||||||
return "mlx_lm.server --model mlx-community/Mistral-7B-Instruct-v0.3-4bit --host 0.0.0.0 --port 8080";
|
|
||||||
case BackendType.VLLM:
|
|
||||||
return "vllm serve --model microsoft/DialoGPT-medium --tensor-parallel-size 2 --gpu-memory-utilization 0.9";
|
|
||||||
default:
|
|
||||||
return "Enter your command here...";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const ZodFormField: React.FC<ZodFormFieldProps> = ({ fieldKey, value, onChange }
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
|
||||||
</Label>
|
</Label>
|
||||||
<select
|
<select
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
@@ -71,8 +70,7 @@ const ZodFormField: React.FC<ZodFormFieldProps> = ({ fieldKey, value, onChange }
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
</Label>
|
||||||
</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
type="number"
|
type="number"
|
||||||
@@ -98,8 +96,7 @@ const ZodFormField: React.FC<ZodFormFieldProps> = ({ fieldKey, value, onChange }
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
</Label>
|
||||||
</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -125,8 +122,7 @@ const ZodFormField: React.FC<ZodFormFieldProps> = ({ fieldKey, value, onChange }
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={fieldKey}>
|
<Label htmlFor={fieldKey}>
|
||||||
{config.label}
|
{config.label}
|
||||||
{config.required && <span className="text-red-500 ml-1">*</span>}
|
</Label>
|
||||||
</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id={fieldKey}
|
id={fieldKey}
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
62
webui/src/components/form/ArrayInput.tsx
Normal file
62
webui/src/components/form/ArrayInput.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
interface ArrayInputProps {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: string[] | undefined
|
||||||
|
onChange: (value: string[] | undefined) => void
|
||||||
|
placeholder?: string
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArrayInput: React.FC<ArrayInputProps> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder = "item1, item2, item3",
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
const handleChange = (inputValue: string) => {
|
||||||
|
if (inputValue === '') {
|
||||||
|
onChange(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayValue = inputValue
|
||||||
|
.split(',')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
onChange(arrayValue.length > 0 ? arrayValue : undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
type="text"
|
||||||
|
value={Array.isArray(value) ? value.join(', ') : ''}
|
||||||
|
onChange={(e) => handleChange(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
)}
|
||||||
|
<p className="text-xs text-muted-foreground">Separate multiple values with commas</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArrayInput
|
||||||
42
webui/src/components/form/CheckboxInput.tsx
Normal file
42
webui/src/components/form/CheckboxInput.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
interface CheckboxInputProps {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: boolean | undefined
|
||||||
|
onChange: (value: boolean) => void
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheckboxInput: React.FC<CheckboxInputProps> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center space-x-2 ${className || ''}`}>
|
||||||
|
<Checkbox
|
||||||
|
id={id}
|
||||||
|
checked={value === true}
|
||||||
|
onCheckedChange={(checked) => onChange(!!checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={id} className="text-sm font-normal">
|
||||||
|
{label}
|
||||||
|
{description && (
|
||||||
|
<span className="text-muted-foreground ml-1">- {description}</span>
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CheckboxInput
|
||||||
60
webui/src/components/form/NumberInput.tsx
Normal file
60
webui/src/components/form/NumberInput.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
interface NumberInputProps {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: number | undefined
|
||||||
|
onChange: (value: number | undefined) => void
|
||||||
|
placeholder?: string
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const NumberInput: React.FC<NumberInputProps> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
const handleChange = (inputValue: string) => {
|
||||||
|
if (inputValue === '') {
|
||||||
|
onChange(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const numValue = parseFloat(inputValue)
|
||||||
|
if (!isNaN(numValue)) {
|
||||||
|
onChange(numValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
value={value !== undefined ? value : ''}
|
||||||
|
onChange={(e) => handleChange(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NumberInput
|
||||||
55
webui/src/components/form/SelectInput.tsx
Normal file
55
webui/src/components/form/SelectInput.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
interface SelectOption {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectInputProps {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: string | undefined
|
||||||
|
onChange: (value: string | undefined) => void
|
||||||
|
options: SelectOption[]
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectInput: React.FC<SelectInputProps> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
<select
|
||||||
|
id={id}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={(e) => onChange(e.target.value || undefined)}
|
||||||
|
disabled={disabled}
|
||||||
|
className={`flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ''}`}
|
||||||
|
>
|
||||||
|
{options.map(option => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectInput
|
||||||
47
webui/src/components/form/TextInput.tsx
Normal file
47
webui/src/components/form/TextInput.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
interface TextInputProps {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: string | number | undefined
|
||||||
|
onChange: (value: string | undefined) => void
|
||||||
|
placeholder?: string
|
||||||
|
description?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextInput: React.FC<TextInputProps> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
type="text"
|
||||||
|
value={typeof value === 'string' || typeof value === 'number' ? value : ''}
|
||||||
|
onChange={(e) => onChange(e.target.value || undefined)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextInput
|
||||||
98
webui/src/components/instance/AdvancedInstanceFields.tsx
Normal file
98
webui/src/components/instance/AdvancedInstanceFields.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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<AdvancedInstanceFieldsProps> = ({
|
||||||
|
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 (
|
||||||
|
<CheckboxInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as boolean | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<NumberInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as number | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
placeholder={config.placeholder}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
return (
|
||||||
|
<ArrayInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as string[] | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
placeholder={config.placeholder}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as string | number | undefined}
|
||||||
|
onChange={(value) => 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 (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-md font-medium">Advanced Instance Configuration</h4>
|
||||||
|
{fieldsToRender
|
||||||
|
.sort()
|
||||||
|
.map(renderField)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdvancedInstanceFields
|
||||||
53
webui/src/components/instance/AutoRestartConfiguration.tsx
Normal file
53
webui/src/components/instance/AutoRestartConfiguration.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import type { CreateInstanceOptions } from '@/types/instance'
|
||||||
|
import CheckboxInput from '@/components/form/CheckboxInput'
|
||||||
|
import NumberInput from '@/components/form/NumberInput'
|
||||||
|
|
||||||
|
interface AutoRestartConfigurationProps {
|
||||||
|
formData: CreateInstanceOptions
|
||||||
|
onChange: (key: keyof CreateInstanceOptions, value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutoRestartConfiguration: React.FC<AutoRestartConfigurationProps> = ({
|
||||||
|
formData,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const isAutoRestartEnabled = formData.auto_restart === true
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-medium">Auto Restart Configuration</h3>
|
||||||
|
|
||||||
|
<CheckboxInput
|
||||||
|
id="auto_restart"
|
||||||
|
label="Auto Restart"
|
||||||
|
value={formData.auto_restart}
|
||||||
|
onChange={(value) => onChange('auto_restart', value)}
|
||||||
|
description="Automatically restart the instance on failure"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isAutoRestartEnabled && (
|
||||||
|
<div className="ml-6 space-y-4 border-l-2 border-muted pl-4">
|
||||||
|
<NumberInput
|
||||||
|
id="max_restarts"
|
||||||
|
label="Max Restarts"
|
||||||
|
value={formData.max_restarts}
|
||||||
|
onChange={(value) => onChange('max_restarts', value)}
|
||||||
|
placeholder="3"
|
||||||
|
description="Maximum number of restart attempts (0 = unlimited)"
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="restart_delay"
|
||||||
|
label="Restart Delay (seconds)"
|
||||||
|
value={formData.restart_delay}
|
||||||
|
onChange={(value) => onChange('restart_delay', value)}
|
||||||
|
placeholder="5"
|
||||||
|
description="Delay in seconds before attempting restart"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutoRestartConfiguration
|
||||||
54
webui/src/components/instance/BackendConfiguration.tsx
Normal file
54
webui/src/components/instance/BackendConfiguration.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import type { CreateInstanceOptions } from '@/types/instance'
|
||||||
|
import { getBasicBackendFields, getAdvancedBackendFields } from '@/lib/zodFormUtils'
|
||||||
|
import BackendFormField from '@/components/BackendFormField'
|
||||||
|
|
||||||
|
interface BackendConfigurationProps {
|
||||||
|
formData: CreateInstanceOptions
|
||||||
|
onBackendFieldChange: (key: string, value: any) => void
|
||||||
|
showAdvanced?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackendConfiguration: React.FC<BackendConfigurationProps> = ({
|
||||||
|
formData,
|
||||||
|
onBackendFieldChange,
|
||||||
|
showAdvanced = false
|
||||||
|
}) => {
|
||||||
|
const basicBackendFields = getBasicBackendFields(formData.backend_type)
|
||||||
|
const advancedBackendFields = getAdvancedBackendFields(formData.backend_type)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-medium">Backend Configuration</h3>
|
||||||
|
|
||||||
|
{/* Basic backend fields */}
|
||||||
|
{basicBackendFields.map((fieldKey) => (
|
||||||
|
<BackendFormField
|
||||||
|
key={fieldKey}
|
||||||
|
fieldKey={fieldKey}
|
||||||
|
value={(formData.backend_options as any)?.[fieldKey]}
|
||||||
|
onChange={onBackendFieldChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Advanced backend fields */}
|
||||||
|
{showAdvanced && advancedBackendFields.length > 0 && (
|
||||||
|
<div className="space-y-4 pl-6 border-l-2 border-muted">
|
||||||
|
<h4 className="text-md font-medium">Advanced Backend Configuration</h4>
|
||||||
|
{advancedBackendFields
|
||||||
|
.sort()
|
||||||
|
.map((fieldKey) => (
|
||||||
|
<BackendFormField
|
||||||
|
key={fieldKey}
|
||||||
|
fieldKey={fieldKey}
|
||||||
|
value={(formData.backend_options as any)?.[fieldKey]}
|
||||||
|
onChange={onBackendFieldChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BackendConfiguration
|
||||||
99
webui/src/components/instance/BasicInstanceFields.tsx
Normal file
99
webui/src/components/instance/BasicInstanceFields.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { BackendType, type CreateInstanceOptions } from '@/types/instance'
|
||||||
|
import { getBasicFields, 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 SelectInput from '@/components/form/SelectInput'
|
||||||
|
|
||||||
|
interface BasicInstanceFieldsProps {
|
||||||
|
formData: CreateInstanceOptions
|
||||||
|
onChange: (key: keyof CreateInstanceOptions, value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasicInstanceFields: React.FC<BasicInstanceFieldsProps> = ({
|
||||||
|
formData,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const basicFields = getBasicFields()
|
||||||
|
|
||||||
|
const renderField = (fieldKey: keyof CreateInstanceOptions) => {
|
||||||
|
const config = basicFieldsConfig[fieldKey as string] || { label: fieldKey }
|
||||||
|
const fieldType = getFieldType(fieldKey)
|
||||||
|
|
||||||
|
// Special handling for backend_type field
|
||||||
|
if (fieldKey === 'backend_type') {
|
||||||
|
return (
|
||||||
|
<SelectInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] || BackendType.LLAMA_CPP}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
options={[
|
||||||
|
{ value: BackendType.LLAMA_CPP, label: 'Llama Server' },
|
||||||
|
{ value: BackendType.MLX_LM, label: 'MLX LM' },
|
||||||
|
{ value: BackendType.VLLM, label: 'vLLM' }
|
||||||
|
]}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render based on field type
|
||||||
|
switch (fieldType) {
|
||||||
|
case 'boolean':
|
||||||
|
return (
|
||||||
|
<CheckboxInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as boolean | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<NumberInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as number | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
placeholder={config.placeholder}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
key={fieldKey}
|
||||||
|
id={fieldKey}
|
||||||
|
label={config.label}
|
||||||
|
value={formData[fieldKey] as string | number | undefined}
|
||||||
|
onChange={(value) => onChange(fieldKey, value)}
|
||||||
|
placeholder={config.placeholder}
|
||||||
|
description={config.description}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out auto restart fields and backend_options (handled separately)
|
||||||
|
const fieldsToRender = basicFields.filter(
|
||||||
|
fieldKey => !['auto_restart', 'max_restarts', 'restart_delay', 'backend_options'].includes(fieldKey as string)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-medium">Basic Configuration</h3>
|
||||||
|
{fieldsToRender.map(renderField)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BasicInstanceFields
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { CreateInstanceOptions, Instance } from "@/types/instance";
|
import type { CreateInstanceOptions, Instance } from "@/types/instance";
|
||||||
|
import { handleApiError } from "./errorUtils";
|
||||||
|
|
||||||
const API_BASE = "/api/v1";
|
const API_BASE = "/api/v1";
|
||||||
|
|
||||||
@@ -30,25 +31,8 @@ async function apiCall<T>(
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle authentication errors
|
// Handle errors using centralized error handler
|
||||||
if (response.status === 401) {
|
await handleApiError(response);
|
||||||
throw new Error('Authentication required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
// Try to get error message from response
|
|
||||||
let errorMessage = `HTTP ${response.status}`;
|
|
||||||
try {
|
|
||||||
const errorText = await response.text();
|
|
||||||
if (errorText) {
|
|
||||||
errorMessage += `: ${errorText}`;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// If we can't read the error, just use status
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle empty responses (like DELETE)
|
// Handle empty responses (like DELETE)
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
|
|||||||
32
webui/src/lib/errorUtils.ts
Normal file
32
webui/src/lib/errorUtils.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Parses error response from API calls and returns a formatted error message
|
||||||
|
*/
|
||||||
|
export async function parseErrorResponse(response: Response): Promise<string> {
|
||||||
|
let errorMessage = `HTTP ${response.status}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const errorText = await response.text()
|
||||||
|
if (errorText) {
|
||||||
|
errorMessage += `: ${errorText}`
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If we can't read the error, just use status
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles common API call errors and throws appropriate Error objects
|
||||||
|
*/
|
||||||
|
export async function handleApiError(response: Response): Promise<void> {
|
||||||
|
// Handle authentication errors
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Authentication required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorMessage = await parseErrorResponse(response)
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,6 @@ export const basicFieldsConfig: Record<string, {
|
|||||||
label: string
|
label: string
|
||||||
description?: string
|
description?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
|
||||||
}> = {
|
}> = {
|
||||||
auto_restart: {
|
auto_restart: {
|
||||||
label: 'Auto Restart',
|
label: 'Auto Restart',
|
||||||
@@ -56,13 +55,11 @@ const basicLlamaCppFieldsConfig: Record<string, {
|
|||||||
label: string
|
label: string
|
||||||
description?: string
|
description?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
|
||||||
}> = {
|
}> = {
|
||||||
model: {
|
model: {
|
||||||
label: 'Model Path',
|
label: 'Model Path',
|
||||||
placeholder: '/path/to/model.gguf',
|
placeholder: '/path/to/model.gguf',
|
||||||
description: 'Path to the model file',
|
description: 'Path to the model file'
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
hf_repo: {
|
hf_repo: {
|
||||||
label: 'Hugging Face Repository',
|
label: 'Hugging Face Repository',
|
||||||
@@ -86,13 +83,11 @@ const basicMlxFieldsConfig: Record<string, {
|
|||||||
label: string
|
label: string
|
||||||
description?: string
|
description?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
|
||||||
}> = {
|
}> = {
|
||||||
model: {
|
model: {
|
||||||
label: 'Model',
|
label: 'Model',
|
||||||
placeholder: 'mlx-community/Mistral-7B-Instruct-v0.3-4bit',
|
placeholder: 'mlx-community/Mistral-7B-Instruct-v0.3-4bit',
|
||||||
description: 'The path to the MLX model weights, tokenizer, and config',
|
description: 'The path to the MLX model weights, tokenizer, and config'
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
temp: {
|
temp: {
|
||||||
label: 'Temperature',
|
label: 'Temperature',
|
||||||
@@ -126,13 +121,11 @@ const basicVllmFieldsConfig: Record<string, {
|
|||||||
label: string
|
label: string
|
||||||
description?: string
|
description?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
|
||||||
}> = {
|
}> = {
|
||||||
model: {
|
model: {
|
||||||
label: 'Model',
|
label: 'Model',
|
||||||
placeholder: 'microsoft/DialoGPT-medium',
|
placeholder: 'microsoft/DialoGPT-medium',
|
||||||
description: 'The name or path of the Hugging Face model to use',
|
description: 'The name or path of the Hugging Face model to use'
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
tensor_parallel_size: {
|
tensor_parallel_size: {
|
||||||
label: 'Tensor Parallel Size',
|
label: 'Tensor Parallel Size',
|
||||||
@@ -146,11 +139,23 @@ const basicVllmFieldsConfig: Record<string, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend field configuration lookup
|
||||||
|
const backendFieldConfigs = {
|
||||||
|
mlx_lm: basicMlxFieldsConfig,
|
||||||
|
vllm: basicVllmFieldsConfig,
|
||||||
|
llama_cpp: basicLlamaCppFieldsConfig,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const backendFieldGetters = {
|
||||||
|
mlx_lm: getAllMlxFieldKeys,
|
||||||
|
vllm: getAllVllmFieldKeys,
|
||||||
|
llama_cpp: getAllLlamaCppFieldKeys,
|
||||||
|
} as const
|
||||||
|
|
||||||
function isBasicField(key: keyof CreateInstanceOptions): boolean {
|
function isBasicField(key: keyof CreateInstanceOptions): boolean {
|
||||||
return key in basicFieldsConfig
|
return key in basicFieldsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getBasicFields(): (keyof CreateInstanceOptions)[] {
|
export function getBasicFields(): (keyof CreateInstanceOptions)[] {
|
||||||
return Object.keys(basicFieldsConfig) as (keyof CreateInstanceOptions)[]
|
return Object.keys(basicFieldsConfig) as (keyof CreateInstanceOptions)[]
|
||||||
}
|
}
|
||||||
@@ -159,29 +164,18 @@ export function getAdvancedFields(): (keyof CreateInstanceOptions)[] {
|
|||||||
return getAllFieldKeys().filter(key => !isBasicField(key))
|
return getAllFieldKeys().filter(key => !isBasicField(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getBasicBackendFields(backendType?: string): string[] {
|
export function getBasicBackendFields(backendType?: string): string[] {
|
||||||
if (backendType === 'mlx_lm') {
|
const normalizedType = (backendType || 'llama_cpp') as keyof typeof backendFieldConfigs
|
||||||
return Object.keys(basicMlxFieldsConfig)
|
const config = backendFieldConfigs[normalizedType] || basicLlamaCppFieldsConfig
|
||||||
} else if (backendType === 'vllm') {
|
return Object.keys(config)
|
||||||
return Object.keys(basicVllmFieldsConfig)
|
|
||||||
} else if (backendType === 'llama_cpp') {
|
|
||||||
return Object.keys(basicLlamaCppFieldsConfig)
|
|
||||||
}
|
|
||||||
// Default to LlamaCpp for backward compatibility
|
|
||||||
return Object.keys(basicLlamaCppFieldsConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdvancedBackendFields(backendType?: string): string[] {
|
export function getAdvancedBackendFields(backendType?: string): string[] {
|
||||||
if (backendType === 'mlx_lm') {
|
const normalizedType = (backendType || 'llama_cpp') as keyof typeof backendFieldGetters
|
||||||
return getAllMlxFieldKeys().filter(key => !(key in basicMlxFieldsConfig))
|
const fieldGetter = backendFieldGetters[normalizedType] || getAllLlamaCppFieldKeys
|
||||||
} else if (backendType === 'vllm') {
|
const basicConfig = backendFieldConfigs[normalizedType] || basicLlamaCppFieldsConfig
|
||||||
return getAllVllmFieldKeys().filter(key => !(key in basicVllmFieldsConfig))
|
|
||||||
} else if (backendType === 'llama_cpp') {
|
return fieldGetter().filter(key => !(key in basicConfig))
|
||||||
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
|
// Combined backend fields config for use in BackendFormField
|
||||||
@@ -189,7 +183,6 @@ export const basicBackendFieldsConfig: Record<string, {
|
|||||||
label: string
|
label: string
|
||||||
description?: string
|
description?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
|
||||||
}> = {
|
}> = {
|
||||||
...basicLlamaCppFieldsConfig,
|
...basicLlamaCppFieldsConfig,
|
||||||
...basicMlxFieldsConfig,
|
...basicMlxFieldsConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user