fix(quick-pane): load fresh categorized models
This commit is contained in:
@@ -134,6 +134,41 @@ function bridgeMock(
|
|||||||
return { runtimeCount: () => runtimeCount };
|
return { runtimeCount: () => runtimeCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deferredCatalogBridge() {
|
||||||
|
const firstSnapshot = deferred<RuntimeSnapshot>();
|
||||||
|
const staleModels: Model[] = [
|
||||||
|
{ provider: "provider", id: "stale/model", name: "Stale" },
|
||||||
|
];
|
||||||
|
const freshModels: Model[] = [
|
||||||
|
{ provider: "provider", id: "fresh/model", name: "Fresh" },
|
||||||
|
];
|
||||||
|
let runtimeCount = 0;
|
||||||
|
mocks.invoke.mockImplementation(
|
||||||
|
(command: string, payload?: { runtimeId?: string }) => {
|
||||||
|
if (command === "load_ui_state") return Promise.resolve(uiState);
|
||||||
|
if (command === "create_quick_runtime") {
|
||||||
|
runtimeCount += 1;
|
||||||
|
return Promise.resolve({
|
||||||
|
runtime: {
|
||||||
|
agentId: `agent-${runtimeCount}`,
|
||||||
|
runtimeId: `runtime-${runtimeCount}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (command === "get_session_runtime_snapshot") {
|
||||||
|
if (payload?.runtimeId === "runtime-1") return firstSnapshot.promise;
|
||||||
|
return Promise.resolve(runtimeSnapshot(freshModels));
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
firstSnapshot,
|
||||||
|
staleModels,
|
||||||
|
runtimeCount: () => runtimeCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function preparedTrigger() {
|
async function preparedTrigger() {
|
||||||
return screen.findByRole("button", { name: /provider ·/i });
|
return screen.findByRole("button", { name: /provider ·/i });
|
||||||
}
|
}
|
||||||
@@ -578,6 +613,112 @@ describe("QuickPane fresh runtime catalog", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
action: "Close",
|
||||||
|
dismiss: async () => {
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole("button", { name: "Close Quick Pane" }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "Escape",
|
||||||
|
dismiss: async () => {
|
||||||
|
fireEvent.keyDown(window, { key: "Escape" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"discards deferred catalog preparation after $action dismissal",
|
||||||
|
async ({ dismiss }) => {
|
||||||
|
const tracker = deferredCatalogBridge();
|
||||||
|
render(<QuickPane />);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith(
|
||||||
|
"get_session_runtime_snapshot",
|
||||||
|
{ runtimeId: "runtime-1" },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await dismiss();
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith("dismiss_quick_pane"),
|
||||||
|
);
|
||||||
|
await act(async () =>
|
||||||
|
tracker.firstSnapshot.resolve(runtimeSnapshot(tracker.staleModels)),
|
||||||
|
);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith("close_quick_runtime", {
|
||||||
|
runtimeId: "runtime-1",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: /provider · Stale/i }),
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
await act(async () => mocks.focusHandler?.({ payload: true }));
|
||||||
|
expect(
|
||||||
|
await screen.findByRole("button", { name: /provider · Fresh/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(tracker.runtimeCount()).toBe(2);
|
||||||
|
await ask();
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith("set_model", {
|
||||||
|
agentId: "agent-2",
|
||||||
|
provider: "provider",
|
||||||
|
modelId: "fresh/model",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(mocks.invoke).not.toHaveBeenCalledWith(
|
||||||
|
"set_model",
|
||||||
|
expect.objectContaining({ agentId: "agent-1" }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("discards deferred catalog preparation after idle focus loss", async () => {
|
||||||
|
const tracker = deferredCatalogBridge();
|
||||||
|
render(<QuickPane />);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith(
|
||||||
|
"get_session_runtime_snapshot",
|
||||||
|
{ runtimeId: "runtime-1" },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => mocks.focusHandler?.({ payload: false }));
|
||||||
|
expect(mocks.invoke).not.toHaveBeenCalledWith("dismiss_quick_pane");
|
||||||
|
await act(async () =>
|
||||||
|
tracker.firstSnapshot.resolve(runtimeSnapshot(tracker.staleModels)),
|
||||||
|
);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith("close_quick_runtime", {
|
||||||
|
runtimeId: "runtime-1",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: /provider · Stale/i }),
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
await act(async () => mocks.focusHandler?.({ payload: true }));
|
||||||
|
expect(
|
||||||
|
await screen.findByRole("button", { name: /provider · Fresh/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(tracker.runtimeCount()).toBe(2);
|
||||||
|
await ask();
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mocks.invoke).toHaveBeenCalledWith("set_model", {
|
||||||
|
agentId: "agent-2",
|
||||||
|
provider: "provider",
|
||||||
|
modelId: "fresh/model",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(mocks.invoke).not.toHaveBeenCalledWith(
|
||||||
|
"set_model",
|
||||||
|
expect.objectContaining({ agentId: "agent-1" }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("retains a prior runtime when close fails and retries before replacement", async () => {
|
it("retains a prior runtime when close fails and retries before replacement", async () => {
|
||||||
let runtimeCount = 0;
|
let runtimeCount = 0;
|
||||||
let closeAttempts = 0;
|
let closeAttempts = 0;
|
||||||
|
|||||||
@@ -311,9 +311,17 @@ export default function QuickPane() {
|
|||||||
[clearRuntimeIfCurrent, closeRuntime],
|
[clearRuntimeIfCurrent, closeRuntime],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const invalidateIdlePreparation = useCallback(() => {
|
||||||
|
if (requestActiveRef.current || submissionStartingRef.current) return;
|
||||||
|
preparationGenerationRef.current += 1;
|
||||||
|
preparationLockRef.current = false;
|
||||||
|
catalogReadyRef.current = false;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const closeIdleRuntime = useCallback(async () => {
|
const closeIdleRuntime = useCallback(async () => {
|
||||||
const current = runtimeRef.current;
|
const current = runtimeRef.current;
|
||||||
if (!current || requestActiveRef.current) return true;
|
if (!current || requestActiveRef.current || submissionStartingRef.current)
|
||||||
|
return true;
|
||||||
try {
|
try {
|
||||||
await closeRuntime(current.runtimeId);
|
await closeRuntime(current.runtimeId);
|
||||||
clearRuntimeIfCurrent(current.runtimeId);
|
clearRuntimeIfCurrent(current.runtimeId);
|
||||||
@@ -326,8 +334,9 @@ export default function QuickPane() {
|
|||||||
}, [clearRuntimeIfCurrent, closeRuntime]);
|
}, [clearRuntimeIfCurrent, closeRuntime]);
|
||||||
|
|
||||||
const dismissPane = useCallback(async () => {
|
const dismissPane = useCallback(async () => {
|
||||||
|
invalidateIdlePreparation();
|
||||||
if (await closeIdleRuntime()) await invoke("dismiss_quick_pane");
|
if (await closeIdleRuntime()) await invoke("dismiss_quick_pane");
|
||||||
}, [closeIdleRuntime]);
|
}, [closeIdleRuntime, invalidateIdlePreparation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true;
|
let active = true;
|
||||||
@@ -420,7 +429,8 @@ export default function QuickPane() {
|
|||||||
focusInput();
|
focusInput();
|
||||||
if (!requestActiveRef.current && !submissionStartingRef.current)
|
if (!requestActiveRef.current && !submissionStartingRef.current)
|
||||||
void prepareRuntime();
|
void prepareRuntime();
|
||||||
} else if (!submissionStartingRef.current) {
|
} else if (!requestActiveRef.current && !submissionStartingRef.current) {
|
||||||
|
invalidateIdlePreparation();
|
||||||
void closeIdleRuntime();
|
void closeIdleRuntime();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -429,7 +439,12 @@ export default function QuickPane() {
|
|||||||
void settingsListener.then((unlisten) => unlisten());
|
void settingsListener.then((unlisten) => unlisten());
|
||||||
void focusListener.then((unlisten) => unlisten());
|
void focusListener.then((unlisten) => unlisten());
|
||||||
};
|
};
|
||||||
}, [closeIdleRuntime, dismissPane, prepareRuntime]);
|
}, [
|
||||||
|
closeIdleRuntime,
|
||||||
|
dismissPane,
|
||||||
|
invalidateIdlePreparation,
|
||||||
|
prepareRuntime,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!runtime || !requestActive) return;
|
if (!runtime || !requestActive) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user