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
+2 -2
View File
@@ -156,7 +156,7 @@ describe('_quickStartWithCustomModelConfirm', () => {
it('confirming re-sends with confirmed:true and returns the second response', async () => {
const { win, app } = bootApp();
win.confirm = (() => true) as typeof win.confirm;
app._confirmModelSwap = async () => true;
let calls = 0;
withFetch(win, (body) => {
calls += 1;
@@ -184,7 +184,7 @@ describe('_quickStartWithCustomModelConfirm', () => {
it('cancelling never re-sends, and reports a cancellation error', async () => {
const { win, app } = bootApp();
win.confirm = (() => false) as typeof win.confirm;
app._confirmModelSwap = async () => false;
let calls = 0;
withFetch(win, () => {
calls += 1;
+37 -8
View File
@@ -54,6 +54,9 @@ function bootApp(
<p id="customModelPickHint"></p>
<div id="customModelPickList"></div>
</div>
<div class="modal" id="customModelSwapConfirmModal">
<p id="customModelSwapConfirmMessage"></p>
</div>
</body>`,
{ url: 'http://localhost/', runScripts: 'dangerously' }
);
@@ -515,8 +518,8 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and
return { win, app, applyBodies };
}
it('confirming the native window.confirm() re-sends the apply with confirmed:true', async () => {
const { win, app, applyBodies } = launchHarness([
it('confirming the in-app swap-confirm modal re-sends the apply with confirmed:true', async () => {
const { app, applyBodies } = launchHarness([
{
requiresConfirmation: true,
currentlyLoadedModel: 'llama3',
@@ -525,10 +528,10 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and
{ customModel: { endpointId: 'llama-box' }, restarted: true, modelSwapInProgress: true },
]);
let confirmMessage: string | undefined;
win.confirm = ((msg: string) => {
confirmMessage = msg;
app._confirmModelSwap = async (message: string) => {
confirmMessage = message;
return true;
}) as typeof win.confirm;
};
app._watchLlamaSwapLoading = async () => {}; // not under test here
await app.runCustomModelEntry('claude', 'llama-box', 'qwen3');
@@ -542,11 +545,11 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and
]);
});
it('cancelling window.confirm() keeps the native backend and never re-sends the apply', async () => {
const { win, app, applyBodies } = launchHarness([
it('cancelling the in-app swap-confirm modal keeps the native backend and never re-sends the apply', async () => {
const { app, applyBodies } = launchHarness([
{ requiresConfirmation: true, currentlyLoadedModel: 'llama3', affectedSessions: [{ id: 's2', name: 'w2' }] },
]);
win.confirm = (() => false) as typeof win.confirm;
app._confirmModelSwap = async () => false;
let toastMessage: string | undefined;
app.showToast = (msg: string) => {
toastMessage = msg;
@@ -658,3 +661,29 @@ describe('Custom Model Endpoint Profiles: _watchLlamaSwapLoading polling', () =>
expect(toastCalls.at(-1)).toMatch(/ready/i);
});
});
describe('Custom Model Endpoint Profiles: _confirmModelSwap (in-app modal, replaces a native confirm() popup)', () => {
it('shows the message, activates the modal, and resolves true when "Switch anyway" is clicked', async () => {
const { win, app } = bootApp({});
const promise = app._confirmModelSwap('w2 is using llama3. Switch anyway?');
const modal = win.document.getElementById('customModelSwapConfirmModal')!;
expect(modal.classList.contains('active')).toBe(true);
expect(win.document.getElementById('customModelSwapConfirmMessage')!.textContent).toBe(
'w2 is using llama3. Switch anyway?'
);
app._resolveModelSwapConfirm(true);
expect(await promise).toBe(true);
expect(modal.classList.contains('active')).toBe(false);
});
it('resolves false when Cancel (or the backdrop) is clicked, without ever showing a browser confirm() popup', async () => {
const { win, app } = bootApp({});
const promise = app._confirmModelSwap('w2 is using llama3. Switch anyway?');
app._resolveModelSwapConfirm(false);
expect(await promise).toBe(false);
expect(win.document.getElementById('customModelSwapConfirmModal')!.classList.contains('active')).toBe(false);
});
});