fix(custom-model): replace the native confirm() popup with an in-app modal

The llama-swap "this will unload it for session X" warning used a native
browser confirm() popup, which looks out of place next to the rest of the
app's own modals.

Adds #customModelSwapConfirmModal (index.html) with Cancel/Switch-anyway
buttons, styled to match the app. _confirmModelSwap(message) shows it and
returns a promise that resolves true/false the same way confirm() would;
_resolveModelSwapConfirm(proceed) (wired to both buttons and the backdrop
click) settles it. Both llama-swap conflict call sites
(_quickStartWithCustomModelConfirm for the one-shot launch path,
_runCustomModelEntryViaRestart for Claude's restart path) now await this
instead of calling confirm() directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqZeHrRS6DYcGcGX2p9EwG
This commit is contained in:
Devvyn
2026-09-16 15:29:38 +08:00
co-authored by Claude Sonnet 5
parent 83033b4299
commit 2936ba6e3d
5 changed files with 99 additions and 12 deletions
+21
View File
@@ -927,6 +927,27 @@
</div>
</div>
<!-- Custom Model Endpoint Profiles: llama-swap model-swap confirmation
(docs/custom-model-endpoints-plan.md) — replaces a native confirm()
popup, shown when switching would unload a model another live
session is actively using. See _confirmModelSwap() in session-ui.js. -->
<div class="modal" id="customModelSwapConfirmModal">
<div class="modal-backdrop" onclick="app._resolveModelSwapConfirm(false)"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Switch models?</h3>
<button class="modal-close" onclick="app._resolveModelSwapConfirm(false)" aria-label="Cancel">&times;</button>
</div>
<div class="modal-body">
<p class="form-hint" id="customModelSwapConfirmMessage"></p>
</div>
<div class="modal-footer">
<button class="btn-toolbar" onclick="app._resolveModelSwapConfirm(false)">Cancel</button>
<button class="btn-toolbar btn-primary" onclick="app._resolveModelSwapConfirm(true)">Switch anyway</button>
</div>
</div>
</div>
<!-- Cron Jobs Modal -->
<div class="modal" id="cronModal">
<div class="modal-backdrop" onclick="app.closeCron()"></div>
+28 -2
View File
@@ -667,6 +667,32 @@ Object.assign(CodemanApp.prototype, {
this._pendingCustomModelPick = null;
},
/**
* In-app replacement for a native `confirm()` popup, used specifically for the
* llama-swap "this will unload it for session X" warning (both launch paths below) —
* a browser-chrome dialog there looked out of place next to the rest of the app's own
* modals. Resolves true/false the same way `confirm()` would; `_resolveModelSwapConfirm`
* (the modal's own Cancel/Switch-anyway buttons, and its backdrop click) is what settles
* the returned promise.
*/
_confirmModelSwap(message) {
const modal = document.getElementById('customModelSwapConfirmModal');
const messageEl = document.getElementById('customModelSwapConfirmMessage');
if (messageEl) messageEl.textContent = message;
modal?.classList.add('active');
return new Promise((resolve) => {
this._resolveModelSwapConfirmPromise = resolve;
});
},
/** Called by the modal's Cancel/Switch-anyway buttons and its backdrop click. */
_resolveModelSwapConfirm(proceed) {
document.getElementById('customModelSwapConfirmModal')?.classList.remove('active');
const resolve = this._resolveModelSwapConfirmPromise;
this._resolveModelSwapConfirmPromise = null;
resolve?.(proceed);
},
/** A model row in the picker modal was clicked: close it and launch with that choice. */
chooseCustomModelAndRun(modelId) {
const pending = this._pendingCustomModelPick;
@@ -766,7 +792,7 @@ Object.assign(CodemanApp.prototype, {
if (data?.data?.requiresConfirmation) {
const { currentlyLoadedModel, affectedSessions } = data.data;
const names = affectedSessions.map((s) => s.name || s.id).join(', ');
const proceed = confirm(
const proceed = await this._confirmModelSwap(
`${names} ${affectedSessions.length === 1 ? 'is' : 'are'} currently using ` +
`${currentlyLoadedModel} on this endpoint. Switching will unload it for ` +
`${affectedSessions.length === 1 ? 'that session' : 'those sessions'} too. Continue?`
@@ -850,7 +876,7 @@ Object.assign(CodemanApp.prototype, {
// call with `confirmed: true` so the route skips the check the second time.
if (ok && payload?.requiresConfirmation) {
const names = payload.affectedSessions.map((s) => s.name || s.id).join(', ');
const proceed = confirm(
const proceed = await this._confirmModelSwap(
`${names} ${payload.affectedSessions.length === 1 ? 'is' : 'are'} currently using ` +
`${payload.currentlyLoadedModel} on this endpoint. Switching to ${modelId} will unload it ` +
`for ${payload.affectedSessions.length === 1 ? 'that session' : 'those sessions'} too. Continue?`
+11
View File
@@ -6825,6 +6825,17 @@ body.touch-device .terminal-container .xterm .xterm-helper-textarea {
min-height: 0;
}
/* Custom Model Endpoint Profiles: llama-swap model-swap confirmation — replaces a native
confirm() popup (docs/custom-model-endpoints-plan.md) so it looks and feels like the
rest of the app instead of a browser chrome dialog. */
#customModelSwapConfirmModal .modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-top: 1px solid var(--border-color);
}
/* Mobile Case Picker - Base Styles */
.mobile-case-picker-sheet {