@aria-framework/ai 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/ai-panels.js +113 -0
- package/index.js +261 -240
- package/lmxStatus.js +120 -0
- package/lmxStore.js +188 -0
- package/lmxVerify.js +395 -0
- package/package.json +53 -48
- package/providerStore.js +10 -0
- package/views/ai/lmx-stack.ejs +264 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<%# One supervised stack, as a folding panel.
|
|
2
|
+
────────────────────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
A STACK IS A CONTAINER, not an endpoint. Its address is discovered, its model is a fact the
|
|
4
|
+
supervisor reports, and unfolding it lists engines rather than settings. The header reads
|
|
5
|
+
identity, kind, status, and then A COUNT OF WHAT DEPENDS ON IT — because the question anybody
|
|
6
|
+
has before touching a machine is "if this goes away, what stops working?".
|
|
7
|
+
|
|
8
|
+
FOLDED IS THE DEFAULT ONCE STABLE, so the folded line has to carry the whole verdict: status,
|
|
9
|
+
counts, all three credentials, and when it was last checked. Folding may hide detail; it must
|
|
10
|
+
never hide the reason you would have opened it. The app this was written for previously showed
|
|
11
|
+
a card with name, id and URL — none of which change when you paste a token — so a save that
|
|
12
|
+
worked and one that was discarded were pixel-identical.
|
|
13
|
+
|
|
14
|
+
A PANEL WITH A PROBLEM IS OPEN IN THE SERVER'S HTML, never opened later by script. If attention
|
|
15
|
+
depended on JavaScript, the one state that must never be missed would be the state that
|
|
16
|
+
disappears when a script fails to load.
|
|
17
|
+
|
|
18
|
+
Locals:
|
|
19
|
+
inst a supervisor row, merged with lmxStatus.describeStack() and carrying
|
|
20
|
+
`report` (lmxVerify), `pin` (lmxVerify.readPin), `creds` {statusToken, enginesKey}
|
|
21
|
+
_edit render the controls at all
|
|
22
|
+
csrfToken () => token, for the forms
|
|
23
|
+
basePath where this app mounts its AI admin, e.g. '/admin/ai'. NOT hardcoded: the routes
|
|
24
|
+
are the consumer's, and only the consumer knows where they live.
|
|
25
|
+
newAnchor where Edit sends somebody to change the stack, e.g. '#lmx-new'
|
|
26
|
+
routeLabel optional (id) => human name for a job; the id is used when absent
|
|
27
|
+
%>
|
|
28
|
+
<%
|
|
29
|
+
// DISTINCT NAMES, deliberately. `var _edit = (typeof canEdit === "undefined") ? ... : _edit`
|
|
30
|
+
// reads as a defaulting idiom and is not one: `var` hoists, so inside this template the name is
|
|
31
|
+
// already declared and undefined when the typeof runs. The test is therefore always true, the
|
|
32
|
+
// passed-in local is shadowed, and every consumer silently gets the default.
|
|
33
|
+
var _edit = (typeof canEdit !== 'undefined') && canEdit;
|
|
34
|
+
var _base = (typeof basePath !== 'undefined' && basePath) ? basePath : '/admin/ai';
|
|
35
|
+
var _anchor = (typeof newAnchor !== 'undefined' && newAnchor) ? newAnchor : '#lmx-new';
|
|
36
|
+
var _label = (typeof routeLabel === 'function') ? routeLabel : function (x) { return x; };
|
|
37
|
+
var _csrf = (typeof csrfToken === 'function') ? csrfToken : function () { return ''; };
|
|
38
|
+
var _ago = function (iso) {
|
|
39
|
+
if (!iso) return null;
|
|
40
|
+
var secs = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 1000));
|
|
41
|
+
if (secs < 60) return secs + 's ago';
|
|
42
|
+
if (secs < 3600) return Math.round(secs / 60) + 'm ago';
|
|
43
|
+
return Math.round(secs / 3600) + 'h ago';
|
|
44
|
+
};
|
|
45
|
+
var _glyph = { pass: '✔', fail: '✖', warn: '⚠', skip: '—' };
|
|
46
|
+
var _gcls = { pass: 'text-success', fail: 'text-danger', warn: 'text-warning', skip: 'text-body-tertiary' };
|
|
47
|
+
var r = inst.report;
|
|
48
|
+
var _open = !!inst.attention;
|
|
49
|
+
// NOT CHECKED is its own state, distinct from both answering and broken. A stack nobody has asked
|
|
50
|
+
// since the last restart is not a stack in trouble, and colouring it as one would train an
|
|
51
|
+
// operator to ignore the colour.
|
|
52
|
+
var pillCls = !r ? 'text-bg-secondary'
|
|
53
|
+
: (r.ok ? 'text-bg-success' : (r.level === 'bad' ? 'text-bg-danger' : 'text-bg-warning'));
|
|
54
|
+
var pillText = !r ? 'not checked' : (r.ok ? 'answering' : 'needs attention');
|
|
55
|
+
%>
|
|
56
|
+
<div class="card mb-3 <%= _open ? 'border-warning' : '' %>" data-panel="lmx-<%= inst.id %>"<% if (_open) { %> data-panel-attention<% } %>
|
|
57
|
+
data-lmx-id="<%= inst.id %>" data-lmx-label="<%= inst.label || inst.id %>" data-lmx-url="<%= inst.status_url %>">
|
|
58
|
+
<%# THE TOGGLE AND THE ACTIONS ARE SIBLINGS, not nested. A <button> may not contain a link
|
|
59
|
+
or another button — the markup parses unpredictably and the inner control stops being
|
|
60
|
+
reachable by keyboard, which would put Check and Edit behind a mouse. %>
|
|
61
|
+
<div class="d-flex gap-2 align-items-start p-3">
|
|
62
|
+
<button type="button" class="btn text-start border-0 p-0 flex-grow-1 d-flex gap-2 align-items-start"
|
|
63
|
+
data-panel-toggle="lmx-<%= inst.id %>" aria-expanded="<%= _open ? 'true' : 'false' %>"
|
|
64
|
+
aria-controls="body-lmx-<%= inst.id %>">
|
|
65
|
+
<span class="panel-chev text-body-secondary" aria-hidden="true">›</span>
|
|
66
|
+
<span class="flex-grow-1">
|
|
67
|
+
<span class="d-flex align-items-center gap-2 flex-wrap">
|
|
68
|
+
<strong><%= inst.label || inst.id %></strong>
|
|
69
|
+
<code class="small"><%= inst.id %></code>
|
|
70
|
+
<span class="badge text-bg-light border">lmx</span>
|
|
71
|
+
<span class="badge <%= pillCls %>"><%= pillText %></span>
|
|
72
|
+
<%# THE COUNT IS "WHAT AM I RELYING ON", not what exists. Active means added, enabled
|
|
73
|
+
and healthy in the stack's own document — the number that was missing when a
|
|
74
|
+
configured-but-broken engine looked exactly like a working one. %>
|
|
75
|
+
<span class="badge text-bg-light border font-monospace">
|
|
76
|
+
<% if (r && r.engines) { %>
|
|
77
|
+
<%= inst.activeCount %> engine<%= inst.activeCount === 1 ? '' : 's' %> active · <%= inst.reportedCount %> reported<% if (inst.missingCount) { %> · <span class="text-danger"><%= inst.missingCount %> missing</span><% } %>
|
|
78
|
+
<% } else if (r) { %>
|
|
79
|
+
<%# CHECKED, BUT THE LIST DID NOT ARRIVE. "not checked yet" here would be a lie
|
|
80
|
+
about something that just happened 0 seconds ago, and would send somebody to
|
|
81
|
+
press Check again rather than read the reason sitting directly below. %>
|
|
82
|
+
<%= inst.added.length %> added · engines could not be listed
|
|
83
|
+
<% } else { %>
|
|
84
|
+
<%= inst.added.length %> added · not checked yet
|
|
85
|
+
<% } %>
|
|
86
|
+
</span>
|
|
87
|
+
</span>
|
|
88
|
+
<span class="d-block small text-body-secondary font-monospace mt-1" style="word-break:break-all">
|
|
89
|
+
<%= inst.status_url %>
|
|
90
|
+
· token <%= inst.creds.statusToken ? '✔' : '✖' %>
|
|
91
|
+
· key <%= inst.creds.enginesKey ? '✔' : '✖' %>
|
|
92
|
+
· cert <% if (!inst.pin.present) { %>none<% } else if (!inst.pin.valid) { %><span class="text-danger">unreadable</span><% } else if (inst.pin.expired) { %><span class="text-danger">EXPIRED</span><% } else if (inst.pin.expiringSoon) { %><span class="text-warning"><%= inst.pin.expiresDays %>d left</span><% } else { %>✔<% } %>
|
|
93
|
+
<% if (r) { %>· checked <%= _ago(r.at) %><% } %>
|
|
94
|
+
<% if (!inst.enabled) { %>· <span class="badge text-bg-secondary">disabled</span><% } %>
|
|
95
|
+
</span>
|
|
96
|
+
</span>
|
|
97
|
+
</button>
|
|
98
|
+
<% if (_edit) { %>
|
|
99
|
+
<span class="d-flex gap-2 align-items-center">
|
|
100
|
+
<form method="POST" action="<%= _base %>/lmx/<%= inst.id %>/check">
|
|
101
|
+
<input type="hidden" name="_csrf" value="<%= _csrf() %>">
|
|
102
|
+
<button type="submit" class="btn btn-sm btn-outline-secondary">Check</button>
|
|
103
|
+
</form>
|
|
104
|
+
<a class="btn btn-sm btn-outline-secondary" href="<%= _anchor %>"
|
|
105
|
+
data-lmx-edit="<%= inst.id %>">Edit</a>
|
|
106
|
+
</span>
|
|
107
|
+
<% } %>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div id="body-lmx-<%= inst.id %>" class="border-top"<% if (!_open) { %> hidden<% } %>>
|
|
111
|
+
|
|
112
|
+
<%# THE LAST VERIFICATION, per check. Never a single line: "401" named neither of the two
|
|
113
|
+
credentials it could have been, which is exactly how an engines key pasted into the
|
|
114
|
+
status token's field stayed hidden. %>
|
|
115
|
+
<% if (r) { %>
|
|
116
|
+
<div class="p-3 border-bottom">
|
|
117
|
+
<div class="small text-uppercase fw-semibold text-body-secondary mb-2" style="letter-spacing:.08em">
|
|
118
|
+
Last checked <%= _ago(r.at) %>
|
|
119
|
+
</div>
|
|
120
|
+
<% r.checks.forEach(function (c) { %>
|
|
121
|
+
<div class="d-flex gap-2 small font-monospace">
|
|
122
|
+
<span class="<%= _gcls[c.status] || '' %>" style="width:1rem"><%= _glyph[c.status] || '' %></span>
|
|
123
|
+
<span class="<%= c.status === 'skip' ? 'text-body-tertiary' : '' %>"><%= c.text %></span>
|
|
124
|
+
</div>
|
|
125
|
+
<% }); %>
|
|
126
|
+
</div>
|
|
127
|
+
<% } else { %>
|
|
128
|
+
<div class="p-3 border-bottom small text-body-secondary">
|
|
129
|
+
This stack has not been checked since the last restart.
|
|
130
|
+
<%# NOT PROBED ON RENDER, deliberately: opening a settings page is not evidence that
|
|
131
|
+
anything changed, and a sleeping machine would hold the whole page while it timed
|
|
132
|
+
out. Check asks, when somebody wants to know. %>
|
|
133
|
+
Press <strong>Check</strong> to ask it now.
|
|
134
|
+
</div>
|
|
135
|
+
<% } %>
|
|
136
|
+
|
|
137
|
+
<%# ── ENGINES IN USE ── %>
|
|
138
|
+
<% if (inst.added.length) { %>
|
|
139
|
+
<div class="px-3 py-2 bg-body-tertiary d-flex justify-content-between small text-body-secondary">
|
|
140
|
+
<span>In use — <%= inst.added.length %></span>
|
|
141
|
+
<span>endpoint · jobs served</span>
|
|
142
|
+
</div>
|
|
143
|
+
<% inst.added.forEach(function (a) { %>
|
|
144
|
+
<div class="d-flex justify-content-between align-items-start gap-3 border-top p-3 <%= a.missing ? 'bg-danger-subtle' : '' %>">
|
|
145
|
+
<div>
|
|
146
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
147
|
+
<strong><%= a.row.lmx_engine %></strong>
|
|
148
|
+
<% if (a.missing) { %>
|
|
149
|
+
<span class="badge text-bg-danger">not reported</span>
|
|
150
|
+
<% } else if (a.engine) { %>
|
|
151
|
+
<span class="badge <%= a.engine.state === 'healthy' ? 'text-bg-success' : 'text-bg-warning' %>"><%= a.engine.state %></span>
|
|
152
|
+
<% if (a.engine.role) { %><span class="badge text-bg-light border"><%= a.engine.role %></span><% } %>
|
|
153
|
+
<% } %>
|
|
154
|
+
<% if (!Number(a.row.enabled)) { %><span class="badge text-bg-secondary">disabled</span><% } %>
|
|
155
|
+
</div>
|
|
156
|
+
<% if (a.engine) { %>
|
|
157
|
+
<div class="small text-body-tertiary font-monospace mt-1">
|
|
158
|
+
<%= [a.engine.modelName || a.engine.model, a.engine.quantisation, a.engine.device,
|
|
159
|
+
a.engine.maxInputTokens ? Number(a.engine.maxInputTokens).toLocaleString() + ' tok/slot' : null,
|
|
160
|
+
a.engine.slots ? a.engine.slots + (a.engine.slots === 1 ? ' slot' : ' slots') : null,
|
|
161
|
+
a.engine.dimensions ? a.engine.dimensions + ' dims' : null]
|
|
162
|
+
.filter(Boolean).join(' · ') %>
|
|
163
|
+
</div>
|
|
164
|
+
<% } %>
|
|
165
|
+
<%# WHAT STILL POINTS AT IT. A missing engine whose jobs nobody has repointed is
|
|
166
|
+
an endpoint that will fail every call — the same silent-but-configured shape
|
|
167
|
+
as the credential problem, so it names the jobs rather than only itself. %>
|
|
168
|
+
<div class="small mt-1 <%= a.missing ? 'text-danger fw-semibold' : 'text-body-secondary' %>">
|
|
169
|
+
<a href="<%= _base %>?edit=<%= encodeURIComponent(a.row.id) %>" class="text-decoration-none"><%= a.row.id %></a>
|
|
170
|
+
<% if (a.routes.length) { %>
|
|
171
|
+
· <%= a.routes.map(function (x) { return _label(x.id); }).join(', ') %>
|
|
172
|
+
<% } else { %>
|
|
173
|
+
· serves no jobs
|
|
174
|
+
<% } %>
|
|
175
|
+
</div>
|
|
176
|
+
<% if (a.missing) { %>
|
|
177
|
+
<div class="small text-danger mt-1">
|
|
178
|
+
The stack no longer lists this engine. Either point its jobs elsewhere, or remove the endpoint.
|
|
179
|
+
</div>
|
|
180
|
+
<% } %>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="d-flex gap-2">
|
|
183
|
+
<a class="btn btn-sm btn-outline-secondary" href="<%= _base %>?edit=<%= encodeURIComponent(a.row.id) %>">Settings</a>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<% }); %>
|
|
187
|
+
<% } %>
|
|
188
|
+
|
|
189
|
+
<%# ── ENGINES AVAILABLE ── %>
|
|
190
|
+
<% if (inst.available && inst.available.length) { %>
|
|
191
|
+
<div class="px-3 py-2 bg-body-tertiary d-flex justify-content-between small text-body-secondary border-top">
|
|
192
|
+
<span>Also on this stack — not in use</span>
|
|
193
|
+
<span><%= inst.available.length %></span>
|
|
194
|
+
</div>
|
|
195
|
+
<% inst.available.forEach(function (e) { %>
|
|
196
|
+
<div class="d-flex justify-content-between align-items-start gap-3 border-top p-3">
|
|
197
|
+
<div>
|
|
198
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
199
|
+
<strong><%= e.label || e.name %></strong>
|
|
200
|
+
<code class="small"><%= e.name %></code>
|
|
201
|
+
<span class="badge <%= e.state === 'healthy' ? 'text-bg-success' : 'text-bg-warning' %>"><%= e.state || 'unknown' %></span>
|
|
202
|
+
<span class="badge text-bg-light border"><%= e.role || '?' %></span>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="small text-body-tertiary font-monospace mt-1">
|
|
205
|
+
<%= [e.modelName || e.model, e.quantisation, e.device,
|
|
206
|
+
e.maxInputTokens ? Number(e.maxInputTokens).toLocaleString() + ' tok/slot' : null,
|
|
207
|
+
e.slots ? e.slots + (e.slots === 1 ? ' slot' : ' slots') : null,
|
|
208
|
+
e.dimensions ? e.dimensions + ' dims' : null].filter(Boolean).join(' · ') %>
|
|
209
|
+
</div>
|
|
210
|
+
<%# WHICH REASONING FLAG THIS MODEL NEEDS. Getting it wrong is silent: the model
|
|
211
|
+
spends its whole budget thinking and returns nothing, with no error at all. %>
|
|
212
|
+
<% if (e.reasoning) { %>
|
|
213
|
+
<div class="small text-body-secondary mt-1">reasoning: <code><%= e.reasoning %></code></div>
|
|
214
|
+
<% } else if (e.role === 'chat') { %>
|
|
215
|
+
<div class="small text-warning mt-1">no known reasoning flag for this model family — neither will be sent</div>
|
|
216
|
+
<% } %>
|
|
217
|
+
</div>
|
|
218
|
+
<% if (_edit) { %>
|
|
219
|
+
<form method="POST" action="<%= _base %>/lmx/<%= inst.id %>/adopt">
|
|
220
|
+
<input type="hidden" name="_csrf" value="<%= _csrf() %>">
|
|
221
|
+
<input type="hidden" name="engine" value="<%= e.name %>">
|
|
222
|
+
<input type="hidden" name="label" value="<%= e.label || e.name %>">
|
|
223
|
+
<input type="hidden" name="role" value="<%= e.role || '' %>">
|
|
224
|
+
<input type="hidden" name="max_input_tokens" value="<%= e.maxInputTokens || '' %>">
|
|
225
|
+
<button type="submit" class="btn btn-sm btn-primary">Add</button>
|
|
226
|
+
</form>
|
|
227
|
+
<% } %>
|
|
228
|
+
</div>
|
|
229
|
+
<% }); %>
|
|
230
|
+
<% } %>
|
|
231
|
+
|
|
232
|
+
<%# ── CREDENTIALS: what is set, never what it is ── %>
|
|
233
|
+
<div class="px-3 py-2 bg-body-tertiary border-top small text-body-secondary">Credentials</div>
|
|
234
|
+
<div class="p-3 border-top d-flex gap-2 flex-wrap">
|
|
235
|
+
<span class="badge text-bg-light border font-monospace">
|
|
236
|
+
<span class="<%= inst.creds.statusToken ? 'text-success' : 'text-danger' %>"><%= inst.creds.statusToken ? '✔' : '✖' %></span>
|
|
237
|
+
status token
|
|
238
|
+
</span>
|
|
239
|
+
<span class="badge text-bg-light border font-monospace">
|
|
240
|
+
<span class="<%= inst.creds.enginesKey ? 'text-success' : 'text-danger' %>"><%= inst.creds.enginesKey ? '✔' : '✖' %></span>
|
|
241
|
+
engines key
|
|
242
|
+
</span>
|
|
243
|
+
<span class="badge <%= (inst.pin.expired || !inst.pin.valid) ? 'text-bg-danger' : (inst.pin.expiringSoon ? 'text-bg-warning' : 'text-bg-light border') %> font-monospace">
|
|
244
|
+
<% if (!inst.pin.present) { %>no certificate pinned
|
|
245
|
+
<% } else if (!inst.pin.valid) { %>certificate unreadable
|
|
246
|
+
<% } else { %>cert <%= inst.pin.fingerprint %> · expires <%= inst.pin.expires.toISOString().slice(0, 10) %><% if (inst.pin.expiringSoon) { %> · <%= inst.pin.expiresDays %>d left<% } %><% } %>
|
|
247
|
+
</span>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<% if (_edit) { %>
|
|
251
|
+
<div class="p-3 border-top d-flex gap-2">
|
|
252
|
+
<form method="POST" action="<%= _base %>/lmx/<%= inst.id %>/check">
|
|
253
|
+
<input type="hidden" name="_csrf" value="<%= _csrf() %>">
|
|
254
|
+
<button type="submit" class="btn btn-sm btn-outline-secondary">Check now</button>
|
|
255
|
+
</form>
|
|
256
|
+
<form method="POST" action="<%= _base %>/lmx/<%= inst.id %>/delete"
|
|
257
|
+
data-confirm="Remove the “<%= inst.label || inst.id %>” supervisor?">
|
|
258
|
+
<input type="hidden" name="_csrf" value="<%= _csrf() %>">
|
|
259
|
+
<button type="submit" class="btn btn-sm btn-outline-danger">Remove</button>
|
|
260
|
+
</form>
|
|
261
|
+
</div>
|
|
262
|
+
<% } %>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|