From ce4b46972725ac2f32f05142c5925adff7946527 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Mon, 27 Jul 2026 09:40:24 +0200 Subject: [PATCH] feat(ui): add model and thinking selector actions --- .changeset/model-thinking-picker-actions.md | 5 ++++ src/client/src/components/PiWebApp.ts | 2 ++ src/client/src/plugins/core/actions.ts | 21 +++++++++++++++ src/client/src/plugins/registry.test.ts | 30 +++++++++++++++++++++ src/client/src/plugins/types.ts | 2 ++ 5 files changed, 60 insertions(+) create mode 100644 .changeset/model-thinking-picker-actions.md diff --git a/.changeset/model-thinking-picker-actions.md b/.changeset/model-thinking-picker-actions.md new file mode 100644 index 0000000..9a8f0db --- /dev/null +++ b/.changeset/model-thinking-picker-actions.md @@ -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. diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index e12cc72..15dec34 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -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); }, diff --git a/src/client/src/plugins/core/actions.ts b/src/client/src/plugins/core/actions.ts index 322e1e9..2d0f0b4 100644 --- a/src/client/src/plugins/core/actions.ts +++ b/src/client/src/plugins/core/actions.ts @@ -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)); } diff --git a/src/client/src/plugins/registry.test.ts b/src/client/src/plugins/registry.test.ts index 7c74fda..cfc53dd 100644 --- a/src/client/src/plugins/registry.test.ts +++ b/src/client/src/plugins/registry.test.ts @@ -38,6 +38,8 @@ function createContext(statePatch: Partial = {}) { 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 }); diff --git a/src/client/src/plugins/types.ts b/src/client/src/plugins/types.ts index a0cc1ee..755cad3 100644 --- a/src/client/src/plugins/types.ts +++ b/src/client/src/plugins/types.ts @@ -106,6 +106,8 @@ export interface PluginRuntimeContext { configureAuth: () => void | Promise; logoutAuth: () => void | Promise; openThemePicker: () => void; + openModelPicker: () => void | Promise; + openThinkingLevelPicker: () => void | Promise; selectMainView: (view: AppState["mainView"]) => void; selectWorkspaceTool: (tool: QualifiedContributionId) => void; openTerminal: (options?: { terminalId?: string | undefined }) => void;