|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
{{template "views/partials/head" .}} |
|
|
|
|
|
<body class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]"> |
|
|
<div class="flex flex-col min-h-screen" x-data="settingsDashboard()"> |
|
|
|
|
|
{{template "views/partials/navbar" .}} |
|
|
|
|
|
|
|
|
<div class="fixed top-20 right-4 z-50 space-y-2" style="max-width: 400px;"> |
|
|
<template x-for="notification in notifications" :key="notification.id"> |
|
|
<div x-show="true" |
|
|
x-transition:enter="transition ease-out duration-200" |
|
|
x-transition:enter-start="opacity-0" |
|
|
x-transition:enter-end="opacity-100" |
|
|
x-transition:leave="transition ease-in duration-150" |
|
|
x-transition:leave-start="opacity-100" |
|
|
x-transition:leave-end="opacity-0" |
|
|
:class="notification.type === 'error' ? 'bg-red-500' : 'bg-green-500'" |
|
|
class="rounded-lg p-4 text-white flex items-start space-x-3"> |
|
|
<div class="flex-shrink-0"> |
|
|
<i :class="notification.type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" class="text-xl"></i> |
|
|
</div> |
|
|
<div class="flex-1 min-w-0"> |
|
|
<p class="text-sm font-medium break-words" x-text="notification.message"></p> |
|
|
</div> |
|
|
<button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:opacity-80 transition-opacity"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
</template> |
|
|
</div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-6 flex-grow max-w-4xl"> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<h1 class="h2"> |
|
|
Application Settings |
|
|
</h1> |
|
|
<a href="/manage" |
|
|
class="inline-flex items-center text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors"> |
|
|
<i class="fas fa-arrow-left mr-2 text-sm"></i> |
|
|
<span class="text-sm">Back to Manage</span> |
|
|
</a> |
|
|
</div> |
|
|
<p class="text-sm text-[var(--color-text-secondary)]">Configure watchdog and backend request settings</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<form @submit.prevent="saveSettings()" class="space-y-6"> |
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-shield-alt mr-2 text-[var(--color-primary)] text-sm"></i> |
|
|
Watchdog Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure automatic monitoring and management of backend processes |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Watchdog</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable automatic monitoring of backend processes</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.watchdog_enabled" |
|
|
@change="updateWatchdogEnabled()" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Idle Check</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Automatically stop backends that are idle for too long</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.watchdog_idle_enabled" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
class="sr-only peer" :class="!settings.watchdog_enabled ? 'opacity-50' : ''"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Idle Timeout</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Time before an idle backend is stopped (e.g., 15m, 1h)</p> |
|
|
<input type="text" x-model="settings.watchdog_idle_timeout" |
|
|
:disabled="!settings.watchdog_idle_enabled" |
|
|
placeholder="15m" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-border)]" |
|
|
:class="!settings.watchdog_idle_enabled ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Busy Check</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Automatically stop backends that are busy for too long (stuck processes)</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.watchdog_busy_enabled" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
class="sr-only peer" :class="!settings.watchdog_enabled ? 'opacity-50' : ''"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Busy Timeout</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Time before a busy backend is stopped (e.g., 5m, 30m)</p> |
|
|
<input type="text" x-model="settings.watchdog_busy_timeout" |
|
|
:disabled="!settings.watchdog_busy_enabled" |
|
|
placeholder="5m" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-border)]" |
|
|
:class="!settings.watchdog_busy_enabled ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Check Interval</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">How often the watchdog checks backends and memory usage (e.g., 2s, 30s)</p> |
|
|
<input type="text" x-model="settings.watchdog_interval" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
placeholder="2s" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-border)]" |
|
|
:class="!settings.watchdog_enabled ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Force Eviction When Busy</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Allow evicting models even when they have active API calls (default: disabled for safety)</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.force_eviction_when_busy" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
class="sr-only peer" :class="!settings.watchdog_enabled ? 'opacity-50' : ''"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">LRU Eviction Max Retries</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Maximum number of retries when waiting for busy models to become idle (default: 30)</p> |
|
|
<input type="number" x-model="settings.lru_eviction_max_retries" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
min="1" |
|
|
placeholder="30" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-border)]" |
|
|
:class="!settings.watchdog_enabled ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">LRU Eviction Retry Interval</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Interval between retries when waiting for busy models (e.g., 1s, 2s) (default: 1s)</p> |
|
|
<input type="text" x-model="settings.lru_eviction_retry_interval" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
placeholder="1s" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-border)]" |
|
|
:class="!settings.watchdog_enabled ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-6 pt-4 border-t border-[var(--color-primary-border)]/20"> |
|
|
<h3 class="text-md font-medium text-[var(--color-text-primary)] mb-3 flex items-center"> |
|
|
<i class="fas fa-memory mr-2 text-[var(--color-primary)] text-xs"></i> |
|
|
Memory Reclaimer |
|
|
</h3> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Automatically evict backends when memory usage exceeds a threshold. Uses GPU VRAM if available, otherwise system RAM. Uses LRU strategy. |
|
|
</p> |
|
|
|
|
|
|
|
|
<div x-data="resourceStatus()" x-init="fetchResource()" class="p-3 bg-[var(--color-bg-primary)] rounded mb-4"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<span class="text-xs text-[var(--color-text-secondary)]" x-text="resourceData && resourceData.type === 'gpu' ? 'Current GPU Status' : 'Current Memory Status'">Current Memory Status</span> |
|
|
<button @click="fetchResource()" class="text-[10px] text-[var(--color-primary)] hover:underline"> |
|
|
<i class="fas fa-sync-alt mr-1"></i>Refresh |
|
|
</button> |
|
|
</div> |
|
|
<template x-if="resourceData && resourceData.available && resourceData.type === 'gpu'"> |
|
|
<div class="space-y-2"> |
|
|
<template x-for="gpu in resourceData.gpus" :key="gpu.index"> |
|
|
<div class="flex items-center justify-between text-xs"> |
|
|
<span class="text-[var(--color-text-primary)] truncate max-w-[200px]" x-text="gpu.name"></span> |
|
|
<span class="font-mono" |
|
|
:class="gpu.usage_percent > 90 ? 'text-red-400' : gpu.usage_percent > 70 ? 'text-yellow-400' : 'text-green-400'" |
|
|
x-text="`${gpu.usage_percent.toFixed(1)}%`"></span> |
|
|
</div> |
|
|
</template> |
|
|
</div> |
|
|
</template> |
|
|
<template x-if="resourceData && resourceData.available && resourceData.type === 'ram'"> |
|
|
<div class="flex items-center justify-between text-xs"> |
|
|
<span class="text-[var(--color-text-primary)]">System RAM</span> |
|
|
<span class="font-mono" |
|
|
:class="resourceData.ram.usage_percent > 90 ? 'text-red-400' : resourceData.ram.usage_percent > 70 ? 'text-yellow-400' : 'text-green-400'" |
|
|
x-text="`${resourceData.ram.usage_percent.toFixed(1)}%`"></span> |
|
|
</div> |
|
|
</template> |
|
|
<template x-if="!resourceData || !resourceData.available"> |
|
|
<p class="text-xs text-[var(--color-text-secondary)]">Memory monitoring unavailable</p> |
|
|
</template> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Memory Reclaimer</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Evict backends when memory usage exceeds threshold</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.memory_reclaimer_enabled" |
|
|
:disabled="!settings.watchdog_enabled" |
|
|
class="sr-only peer" :class="!settings.watchdog_enabled ? 'opacity-50' : ''"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Memory Threshold (%)</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">When memory usage exceeds this, backends will be evicted (50-100%)</p> |
|
|
<div class="flex items-center gap-3"> |
|
|
<input type="range" x-model="settings.memory_reclaimer_threshold_percent" |
|
|
min="50" max="100" step="1" |
|
|
:disabled="!settings.memory_reclaimer_enabled || !settings.watchdog_enabled" |
|
|
class="flex-1 h-2 bg-[var(--color-bg-primary)] rounded-lg appearance-none cursor-pointer" |
|
|
:class="(!settings.memory_reclaimer_enabled || !settings.watchdog_enabled) ? 'opacity-50' : ''"> |
|
|
<span class="text-sm font-mono text-[var(--color-text-primary)] w-12 text-right" |
|
|
x-text="`${settings.memory_reclaimer_threshold_percent}%`"></span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-accent-light)] rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-cogs mr-2 text-[var(--color-accent)] text-sm"></i> |
|
|
Backend Request Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure how backends handle multiple requests |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Max Active Backends</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Maximum number of models to keep loaded at once (0 = unlimited, 1 = single backend mode). Least recently used models are evicted when limit is reached.</p> |
|
|
<input type="number" x-model="settings.max_active_backends" |
|
|
min="0" |
|
|
placeholder="0" |
|
|
@change="updateMaxActiveBackends()" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-accent-light)] rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-light)]"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Parallel Backend Requests</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable backends to handle multiple requests in parallel (if supported)</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.parallel_backend_requests" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-accent-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-accent)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-success-light)] rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-tachometer-alt mr-2 text-[var(--color-success)] text-sm"></i> |
|
|
Performance Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure default performance parameters for models |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Default Threads</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Number of threads to use for model inference (0 = auto)</p> |
|
|
<input type="number" x-model="settings.threads" |
|
|
min="0" |
|
|
placeholder="0" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-success-light)] rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-success-light)]"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Default Context Size</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Default context window size for models</p> |
|
|
<input type="number" x-model="settings.context_size" |
|
|
min="0" |
|
|
placeholder="512" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-success-light)] rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-success-light)]"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">F16 Precision</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Use 16-bit floating point precision</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.f16" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-success-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-success)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Debug Mode</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable debug logging</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.debug" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-success-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-success)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Tracing</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable tracing of requests and responses</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.enable_tracing" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-success-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-success)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Tracing Max Items</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Maximum number of tracing items to keep</p> |
|
|
<input type="number" x-model="settings.tracing_max_items" |
|
|
min="0" |
|
|
placeholder="0" |
|
|
:disabled="!settings.enable_tracing" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-success-light)] rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-success-light)]" |
|
|
:class="!settings.enable_tracing ? 'opacity-50 cursor-not-allowed' : ''"> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-warning-light)] rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-globe mr-2 text-[var(--color-warning)] text-sm"></i> |
|
|
API Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure CORS and CSRF protection |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable CORS</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable Cross-Origin Resource Sharing</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.cors" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-warning-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-warning)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">CORS Allow Origins</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Comma-separated list of allowed origins</p> |
|
|
<input type="text" x-model="settings.cors_allow_origins" |
|
|
placeholder="*" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-warning-light)] rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-warning-light)]"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Enable CSRF Protection</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable Cross-Site Request Forgery protection</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.csrf" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-warning-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-warning)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-accent)]/20 rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-network-wired mr-2 text-[var(--color-accent)] text-sm"></i> |
|
|
P2P Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure peer-to-peer networking |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">P2P Token</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Authentication token for P2P network (set to 0 to generate a new token)</p> |
|
|
<input type="text" x-model="settings.p2p_token" |
|
|
placeholder="" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-accent)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/50"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">P2P Network ID</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Network identifier for P2P connections</p> |
|
|
<input type="text" x-model="settings.p2p_network_id" |
|
|
placeholder="" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-accent)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/50"> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Federated Mode</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable federated instance mode</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.federated" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-accent)]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-accent)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary)]/20 rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-tasks mr-2 text-[var(--color-primary)] text-sm"></i> |
|
|
Agent Jobs Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure agent job retention and cleanup |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Job Retention Days</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Number of days to keep job history (default: 30)</p> |
|
|
<input type="number" x-model="settings.agent_job_retention_days" |
|
|
min="0" |
|
|
placeholder="30" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/50"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-error-light)] rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-key mr-2 text-[var(--color-error)] text-sm"></i> |
|
|
API Keys |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Manage API keys for authentication. Keys from environment variables are always included. |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">API Keys</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">List of API keys (one per line or comma-separated)</p> |
|
|
<textarea x-model="settings.api_keys_text" |
|
|
rows="4" |
|
|
placeholder="sk-1234567890abcdef sk-0987654321fedcba" |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-error-light)] rounded text-sm text-[var(--color-text-primary)] font-mono focus:outline-none focus:ring-2 focus:ring-[var(--color-error-light)]"></textarea> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Note: API keys are sensitive. Handle with care.</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-[var(--color-bg-secondary)] border border-[var(--color-accent)]/20 rounded-lg p-6"> |
|
|
<h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center"> |
|
|
<i class="fas fa-images mr-2 text-[var(--color-accent)] text-sm"></i> |
|
|
Gallery Settings |
|
|
</h2> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-4"> |
|
|
Configure model and backend galleries |
|
|
</p> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Autoload Galleries</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Automatically load model galleries on startup</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.autoload_galleries" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-accent)]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-accent)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">Autoload Backend Galleries</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mt-1">Automatically load backend galleries on startup</p> |
|
|
</div> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" x-model="settings.autoload_backend_galleries" |
|
|
class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-accent)]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-accent)]"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Model Galleries (JSON)</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Array of gallery objects with 'url' and 'name' fields</p> |
|
|
<textarea x-model="settings.galleries_json" |
|
|
rows="4" |
|
|
placeholder='[{"url": "https://example.com", "name": "Example Gallery"}]' |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-accent)]/20 rounded text-sm text-[var(--color-text-primary)] font-mono focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/50"></textarea> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Backend Galleries (JSON)</label> |
|
|
<p class="text-xs text-[var(--color-text-secondary)] mb-2">Array of backend gallery objects with 'url' and 'name' fields</p> |
|
|
<textarea x-model="settings.backend_galleries_json" |
|
|
rows="4" |
|
|
placeholder='[{"url": "https://example.com", "name": "Example Backend Gallery"}]' |
|
|
class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-accent)]/20 rounded text-sm text-[var(--color-text-primary)] font-mono focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/50"></textarea> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-yellow-500/10 border border-yellow-500/20 rounded-lg p-4" x-show="sourceInfo"> |
|
|
<div class="flex items-start"> |
|
|
<i class="fas fa-info-circle text-yellow-400 mr-2 mt-0.5"></i> |
|
|
<div class="flex-1"> |
|
|
<p class="text-sm text-yellow-300 font-medium mb-1">Configuration Source</p> |
|
|
<p class="text-xs text-yellow-200" x-text="'Settings are currently loaded from: ' + sourceInfo"></p> |
|
|
<p class="text-xs text-yellow-200 mt-1" x-show="sourceInfo === 'env'"> |
|
|
Environment variables take precedence. To modify settings via the UI, unset the relevant environment variables first. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex justify-end"> |
|
|
<button type="submit" |
|
|
:disabled="saving" |
|
|
class="btn-primary"> |
|
|
<i class="fas fa-save mr-2" :class="saving ? 'fa-spin fa-spinner' : ''"></i> |
|
|
<span x-text="saving ? 'Saving...' : 'Save Settings'"></span> |
|
|
</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
|
|
|
{{template "views/partials/footer" .}} |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
function settingsDashboard() { |
|
|
return { |
|
|
notifications: [], |
|
|
settings: { |
|
|
watchdog_enabled: false, |
|
|
watchdog_idle_enabled: false, |
|
|
watchdog_busy_enabled: false, |
|
|
watchdog_idle_timeout: '15m', |
|
|
watchdog_busy_timeout: '5m', |
|
|
watchdog_interval: '2s', |
|
|
force_eviction_when_busy: false, |
|
|
lru_eviction_max_retries: 30, |
|
|
lru_eviction_retry_interval: '1s', |
|
|
max_active_backends: 0, |
|
|
parallel_backend_requests: false, |
|
|
memory_reclaimer_enabled: false, |
|
|
memory_reclaimer_threshold: 0.95, |
|
|
memory_reclaimer_threshold_percent: 95, |
|
|
threads: 0, |
|
|
context_size: 0, |
|
|
f16: false, |
|
|
debug: false, |
|
|
enable_tracing: false, |
|
|
tracing_max_items: 0, |
|
|
cors: false, |
|
|
csrf: false, |
|
|
cors_allow_origins: '', |
|
|
p2p_token: '', |
|
|
p2p_network_id: '', |
|
|
federated: false, |
|
|
autoload_galleries: false, |
|
|
autoload_backend_galleries: false, |
|
|
galleries_json: '[]', |
|
|
backend_galleries_json: '[]', |
|
|
api_keys_text: '', |
|
|
agent_job_retention_days: 30 |
|
|
}, |
|
|
sourceInfo: '', |
|
|
saving: false, |
|
|
|
|
|
init() { |
|
|
this.loadSettings(); |
|
|
}, |
|
|
|
|
|
async loadSettings() { |
|
|
try { |
|
|
const response = await fetch('/api/settings'); |
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok) { |
|
|
this.settings = { |
|
|
watchdog_enabled: data.watchdog_enabled, |
|
|
watchdog_idle_enabled: data.watchdog_idle_enabled, |
|
|
watchdog_busy_enabled: data.watchdog_busy_enabled, |
|
|
watchdog_idle_timeout: data.watchdog_idle_timeout || '15m', |
|
|
watchdog_busy_timeout: data.watchdog_busy_timeout || '5m', |
|
|
watchdog_interval: data.watchdog_interval || '2s', |
|
|
force_eviction_when_busy: data.force_eviction_when_busy || false, |
|
|
lru_eviction_max_retries: data.lru_eviction_max_retries || 30, |
|
|
lru_eviction_retry_interval: data.lru_eviction_retry_interval || '1s', |
|
|
max_active_backends: data.max_active_backends || 0, |
|
|
parallel_backend_requests: data.parallel_backend_requests, |
|
|
memory_reclaimer_enabled: data.memory_reclaimer_enabled || false, |
|
|
memory_reclaimer_threshold: data.memory_reclaimer_threshold || 0.95, |
|
|
memory_reclaimer_threshold_percent: Math.round((data.memory_reclaimer_threshold || 0.95) * 100), |
|
|
threads: data.threads || 0, |
|
|
context_size: data.context_size || 0, |
|
|
f16: data.f16 || false, |
|
|
debug: data.debug || false, |
|
|
enable_tracing: data.enable_tracing || false, |
|
|
tracing_max_items: data.tracing_max_items || 0, |
|
|
cors: data.cors || false, |
|
|
csrf: data.csrf || false, |
|
|
cors_allow_origins: data.cors_allow_origins || '', |
|
|
p2p_token: data.p2p_token || '', |
|
|
p2p_network_id: data.p2p_network_id || '', |
|
|
federated: data.federated || false, |
|
|
autoload_galleries: data.autoload_galleries || false, |
|
|
autoload_backend_galleries: data.autoload_backend_galleries || false, |
|
|
galleries_json: JSON.stringify(data.galleries || [], null, 2), |
|
|
backend_galleries_json: JSON.stringify(data.backend_galleries || [], null, 2), |
|
|
api_keys_text: (data.api_keys || []).join('\n'), |
|
|
agent_job_retention_days: data.agent_job_retention_days || 30 |
|
|
}; |
|
|
this.sourceInfo = data.source || 'default'; |
|
|
} else { |
|
|
this.addNotification('Failed to load settings: ' + (data.error || 'Unknown error'), 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error loading settings:', error); |
|
|
this.addNotification('Failed to load settings: ' + error.message, 'error'); |
|
|
} |
|
|
}, |
|
|
|
|
|
updateWatchdogEnabled() { |
|
|
if (!this.settings.watchdog_enabled) { |
|
|
this.settings.watchdog_idle_enabled = false; |
|
|
this.settings.watchdog_busy_enabled = false; |
|
|
this.settings.memory_reclaimer_enabled = false; |
|
|
} |
|
|
}, |
|
|
|
|
|
updateMaxActiveBackends() { |
|
|
|
|
|
const value = parseInt(this.settings.max_active_backends) || 0; |
|
|
this.settings.max_active_backends = Math.max(0, value); |
|
|
}, |
|
|
|
|
|
updateTracingEnabled() { |
|
|
if (!this.settings.enable_tracing) { |
|
|
this.settings.tracing_max_items = 0; |
|
|
} |
|
|
}, |
|
|
|
|
|
async saveSettings() { |
|
|
if (this.saving) return; |
|
|
|
|
|
this.saving = true; |
|
|
|
|
|
try { |
|
|
const payload = {}; |
|
|
|
|
|
|
|
|
if (this.settings.watchdog_enabled !== undefined) { |
|
|
payload.watchdog_enabled = this.settings.watchdog_enabled; |
|
|
} |
|
|
if (this.settings.watchdog_idle_enabled !== undefined) { |
|
|
payload.watchdog_idle_enabled = this.settings.watchdog_idle_enabled; |
|
|
} |
|
|
if (this.settings.watchdog_busy_enabled !== undefined) { |
|
|
payload.watchdog_busy_enabled = this.settings.watchdog_busy_enabled; |
|
|
} |
|
|
if (this.settings.watchdog_idle_timeout) { |
|
|
payload.watchdog_idle_timeout = this.settings.watchdog_idle_timeout; |
|
|
} |
|
|
if (this.settings.watchdog_busy_timeout) { |
|
|
payload.watchdog_busy_timeout = this.settings.watchdog_busy_timeout; |
|
|
} |
|
|
if (this.settings.watchdog_interval) { |
|
|
payload.watchdog_interval = this.settings.watchdog_interval; |
|
|
} |
|
|
if (this.settings.force_eviction_when_busy !== undefined) { |
|
|
payload.force_eviction_when_busy = this.settings.force_eviction_when_busy; |
|
|
} |
|
|
if (this.settings.lru_eviction_max_retries !== undefined) { |
|
|
payload.lru_eviction_max_retries = parseInt(this.settings.lru_eviction_max_retries) || 30; |
|
|
} |
|
|
if (this.settings.lru_eviction_retry_interval) { |
|
|
payload.lru_eviction_retry_interval = this.settings.lru_eviction_retry_interval; |
|
|
} |
|
|
if (this.settings.max_active_backends !== undefined) { |
|
|
payload.max_active_backends = parseInt(this.settings.max_active_backends) || 0; |
|
|
} |
|
|
if (this.settings.parallel_backend_requests !== undefined) { |
|
|
payload.parallel_backend_requests = this.settings.parallel_backend_requests; |
|
|
} |
|
|
if (this.settings.memory_reclaimer_enabled !== undefined) { |
|
|
payload.memory_reclaimer_enabled = this.settings.memory_reclaimer_enabled; |
|
|
} |
|
|
if (this.settings.memory_reclaimer_threshold_percent !== undefined) { |
|
|
|
|
|
payload.memory_reclaimer_threshold = parseInt(this.settings.memory_reclaimer_threshold_percent) / 100; |
|
|
} |
|
|
if (this.settings.threads !== undefined) { |
|
|
payload.threads = parseInt(this.settings.threads) || 0; |
|
|
} |
|
|
if (this.settings.context_size !== undefined) { |
|
|
payload.context_size = parseInt(this.settings.context_size) || 0; |
|
|
} |
|
|
if (this.settings.f16 !== undefined) { |
|
|
payload.f16 = this.settings.f16; |
|
|
} |
|
|
if (this.settings.debug !== undefined) { |
|
|
payload.debug = this.settings.debug; |
|
|
} |
|
|
if (this.settings.enable_tracing !== undefined) { |
|
|
payload.enable_tracing = this.settings.enable_tracing; |
|
|
} |
|
|
if (this.settings.tracing_max_items !== undefined) { |
|
|
payload.tracing_max_items = parseInt(this.settings.tracing_max_items) || 0; |
|
|
} |
|
|
if (this.settings.cors !== undefined) { |
|
|
payload.cors = this.settings.cors; |
|
|
} |
|
|
if (this.settings.csrf !== undefined) { |
|
|
payload.csrf = this.settings.csrf; |
|
|
} |
|
|
if (this.settings.cors_allow_origins !== undefined) { |
|
|
payload.cors_allow_origins = this.settings.cors_allow_origins; |
|
|
} |
|
|
if (this.settings.p2p_token !== undefined) { |
|
|
payload.p2p_token = this.settings.p2p_token; |
|
|
} |
|
|
if (this.settings.p2p_network_id !== undefined) { |
|
|
payload.p2p_network_id = this.settings.p2p_network_id; |
|
|
} |
|
|
if (this.settings.federated !== undefined) { |
|
|
payload.federated = this.settings.federated; |
|
|
} |
|
|
if (this.settings.autoload_galleries !== undefined) { |
|
|
payload.autoload_galleries = this.settings.autoload_galleries; |
|
|
} |
|
|
if (this.settings.autoload_backend_galleries !== undefined) { |
|
|
payload.autoload_backend_galleries = this.settings.autoload_backend_galleries; |
|
|
} |
|
|
|
|
|
if (this.settings.api_keys_text !== undefined) { |
|
|
const keys = this.settings.api_keys_text |
|
|
.split(/[\n,]/) |
|
|
.map(k => k.trim()) |
|
|
.filter(k => k.length > 0); |
|
|
if (keys.length > 0) { |
|
|
payload.api_keys = keys; |
|
|
} else { |
|
|
|
|
|
payload.api_keys = []; |
|
|
} |
|
|
} |
|
|
|
|
|
if (this.settings.galleries_json) { |
|
|
try { |
|
|
payload.galleries = JSON.parse(this.settings.galleries_json); |
|
|
} catch (e) { |
|
|
this.addNotification('Invalid galleries JSON: ' + e.message, 'error'); |
|
|
this.saving = false; |
|
|
return; |
|
|
} |
|
|
} |
|
|
if (this.settings.backend_galleries_json) { |
|
|
try { |
|
|
payload.backend_galleries = JSON.parse(this.settings.backend_galleries_json); |
|
|
} catch (e) { |
|
|
this.addNotification('Invalid backend galleries JSON: ' + e.message, 'error'); |
|
|
this.saving = false; |
|
|
return; |
|
|
} |
|
|
} |
|
|
if (this.settings.agent_job_retention_days !== undefined) { |
|
|
payload.agent_job_retention_days = parseInt(this.settings.agent_job_retention_days) || 30; |
|
|
} |
|
|
|
|
|
const response = await fetch('/api/settings', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok && data.success) { |
|
|
this.addNotification('Settings saved successfully!', 'success'); |
|
|
|
|
|
setTimeout(() => this.loadSettings(), 1000); |
|
|
} else { |
|
|
this.addNotification('Failed to save settings: ' + (data.error || 'Unknown error'), 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error saving settings:', error); |
|
|
this.addNotification('Failed to save settings: ' + error.message, 'error'); |
|
|
} finally { |
|
|
this.saving = false; |
|
|
} |
|
|
}, |
|
|
|
|
|
addNotification(message, type = 'success') { |
|
|
const id = Date.now(); |
|
|
this.notifications.push({ id, message, type }); |
|
|
setTimeout(() => this.dismissNotification(id), 5000); |
|
|
}, |
|
|
|
|
|
dismissNotification(id) { |
|
|
this.notifications = this.notifications.filter(n => n.id !== id); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function resourceStatus() { |
|
|
return { |
|
|
resourceData: null, |
|
|
|
|
|
async fetchResource() { |
|
|
try { |
|
|
const response = await fetch('/api/resources'); |
|
|
if (response.ok) { |
|
|
this.resourceData = await response.json(); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error fetching resource data:', error); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
|
|
|
|