Say there is one chat component, in the places that claimed otherwise

Several headers argued the Scheduler dock was deliberately separate, and the Scheduler's help
never mentioned the assistant at all — including the fix flow that just changed shape.
This commit is contained in:
Gmer4Lfe
2026-08-09 12:22:25 -04:00
parent 8eeb4c3d6c
commit 6be765a9bf
6 changed files with 95 additions and 24 deletions
+16 -9
View File
@@ -121,16 +121,23 @@ soon a page notices the writer's update.
> cache miss. A key added to the endpoint and not to the writer leaves its card loading forever > cache miss. A key added to the endpoint and not to the writer leaves its card loading forever
> on every ordinary page load and working only on the request that happens to miss. > on every ordinary page load and working only on the request that happens to miss.
**One widget, rendered twice.** `include/ai_chat.php` owns the conversation surface — profile **One widget, rendered three times.** `include/ai_chat.php` owns the conversation surface —
bar, transcript, composer, source viewer, stored-chat list — and both the AI tab and the Monitor transcript, composer, profile and history pickers, source viewer, stored-chat list — and the AI
tab's AI row construct it. Every id is composed from a prefix so instances can coexist, and each tab, the Monitor tab's AI row and the Scheduler's right-hand panel all construct it. Every id is
tears down the previous holder of its prefix, because Unraid swaps tab content by AJAX without composed from a prefix so instances can coexist, and each tears down the previous holder of its
unloading the old page's JavaScript. prefix, because Unraid swaps tab content by AJAX without unloading the old page's JavaScript.
The Scheduler tab's dock is deliberately *not* built on it: a one-line bar that follows the view A placement chooses how much room it gets and which extra controls it needs. It does not choose
you have open, with its own scope chip, fix flow and incident capture, is a different component the shape: the control row is fixed everywhere — profile chip hard left, `⤢` `▾` `New` `Ask` hard
that happens to share an endpoint. Folding it in would produce one widget with two personalities right — because a chat that rearranges itself per tab is three components wearing one name.
and a mode flag choosing between them.
The Scheduler panel was exactly that until it was folded in, and it proved the cost: a thread
there died on reload while the other two were saved, and its bar had drifted into a different
shape from the same control everywhere else. What was genuinely particular to it became options
rather than a second implementation — `scope` as a function for a subject that follows the open
view, `beforeSend` for the one reply that is recorded rather than asked, `think` for reasoning on
diagnosis only, and `setHeights()` for a placement sized as a share of a panel, which is not a
number that exists until layout has run.
**Timezone.** `include/config.php` adopts Unraid's own `timeZone` from `ident.cfg` for the whole **Timezone.** `include/config.php` adopts Unraid's own `timeZone` from `ident.cfg` for the whole
PHP layer. PHP here has no `date.timezone` and therefore ran in UTC while the server ran local, PHP layer. PHP here has no `date.timezone` and therefore ran in UTC while the server ran local,
+5 -5
View File
@@ -90,7 +90,7 @@ require_once __DIR__ . '/config.php';
const VV_AI_KINDS = ['header', 'readme', 'manual', 'template', 'doc', 'ui']; const VV_AI_KINDS = ['header', 'readme', 'manual', 'template', 'doc', 'ui'];
// Profiles — what each one is, and what it is allowed to see and do. One table, in one file, // Profiles — what each one is, and what it is allowed to see and do. One table, in one file,
// read by everything: this endpoint, the worker, the shared chat include and the Scheduler dock. // read by everything: this endpoint, the worker, and the shared chat include every page renders.
// vv_ai_profile_can() and friends come from there. // vv_ai_profile_can() and friends come from there.
require_once __DIR__ . '/ai_profiles.php'; require_once __DIR__ . '/ai_profiles.php';
@@ -816,7 +816,7 @@ function vv_ai_token_stats(): array {
return $out; return $out;
} }
// ── Scoped lookups for the WebGUI dock ─────────────────────────────────────────────────────── // ── Scoped lookups for the Scheduler's assistant ──────────────────────────────────────────────
// The tail of one named script's log, for the troubleshooting profile. vv_ai_recent_logs() // The tail of one named script's log, for the troubleshooting profile. vv_ai_recent_logs()
// answers "is anything wrong anywhere"; this answers "why did THIS fail", which is a different // answers "is anything wrong anywhere"; this answers "why did THIS fail", which is a different
@@ -1308,11 +1308,11 @@ function vv_ai_chat_save(string $id, string $profile, array $messages, string $s
$prev = vv_ai_chat_read($id); $prev = vv_ai_chat_read($id);
$created = (int)($prev['created'] ?? time()); $created = (int)($prev['created'] ?? time());
// Scope travels with the conversation. A Scheduler dock thread is bound to what the operator // Scope travels with the conversation. A Scheduler thread is bound to what the operator had
// had open — a script, a log, a conf key — and its turns carry log excerpts chosen for that // open — a script, a log, a conf key — and its turns carry log excerpts chosen for that
// thing. Storing the scope means reopening the thread anywhere restores the context it was // thing. Storing the scope means reopening the thread anywhere restores the context it was
// reasoned in, rather than silently continuing a troubleshooting conversation against // reasoned in, rather than silently continuing a troubleshooting conversation against
// whatever happens to be on screen. The isolation the dock enforces in memory becomes a // whatever happens to be on screen. The isolation the page enforces in memory becomes a
// property of the record instead of something lost the moment it is saved. // property of the record instead of something lost the moment it is saved.
$rec = [ $rec = [
'id' => $id, 'id' => $id,
+5 -4
View File
@@ -2,7 +2,8 @@
// ═══════════════════════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE // PURPOSE
// The one definition of what an AI profile is. Every consumer — the endpoint, the worker, the // The one definition of what an AI profile is. Every consumer — the endpoint, the worker, the
// shared chat include, the Scheduler dock — reads it from here instead of restating it. // shared chat include and every page that renders it — reads it from here instead of
// restating it.
// //
// WHY THIS EXISTS // WHY THIS EXISTS
// A profile used to be defined in five places: history depth in api/ai.php, capabilities in // A profile used to be defined in five places: history depth in api/ai.php, capabilities in
@@ -187,9 +188,9 @@ function vv_ai_profiles_client(): array {
// surfaces ask for it. Emitted as its own tag so any script block that consumes it stays pure // surfaces ask for it. Emitted as its own tag so any script block that consumes it stays pure
// JavaScript and remains syntax-checkable outside PHP. // JavaScript and remains syntax-checkable outside PHP.
// //
// Both the shared chat include and the Scheduler dock call this. Before it existed the dock had // The shared chat include calls this, as does any page emitting the registry ahead of it. Before
// its own literal `{ code: 'Code', troubleshoot: 'Troubleshoot' }` map, which is how a fourth // it existed the Scheduler had its own literal `{ code: 'Code', troubleshoot: 'Troubleshoot' }`
// copy of the profile list came to exist in the first place. // map, which is how a fourth copy of the profile list came to exist in the first place.
function vv_ai_profiles_script(): void { function vv_ai_profiles_script(): void {
static $done = false; static $done = false;
if ($done) return; if ($done) return;
+3 -2
View File
@@ -356,8 +356,9 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
<?php vv_ai_chat_list_markup('vv-mon-ai', true); ?> <?php vv_ai_chat_list_markup('vv-mon-ai', true); ?>
</div> </div>
<!-- Named for what it is, not "dock" — the Scheduler tab's vv-ai-dock is a different <!-- Named for what it is, not "dock". The Scheduler renders the same component inside a
component and vvAiDockOn() there tests for that id by name. --> wrapper that still carries the #vv-ai-dock id, and two elements answering to that name
on one page would be a trap the day anything queries it globally. -->
<div class="vv-card" id="vv-ai-assistant-card"> <div class="vv-card" id="vv-ai-assistant-card">
<h3> <h3>
<span style="display:flex;align-items:center;gap:5px;"> <span style="display:flex;align-items:center;gap:5px;">
+25 -4
View File
@@ -56,11 +56,13 @@ as the GPU and UPS cards.
| Card | What it tells you | | Card | What it tells you |
|---|---| |---|---|
| AI | Model residency, context size, VRAM, index size and staleness, tokens used today | | AI | Model residency, context size, VRAM, index size and staleness |
| Tokens | What the assistant has spent — per node, and for today, the last 7 days and all time |
| Conversations | The last `$AI_CHAT_HISTORY_MAX` chats. Click to reopen, × to delete | | Conversations | The last `$AI_CHAT_HISTORY_MAX` chats. Click to reopen, × to delete |
| Assistant | Ask a question without leaving the dashboard | | Assistant | Ask a question without leaving the dashboard |
| ⤢ | Expands the conversation to about two and a half times its height. Remembered | | Profile chip | Which contract answers — General Chat, Varaverk Assistant, Code Sketcher, Troubleshoot |
| Profile buttons | Which contract answers — General Chat, Varaverk Assistant, Code Sketcher, Troubleshoot | | ⤢ | Trades more room for the conversation, and back. Always opens at the smaller size |
| ▾ | Saved conversations, the same ones the Conversations card lists |
--- ---
@@ -79,6 +81,25 @@ implied.
These figures come from the same one-minute collection the AI tab's banner uses, so the two These figures come from the same one-minute collection the AI tab's banner uses, so the two
cannot disagree about whether the model is loaded. cannot disagree about whether the model is loaded.
## Tokens is its own card, not a line on the AI one
The AI card answers whether the model is healthy right now. What it has spent over a month is a
different question, and it was crowding that card out one line at a time.
`Nodes` lists each host with its total. A host with no rows reads **not collected here**, never
zero — each host writes to its own `data/` and only `ai_token_sync.sh` moves a ledger between
them, so a zero would claim the partner sat idle when the truth is that this host cannot see its
ledger at all. Click a node to scope the figures below it to that host; click **All hosts** to go
back.
The totals cover today, the last 7 days and all time, each with the turn count and the prompt /
completion split. The footer adds how long the ledger has been running, the best throughput seen,
and — under All hosts only — the split by profile and by source. Those last two describe the whole
ledger, so they are not shown under a single host's heading where they would not describe it.
This is the same ledger the AI tab shows, read from the same endpoint. It refreshes when a turn
finishes here, and otherwise once a minute, because the numbers only move when a turn completes.
## The assistant here starts on General Chat ## The assistant here starts on General Chat
The AI tab starts on Varaverk Assistant, which answers only from this installation's The AI tab starts on Varaverk Assistant, which answers only from this installation's
@@ -91,7 +112,7 @@ to the strict profile automatically, and handed back if the documentation turns
it. Starting strict would refuse ordinary questions to guard against a mistake the server already it. Starting strict would refuse ordinary questions to guard against a mistake the server already
prevents. prevents.
Switching profile with the buttons keeps what is on screen but stops sending earlier turns to the Switching profile with the chip keeps what is on screen but stops sending earlier turns to the
model. Carrying cited, retrieval-grounded answers into a mode with no retrieval makes the model model. Carrying cited, retrieval-grounded answers into a mode with no retrieval makes the model
keep referring to sources it can no longer see. keep referring to sources it can no longer see.
@@ -80,6 +80,42 @@ Two blocks on the Scheduler Info panel get you there faster:
The status dot in the Orch tree is green, orange or red for the last run's outcome, dim if the The status dot in the Orch tree is green, orange or red for the last run's outcome, dim if the
script has never run. Hover for detail. script has never run. Hover for detail.
## Asking the assistant about what is on screen
The panel below the right-hand views is the same chat component the AI tab and the Monitor
dashboard use, at this panel's size. What is particular to this tab is that its subject follows
you: open a log, a conf or a script and the chip states both the profile answering and the thing
it is pointed at. Nothing is inferred silently — if what the chip says is wrong, that costs a
glance rather than a confidently wrong answer.
Every row in **Recent Activity** carries a **why?** (failed) or **recap** (clean) button. It opens
that script's log first and then asks, because the question is only worth asking with the log
attached — the profile that reads it is the one that gets the log tail. The question is posted as
visible text, so what was asked on your behalf is never a mystery.
Moving to a different subject keeps the transcript on screen but stops sending the earlier turns
to the model, and starts a new stored conversation. A troubleshooting thread carrying log excerpts
must not bleed into a question about a conf key; hiding that the earlier exchange happened would
be worse than carrying it visibly.
Reasoning is enabled only for Troubleshoot. Working out what a log means is worth waiting for; a
lookup like "what does this setting do" is not, and an inline answer that stalls reads as broken.
## Recording what actually fixed something
When a diagnosis is followed by that same script running clean, the assistant offers to record it:
*"`<script>` just ran clean. Want me to record what fixed it?"* Answer **Yes** and the next thing
you type is stored as the fix rather than asked as a question.
The trigger is the run record, not the conversation. It fires once, at the moment the trouble is
demonstrably over, rather than after every answer — an offer that appears constantly is one you
learn to skip past.
What gets stored is the symptom you asked about and the fix you describe. The model's diagnosis is
deliberately not stored: it is a reading of evidence, and writing a hypothesis into institutional
memory as settled fact is how a wrong answer outlives the incident it came from. What is
remembered is what actually worked, and it is shown the next time the same thing is diagnosed.
## Stopping a script that is stuck ## Stopping a script that is stuck
**Stop** sends SIGTERM, waits 3 seconds, then SIGKILL, and clears any lock files the script left **Stop** sends SIGTERM, waits 3 seconds, then SIGKILL, and clears any lock files the script left
@@ -138,6 +174,11 @@ on disk is never touched until you press Save.
| **Auto Scroll** | Toolbar | Follows the newest line; pauses if you scroll up | | **Auto Scroll** | Toolbar | Follows the newest line; pauses if you scroll up |
| **Cancel** | Toolbar, editing | Discards changes and returns here. The file is untouched until Save | | **Cancel** | Toolbar, editing | Discards changes and returns here. The file is untouched until Save |
| **+ Folder** | Custom Scripts | A collapsible subfolder you can drag scripts into | | **+ Folder** | Custom Scripts | A collapsible subfolder you can drag scripts into |
| **why? / recap** | Recent Activity | Opens that run's log and asks the assistant about it in one click |
| **Profile chip** | Assistant | What is answering, and what it is pointed at. Click to change the profile |
| **⤢** | Assistant | Trades more of the panel for the conversation, and back |
| **▾** | Assistant | Saved conversations, shared with the AI tab and the Monitor dashboard |
| **New** | Assistant | Starts a fresh conversation; the old one stays in the store |
## Reference — orchestrator and step behaviour ## Reference — orchestrator and step behaviour