AUTARCH v1.9 — remote monitoring, SSH manager, daemon, vault, cleanup
- Add Remote Monitoring Station with PIAP device profile system - Add SSH/SSHD manager with fail2ban integration - Add privileged daemon architecture for safe root operations - Add encrypted vault, HAL memory, HAL auto-analyst - Add network security suite, module creator, codex training - Add start.sh launcher script and GTK3 desktop launcher - Remove Output/ build artifacts, installer files, loose docs - Update .gitignore for runtime data and build artifacts - Update README for v1.9 with new launch method, screenshots, and features Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<p style="font-size:0.8rem;color:var(--text-secondary);margin-top:0.5rem">
|
||||
Configured backend: <strong style="color:var(--accent)">{{ llm_backend }}</strong>
|
||||
— select a tab, fill in settings, and click <em>Save & Activate</em>, then <em>Load Model</em> to initialise.
|
||||
— select a tab, fill in settings, and click <em>Save & Activate</em>, then <em>Initialize LLM</em> to initialise.
|
||||
</p>
|
||||
|
||||
<!-- Load / Status bar -->
|
||||
@@ -32,10 +32,10 @@
|
||||
<div id="llm-status-dot" style="width:10px;height:10px;border-radius:50%;
|
||||
background:var(--text-muted);flex-shrink:0" title="Not loaded"></div>
|
||||
<span id="llm-status-text" style="font-size:0.83rem;color:var(--text-secondary);flex:1">
|
||||
Not loaded — click <strong>Load Model</strong> to initialise the current backend.
|
||||
Not loaded — click <strong>Initialize LLM</strong> to initialise the current backend.
|
||||
</span>
|
||||
<button id="btn-llm-load" class="btn btn-primary btn-sm" onclick="loadLLM()">
|
||||
Load Model
|
||||
Initialize LLM
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="debugOpen()"
|
||||
title="Open debug console to see detailed load output">
|
||||
@@ -367,62 +367,71 @@
|
||||
<div class="section">
|
||||
<h2>Claude API</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary)">
|
||||
Requires an <a href="https://console.anthropic.com" target="_blank" rel="noopener">Anthropic account</a>.
|
||||
Get your API key from the console.
|
||||
Requires an <a href="https://console.anthropic.com" target="_blank" rel="noopener">Anthropic API</a> key.
|
||||
AUTARCH calls Claude directly for chat, agent, and analysis tasks.
|
||||
</p>
|
||||
<form method="POST" action="{{ url_for('settings.update_llm') }}" class="settings-form">
|
||||
<input type="hidden" name="backend" value="claude">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="claude-key">API Key</label>
|
||||
<input type="password" id="claude-key" name="api_key" value="{{ claude.api_key }}" placeholder="sk-ant-api03-...">
|
||||
<small>Stored in autarch_settings.conf — keep it safe.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-key">API Key</label>
|
||||
<input type="password" id="claude-key" value="{{ claude.api_key }}" placeholder="sk-ant-api03-...">
|
||||
<small>Stored in autarch_settings.conf — keep it safe.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="claude-model">Model</label>
|
||||
<select id="claude-model" name="model">
|
||||
<optgroup label="Claude 4.6">
|
||||
<option value="claude-opus-4-6" {% if claude.model == 'claude-opus-4-6' %}selected{% endif %}>claude-opus-4-6 (most capable)</option>
|
||||
<option value="claude-sonnet-4-6" {% if claude.model == 'claude-sonnet-4-6' %}selected{% endif %}>claude-sonnet-4-6 (balanced)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Claude 4.5">
|
||||
<option value="claude-opus-4-5" {% if claude.model == 'claude-opus-4-5' %}selected{% endif %}>claude-opus-4-5</option>
|
||||
<option value="claude-sonnet-4-5" {% if claude.model == 'claude-sonnet-4-5' %}selected{% endif %}>claude-sonnet-4-5</option>
|
||||
<option value="claude-haiku-4-5-20251001" {% if claude.model == 'claude-haiku-4-5-20251001' %}selected{% endif %}>claude-haiku-4-5 (fastest)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Claude 3.5 / 3">
|
||||
<option value="claude-3-5-sonnet-20241022" {% if claude.model == 'claude-3-5-sonnet-20241022' %}selected{% endif %}>claude-3-5-sonnet-20241022</option>
|
||||
<option value="claude-3-5-haiku-20241022" {% if claude.model == 'claude-3-5-haiku-20241022' %}selected{% endif %}>claude-3-5-haiku-20241022</option>
|
||||
<option value="claude-3-opus-20240229" {% if claude.model == 'claude-3-opus-20240229' %}selected{% endif %}>claude-3-opus-20240229</option>
|
||||
</optgroup>
|
||||
<div class="form-group">
|
||||
<label for="claude-model">Model</label>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center">
|
||||
<select id="claude-model" style="flex:1">
|
||||
<option value="{{ claude.model }}" selected>{{ claude.model }}</option>
|
||||
</select>
|
||||
<button class="btn btn-sm" onclick="claudeFetchModels()" id="btn-claude-refresh" title="Fetch available models from API">Refresh</button>
|
||||
</div>
|
||||
<small id="claude-model-hint">Click <strong>Refresh</strong> to fetch available models from the API.</small>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="claude-max-tok">Max Tokens</label>
|
||||
<input type="number" id="claude-max-tok" name="max_tokens" value="{{ claude.max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-temp">Temperature</label>
|
||||
<input type="number" id="claude-temp" name="temperature" value="{{ claude.temperature }}" step="0.05" min="0" max="1">
|
||||
<small>0–1. Claude default is 1.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-p">Top-P</label>
|
||||
<input type="number" id="claude-top-p" name="top_p" value="{{ claude.get('top_p', 1.0) }}" step="0.05" min="0" max="1">
|
||||
<small>Use with lower temp.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-k">Top-K</label>
|
||||
<input type="number" id="claude-top-k" name="top_k" value="{{ claude.get('top_k', 0) }}" min="0">
|
||||
<small>0 = disabled.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="claude-max-tok">Max Tokens</label>
|
||||
<input type="number" id="claude-max-tok" value="{{ claude.max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-temp">Temperature</label>
|
||||
<input type="number" id="claude-temp" value="{{ claude.temperature }}" step="0.05" min="0" max="1">
|
||||
<small>0–1. Claude default is 1.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-p">Top-P</label>
|
||||
<input type="number" id="claude-top-p" value="{{ claude.get('top_p', 1.0) }}" step="0.05" min="0" max="1">
|
||||
<small>Use with lower temp.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-k">Top-K</label>
|
||||
<input type="number" id="claude-top-k" value="{{ claude.get('top_k', 0) }}" min="0">
|
||||
<small>0 = disabled.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save & Activate Claude</button>
|
||||
</form>
|
||||
<!-- Action buttons + inline status -->
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;margin-top:0.5rem">
|
||||
<button class="btn btn-primary" onclick="claudeActivate()" id="btn-claude-activate">
|
||||
Activate Claude
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="claudeSave()" id="btn-claude-save">
|
||||
Save Settings
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="claudeReload()" id="btn-claude-reload" title="Re-test API key and reconnect">
|
||||
Reload
|
||||
</button>
|
||||
<div id="claude-status-dot" style="width:10px;height:10px;border-radius:50%;
|
||||
background:{% if llm_backend == 'claude' %}var(--success, #34c759){% else %}var(--text-muted){% endif %};flex-shrink:0"></div>
|
||||
<span id="claude-status-text" style="font-size:0.83rem;color:var(--text-secondary)">
|
||||
{% if llm_backend == 'claude' %}Active — {{ claude.model }}{% else %}Not active{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.35rem">
|
||||
<strong>Save</strong> stores settings + API key to encrypted vault.
|
||||
<strong>Reload</strong> re-tests the connection without changing settings.
|
||||
<strong>Activate</strong> saves + loads Claude as the active backend.
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- end tab-claude -->
|
||||
|
||||
@@ -605,9 +614,159 @@
|
||||
</div>
|
||||
</div><!-- end tab-huggingface -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<!-- AGENTS SECTION -->
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<div class="section" style="margin-top:2rem">
|
||||
<h2>Agent Configuration</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
|
||||
Configure the AI agent backends used by Agent Hal and Autonomy.
|
||||
Agents use the THOUGHT/ACTION/PARAMS loop to accomplish tasks with tools.
|
||||
</p>
|
||||
|
||||
<!-- Agent sub-tabs -->
|
||||
<div class="tab-bar" id="agent-tab-bar">
|
||||
<button class="tab active" onclick="agentTab('local')">Local Agent</button>
|
||||
<button class="tab" onclick="agentTab('claude')">Claude Agent</button>
|
||||
<button class="tab" onclick="agentTab('openai')">OpenAI Agent</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Local Agent Sub-Tab ──────────────────────────────────────── -->
|
||||
<div id="agent-tab-local" class="agent-tab-panel">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the currently loaded LLM backend (configured above) for agent operations.
|
||||
Best for offline or privacy-sensitive work.
|
||||
</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-local-steps">Max Steps</label>
|
||||
<input type="number" id="agent-local-steps" value="{{ agents.local_max_steps }}" min="1" max="100">
|
||||
<small>Maximum tool-use steps per task.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-local-verbose">
|
||||
<input type="checkbox" id="agent-local-verbose" {% if agents.local_verbose %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Verbose Output
|
||||
</label>
|
||||
<small>Show step-by-step agent reasoning.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('local')">Save Local Agent Settings</button>
|
||||
<span id="agent-local-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Claude Agent Sub-Tab ─────────────────────────────────────── -->
|
||||
<div id="agent-tab-claude" class="agent-tab-panel hidden">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the Anthropic Claude API with native tool use for agent operations.
|
||||
Requires a Claude API key (configured in the Claude tab above).
|
||||
Supports extended thinking and structured tool calls.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-model">Agent Model</label>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center">
|
||||
<select id="agent-claude-model" style="flex:1">
|
||||
<option value="{{ agents.claude_model }}" selected>{{ agents.claude_model }}</option>
|
||||
</select>
|
||||
<button class="btn btn-sm" onclick="agentClaudeFetchModels()" id="btn-agent-claude-refresh">Refresh</button>
|
||||
</div>
|
||||
<small id="agent-claude-model-hint">Click Refresh to fetch models from the API. Uses the API key from the Claude tab.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-tokens">Max Tokens</label>
|
||||
<input type="number" id="agent-claude-tokens" value="{{ agents.claude_max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-steps">Max Steps</label>
|
||||
<input type="number" id="agent-claude-steps" value="{{ agents.claude_max_steps }}" min="1" max="200">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-enabled">
|
||||
<input type="checkbox" id="agent-claude-enabled" {% if agents.claude_enabled %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Enable Claude Agent
|
||||
</label>
|
||||
<small>Allow agent tasks to use Claude.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('claude')">Save Claude Agent Settings</button>
|
||||
<span id="agent-claude-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── OpenAI Agent Sub-Tab ─────────────────────────────────────── -->
|
||||
<div id="agent-tab-openai" class="agent-tab-panel hidden">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the OpenAI API (or any compatible endpoint: Ollama, vLLM, LiteLLM) with function calling for agent operations.
|
||||
Requires an API key configured in the OpenAI tab above.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-model">Agent Model</label>
|
||||
<input type="text" id="agent-openai-model" value="{{ agents.openai_model }}" placeholder="gpt-4o"
|
||||
list="agent-openai-model-list">
|
||||
<datalist id="agent-openai-model-list">
|
||||
<option value="gpt-4o">
|
||||
<option value="gpt-4o-mini">
|
||||
<option value="gpt-4.1">
|
||||
<option value="gpt-4.1-mini">
|
||||
<option value="gpt-4.1-nano">
|
||||
<option value="o3">
|
||||
<option value="o4-mini">
|
||||
</datalist>
|
||||
<small>Model ID. For local servers (Ollama, etc.) use the name you pulled.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-base-url">Base URL</label>
|
||||
<input type="text" id="agent-openai-base-url" value="{{ agents.openai_base_url }}" placeholder="https://api.openai.com/v1">
|
||||
<small>Change for Ollama (<code>http://localhost:11434/v1</code>), vLLM, LiteLLM, etc.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-tokens">Max Tokens</label>
|
||||
<input type="number" id="agent-openai-tokens" value="{{ agents.openai_max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-steps">Max Steps</label>
|
||||
<input type="number" id="agent-openai-steps" value="{{ agents.openai_max_steps }}" min="1" max="200">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-enabled">
|
||||
<input type="checkbox" id="agent-openai-enabled" {% if agents.openai_enabled %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Enable OpenAI Agent
|
||||
</label>
|
||||
<small>Allow agent tasks to use OpenAI.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('openai')">Save OpenAI Agent Settings</button>
|
||||
<span id="agent-openai-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active agent backend indicator -->
|
||||
<div style="margin-top:0.75rem;padding:0.5rem 0.75rem;border-radius:var(--radius);border:1px solid var(--border);
|
||||
background:var(--bg-card);font-size:0.82rem;color:var(--text-secondary)">
|
||||
Active agent backend: <strong id="agent-active-backend" style="color:var(--accent)">{{ agents.backend }}</strong>
|
||||
— The agent will use this backend when processing tasks.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hidden { display: none !important; }
|
||||
.form-group small { display:block; font-size:0.72rem; color:var(--text-muted); margin-top:0.2rem; }
|
||||
.agent-tab-panel.hidden { display: none !important; }
|
||||
|
||||
/* ── GPU Preset Cards ───────────────────────────────────────────────────── */
|
||||
.gpu-presets {
|
||||
@@ -667,7 +826,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ── Load Model ────────────────────────────────────────────────────────────────
|
||||
// ── Initialize LLM ────────────────────────────────────────────────────────────────
|
||||
function loadLLM() {
|
||||
var btn = document.getElementById('btn-llm-load');
|
||||
var dot = document.getElementById('llm-status-dot');
|
||||
@@ -683,7 +842,7 @@ function loadLLM() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Load Model';
|
||||
btn.textContent = 'Initialize LLM';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
dot.title = 'Loaded';
|
||||
@@ -700,7 +859,7 @@ function loadLLM() {
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Load Model';
|
||||
btn.textContent = 'Initialize LLM';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
dot.title = 'Error';
|
||||
text.textContent = 'Request failed: ' + e.message;
|
||||
@@ -866,5 +1025,277 @@ function hfVerifyToken() {
|
||||
})
|
||||
.catch(function(e) { btn.textContent = 'Verify Token'; btn.disabled = false; alert('Request failed: ' + e.message); });
|
||||
}
|
||||
|
||||
// ── Claude: Activate (save + load in one click) ──────────────────────────────
|
||||
function claudeSave() {
|
||||
var btn = document.getElementById('btn-claude-save');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
btn.disabled = true; btn.textContent = 'Saving…';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('backend', 'claude');
|
||||
fd.append('api_key', document.getElementById('claude-key').value);
|
||||
fd.append('model', document.getElementById('claude-model').value);
|
||||
fd.append('max_tokens', document.getElementById('claude-max-tok').value);
|
||||
fd.append('temperature', document.getElementById('claude-temp').value);
|
||||
|
||||
fetch('/settings/llm', {method: 'POST', body: fd, redirect: 'manual'})
|
||||
.then(function() {
|
||||
btn.disabled = false; btn.textContent = 'Save Settings';
|
||||
text.innerHTML = '<span style="color:var(--success,#34c759)">✓ Settings saved to encrypted vault</span>';
|
||||
setTimeout(function() { text.textContent = ''; }, 3000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false; btn.textContent = 'Save Settings';
|
||||
text.textContent = 'Save failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function claudeReload() {
|
||||
var btn = document.getElementById('btn-claude-reload');
|
||||
var dot = document.getElementById('claude-status-dot');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
btn.disabled = true; btn.textContent = 'Reloading…';
|
||||
dot.style.background = '#f59e0b';
|
||||
text.innerHTML = '<em>Re-testing Claude connection…</em>';
|
||||
|
||||
fetch('/settings/llm/load', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Reload';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
text.innerHTML = '✓ <strong style="color:var(--success,#34c759)">Connected</strong> — ' + escapeHtml(d.model_name);
|
||||
} else {
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.innerHTML = '✕ ' + escapeHtml(d.error || 'Connection failed');
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false; btn.textContent = 'Reload';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function claudeActivate() {
|
||||
var btn = document.getElementById('btn-claude-activate');
|
||||
var dot = document.getElementById('claude-status-dot');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving…';
|
||||
dot.style.background = '#f59e0b';
|
||||
text.textContent = 'Saving settings…';
|
||||
|
||||
// Build form data from the Claude tab fields
|
||||
var fd = new FormData();
|
||||
fd.append('backend', 'claude');
|
||||
fd.append('api_key', document.getElementById('claude-key').value);
|
||||
fd.append('model', document.getElementById('claude-model').value);
|
||||
fd.append('max_tokens', document.getElementById('claude-max-tok').value);
|
||||
fd.append('temperature', document.getElementById('claude-temp').value);
|
||||
|
||||
// Step 1: Save settings via the existing update_llm route (no redirect — use fetch)
|
||||
fetch('/settings/llm', {method: 'POST', body: fd, redirect: 'manual'})
|
||||
.then(function() {
|
||||
// Step 2: Now load the backend
|
||||
btn.textContent = 'Loading…';
|
||||
text.innerHTML = '<em>Initialising Claude — please wait…</em>';
|
||||
return fetch('/settings/llm/load', {method: 'POST'});
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activate Claude';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
text.innerHTML = '✓ <strong style="color:var(--success,#34c759)">Claude active</strong> — '
|
||||
+ escapeHtml(d.model_name);
|
||||
// Also update the top-level status bar
|
||||
var topDot = document.getElementById('llm-status-dot');
|
||||
var topText = document.getElementById('llm-status-text');
|
||||
if (topDot) { topDot.style.background = 'var(--success, #34c759)'; topDot.title = 'Loaded'; }
|
||||
if (topText) { topText.innerHTML = '✓ <strong style="color:var(--success,#34c759)">claude</strong> ready — ' + escapeHtml(d.model_name); }
|
||||
} else {
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.innerHTML = '✕ <strong style="color:var(--danger,#ff3b30)">Load failed:</strong> '
|
||||
+ escapeHtml(d.error || 'Unknown error')
|
||||
+ ' — <em>check Debug Log for details</em>';
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activate Claude';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.textContent = 'Request failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent tab switching ───────────────────────────────────────────────────────
|
||||
function agentTab(name) {
|
||||
// Hide all agent panels
|
||||
var panels = document.querySelectorAll('.agent-tab-panel');
|
||||
for (var i = 0; i < panels.length; i++) panels[i].classList.add('hidden');
|
||||
// Deactivate all agent tabs
|
||||
var tabs = document.querySelectorAll('#agent-tab-bar .tab');
|
||||
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
|
||||
// Show selected
|
||||
var panel = document.getElementById('agent-tab-' + name);
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
// Activate tab button (find by text match)
|
||||
var labels = {local: 'Local Agent', claude: 'Claude Agent', openai: 'OpenAI Agent'};
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i].textContent.trim() === labels[name]) tabs[i].classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Agent save ───────────────────────────────────────────────────────────────
|
||||
function agentSave(tab) {
|
||||
var status = document.getElementById('agent-' + tab + '-status');
|
||||
var data = {backend: tab};
|
||||
|
||||
if (tab === 'local') {
|
||||
data.local_max_steps = parseInt(document.getElementById('agent-local-steps').value) || 20;
|
||||
data.local_verbose = document.getElementById('agent-local-verbose').checked;
|
||||
} else if (tab === 'claude') {
|
||||
data.claude_enabled = document.getElementById('agent-claude-enabled').checked;
|
||||
data.claude_model = document.getElementById('agent-claude-model').value;
|
||||
data.claude_max_tokens = parseInt(document.getElementById('agent-claude-tokens').value) || 16384;
|
||||
data.claude_max_steps = parseInt(document.getElementById('agent-claude-steps').value) || 30;
|
||||
} else if (tab === 'openai') {
|
||||
data.openai_enabled = document.getElementById('agent-openai-enabled').checked;
|
||||
data.openai_model = document.getElementById('agent-openai-model').value;
|
||||
data.openai_base_url = document.getElementById('agent-openai-base-url').value;
|
||||
data.openai_max_tokens = parseInt(document.getElementById('agent-openai-tokens').value) || 16384;
|
||||
data.openai_max_steps = parseInt(document.getElementById('agent-openai-steps').value) || 30;
|
||||
}
|
||||
|
||||
status.textContent = 'Saving…';
|
||||
fetch('/settings/agents/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) {
|
||||
status.innerHTML = '<span style="color:var(--success,#34c759)">✓ Saved</span>';
|
||||
document.getElementById('agent-active-backend').textContent = tab;
|
||||
} else {
|
||||
status.innerHTML = '<span style="color:var(--danger,#ff3b30)">✕ ' + escapeHtml(d.error || 'Error') + '</span>';
|
||||
}
|
||||
setTimeout(function() { status.textContent = ''; }, 4000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
status.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent Claude: Fetch models (reuses the same API endpoint) ────────────────
|
||||
function agentClaudeFetchModels() {
|
||||
var btn = document.getElementById('btn-agent-claude-refresh');
|
||||
var sel = document.getElementById('agent-claude-model');
|
||||
var hint = document.getElementById('agent-claude-model-hint');
|
||||
var curVal = sel.value;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fetching…';
|
||||
hint.textContent = 'Querying Anthropic API…';
|
||||
|
||||
fetch('/settings/llm/claude-models', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
if (!d.ok) {
|
||||
hint.textContent = 'Error: ' + (d.error || 'Unknown');
|
||||
return;
|
||||
}
|
||||
sel.innerHTML = '';
|
||||
var models = d.models || [];
|
||||
if (models.length === 0) {
|
||||
hint.textContent = 'No models returned.';
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal; opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
var foundCurrent = false;
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
var m = models[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name || m.id;
|
||||
if (m.id === curVal) { opt.selected = true; foundCurrent = true; }
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
if (!foundCurrent && curVal) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal + ' (current)'; opt.selected = true;
|
||||
sel.insertBefore(opt, sel.firstChild);
|
||||
}
|
||||
hint.textContent = models.length + ' models available.';
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
hint.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Claude: Fetch models from API ────────────────────────────────────────────
|
||||
function claudeFetchModels() {
|
||||
var btn = document.getElementById('btn-claude-refresh');
|
||||
var sel = document.getElementById('claude-model');
|
||||
var hint = document.getElementById('claude-model-hint');
|
||||
var curVal = sel.value;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fetching…';
|
||||
hint.textContent = 'Querying Anthropic API…';
|
||||
|
||||
fetch('/settings/llm/claude-models', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
if (!d.ok) {
|
||||
hint.textContent = 'Error: ' + (d.error || 'Unknown error');
|
||||
return;
|
||||
}
|
||||
// Rebuild the dropdown with live models
|
||||
sel.innerHTML = '';
|
||||
var models = d.models || [];
|
||||
if (models.length === 0) {
|
||||
hint.textContent = 'No models returned — check API key permissions.';
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal; opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
var foundCurrent = false;
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
var m = models[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name || m.id;
|
||||
if (m.id === curVal) { opt.selected = true; foundCurrent = true; }
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
// Keep current value selected even if not in list
|
||||
if (!foundCurrent && curVal) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal + ' (current)'; opt.selected = true;
|
||||
sel.insertBefore(opt, sel.firstChild);
|
||||
}
|
||||
hint.textContent = models.length + ' models available.';
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
hint.textContent = 'Request failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user