mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat(custom-model): live countdown on the loading banner; timeout is now an error
The loading banner now shows a live countdown against its own timeout (updated every poll, so every second by default) instead of a static "this can take a while" — e.g. "Loading qwen3.8-27b (16.4 GB, typically ~1-3 min) on llama-swap - 47s remaining". If the countdown reaches zero and the model still isn't ready, this is now treated as a real failure rather than a "keep waiting" shrug: - The banner turns into a sticky error (_showCenterStatus gains a `type` option - 'error' drops the spinner and adds a close button, since nothing is "in progress" anymore and a sticky message needs a way to dismiss it), naming the llama-swap server's own logs as where to look for detail. - The session that load was for is closed automatically (closeSession) - requested explicitly: a console left open and pointed at a model that never finished loading is worse than no console at all. Both apply paths now thread the new session's id through to _watchLlamaSwapLoading for this (new required 3rd parameter, after endpointId/modelId). _watchLlamaSwapGeneration's existing stale-call guard extends naturally to this: a superseded call's own eventual timeout recognises it no longer owns the banner and neither shows the error nor closes a session that may by then belong to a different, newer launch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqZeHrRS6DYcGcGX2p9EwG
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
55dae31530
commit
7bbe408e44
+40
-18
@@ -5556,37 +5556,59 @@ Object.assign(CodemanApp.prototype, {
|
||||
* genuinely worth interrupting the eye for rather than living in the corner with every
|
||||
* other toast (currently: a custom-model session's "switching backends" and "loading
|
||||
* model" states, both of which can sit on screen for well over a minute and are easy to
|
||||
* mistake for nothing happening). Non-blocking (`pointer-events: none`, no backdrop) —
|
||||
* this is informational, never a gate the user has to dismiss to keep working. Only one
|
||||
* is ever shown at a time (the DOM node is created once and reused), which matches every
|
||||
* current caller: each hands off to the next rather than stacking.
|
||||
* mistake for nothing happening). Non-blocking (`pointer-events: none` on the wrapper,
|
||||
* restored only on the card) — an info banner is never a gate the user has to dismiss to
|
||||
* keep working. Only one is ever shown at a time (the DOM node is created once and
|
||||
* reused), which matches every current caller: each hands off to the next rather than
|
||||
* stacking.
|
||||
*
|
||||
* `opts.type` — `'info'` (default, spinner, no close button — a caller ends it itself via
|
||||
* `dismiss()`) or `'error'` (no spinner — nothing is in progress once this shows — with a
|
||||
* close button, since a sticky error the user cannot dismiss would just sit there). The
|
||||
* DOM is rebuilt fresh each call rather than patched, since which children exist differs
|
||||
* by type; `setMessage` still only ever touches the text node afterwards.
|
||||
*/
|
||||
_showCenterStatus(message) {
|
||||
_showCenterStatus(message, opts = {}) {
|
||||
const { type = 'info' } = opts;
|
||||
let el = document.getElementById('customModelCenterStatus');
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.id = 'customModelCenterStatus';
|
||||
el.className = 'center-status-banner';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
el.className = `center-status-banner center-status-${type}`;
|
||||
el.innerHTML = '';
|
||||
const dismiss = () => {
|
||||
el.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
el.hidden = true;
|
||||
}, 200);
|
||||
};
|
||||
if (type !== 'error') {
|
||||
const spinner = document.createElement('span');
|
||||
spinner.className = 'center-status-spinner';
|
||||
spinner.setAttribute('aria-hidden', 'true');
|
||||
const text = document.createElement('span');
|
||||
text.className = 'center-status-text';
|
||||
el.appendChild(spinner);
|
||||
el.appendChild(text);
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
const textEl = el.querySelector('.center-status-text');
|
||||
if (textEl) textEl.textContent = message;
|
||||
const text = document.createElement('span');
|
||||
text.className = 'center-status-text';
|
||||
text.textContent = message;
|
||||
el.appendChild(text);
|
||||
if (type === 'error') {
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'center-status-close';
|
||||
closeBtn.textContent = '×';
|
||||
closeBtn.setAttribute('aria-label', 'Dismiss');
|
||||
closeBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
dismiss();
|
||||
};
|
||||
el.appendChild(closeBtn);
|
||||
}
|
||||
el.hidden = false;
|
||||
requestAnimationFrame(() => el.classList.add('show'));
|
||||
return {
|
||||
dismiss: () => {
|
||||
el.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
el.hidden = true;
|
||||
}, 200);
|
||||
},
|
||||
dismiss,
|
||||
setMessage: (next) => {
|
||||
const t = el.querySelector('.center-status-text');
|
||||
if (t) t.textContent = next;
|
||||
|
||||
Reference in New Issue
Block a user