From 09605d07ab9ff67c3494bf7e3006795da5a623bd Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 14 Nov 2025 19:24:18 +0100 Subject: [PATCH] Implement ConfigContext for instance defaults --- webui/src/components/InstanceDialog.tsx | 11 ++- webui/src/contexts/ConfigContext.tsx | 100 ++++++++++++++++++++++++ webui/src/lib/api.ts | 4 + webui/src/main.tsx | 9 ++- webui/src/types/config.ts | 70 +++++++++++++++++ 5 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 webui/src/contexts/ConfigContext.tsx create mode 100644 webui/src/types/config.ts diff --git a/webui/src/components/InstanceDialog.tsx b/webui/src/components/InstanceDialog.tsx index 8711218..d88b2a3 100644 --- a/webui/src/components/InstanceDialog.tsx +++ b/webui/src/components/InstanceDialog.tsx @@ -14,6 +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"; interface InstanceDialogProps { open: boolean; @@ -29,6 +30,7 @@ const InstanceDialog: React.FC = ({ instance, }) => { const isEditing = !!instance; + const instanceDefaults = useInstanceDefaults(); const [instanceName, setInstanceName] = useState(""); const [formData, setFormData] = useState({}); @@ -45,17 +47,20 @@ const InstanceDialog: React.FC = ({ setInstanceName(instance.name); setFormData(instance.options || {}); } else { - // Reset form for new instance + // Reset form for new instance with defaults from config setInstanceName(""); setFormData({ - auto_restart: true, // Default value + auto_restart: instanceDefaults?.autoRestart ?? true, + max_restarts: instanceDefaults?.maxRestarts, + restart_delay: instanceDefaults?.restartDelay, + on_demand_start: instanceDefaults?.onDemandStart, backend_type: BackendType.LLAMA_CPP, // Default backend type backend_options: {}, }); } setNameError(""); // Reset any name errors } - }, [open, instance]); + }, [open, instance, instanceDefaults]); const handleFieldChange = (key: keyof CreateInstanceOptions, value: unknown) => { setFormData((prev) => { diff --git a/webui/src/contexts/ConfigContext.tsx b/webui/src/contexts/ConfigContext.tsx new file mode 100644 index 0000000..854e8b4 --- /dev/null +++ b/webui/src/contexts/ConfigContext.tsx @@ -0,0 +1,100 @@ +import { type ReactNode, createContext, useCallback, useContext, useEffect, useState } from 'react' +import { serverApi } from '@/lib/api' +import type { AppConfig } from '@/types/config' + +interface ConfigContextState { + config: AppConfig | null + isLoading: boolean + error: string | null +} + +interface ConfigContextActions { + refetchConfig: () => Promise +} + +type ConfigContextType = ConfigContextState & ConfigContextActions + +const ConfigContext = createContext(undefined) + +interface ConfigProviderProps { + children: ReactNode +} + +export const ConfigProvider = ({ children }: ConfigProviderProps) => { + const [config, setConfig] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + const fetchConfig = useCallback(async () => { + setIsLoading(true) + setError(null) + + try { + const data = await serverApi.getConfig() + setConfig(data) + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to load configuration' + setError(errorMessage) + console.error('Error loading config:', err) + } 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, + } + + return ( + + {children} + + ) +} + +export const useConfig = (): ConfigContextType => { + const context = useContext(ConfigContext) + if (context === undefined) { + throw new Error('useConfig must be used within a ConfigProvider') + } + return context +} + +// Helper hook to get instance default values from config +export const useInstanceDefaults = () => { + const { config } = useConfig() + + if (!config) { + return null + } + + return { + autoRestart: config.instances.default_auto_restart, + maxRestarts: config.instances.default_max_restarts, + restartDelay: config.instances.default_restart_delay, + onDemandStart: config.instances.default_on_demand_start, + } +} + +// Helper hook to get backend settings from config +export const useBackendConfig = () => { + const { config } = useConfig() + + if (!config) { + return null + } + + return config.backends +} diff --git a/webui/src/lib/api.ts b/webui/src/lib/api.ts index ef03408..2ac679c 100644 --- a/webui/src/lib/api.ts +++ b/webui/src/lib/api.ts @@ -1,4 +1,5 @@ import type { CreateInstanceOptions, Instance } from "@/types/instance"; +import type { AppConfig } from "@/types/config"; import { handleApiError } from "./errorUtils"; // Adding baseURI as a prefix to support being served behind a subpath @@ -73,6 +74,9 @@ export const serverApi = { // GET /backends/llama-cpp/devices getDevices: () => apiCall("/backends/llama-cpp/devices", {}, "text"), + + // GET /config + getConfig: () => apiCall("/config"), }; // Backend API functions diff --git a/webui/src/main.tsx b/webui/src/main.tsx index ab046c2..6418a1e 100644 --- a/webui/src/main.tsx +++ b/webui/src/main.tsx @@ -4,13 +4,16 @@ import App from './App' import { InstancesProvider } from './contexts/InstancesContext' import './index.css' import { AuthProvider } from './contexts/AuthContext' +import { ConfigProvider } from './contexts/ConfigContext' ReactDOM.createRoot(document.getElementById('root')!).render( - - - + + + + + , ) \ No newline at end of file diff --git a/webui/src/types/config.ts b/webui/src/types/config.ts new file mode 100644 index 0000000..21f15fa --- /dev/null +++ b/webui/src/types/config.ts @@ -0,0 +1,70 @@ +export interface BackendSettings { + command: string + args: string[] + environment?: Record + docker?: DockerSettings + response_headers?: Record +} + +export interface DockerSettings { + enabled: boolean + image: string + args: string[] + environment?: Record +} + +export interface BackendConfig { + 'llama-cpp': BackendSettings + vllm: BackendSettings + mlx: BackendSettings +} + +export interface ServerConfig { + host: string + port: number + allowed_origins: string[] + allowed_headers: string[] + enable_swagger: boolean + response_headers?: Record +} + +export interface InstancesConfig { + port_range: [number, number] + data_dir: string + configs_dir: string + logs_dir: string + auto_create_dirs: boolean + max_instances: number + max_running_instances: number + enable_lru_eviction: boolean + default_auto_restart: boolean + default_max_restarts: number + default_restart_delay: number + default_on_demand_start: boolean + on_demand_start_timeout: number + timeout_check_interval: number +} + +export interface AuthConfig { + require_inference_auth: boolean + inference_keys: string[] // Will be empty in sanitized response + require_management_auth: boolean + management_keys: string[] // Will be empty in sanitized response +} + +export interface NodeConfig { + address: string + api_key: string // Will be empty in sanitized response +} + +export interface AppConfig { + server: ServerConfig + backends: BackendConfig + instances: InstancesConfig + auth: AuthConfig + local_node: string + nodes: Record + version?: string + commit_hash?: string + build_time?: string +}