mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
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:
co-authored by
Claude Sonnet 5
parent
83033b4299
commit
2936ba6e3d
@@ -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">×</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>
|
||||
|
||||
@@ -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?`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user