mirror of
https://github.com/ParisNeo/lollms_hub.git
synced 2026-05-04 03:01:01 -04:00
This commit updates several core components related to administrative settings, data store management, and memory management within the application. Specifically, it includes: - Updates to API routes for managing data stores and proxy response wrapping. - Refinements in the memory management system. - Adjustments to database migration logic related to settings. - Updates to relevant schema and template files.
611 lines
46 KiB
HTML
611 lines
46 KiB
HTML
{% extends "admin/base.html" %}
|
|
|
|
{% block title %}Settings{% endblock %}
|
|
|
|
{% block header_title %}Application Settings{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<form action="{{ url_for('admin_settings_post') }}" method="post" enctype="multipart/form-data" class="card-style space-y-8">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
|
|
<!-- Branding Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Branding</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="md:col-span-2">
|
|
<label for="branding_title" class="block text-sm font-medium text-current">Branding Title</label>
|
|
<input type="text" name="branding_title" id="branding_title" value="{{ settings.branding_title }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
|
|
{% if settings.branding_logo_url and 'uploads' in settings.branding_logo_url %}
|
|
<!-- VIEW 1: An image has been uploaded. Show preview and Remove button. -->
|
|
<div class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-current">Current Logo</label>
|
|
<div class="mt-2 flex items-center gap-4 p-4 border border-dashed border-gray-500 rounded-md">
|
|
<img src="{{ settings.branding_logo_url }}" alt="Current Logo" class="h-16 w-auto max-w-xs rounded-md bg-white/10 p-1">
|
|
<div class="flex-grow">
|
|
<p class="text-sm font-semibold">An uploaded logo is active.</p>
|
|
<p class="text-xs text-gray-400">To replace it, first remove the current one and save.</p>
|
|
</div>
|
|
<button type="submit" name="remove_logo" value="true" class="flex items-center justify-center px-3 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-md">
|
|
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<!-- VIEW 2: No uploaded image. Show URL and Upload fields. -->
|
|
<div class="md:col-span-2">
|
|
<label for="branding_logo_url" class="block text-sm font-medium text-current">Logo from URL</label>
|
|
<input type="url" name="branding_logo_url" id="branding_logo_url" value="{{ settings.branding_logo_url or '' }}" placeholder="https://example.com/logo.png" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label for="logo_file" class="block text-sm font-medium text-current">Or Upload Logo</label>
|
|
<input type="file" name="logo_file" id="logo_file" class="mt-1 block w-full text-sm rounded-md border border-gray-600 file:mr-4 file:py-2 file:px-4 file:rounded-l-md file:border-0 file:bg-[var(--color-primary-600)] file:text-white hover:file:bg-[var(--color-primary-700)]">
|
|
<p class="mt-1 text-xs text-gray-400">Max 2MB. Allowed types: PNG, JPG, GIF, SVG, WebP.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Appearance Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Appearance</h2>
|
|
|
|
<!-- THEME PREVIEW ENGINE -->
|
|
<script>
|
|
const THEME_MAP = {{ themes | tojson | safe }};
|
|
|
|
function applyLiveTheme() {
|
|
const selectedStyle = document.querySelector('input[name="ui_style"]:checked').value;
|
|
const selectedColor = document.querySelector('input[name="selected_theme"]:checked').value;
|
|
|
|
// 1. Update Body Class (UI Style)
|
|
const body = document.body;
|
|
// Remove all existing ui- classes
|
|
body.className = body.className.replace(/\bui-\S+/g, '');
|
|
body.classList.add('ui-' + selectedStyle);
|
|
|
|
// 2. Update CSS Variables (Primary Colors)
|
|
const colors = THEME_MAP[selectedColor];
|
|
if (colors) {
|
|
const root = document.documentElement;
|
|
root.style.setProperty('--color-primary-500', colors['500']);
|
|
root.style.setProperty('--color-primary-600', colors['600']);
|
|
root.style.setProperty('--color-primary-700', colors['700']);
|
|
root.style.setProperty('--color-primary-800', colors['800']);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const inputs = document.querySelectorAll('input[name="ui_style"], input[name="selected_theme"]');
|
|
inputs.forEach(input => {
|
|
input.addEventListener('change', applyLiveTheme);
|
|
});
|
|
});
|
|
</script>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
<div>
|
|
<label class="block text-sm font-medium text-current mb-2">UI Style</label>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="dark-glass" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'dark-glass' %}checked{% endif %}>
|
|
<span>Dark Glass</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="dark-flat" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'dark-flat' %}checked{% endif %}>
|
|
<span>Dark Flat</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="black" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'black' %}checked{% endif %}>
|
|
<span>Black</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="light-glass" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'light-glass' %}checked{% endif %}>
|
|
<span>Light Glass</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="light-flat" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'light-flat' %}checked{% endif %}>
|
|
<span>Light Flat</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="white" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'white' %}checked{% endif %}>
|
|
<span>White</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="aurora" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'aurora' %}checked{% endif %}>
|
|
<span>Aurora</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="dark-neumorphic" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'dark-neumorphic' %}checked{% endif %}>
|
|
<span>Dark Neumorphic</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="light-neumorphic" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'light-neumorphic' %}checked{% endif %}>
|
|
<span>Light Neumorphic</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="brutalism" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'brutalism' %}checked{% endif %}>
|
|
<span>Brutalism</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="retro-terminal" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'retro-terminal' %}checked{% endif %}>
|
|
<span>Retro Terminal</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="cyberpunk" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'cyberpunk' %}checked{% endif %}>
|
|
<span>Cyberpunk</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="material-flat" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'material-flat' %}checked{% endif %}>
|
|
<span>Material Flat</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="ui_style" value="ink" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.ui_style == 'ink' %}checked{% endif %}>
|
|
<span>Ink</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-current mb-2">Theme Color</label>
|
|
<div class="flex flex-wrap items-center gap-4">
|
|
{% for theme_name, theme_colors in themes.items() %}
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="radio" name="selected_theme" value="{{ theme_name }}" class="h-4 w-4 text-[var(--color-primary-600)]" {% if settings.selected_theme == theme_name %}checked{% endif %}>
|
|
<div class="flex items-center space-x-1">
|
|
<span class="w-5 h-5 rounded-full border border-gray-500" style="background-color: {{ theme_colors['600'] }};"></span>
|
|
<span class="capitalize text-sm">{{ theme_name }}</span>
|
|
</div>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Redis Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Redis & Rate Limiting</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="redis_host" class="block text-sm font-medium text-current">Redis Host</label>
|
|
<input type="text" name="redis_host" id="redis_host" value="{{ settings.redis_host }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div>
|
|
<label for="redis_port" class="block text-sm font-medium text-current">Redis Port</label>
|
|
<input type="number" name="redis_port" id="redis_port" value="{{ settings.redis_port }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div>
|
|
<label for="redis_username" class="block text-sm font-medium text-current">Redis Username</label>
|
|
<input type="text" name="redis_username" id="redis_username" value="{{ settings.redis_username or '' }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div>
|
|
<label for="redis_password" class="block text-sm font-medium text-current">Redis Password</label>
|
|
<input type="password" name="redis_password" id="redis_password" placeholder="Leave blank to keep current" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div>
|
|
<label for="model_update_interval_minutes" class="block text-sm font-medium text-current">Model Refresh Interval (minutes)</label>
|
|
<input type="number" name="model_update_interval_minutes" id="model_update_interval_minutes" value="{{ settings.model_update_interval_minutes }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label for="default_models_path" class="block text-sm font-medium text-current">Default Models Storage Path</label>
|
|
<input type="text" name="default_models_path" id="default_models_path" value="{{ settings.default_models_path }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm border border-white/10 bg-black/20 focus:border-sky-500">
|
|
<p class="mt-1 text-[10px] text-gray-500 italic">Relative to app root or absolute path. HF models will be downloaded here.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Instance Management Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Instance Management</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="instance_scan_start_port" class="block text-sm font-medium text-current">Scan Start Port</label>
|
|
<input type="number" name="instance_scan_start_port" id="instance_scan_start_port" value="{{ settings.instance_scan_start_port }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
<div>
|
|
<label for="instance_scan_end_port" class="block text-sm font-medium text-current">Scan End Port</label>
|
|
<input type="number" name="instance_scan_end_port" id="instance_scan_end_port" value="{{ settings.instance_scan_end_port }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]">
|
|
</div>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-400">Controls the range of local ports the Fortress scans to discover unmanaged Ollama instances.</p>
|
|
</div>
|
|
|
|
<!-- IP Filtering -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">IP Filtering</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="allowed_ips" class="block text-sm font-medium text-current">Allowed IPs</label>
|
|
<textarea name="allowed_ips" id="allowed_ips" rows="3" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]" placeholder="e.g., 192.168.1.10, 10.0.0.0/8, *">{{ settings.allowed_ips }}</textarea>
|
|
<p class="mt-1 text-xs text-gray-400">Comma-separated. Use * to allow all.</p>
|
|
</div>
|
|
<div>
|
|
<label for="denied_ips" class="block text-sm font-medium text-current">Denied IPs</label>
|
|
<textarea name="denied_ips" id="denied_ips" rows="3" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]" placeholder="e.g., 1.2.3.4">{{ settings.denied_ips }}</textarea>
|
|
<p class="mt-1 text-xs text-gray-400">Comma-separated. This list is checked after the allow list.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Management -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Diagnostic Logs & Retention</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="log_max_size_mb" class="block text-sm font-medium text-current">Max Log Size (MB)</label>
|
|
<input type="number" name="log_max_size_mb" id="log_max_size_mb" value="{{ settings.log_max_size_mb }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label for="log_backup_count" class="block text-sm font-medium text-current">Rotary History (Files)</label>
|
|
<input type="number" name="log_backup_count" id="log_backup_count" value="{{ settings.log_backup_count }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm">
|
|
</div>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-400">Controls the automatic cleanup of application logs. Current file: <code class="text-indigo-400">lollms_hub.log</code></p>
|
|
</div>
|
|
|
|
<!-- Neural Memory Recovery -->
|
|
<div class="card-style border-l-4 border-fuchsia-500 bg-fuchsia-500/5" id="memory-settings-zone">
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2 flex items-center gap-2">
|
|
<svg class="w-6 h-6 text-fuchsia-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
Neural Memory Recovery
|
|
</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
<div class="space-y-4">
|
|
<label class="block text-[10px] font-black text-gray-500 uppercase">Recovery Logic</label>
|
|
<select name="memory_recovery_mode" class="w-full bg-black/20 border border-white/10 rounded p-2 text-sm">
|
|
<option value="handles" {% if settings.memory_recovery_mode == 'handles' %}selected{% endif %}>Handles (AI Manual Search - Saves Context)</option>
|
|
<option value="vector" {% if settings.memory_recovery_mode == 'vector' %}selected{% endif %}>Vector (Automatic RAG - High Fidelity)</option>
|
|
</select>
|
|
<p class="text-[10px] text-gray-500 italic">Vector mode uses the Shared Routing Vectorizer defined in SB-MRA settings.</p>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-[10px] font-black text-gray-500 uppercase">Memory Top K</label>
|
|
<input type="number" name="memory_vector_top_k" value="{{ settings.memory_vector_top_k }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-black text-gray-500 uppercase">Similarity Min</label>
|
|
<input type="number" step="0.05" name="memory_vector_threshold" value="{{ settings.memory_vector_threshold }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- External Integration Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">External Search & AI Tools</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="google_search_api_key" class="block text-sm font-medium text-current">Google Search API Key (SerpApi)</label>
|
|
<input type="password" name="google_search_api_key" id="google_search_api_key" value="{{ settings.google_search_api_key or '' }}" placeholder="Enter your SerpApi key" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm border border-white/10 bg-black/20">
|
|
<p class="mt-1 text-[10px] text-gray-500">Required for the 'Google Search' source in Skill Architect.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Auto-Routing Intelligence (SB-MRA) -->
|
|
<div class="card-style border-l-4 border-indigo-500 bg-indigo-500/5">
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2 flex items-center gap-2">
|
|
<svg class="w-6 h-6 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
Cluster Intelligence (SB-MRA)
|
|
</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
<div class="space-y-4">
|
|
<label class="flex items-center gap-3 cursor-pointer group">
|
|
<input type="checkbox" name="enable_sb_mra" value="true" class="h-5 w-5 rounded text-indigo-600" {% if settings.enable_sb_mra %}checked{% endif %}>
|
|
<div>
|
|
<span class="block font-bold group-hover:text-indigo-400">Activate Semantic-Bayesian Routing</span>
|
|
<span class="text-[11px] text-gray-500 italic">Enables context-aware optimization and ecological penalties.</span>
|
|
</div>
|
|
</label>
|
|
<div class="grid grid-cols-2 gap-4 pt-2">
|
|
<div>
|
|
<label class="block text-[10px] font-black text-gray-500 uppercase">Weight: Priority</label>
|
|
<input type="number" step="0.1" name="routing_weight_priority" value="{{ settings.routing_weight_priority }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-black text-gray-500 uppercase">Weight: Reliability</label>
|
|
<input type="number" step="0.1" name="routing_weight_reliability" value="{{ settings.routing_weight_reliability }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-black text-emerald-500 uppercase">Weight: Ecology (EPP)</label>
|
|
<input type="number" step="0.1" name="routing_weight_ecology" value="{{ settings.routing_weight_ecology }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-black text-purple-500 uppercase">Weight: Semantic (SAF)</label>
|
|
<input type="number" step="0.1" name="routing_weight_semantic" value="{{ settings.routing_weight_semantic }}" class="w-full bg-black/20 rounded p-2 text-xs">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4 border-l border-white/5 pl-6">
|
|
<h4 class="text-xs font-bold text-gray-400 uppercase tracking-widest">Routing Vectorizer</h4>
|
|
<select name="routing_vectorizer_name" class="w-full bg-black/20 border border-white/10 rounded p-2 text-sm">
|
|
<option value="tf_idf" {% if settings.routing_vectorizer_name == 'tf_idf' %}selected{% endif %}>TF-IDF (Fast/CPU)</option>
|
|
<option value="sentense_transformer" {% if settings.routing_vectorizer_name == 'sentense_transformer' %}selected{% endif %}>SentenceTransformer (High Quality)</option>
|
|
<option value="ollama" {% if settings.routing_vectorizer_name == 'ollama' %}selected{% endif %}>Ollama Loopback</option>
|
|
</select>
|
|
<input type="text" name="routing_vectorizer_model" value="{{ settings.routing_vectorizer_model or '' }}" placeholder="Model name (e.g. all-MiniLM-L6-v2)" class="w-full bg-black/20 border border-white/10 rounded p-2 text-xs">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hub Orchestration -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Hub Orchestration</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="md:col-span-2 bg-indigo-500/5 border border-indigo-500/20 p-4 rounded-xl">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<span class="block font-bold text-indigo-400">Master Guided Tours Toggle</span>
|
|
<span class="text-xs text-gray-500">Global control for all interactive walkthroughs.</span>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" name="enable_tours" id="master-tours-toggle" value="true" class="sr-only peer" {% if settings.enable_tours %}checked{% endif %}>
|
|
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none 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-indigo-600"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div id="tour-sub-settings" class="mt-4 pt-4 border-t border-white/10 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 {% if not settings.enable_tours %}hidden{% endif %}">
|
|
{% set tour_items = [
|
|
('Dashboard', 'tour_dashboard', settings.tour_dashboard),
|
|
('Models Manager', 'tour_models', settings.tour_models),
|
|
('Workflows', 'tour_workflows', settings.tour_workflows),
|
|
('Data Stores', 'tour_datastores', settings.tour_datastores),
|
|
('Node Studio', 'tour_nodes', settings.tour_nodes)
|
|
] %}
|
|
{% for label, name, active in tour_items %}
|
|
<div class="flex items-center justify-between p-2 bg-black/20 rounded-lg border border-white/5">
|
|
<span class="text-[10px] font-black uppercase text-gray-400">{{ label }}</span>
|
|
<label class="relative inline-flex items-center scale-75 cursor-pointer">
|
|
<input type="checkbox" name="{{ name }}" value="true" class="sr-only peer" {% if active %}checked{% endif %}>
|
|
<div class="w-11 h-6 bg-gray-700 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-500"></div>
|
|
</label>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('master-tours-toggle').addEventListener('change', (e) => {
|
|
document.getElementById('tour-sub-settings').classList.toggle('hidden', !e.target.checked);
|
|
});
|
|
</script>
|
|
<div class="md:col-span-2 bg-indigo-500/5 border border-indigo-500/20 p-4 rounded-xl flex items-center justify-between">
|
|
<div>
|
|
<span class="block font-bold text-indigo-400">Workflow Debug Mode</span>
|
|
<span class="text-xs text-gray-500">When enabled, every node execution in a cognitive graph will emit a live trace to the Live Flow dashboard.</span>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" name="enable_debug_mode" value="true" class="sr-only peer" {% if settings.enable_debug_mode %}checked{% endif %}>
|
|
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none 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-indigo-600"></div>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label for="admin_agent_name" class="block text-sm font-medium text-current">Management Agent</label>
|
|
<select name="admin_agent_name" id="admin_agent_name" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm">
|
|
<option value="">None (Disable internal enhancements)</option>
|
|
{% for agent_name in agent_names %}
|
|
<option value="{{ agent_name }}" {% if settings.admin_agent_name == agent_name %}selected{% endif %}>{{ agent_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="mt-1 text-xs text-gray-400">This agent is used for administrative tasks, like rewriting system prompts using the "Enhance" feature.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gateway Protocol Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">Gateway Protocols</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 bg-white/5 p-4 rounded-lg">
|
|
<div class="space-y-4">
|
|
<label class="flex items-center gap-3 cursor-pointer group">
|
|
<input type="checkbox" name="enable_ollama_api" value="true" class="h-5 w-5 rounded text-indigo-600" {% if settings.enable_ollama_api %}checked{% endif %}>
|
|
<div>
|
|
<span class="block font-bold group-hover:text-indigo-400">Ollama API Protocol</span>
|
|
<span class="text-[11px] text-gray-500">Exposes /api/chat, /api/generate on Primary Port ({{ settings.PROXY_PORT }}).</span>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-center gap-3 cursor-pointer group">
|
|
<input type="checkbox" name="enable_openai_api" value="true" class="h-5 w-5 rounded text-indigo-600" {% if settings.enable_openai_api %}checked{% endif %}>
|
|
<div>
|
|
<span class="block font-bold group-hover:text-indigo-400">OpenAI API Protocol</span>
|
|
<span class="text-[11px] text-gray-500">Exposes /v1/chat/completions on Port {{ settings.openai_port }}.</span>
|
|
</div>
|
|
</label>
|
|
<div class="pt-2 border-t border-white/5">
|
|
<div class="pt-2 border-t border-white/5">
|
|
<label class="flex items-center gap-3 cursor-pointer group">
|
|
<input type="checkbox" name="enable_bot_mode" value="true" class="h-5 w-5 rounded text-indigo-600" {% if settings.enable_bot_mode %}checked{% endif %}>
|
|
<div>
|
|
<span class="block font-bold group-hover:text-indigo-400">Integrated Bot Orchestration</span>
|
|
<span class="text-[11px] text-gray-500">Enables the <b>Bot Connectors</b> UI and background services for Telegram, Discord, and Slack.</span>
|
|
</div>
|
|
</label>
|
|
<div class="mt-3 p-3 bg-indigo-500/5 border border-indigo-500/20 rounded-lg">
|
|
<h4 class="text-[10px] font-black text-indigo-400 uppercase tracking-widest mb-1">Configuration Note</h4>
|
|
<p class="text-[11px] text-gray-400 leading-relaxed">
|
|
Enabling this starts a background manager that maintains persistent connections to messaging APIs.
|
|
It allows your <b>Virtual Agents</b> and <b>Workflows</b> to be reachable via DM or group chat.
|
|
</p>
|
|
</div>
|
|
{% if not settings.enable_bot_mode %}
|
|
<div class="mt-2 text-[10px] text-amber-500 flex items-center gap-2 font-bold uppercase tracking-tighter">
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
Currently Hidden from Sidebar
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="openai_port" class="block text-sm font-medium">OpenAI Dedicated Port</label>
|
|
<input type="number" name="openai_port" id="openai_port" value="{{ settings.openai_port }}" class="mt-1 block w-32 px-3 py-2 rounded-md">
|
|
<p class="mt-2 text-[10px] text-gray-500 italic">If set to the same as the Proxy Port ({{ settings.PROXY_PORT }}), both APIs will share the same listener.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Endpoint Security -->
|
|
<div>
|
|
<div>
|
|
<label for="blocked_ollama_endpoints" class="block text-sm font-medium text-current">Blocked Ollama Endpoints</label>
|
|
<textarea name="blocked_ollama_endpoints" id="blocked_ollama_endpoints" rows="3" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]" placeholder="e.g., pull, delete, create">{{ settings.blocked_ollama_endpoints }}</textarea>
|
|
<p class="mt-1 text-xs text-gray-400">Comma-separated list of Ollama API paths to block for API key holders (e.g., pull, delete). This protects your backend servers from resource-intensive tasks initiated by users. This does not affect the admin UI's model management features.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HTTPS/SSL Settings -->
|
|
<div>
|
|
<h2 class="card-header text-xl font-bold mb-4 pb-2">HTTPS/SSL Settings</h2>
|
|
<div class="space-y-6">
|
|
<!-- SSL Key -->
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-current mb-2">SSL Private Key</h3>
|
|
{% if settings.ssl_keyfile_content %}
|
|
<div class="flex items-center gap-4 p-4 border border-dashed border-green-500 rounded-md bg-green-900/20">
|
|
<svg class="w-8 h-8 text-green-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
|
<div class="flex-grow">
|
|
<p class="text-sm font-semibold">An uploaded key file is currently active.</p>
|
|
<p class="text-xs text-gray-400">Path: <code class="text-xs">{{ settings.ssl_keyfile }}</code></p>
|
|
</div>
|
|
<button type="submit" name="remove_ssl_key" value="true" class="flex items-center justify-center px-3 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-md">
|
|
Remove
|
|
</button>
|
|
</div>
|
|
{% else %}
|
|
<div>
|
|
<label for="ssl_keyfile" class="block text-sm font-medium text-current">Specify Key File Path</label>
|
|
<input type="text" name="ssl_keyfile" id="ssl_keyfile" value="{{ settings.ssl_keyfile or '' }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]" placeholder="/path/to/your/key.pem">
|
|
</div>
|
|
<div class="flex items-center my-3">
|
|
<hr class="flex-grow border-gray-600">
|
|
<span class="px-2 text-sm text-gray-400">OR</span>
|
|
<hr class="flex-grow border-gray-600">
|
|
</div>
|
|
<div>
|
|
<label for="ssl_key_file" class="block text-sm font-medium text-current">Upload Key File</label>
|
|
<input type="file" name="ssl_key_file" id="ssl_key_file" class="mt-1 block w-full text-sm rounded-md border border-gray-600 file:mr-4 file:py-2 file:px-4 file:rounded-l-md file:border-0 file:bg-[var(--color-primary-600)] file:text-white hover:file:bg-[var(--color-primary-700)]">
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- SSL Certificate -->
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-current mb-2">SSL Certificate</h3>
|
|
{% if settings.ssl_certfile_content %}
|
|
<div class="flex items-center gap-4 p-4 border border-dashed border-green-500 rounded-md bg-green-900/20">
|
|
<svg class="w-8 h-8 text-green-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
|
<div class="flex-grow">
|
|
<p class="text-sm font-semibold">An uploaded certificate file is currently active.</p>
|
|
<p class="text-xs text-gray-400">Path: <code class="text-xs">{{ settings.ssl_certfile }}</code></p>
|
|
</div>
|
|
<button type="submit" name="remove_ssl_cert" value="true" class="flex items-center justify-center px-3 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-md">
|
|
Remove
|
|
</button>
|
|
</div>
|
|
{% else %}
|
|
<div>
|
|
<label for="ssl_certfile" class="block text-sm font-medium text-current">Specify Certificate File Path</label>
|
|
<input type="text" name="ssl_certfile" id="ssl_certfile" value="{{ settings.ssl_certfile or '' }}" class="mt-1 block w-full px-3 py-2 rounded-md shadow-sm focus:outline-none focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-500)]" placeholder="/path/to/your/cert.pem">
|
|
</div>
|
|
<div class="flex items-center my-3">
|
|
<hr class="flex-grow border-gray-600">
|
|
<span class="px-2 text-sm text-gray-400">OR</span>
|
|
<hr class="flex-grow border-gray-600">
|
|
</div>
|
|
<div>
|
|
<label for="ssl_cert_file" class="block text-sm font-medium text-current">Upload Certificate File</label>
|
|
<input type="file" name="ssl_cert_file" id="ssl_cert_file" class="mt-1 block w-full text-sm rounded-md border border-gray-600 file:mr-4 file:py-2 file:px-4 file:rounded-l-md file:border-0 file:bg-[var(--color-primary-600)] file:text-white hover:file:bg-[var(--color-primary-700)]">
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-4 p-3 rounded-md text-sm warning-box">
|
|
<strong>Important:</strong> Changes to SSL settings require a full server restart to take effect.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Maintenance & Guidance Section -->
|
|
<div class="pt-8 mt-8 border-t border-white/10 space-y-6">
|
|
<h2 class="text-xl font-bold text-current mb-4">System Maintenance & Guidance</h2>
|
|
|
|
<div class="p-4 bg-white/5 border border-white/10 rounded-lg flex items-center justify-between">
|
|
<div>
|
|
<h3 class="font-bold text-sm">Reset Guided Tours</h3>
|
|
<p class="text-xs text-gray-400">Clear your browser's history for all page-specific and onboarding walkthroughs.</p>
|
|
</div>
|
|
<button type="button" onclick="resetAllTours()" class="px-4 py-2 bg-amber-600/20 hover:bg-amber-600/40 border border-amber-500/30 rounded text-xs font-black uppercase text-amber-400">Reset All Knowledge</button>
|
|
</div>
|
|
|
|
<script>
|
|
function resetAllTours() {
|
|
if(!confirm("This will re-enable all help popups across the entire app. Proceed?")) return;
|
|
|
|
// Clear keys matching the tour pattern
|
|
Object.keys(localStorage).forEach(key => {
|
|
if (key.startsWith('tour_') || key === 'hub_wizard_complete') {
|
|
localStorage.removeItem(key);
|
|
}
|
|
});
|
|
|
|
window.location.href = '/admin/dashboard?start_tour=true';
|
|
}
|
|
</script>
|
|
|
|
<div class="p-4 bg-red-900/10 border border-red-500/20 rounded-lg flex items-center justify-between">
|
|
<div>
|
|
<h3 class="font-bold text-sm text-red-400">Update Static Dependencies</h3>
|
|
<p class="text-xs text-gray-500">Re-downloads all local JS/CSS libraries (LiteGraph, Chart.js, etc.) from secure sources.</p>
|
|
</div>
|
|
<button type="submit" form="refresh-assets-form" class="px-4 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded text-xs font-black uppercase">Update Libraries</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Final Page Tour Definitions -->
|
|
<script>
|
|
const SETTINGS_TOUR = [
|
|
{
|
|
target: null,
|
|
text: "Welcome to the <b>Fortress Engine Room</b>. Let's calibrate your cluster settings."
|
|
},
|
|
{
|
|
target: '#memory-settings-zone',
|
|
text: "<b>Neural Memory</b>: Choose between 'Handles' (where the AI manually fetches old memories) or 'Vector' (which uses SafeStore RAG to automatically inject relevant facts into the prompt)."
|
|
},
|
|
{
|
|
target: '[name="enable_sb_mra"]',
|
|
text: "<b>SB-MRA Logic</b>: This is the brain of the Gateway. It balances Bayesian reliability with ecological impact. Adjust the weights to prioritize speed over carbon savings, or vice versa."
|
|
},
|
|
{
|
|
target: '[name="routing_vectorizer_name"]',
|
|
text: "<b>Shared Vectorizer</b>: Used for both SB-MRA semantic routing and Vector Memory recovery. Using <b>SentenceTransformer</b> is recommended for high-fidelity local execution."
|
|
},
|
|
{
|
|
target: '[name="admin_agent_name"]',
|
|
text: "<b>Management Agent</b>: Select an agent to act as the Hub's internal 'Architect'. This agent will help build new tools, nodes, and fix code bugs."
|
|
}
|
|
];
|
|
|
|
window.addEventListener('load', () => {
|
|
window.startCustomTour(SETTINGS_TOUR, 'tour_settings_v1', true);
|
|
});
|
|
</script>
|
|
|
|
<!-- Save Button (Properly inside the main form) -->
|
|
<div class="flex justify-end pt-8 border-t border-white/10 mt-8">
|
|
<button type="submit" class="w-full md:w-auto justify-center py-3 px-10 border border-transparent rounded-xl shadow-2xl text-sm font-black uppercase tracking-widest text-white bg-[var(--color-primary-600)] hover:bg-[var(--color-primary-500)] transition-all transform hover:scale-105 active:scale-95">
|
|
Save All Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Hidden maintenance form remains separate as it points to a different route -->
|
|
<form id="refresh-assets-form" action="{{ url_for('admin_refresh_vendor_assets') }}" method="post" class="hidden">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
</form>
|
|
</div>
|
|
{% endblock %}
|