diff --git a/Plugin/unraid/Varaverk.page b/Plugin/unraid/Varaverk.page index 0bb5fbc..bcb7eac 100644 --- a/Plugin/unraid/Varaverk.page +++ b/Plugin/unraid/Varaverk.page @@ -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 // 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. -$_vv_ai = vv_is_ai_host() - && strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true'; +$_vv_ai = vv_ai_ui_on(); if ($_vv_ai) $validTabs[] = 'ai'; if (!in_array($tab, $validTabs)) $tab = 'monitor'; diff --git a/Plugin/unraid/api/ai.php b/Plugin/unraid/api/ai.php index 990639b..04866ad 100644 --- a/Plugin/unraid/api/ai.php +++ b/Plugin/unraid/api/ai.php @@ -134,6 +134,16 @@ if (!vv_is_ai_host()) { 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 ───────────────────────────────────────────────────────────────────── if ($action === 'stats') { echo json_encode(['ok' => true, 'stats' => vv_ai_stats()]); @@ -220,10 +230,6 @@ if ($action === 'incident_add') { if ($action === 'ask') { 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(); if ($cfg['model'] === '') { echo json_encode(['ok' => false, 'error' => 'No generation model configured']); exit; diff --git a/Plugin/unraid/include/config.php b/Plugin/unraid/include/config.php index 9dbac87..f1c1713 100644 --- a/Plugin/unraid/include/config.php +++ b/Plugin/unraid/include/config.php @@ -268,6 +268,19 @@ function vv_is_ai_host(): bool { 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 { $path = CONF_DIR . '/' . $filename; return file_exists($path) ? file_get_contents($path) : ''; diff --git a/Plugin/unraid/include/scheduler.php b/Plugin/unraid/include/scheduler.php index e977f0e..a3b7f31 100644 --- a/Plugin/unraid/include/scheduler.php +++ b/Plugin/unraid/include/scheduler.php @@ -319,12 +319,12 @@ function vv_tools_scripts(): array { // chain. Moving the file to match the UI would break the grouping that explains it. $ADOPTED = ['System_Essentials/server_reboot.sh']; - // The AI tools are adopted only where the index and the model actually live. Elsewhere they - // are two rows that can only ever fail — the retrieval index is not synced between hosts and - // Ollama runs on one of them. vv_is_ai_host() comes from include/ai.php, which the page loads - // but this file does not require; absent it, assume not, because showing a tool that cannot - // work is worse than omitting one that could. - if (function_exists('vv_is_ai_host') && vv_is_ai_host()) { + // The AI tools are adopted only where the index and the model actually live, and only while + // the subsystem is switched on. Elsewhere they are two rows that can only ever fail — the + // retrieval index is not synced between hosts, Ollama runs on one of them, and both scripts + // refuse on their own unless AI_ENABLED is true. vv_ai_ui_on() is the same gate the AI tab + // and the assistant dock use, so AI off means no AI anywhere in the UI, not most of it. + if (vv_ai_ui_on()) { array_push($ADOPTED, 'AI/ai_index.sh', 'AI/ai_query.sh'); } diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index 1e122ed..6513cbc 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -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. -// Same two conditions, checked the same way, so the page cannot offer a chat the endpoint will -// refuse — api/ai.php 404s every action off HOST1 regardless of what this page draws. -$_vv_ai_on = vv_is_ai_host() - && strtolower(trim(vv_conf_vars()['AI_ENABLED'] ?? 'false')) === 'true'; +// The same gate function, not a second copy of the condition, so the page cannot offer a chat +// the endpoint will refuse — api/ai.php rejects every action on both counts regardless of what +// this page draws. With it false the dock markup is never emitted, and every caller into the +// 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 $vv_setup_conf = preg_match('/^[\w.]+\.conf$/', $_GET['vv_setup'] ?? '')