Просмотр исходного кода

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 месяцев назад
Родитель
Сommit
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 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 {
       return {
         current: currentModel,
         current: currentModel,
         recent,
         recent,
         list,
         list,
+        cycle,
         set(model: ModelKey | undefined, options?: { recent?: boolean }) {
         set(model: ModelKey | undefined, options?: { recent?: boolean }) {
           batch(() => {
           batch(() => {
             setStore("model", agent.current().name, model ?? fallbackModel())
             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 />)
         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",
       title: "Switch agent",
       value: "agent.list",
       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)
           setModelStore("ready", true)
         })
         })
 
 
-      createEffect(() => {
-        Bun.write(
-          file,
-          JSON.stringify({
-            recent: modelStore.recent,
-          }),
-        )
-      })
-
       const fallbackModel = createMemo(() => {
       const fallbackModel = createMemo(() => {
         if (sync.data.config.model) {
         if (sync.data.config.model) {
           const [providerID, modelID] = sync.data.config.model.split("/")
           const [providerID, modelID] = sync.data.config.model.split("/")
@@ -206,6 +197,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
             model: model.name ?? value.modelID,
             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 }) {
         set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
           batch(() => {
           batch(() => {
             if (!isModelValid(model)) {
             if (!isModelValid(model)) {
@@ -216,12 +222,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
               })
               })
               return
               return
             }
             }
-
             setModelStore("model", agent.current().name, model)
             setModelStore("model", agent.current().name, model)
             if (options?.recent) {
             if (options?.recent) {
               const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
               const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
               if (uniq.length > 5) uniq.pop()
               if (uniq.length > 5) uniq.pop()
               setModelStore("recent", uniq)
               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")
         .default("<leader>h")
         .describe("Toggle code block concealment in messages"),
         .describe("Toggle code block concealment in messages"),
       model_list: z.string().optional().default("<leader>m").describe("List available models"),
       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"),
       command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
       agent_list: z.string().optional().default("<leader>a").describe("List agents"),
       agent_list: z.string().optional().default("<leader>a").describe("List agents"),
       agent_cycle: z.string().optional().default("tab").describe("Next agent"),
       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
    * List available models
    */
    */
   model_list?: string
   model_list?: string
+  /**
+   * Next recently used model
+   */
+  model_cycle_recent?: string
+  /**
+   * Previous recently used model
+   */
+  model_cycle_recent_reverse?: string
   /**
   /**
    * List available commands
    * List available commands
    */
    */