feat(ui): add model and thinking selector actions

This commit is contained in:
Federico Jaramillo Martinez
2026-07-27 09:40:24 +02:00
parent e502d569a9
commit ce4b469727
5 changed files with 60 additions and 0 deletions
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Add action-palette commands for selecting a session's model and thinking level, with support for assigning custom shortcuts in Settings.
+2
View File
@@ -1791,6 +1791,8 @@ export class PiWebApp extends LitElement {
configureAuth: () => this.auth.openLogin(),
logoutAuth: () => this.auth.openLogout(),
openThemePicker: () => { this.openThemeDialog(); },
openModelPicker: () => this.openModelDialog(),
openThinkingLevelPicker: () => this.openThinkingDialog(),
selectMainView: (view) => { this.selectMainView(view); },
selectWorkspaceTool: (tool) => { this.openWorkspaceTool(tool); },
openTerminal: (options) => { this.openTerminal(options); },
+21
View File
@@ -167,6 +167,22 @@ export function createCoreActions(): PluginAction[] {
enabled: hasWorkspace,
run: (context) => context.startSession(),
},
{
id: "model.select",
title: "Select Model",
description: "Choose the model for the selected session",
group: "Session",
enabled: hasSelectableSession,
run: (context) => context.openModelPicker(),
},
{
id: "thinking.select",
title: "Select Thinking Level",
description: "Choose the thinking level for the selected session",
group: "Session",
enabled: hasSelectableSession,
run: (context) => context.openThinkingLevelPicker(),
},
{
id: "session.archive",
title: "Archive Session",
@@ -216,6 +232,11 @@ function hasDeletableWorkspace(context: { state: AppState }): boolean {
return workspace !== undefined && workspace.isGitWorktree && !workspace.isMain && !isWorkspaceDeletionPending(context.state, workspace);
}
function hasSelectableSession(context: { state: AppState }): boolean {
const session = context.state.selectedSession;
return session !== undefined && session.archived !== true;
}
function hasArchivableSession(context: { state: AppState }): boolean {
return isArchivableSessionInfo(context.state.selectedSession, context.state.status, sessionPersistenceOptions(context.state));
}
+30
View File
@@ -38,6 +38,8 @@ function createContext(statePatch: Partial<AppState> = {}) {
configureAuth: vi.fn(() => { calls.push("configureAuth"); }),
logoutAuth: vi.fn(() => { calls.push("logoutAuth"); }),
openThemePicker: vi.fn(() => { calls.push("openThemePicker"); }),
openModelPicker: vi.fn(() => { calls.push("openModelPicker"); }),
openThinkingLevelPicker: vi.fn(() => { calls.push("openThinkingLevelPicker"); }),
selectMainView: vi.fn((view: AppState["mainView"]) => { calls.push(`selectMainView:${view}`); }),
selectWorkspaceTool: vi.fn((tool: AppState["workspaceTool"]) => { calls.push(`selectWorkspaceTool:${tool}`); }),
openTerminal: vi.fn((options?: { terminalId?: string | undefined }) => { calls.push(`openTerminal:${options?.terminalId ?? ""}`); }),
@@ -290,6 +292,34 @@ describe("PluginRegistry", () => {
expect(calls).toEqual(["deleteCachedNewSession"]);
});
it("exposes model and thinking selectors as configurable actions for writable sessions", () => {
const registry = new PluginRegistry();
registry.register({ id: "core", plugin: corePlugin });
const unavailable = registry.getActions(createContext().context);
expect(unavailable.find((action) => action.id === "core:model.select")?.enabled).toBe(false);
expect(unavailable.find((action) => action.id === "core:thinking.select")?.enabled).toBe(false);
const archivedSession = { ...testSession(), archived: true, archivedAt: "2026-05-20T00:00:00.000Z" };
const archived = registry.getActions(createContext({ selectedSession: archivedSession }).context);
expect(archived.find((action) => action.id === "core:model.select")?.enabled).toBe(false);
expect(archived.find((action) => action.id === "core:thinking.select")?.enabled).toBe(false);
const { context, calls } = createContext({ selectedSession: testSession() });
const actions = registry.getActions(context);
const modelAction = actions.find((action) => action.id === "core:model.select");
const thinkingAction = actions.find((action) => action.id === "core:thinking.select");
expect(modelAction).toMatchObject({ title: "Select Model", enabled: true });
expect(modelAction?.shortcut).toBeUndefined();
expect(thinkingAction).toMatchObject({ title: "Select Thinking Level", enabled: true });
expect(thinkingAction?.shortcut).toBeUndefined();
if (modelAction !== undefined) void modelAction.run();
if (thinkingAction !== undefined) void thinkingAction.run();
expect(calls).toEqual(["openModelPicker", "openThinkingLevelPicker"]);
});
it("routes refresh current to the active core workspace panel", () => {
const registry = new PluginRegistry();
registry.register({ id: "core", plugin: corePlugin });
+2
View File
@@ -106,6 +106,8 @@ export interface PluginRuntimeContext {
configureAuth: () => void | Promise<void>;
logoutAuth: () => void | Promise<void>;
openThemePicker: () => void;
openModelPicker: () => void | Promise<void>;
openThinkingLevelPicker: () => void | Promise<void>;
selectMainView: (view: AppState["mainView"]) => void;
selectWorkspaceTool: (tool: QualifiedContributionId) => void;
openTerminal: (options?: { terminalId?: string | undefined }) => void;