Pass default config values to instance dialog

This commit is contained in:
2025-11-14 23:07:30 +01:00
parent 7544fbb1ce
commit c04c952293
7 changed files with 161 additions and 92 deletions

3
.gitignore vendored
View File

@@ -43,3 +43,6 @@ llamactl.dev.yaml
# Debug files
__debug*
# Binary
llamactl-*

View File

@@ -14,7 +14,7 @@ import ParseCommandDialog from "@/components/ParseCommandDialog";
import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard";
import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard";
import { Upload } from "lucide-react";
import { useInstanceDefaults } from "@/contexts/ConfigContext";
import { useInstanceDefaults, useBackendSettings } from "@/contexts/ConfigContext";
interface InstanceDialogProps {
open: boolean;
@@ -38,6 +38,10 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
const [showParseDialog, setShowParseDialog] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// Get backend settings for all backends (we'll use this to update docker_enabled on backend type change)
const llamaCppSettings = useBackendSettings(BackendType.LLAMA_CPP);
const vllmSettings = useBackendSettings(BackendType.VLLM);
const mlxSettings = useBackendSettings(BackendType.MLX_LM);
// Reset form when dialog opens/closes or when instance changes
useEffect(() => {
@@ -55,20 +59,32 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
restart_delay: instanceDefaults?.restartDelay,
on_demand_start: instanceDefaults?.onDemandStart,
backend_type: BackendType.LLAMA_CPP, // Default backend type
docker_enabled: llamaCppSettings?.dockerEnabled ?? false,
backend_options: {},
});
}
setNameError(""); // Reset any name errors
}
}, [open, instance, instanceDefaults]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, instance]);
const handleFieldChange = (key: keyof CreateInstanceOptions, value: unknown) => {
setFormData((prev) => {
// If backend_type is changing, clear backend_options
// If backend_type is changing, update docker_enabled default and clear backend_options
if (key === 'backend_type' && prev.backend_type !== value) {
let dockerEnabled = false;
if (value === BackendType.LLAMA_CPP) {
dockerEnabled = llamaCppSettings?.dockerEnabled ?? false;
} else if (value === BackendType.VLLM) {
dockerEnabled = vllmSettings?.dockerEnabled ?? false;
} else if (value === BackendType.MLX_LM) {
dockerEnabled = mlxSettings?.dockerEnabled ?? false;
}
return {
...prev,
backend_type: value as CreateInstanceOptions['backend_type'],
docker_enabled: dockerEnabled,
backend_options: {}, // Clear backend options when backend type changes
};
}

View File

@@ -18,7 +18,6 @@ const EnvVarsInput: React.FC<EnvVarsInputProps> = (props) => {
keyPlaceholder="Variable name"
valuePlaceholder="Variable value"
addButtonText="Add Variable"
helperText="Environment variables that will be passed to the backend process"
allowEmptyValues={false}
/>
)

View File

@@ -6,6 +6,7 @@ import { Terminal, ChevronDown, ChevronRight } from 'lucide-react'
import { getBasicBackendFields, getAdvancedBackendFields } from '@/lib/zodFormUtils'
import BackendFormField from '@/components/BackendFormField'
import SelectInput from '@/components/form/SelectInput'
import ExecutionContextSection from '@/components/instance/ExecutionContextSection'
interface BackendConfigurationCardProps {
formData: CreateInstanceOptions
@@ -59,6 +60,12 @@ const BackendConfigurationCard: React.FC<BackendConfigurationCardProps> = ({
</p>
</div>
{/* Execution Context Section */}
<ExecutionContextSection
formData={formData}
onChange={onChange}
/>
{/* Basic Backend Options */}
{basicBackendFields.length > 0 && (
<div className="space-y-4">

View File

@@ -0,0 +1,76 @@
import React from 'react'
import { BackendType, type CreateInstanceOptions } from '@/types/instance'
import CheckboxInput from '@/components/form/CheckboxInput'
import TextInput from '@/components/form/TextInput'
import EnvVarsInput from '@/components/form/EnvVarsInput'
import { useBackendSettings } from '@/contexts/ConfigContext'
interface ExecutionContextSectionProps {
formData: CreateInstanceOptions
onChange: (key: keyof CreateInstanceOptions, value: unknown) => void
}
const ExecutionContextSection: React.FC<ExecutionContextSectionProps> = ({
formData,
onChange
}) => {
const backendSettings = useBackendSettings(formData.backend_type)
// Get placeholder for command override based on backend type and config
const getCommandPlaceholder = () => {
if (backendSettings?.command) {
return backendSettings.command
}
// Fallback placeholders if config is not loaded
switch (formData.backend_type) {
case BackendType.LLAMA_CPP:
return "llama-server"
case BackendType.VLLM:
return "vllm"
case BackendType.MLX_LM:
return "mlx_lm.server"
default:
return ""
}
}
return (
<div className="space-y-4">
<h3 className="text-md font-medium">Execution Context</h3>
{/* Docker Mode Toggle - only for backends that support Docker */}
{formData.backend_type !== BackendType.MLX_LM && (
<CheckboxInput
id="docker_enabled"
label="Enable Docker"
value={formData.docker_enabled}
onChange={(value) => onChange('docker_enabled', value)}
description="Run backend in Docker container"
/>
)}
{/* Command Override - only shown when Docker is disabled or backend is MLX */}
{(formData.backend_type === BackendType.MLX_LM || formData.docker_enabled !== true) && (
<TextInput
id="command_override"
label="Command Override"
value={formData.command_override || ''}
onChange={(value) => onChange('command_override', value)}
placeholder={getCommandPlaceholder()}
description="Custom path to backend executable (leave empty to use config default)"
/>
)}
<EnvVarsInput
id="environment"
label="Environment Variables"
value={formData.environment}
onChange={(value) => onChange('environment', value)}
description="Custom environment variables for the instance"
/>
</div>
)
}
export default ExecutionContextSection

View File

@@ -1,14 +1,12 @@
import React, { useState, useEffect } from 'react'
import { BackendType, type CreateInstanceOptions } from '@/types/instance'
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'
import EnvVarsInput from '@/components/form/EnvVarsInput'
import SelectInput from '@/components/form/SelectInput'
import TextInput from '@/components/form/TextInput'
import { nodesApi, type NodesMap } from '@/lib/api'
interface InstanceSettingsCardProps {
@@ -106,48 +104,6 @@ const InstanceSettingsCard: React.FC<InstanceSettingsCardProps> = ({
/>
)}
{/* Execution Context */}
<div className="space-y-4">
<h3 className="text-lg font-medium">Execution Context</h3>
{/* Docker Mode Toggle - only for backends that support Docker */}
{formData.backend_type !== BackendType.MLX_LM && (
<CheckboxInput
id="docker_enabled"
label="Enable Docker"
value={formData.docker_enabled}
onChange={(value) => onChange('docker_enabled', value)}
description="Run backend in Docker container (overrides config default)"
/>
)}
{/* Command Override - only shown when Docker is disabled or backend is MLX */}
{(formData.backend_type === BackendType.MLX_LM || formData.docker_enabled !== true) && (
<TextInput
id="command_override"
label="Command Override"
value={formData.command_override || ''}
onChange={(value) => onChange('command_override', value)}
placeholder={
formData.backend_type === BackendType.LLAMA_CPP
? "/usr/local/bin/llama-server"
: formData.backend_type === BackendType.VLLM
? "/usr/local/bin/vllm"
: "/usr/local/bin/mlx_lm.server"
}
description="Custom path to backend executable"
/>
)}
<EnvVarsInput
id="environment"
label="Environment Variables"
value={formData.environment}
onChange={(value) => onChange('environment', value)}
description="Custom environment variables for the instance"
/>
</div>
{/* Auto Restart Configuration */}
<AutoRestartConfiguration
formData={formData}

View File

@@ -1,19 +1,14 @@
import { type ReactNode, createContext, useCallback, useContext, useEffect, useState } from 'react'
import { type ReactNode, createContext, useContext, useEffect, useState, useRef } from 'react'
import { serverApi } from '@/lib/api'
import type { AppConfig } from '@/types/config'
import { useAuth } from './AuthContext'
interface ConfigContextState {
interface ConfigContextType {
config: AppConfig | null
isLoading: boolean
error: string | null
}
interface ConfigContextActions {
refetchConfig: () => Promise<void>
}
type ConfigContextType = ConfigContextState & ConfigContextActions
const ConfigContext = createContext<ConfigContextType | undefined>(undefined)
interface ConfigProviderProps {
@@ -21,14 +16,21 @@ interface ConfigProviderProps {
}
export const ConfigProvider = ({ children }: ConfigProviderProps) => {
const { isAuthenticated } = useAuth()
const [config, setConfig] = useState<AppConfig | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const loadedRef = useRef(false)
const fetchConfig = useCallback(async () => {
setIsLoading(true)
setError(null)
useEffect(() => {
if (!isAuthenticated || loadedRef.current) {
setIsLoading(false)
return
}
loadedRef.current = true
const loadConfig = async () => {
try {
const data = await serverApi.getConfig()
setConfig(data)
@@ -39,26 +41,13 @@ export const ConfigProvider = ({ children }: ConfigProviderProps) => {
} finally {
setIsLoading(false)
}
}, [])
// Load config on mount
useEffect(() => {
void fetchConfig()
}, [fetchConfig])
const refetchConfig = useCallback(async () => {
await fetchConfig()
}, [fetchConfig])
const value: ConfigContextType = {
config,
isLoading,
error,
refetchConfig,
}
void loadConfig()
}, [isAuthenticated])
return (
<ConfigContext.Provider value={value}>
<ConfigContext.Provider value={{ config, isLoading, error }}>
{children}
</ConfigContext.Provider>
)
@@ -76,7 +65,7 @@ export const useConfig = (): ConfigContextType => {
export const useInstanceDefaults = () => {
const { config } = useConfig()
if (!config) {
if (!config || !config.instances) {
return null
}
@@ -88,13 +77,36 @@ export const useInstanceDefaults = () => {
}
}
// Helper hook to get backend settings from config
export const useBackendConfig = () => {
// Helper hook to get specific backend settings by backend type
export const useBackendSettings = (backendType: string | undefined) => {
const { config } = useConfig()
if (!config) {
if (!config || !config.backends || !backendType) {
return null
}
return config.backends
// Map backend type to config key
const backendKey = backendType === 'llama_cpp'
? 'llama-cpp'
: backendType === 'mlx_lm'
? 'mlx'
: backendType === 'vllm'
? 'vllm'
: null
if (!backendKey) {
return null
}
const backendConfig = config.backends[backendKey as keyof typeof config.backends]
if (!backendConfig) {
return null
}
return {
command: backendConfig.command || '',
dockerEnabled: backendConfig.docker?.enabled ?? false,
dockerImage: backendConfig.docker?.image || '',
}
}