Implement ConfigContext for instance defaults

This commit is contained in:
2025-11-14 19:24:18 +01:00
parent 623e258a2a
commit 09605d07ab
5 changed files with 188 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ import ParseCommandDialog from "@/components/ParseCommandDialog";
import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard"; import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard";
import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard"; import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard";
import { Upload } from "lucide-react"; import { Upload } from "lucide-react";
import { useInstanceDefaults } from "@/contexts/ConfigContext";
interface InstanceDialogProps { interface InstanceDialogProps {
open: boolean; open: boolean;
@@ -29,6 +30,7 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
instance, instance,
}) => { }) => {
const isEditing = !!instance; const isEditing = !!instance;
const instanceDefaults = useInstanceDefaults();
const [instanceName, setInstanceName] = useState(""); const [instanceName, setInstanceName] = useState("");
const [formData, setFormData] = useState<CreateInstanceOptions>({}); const [formData, setFormData] = useState<CreateInstanceOptions>({});
@@ -45,17 +47,20 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
setInstanceName(instance.name); setInstanceName(instance.name);
setFormData(instance.options || {}); setFormData(instance.options || {});
} else { } else {
// Reset form for new instance // Reset form for new instance with defaults from config
setInstanceName(""); setInstanceName("");
setFormData({ 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_type: BackendType.LLAMA_CPP, // Default backend type
backend_options: {}, backend_options: {},
}); });
} }
setNameError(""); // Reset any name errors setNameError(""); // Reset any name errors
} }
}, [open, instance]); }, [open, instance, instanceDefaults]);
const handleFieldChange = (key: keyof CreateInstanceOptions, value: unknown) => { const handleFieldChange = (key: keyof CreateInstanceOptions, value: unknown) => {
setFormData((prev) => { setFormData((prev) => {

View File

@@ -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<void>
}
type ConfigContextType = ConfigContextState & ConfigContextActions
const ConfigContext = createContext<ConfigContextType | undefined>(undefined)
interface ConfigProviderProps {
children: ReactNode
}
export const ConfigProvider = ({ children }: ConfigProviderProps) => {
const [config, setConfig] = useState<AppConfig | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(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 (
<ConfigContext.Provider value={value}>
{children}
</ConfigContext.Provider>
)
}
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
}

View File

@@ -1,4 +1,5 @@
import type { CreateInstanceOptions, Instance } from "@/types/instance"; import type { CreateInstanceOptions, Instance } from "@/types/instance";
import type { AppConfig } from "@/types/config";
import { handleApiError } from "./errorUtils"; import { handleApiError } from "./errorUtils";
// Adding baseURI as a prefix to support being served behind a subpath // Adding baseURI as a prefix to support being served behind a subpath
@@ -73,6 +74,9 @@ export const serverApi = {
// GET /backends/llama-cpp/devices // GET /backends/llama-cpp/devices
getDevices: () => apiCall<string>("/backends/llama-cpp/devices", {}, "text"), getDevices: () => apiCall<string>("/backends/llama-cpp/devices", {}, "text"),
// GET /config
getConfig: () => apiCall<AppConfig>("/config"),
}; };
// Backend API functions // Backend API functions

View File

@@ -4,13 +4,16 @@ import App from './App'
import { InstancesProvider } from './contexts/InstancesContext' import { InstancesProvider } from './contexts/InstancesContext'
import './index.css' import './index.css'
import { AuthProvider } from './contexts/AuthContext' import { AuthProvider } from './contexts/AuthContext'
import { ConfigProvider } from './contexts/ConfigContext'
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<AuthProvider> <AuthProvider>
<InstancesProvider> <ConfigProvider>
<App /> <InstancesProvider>
</InstancesProvider> <App />
</InstancesProvider>
</ConfigProvider>
</AuthProvider> </AuthProvider>
</React.StrictMode>, </React.StrictMode>,
) )

70
webui/src/types/config.ts Normal file
View File

@@ -0,0 +1,70 @@
export interface BackendSettings {
command: string
args: string[]
environment?: Record<string, string>
docker?: DockerSettings
response_headers?: Record<string, string>
}
export interface DockerSettings {
enabled: boolean
image: string
args: string[]
environment?: Record<string, string>
}
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<string, string>
}
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<string, NodeConfig>
version?: string
commit_hash?: string
build_time?: string
}