Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b02857cf04 | ||
|
|
be306cf86c |
@@ -83,8 +83,7 @@ $validTabs = ['monitor', 'scheduler', 'docker', 'watchdog', 'partnership', 'fall
|
|||||||
// include/ai.php only ever reads the *local* {HOST}_OLLAMA_URL — there is no Tailscale resolver
|
// include/ai.php only ever reads the *local* {HOST}_OLLAMA_URL — there is no Tailscale resolver
|
||||||
// in the PHP layer the way there is in the shell. On any other host the tab could only render
|
// in the PHP layer the way there is in the shell. On any other host the tab could only render
|
||||||
// and then fail its own health check.
|
// and then fail its own health check.
|
||||||
$_vv_ai = vv_is_ai_host()
|
$_vv_ai = vv_ai_ui_on();
|
||||||
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true';
|
|
||||||
if ($_vv_ai) $validTabs[] = 'ai';
|
if ($_vv_ai) $validTabs[] = 'ai';
|
||||||
|
|
||||||
if (!in_array($tab, $validTabs)) $tab = 'monitor';
|
if (!in_array($tab, $validTabs)) $tab = 'monitor';
|
||||||
|
|||||||
@@ -134,6 +134,16 @@ if (!vv_is_ai_host()) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Master switch, on the same footing as the host gate rather than only in front of ask. With
|
||||||
|
// AI_ENABLED false the tab is not in the tab list and the scheduler dock is not rendered, so
|
||||||
|
// nothing in the UI can legitimately reach any action here — including the cheap reads, which
|
||||||
|
// would otherwise still answer with index and token figures for a subsystem the operator has
|
||||||
|
// turned off. Not a 404: the switch is a setting, and the message names the setting.
|
||||||
|
if (!vv_ai_enabled()) {
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'AI_ENABLED is false — AI features are off']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// ── stats ─────────────────────────────────────────────────────────────────────
|
// ── stats ─────────────────────────────────────────────────────────────────────
|
||||||
if ($action === 'stats') {
|
if ($action === 'stats') {
|
||||||
echo json_encode(['ok' => true, 'stats' => vv_ai_stats()]);
|
echo json_encode(['ok' => true, 'stats' => vv_ai_stats()]);
|
||||||
@@ -220,10 +230,6 @@ if ($action === 'incident_add') {
|
|||||||
if ($action === 'ask') {
|
if ($action === 'ask') {
|
||||||
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
|
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
|
||||||
|
|
||||||
if (!vv_ai_enabled()) {
|
|
||||||
echo json_encode(['ok' => false, 'error' => 'AI_ENABLED is false — AI features are off']); exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cfg = vv_ai_config();
|
$cfg = vv_ai_config();
|
||||||
if ($cfg['model'] === '') {
|
if ($cfg['model'] === '') {
|
||||||
echo json_encode(['ok' => false, 'error' => 'No generation model configured']); exit;
|
echo json_encode(['ok' => false, 'error' => 'No generation model configured']); exit;
|
||||||
|
|||||||
@@ -268,6 +268,19 @@ function vv_is_ai_host(): bool {
|
|||||||
return vv_detect_host() === 'host1';
|
return vv_detect_host() === 'host1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the UI may offer anything AI at all: the right host, with the master switch on. Every
|
||||||
|
// AI surface asks this one question — the AI tab, the assistant dock on the Scheduler, and the
|
||||||
|
// two AI rows on the Tools card — so AI off means AI gone, not gone from most places.
|
||||||
|
//
|
||||||
|
// It lives here rather than in include/ai.php because the pages that need it do not all load
|
||||||
|
// that file; the Scheduler loads only config.php, and a gate that silently answers false where
|
||||||
|
// its definition is missing is worse than no gate. Reads AI_ENABLED directly for the same
|
||||||
|
// reason. Fail-closed on anything but the literal "true", matching the conf's own contract.
|
||||||
|
function vv_ai_ui_on(): bool {
|
||||||
|
return vv_is_ai_host()
|
||||||
|
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
function vv_read_conf_raw(string $filename): string {
|
function vv_read_conf_raw(string $filename): string {
|
||||||
$path = CONF_DIR . '/' . $filename;
|
$path = CONF_DIR . '/' . $filename;
|
||||||
return file_exists($path) ? file_get_contents($path) : '';
|
return file_exists($path) ? file_get_contents($path) : '';
|
||||||
|
|||||||
@@ -319,12 +319,12 @@ function vv_tools_scripts(): array {
|
|||||||
// chain. Moving the file to match the UI would break the grouping that explains it.
|
// chain. Moving the file to match the UI would break the grouping that explains it.
|
||||||
$ADOPTED = ['System_Essentials/server_reboot.sh'];
|
$ADOPTED = ['System_Essentials/server_reboot.sh'];
|
||||||
|
|
||||||
// The AI tools are adopted only where the index and the model actually live. Elsewhere they
|
// The AI tools are adopted only where the index and the model actually live, and only while
|
||||||
// are two rows that can only ever fail — the retrieval index is not synced between hosts and
|
// the subsystem is switched on. Elsewhere they are two rows that can only ever fail — the
|
||||||
// Ollama runs on one of them. vv_is_ai_host() comes from include/ai.php, which the page loads
|
// retrieval index is not synced between hosts, Ollama runs on one of them, and both scripts
|
||||||
// but this file does not require; absent it, assume not, because showing a tool that cannot
|
// refuse on their own unless AI_ENABLED is true. vv_ai_ui_on() is the same gate the AI tab
|
||||||
// work is worse than omitting one that could.
|
// and the assistant dock use, so AI off means no AI anywhere in the UI, not most of it.
|
||||||
if (function_exists('vv_is_ai_host') && vv_is_ai_host()) {
|
if (vv_ai_ui_on()) {
|
||||||
array_push($ADOPTED, 'AI/ai_index.sh', 'AI/ai_query.sh');
|
array_push($ADOPTED, 'AI/ai_index.sh', 'AI/ai_query.sh');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,11 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// The assistant dock renders only where the AI tab itself would: HOST1, with AI_ENABLED true.
|
// The assistant dock renders only where the AI tab itself would: HOST1, with AI_ENABLED true.
|
||||||
// Same two conditions, checked the same way, so the page cannot offer a chat the endpoint will
|
// The same gate function, not a second copy of the condition, so the page cannot offer a chat
|
||||||
// refuse — api/ai.php 404s every action off HOST1 regardless of what this page draws.
|
// the endpoint will refuse — api/ai.php rejects every action on both counts regardless of what
|
||||||
$_vv_ai_on = vv_is_ai_host()
|
// this page draws. With it false the dock markup is never emitted, and every caller into the
|
||||||
&& strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true';
|
// dock is guarded by vvAiDockOn(), which reads the element's absence.
|
||||||
|
$_vv_ai_on = vv_ai_ui_on();
|
||||||
|
|
||||||
// Setup mode — auto-open a conf file and force the editing sequence
|
// Setup mode — auto-open a conf file and force the editing sequence
|
||||||
$vv_setup_conf = preg_match('/^[\w.]+\.conf$/', $_GET['vv_setup'] ?? '')
|
$vv_setup_conf = preg_match('/^[\w.]+\.conf$/', $_GET['vv_setup'] ?? '')
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
// Values are escaped on render and on write-back.
|
// Values are escaped on render and on write-back.
|
||||||
//
|
//
|
||||||
// RENDERS
|
// RENDERS
|
||||||
// Storage mode selector, global toggles, general conf fields
|
// Storage mode selector, global toggles, AI master switch, general conf fields
|
||||||
//
|
//
|
||||||
// DEPENDS ON
|
// DEPENDS ON
|
||||||
// api/confform.php conf read/write → include/confform.php
|
// api/confform.php conf read/write → include/confform.php
|
||||||
@@ -42,6 +42,13 @@ $_enableLogging = ($_vars['ENABLE_LOGGING'] ?? 'false') === 'true';
|
|||||||
$_discordKey = $_myId . '_DISCORD_WEBHOOK';
|
$_discordKey = $_myId . '_DISCORD_WEBHOOK';
|
||||||
$_discordHook = $_vars[$_discordKey] ?? '';
|
$_discordHook = $_vars[$_discordKey] ?? '';
|
||||||
|
|
||||||
|
// AI. The card is drawn only on the host that can actually run it — elsewhere AI_ENABLED is a
|
||||||
|
// switch with nothing behind it, since the model and the index are HOST1's. vv_ai_ui_on() is
|
||||||
|
// not the test here: it folds the switch into the host check, and this card is how the switch
|
||||||
|
// gets turned back on. Fail-closed on anything but "true", matching the gate it feeds.
|
||||||
|
$_aiHost = vv_is_ai_host();
|
||||||
|
$_aiEnabled = strtolower(trim($_vars['AI_ENABLED'] ?? 'false')) === 'true';
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<style>
|
<style>
|
||||||
.vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; }
|
.vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; }
|
||||||
@@ -167,6 +174,35 @@ $_discordHook = $_vars[$_discordKey] ?? '';
|
|||||||
<span id="vv-ntf-fb" style="font-size:11px;"></span>
|
<span id="vv-ntf-fb" style="font-size:11px;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($_aiHost): ?>
|
||||||
|
<!-- AI card -->
|
||||||
|
<div class="vv-set-card">
|
||||||
|
<div class="vv-set-hdr">AI</div>
|
||||||
|
|
||||||
|
<label class="vv-set-tog-wrap" style="margin-bottom:12px;">
|
||||||
|
<div class="vv-set-tog-track<?= $_aiEnabled ? ' on' : '' ?>" id="vv-set-ai-tog"
|
||||||
|
onclick="vvSetAiToggle(this)"></div>
|
||||||
|
<div>
|
||||||
|
<div class="vv-set-tog-lbl">AI features</div>
|
||||||
|
<div class="vv-set-tog-sub">Master switch — the AI tab, the Scheduler assistant, and the AI tools on the Tools card</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="vv-set-info">
|
||||||
|
Varaverk works exactly as well with this off. Every feature that can lean on AI has a
|
||||||
|
complete non-AI path, so turning it off removes the AI surfaces from the WebGUI and
|
||||||
|
nothing else — no script changes behaviour, no schedule changes.
|
||||||
|
<br>
|
||||||
|
Off is total: with it off the AI tab is not in the tab bar, the Scheduler's assistant dock
|
||||||
|
is not drawn, the AI tools are not listed, and
|
||||||
|
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">api/ai.php</code>
|
||||||
|
refuses every request. This card is the way back on.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span id="vv-set-ai-fb" style="font-size:11px;color:#444;"></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Unraid API Key card -->
|
<!-- Unraid API Key card -->
|
||||||
<div class="vv-set-card">
|
<div class="vv-set-card">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||||
@@ -356,6 +392,43 @@ function vvNtfSaveWebhook() {
|
|||||||
.catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
.catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── AI ────────────────────────────────────────────────────────────────────────
|
||||||
|
// Reloads on success, unlike the notification toggles, because this one changes the page around
|
||||||
|
// it: the tab bar and the assistant dock are decided server-side at render, so without a reload
|
||||||
|
// the switch would read "off" with the AI tab still sitting next to it. The switch is moved
|
||||||
|
// first and reverted if the write fails, matching the toggles above.
|
||||||
|
function vvSetAiToggle(track) {
|
||||||
|
const on = !track.classList.contains('on');
|
||||||
|
const fb = document.getElementById('vv-set-ai-fb');
|
||||||
|
track.classList.toggle('on', on);
|
||||||
|
fb.style.color = '#444';
|
||||||
|
fb.textContent = 'Saving…';
|
||||||
|
|
||||||
|
const fd = new URLSearchParams();
|
||||||
|
fd.append('id', 'settings');
|
||||||
|
fd.append('changes', JSON.stringify([
|
||||||
|
{ file: 'master.conf', key: 'AI_ENABLED', value: on ? 'true' : 'false', type: 'scalar' }
|
||||||
|
]));
|
||||||
|
fetch('/plugins/varaverk/api/confform.php', { method: 'POST', body: fd })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => {
|
||||||
|
if (!d.ok) {
|
||||||
|
track.classList.toggle('on', !on);
|
||||||
|
fb.style.color = '#ef5350';
|
||||||
|
fb.textContent = d.error || 'Failed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fb.style.color = '#4caf50';
|
||||||
|
fb.textContent = (on ? 'Enabled' : 'Disabled') + ' — reloading…';
|
||||||
|
setTimeout(() => location.reload(), 700);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
track.classList.toggle('on', !on);
|
||||||
|
fb.style.color = '#ef5350';
|
||||||
|
fb.textContent = 'Request failed';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ── Unraid API Keys ───────────────────────────────────────────────────────────
|
// ── Unraid API Keys ───────────────────────────────────────────────────────────
|
||||||
function vvApiCheck() {
|
function vvApiCheck() {
|
||||||
const btn = document.getElementById('vv-api-check-btn');
|
const btn = document.getElementById('vv-api-check-btn');
|
||||||
|
|||||||
Reference in New Issue
Block a user