Browse Source

tui: add keyboard shortcuts to cycle through recently used models

Users can now press F2 to cycle forward and Shift+F2 to cycle backward through their recently used models, making it faster to switch between commonly used AI models without opening the model selection dialog.
Dax Raad 10 months ago
parent
commit
d4cb47eadc

+ 22 - 0
packages/desktop/src/context/local.tsx

@@ -162,10 +162,32 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
 
       const recent = createMemo(() => store.recent.map(find).filter(Boolean))
 
+      const cycle = (direction: 1 | -1) => {
+        const recentList = recent()
+        const current = currentModel()
+        if (!current) return
+
+        const index = recentList.findIndex((x) => x?.provider.id === current.provider.id && x?.id === current.id)
+        if (index === -1) return
+
+        let next = index + direction
+        if (next < 0) next = recentList.length - 1
+        if (next >= recentList.length) next = 0
+
+        const val = recentList[next]
+        if (!val) return
+
+        model.set({
+          providerID: val.provider.id,
+          modelID: val.id,
+        })
+      }
+
       return {
         current: currentModel,
         recent,
         list,
+        cycle,
         set(model: ModelKey | undefined, options?: { recent?: boolean }) {
           batch(() => {
             setStore("model", agent.current().name, model ?? fallbackModel())

+ 18 - 0
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -172,6 +172,24 @@ function App() {
         dialog.replace(() => <DialogModel />)
       },
     },
+    {
+      title: "Model cycle",
+      value: "model.cycle_recent",
+      keybind: "model_cycle_recent",
+      category: "Agent",
+      onSelect: () => {
+        local.model.cycle(1)
+      },
+    },
+    {
+      title: "Model cycle reverse",
+      value: "model.cycle_recent_reverse",
+      keybind: "model_cycle_recent_reverse",
+      category: "Agent",
+      onSelect: () => {
+        local.model.cycle(-1)
+      },
+    },
     {
       title: "Switch agent",
       value: "agent.list",

+ 21 - 10
packages/opencode/src/cli/cmd/tui/context/local.tsx

@@ -147,15 +147,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
           setModelStore("ready", true)
         })
 
-      createEffect(() => {
-        Bun.write(
-          file,
-          JSON.stringify({
-            recent: modelStore.recent,
-          }),
-        )
-      })
-
       const fallbackModel = createMemo(() => {
         if (sync.data.config.model) {
           const [providerID, modelID] = sync.data.config.model.split("/")
@@ -206,6 +197,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
             model: model.name ?? value.modelID,
           }
         }),
+        cycle(direction: 1 | -1) {
+          const current = currentModel()
+          if (!current) return
+          const recent = modelStore.recent
+          const index = recent.findIndex(
+            (x) => x.providerID === current.providerID && x.modelID === current.modelID,
+          )
+          if (index === -1) return
+          let next = index + direction
+          if (next < 0) next = recent.length - 1
+          if (next >= recent.length) next = 0
+          const val = recent[next]
+          if (!val) return
+          setModelStore("model", agent.current().name, { ...val })
+        },
         set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
           batch(() => {
             if (!isModelValid(model)) {
@@ -216,12 +222,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
               })
               return
             }
-
             setModelStore("model", agent.current().name, model)
             if (options?.recent) {
               const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
               if (uniq.length > 5) uniq.pop()
               setModelStore("recent", uniq)
+              Bun.write(
+                file,
+                JSON.stringify({
+                  recent: modelStore.recent,
+                }),
+              )
             }
           })
         },

+ 6 - 0
packages/opencode/src/config/config.ts

@@ -453,6 +453,12 @@ export namespace Config {
         .default("<leader>h")
         .describe("Toggle code block concealment in messages"),
       model_list: z.string().optional().default("<leader>m").describe("List available models"),
+      model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
+      model_cycle_recent_reverse: z
+        .string()
+        .optional()
+        .default("shift+f2")
+        .describe("Previous recently used model"),
       command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
       agent_list: z.string().optional().default("<leader>a").describe("List agents"),
       agent_cycle: z.string().optional().default("tab").describe("Next agent"),

+ 8 - 0
packages/sdk/js/src/gen/types.gen.ts

@@ -114,6 +114,14 @@ export type KeybindsConfig = {
    * List available models
    */
   model_list?: string
+  /**
+   * Next recently used model
+   */
+  model_cycle_recent?: string
+  /**
+   * Previous recently used model
+   */
+  model_cycle_recent_reverse?: string
   /**
    * List available commands
    */