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.
This commit is contained in:
Dax Raad
2025-10-31 19:42:27 -04:00
parent 261ff416a9
commit d4cb47eadc
5 changed files with 75 additions and 10 deletions

View File

@@ -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())

View File

@@ -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",

View File

@@ -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,
}),
)
} }
}) })
}, },

View File

@@ -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"),

View File

@@ -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
*/ */