This commit is contained in:
Dax Raad
2025-12-06 18:21:32 -05:00
parent f0fff56cac
commit 6ea4900f84
9 changed files with 251 additions and 170 deletions
@@ -1,5 +1,5 @@
import { useDialog } from "@tui/ui/dialog"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
import {
createContext,
createMemo,
@@ -18,6 +18,7 @@ const ctx = createContext<Context>()
export type CommandOption = DialogSelectOption & {
keybind?: keyof KeybindsConfig
suggested?: boolean
}
function init() {
@@ -26,7 +27,19 @@ function init() {
const dialog = useDialog()
const keybind = useKeybind()
const options = createMemo(() => {
return registrations().flatMap((x) => x())
const all = registrations().flatMap((x) => x())
const suggested = all.filter((x) => x.suggested)
return [
...suggested.map((x) => ({
...x,
category: "Suggested",
value: "suggested." + x.value,
})),
...all,
].map((x) => ({
...x,
footer: x.keybind ? keybind.print(x.keybind) : undefined,
}))
})
const suspended = () => suspendCount() > 0
@@ -99,14 +112,12 @@ export function CommandProvider(props: ParentProps) {
}
function DialogCommand(props: { options: CommandOption[] }) {
const keybind = useKeybind()
let ref: DialogSelectRef<string>
return (
<DialogSelect
ref={(r) => (ref = r)}
title="Commands"
options={props.options.map((x) => ({
...x,
footer: x.keybind ? keybind.print(x.keybind) : undefined,
}))}
options={props.options.filter((x) => !ref?.filter || !x.value.startsWith("suggested."))}
/>
)
}
@@ -6,28 +6,41 @@ import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { Keybind } from "@/util/keybind"
import { iife } from "@/util/iife"
export function DialogModel() {
export function useConnected() {
const sync = useSync()
return createMemo(() =>
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
)
}
export function DialogModel(props: { providerID?: string }) {
const local = useLocal()
const sync = useSync()
const dialog = useDialog()
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
const connected = createMemo(() =>
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
)
const connected = useConnected()
const providers = createDialogProviderOptions()
const showExtra = createMemo(() => {
if (!connected()) return false
if (props.providerID) return false
return true
})
const options = createMemo(() => {
const query = ref()?.filter
const favorites = connected() ? local.model.favorite() : []
const favorites = showExtra() ? local.model.favorite() : []
const recents = local.model.recent()
const recentList = recents
.filter((item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID))
.slice(0, 5)
const recentList = showExtra()
? recents
.filter(
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
)
.slice(0, 5)
: []
const favoriteOptions = !query
? favorites.flatMap((item) => {
@@ -109,6 +122,7 @@ export function DialogModel() {
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
map(([model, info]) => {
const value = {
providerID: provider.id,
@@ -150,7 +164,10 @@ export function DialogModel() {
if (inRecents) return false
return true
}),
sortBy((x) => x.title),
sortBy(
(x) => x.footer !== "Free",
(x) => x.title,
),
),
),
),
@@ -169,6 +186,15 @@ export function DialogModel() {
]
})
const provider = createMemo(() =>
props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
)
const title = createMemo(() => {
if (provider()) return provider()!.name
return "Select model"
})
return (
<DialogSelect
keybind={[
@@ -189,7 +215,7 @@ export function DialogModel() {
},
]}
ref={setRef}
title="Select model"
title={title()}
current={local.model.current()}
options={options()}
/>
@@ -124,7 +124,7 @@ function AutoMethod(props: AutoMethodProps) {
}
await sdk.client.instance.dispose()
await sync.bootstrap()
dialog.replace(() => <DialogModel />)
dialog.replace(() => <DialogModel providerID={props.providerID} />)
})
return (
@@ -172,7 +172,7 @@ function CodeMethod(props: CodeMethodProps) {
if (!error) {
await sdk.client.instance.dispose()
await sync.bootstrap()
dialog.replace(() => <DialogModel />)
dialog.replace(() => <DialogModel providerID={props.providerID} />)
return
}
setError(true)
@@ -229,7 +229,7 @@ function ApiMethod(props: ApiMethodProps) {
})
await sdk.client.instance.dispose()
await sync.bootstrap()
dialog.replace(() => <DialogModel />)
dialog.replace(() => <DialogModel providerID={props.providerID} />)
}}
/>
)
@@ -106,6 +106,79 @@ export function Prompt(props: PromptProps) {
command.register(() => {
return [
{
title: "Clear prompt",
value: "prompt.clear",
category: "Prompt",
disabled: true,
onSelect: (dialog) => {
input.extmarks.clear()
input.clear()
dialog.clear()
},
},
{
title: "Submit prompt",
value: "prompt.submit",
disabled: true,
keybind: "input_submit",
category: "Prompt",
onSelect: (dialog) => {
if (!input.focused) return
submit()
dialog.clear()
},
},
{
title: "Paste",
value: "prompt.paste",
disabled: true,
keybind: "input_paste",
category: "Prompt",
onSelect: async () => {
const content = await Clipboard.read()
if (content?.mime.startsWith("image/")) {
await pasteImage({
filename: "clipboard",
mime: content.mime,
content: content.data,
})
}
},
},
{
title: "Interrupt session",
value: "session.interrupt",
keybind: "session_interrupt",
disabled: status().type === "idle",
category: "Session",
onSelect: (dialog) => {
if (autocomplete.visible) return
if (!input.focused) return
// TODO: this should be its own command
if (store.mode === "shell") {
setStore("mode", "normal")
return
}
if (!props.sessionID) return
setStore("interrupt", store.interrupt + 1)
setTimeout(() => {
setStore("interrupt", 0)
}, 5000)
if (store.interrupt >= 2) {
sdk.client.session.abort({
path: {
id: props.sessionID,
},
})
setStore("interrupt", 0)
}
dialog.clear()
},
},
{
title: "Open editor",
category: "Session",
@@ -190,79 +263,6 @@ export function Prompt(props: PromptProps) {
input.cursorOffset = Bun.stringWidth(content)
},
},
{
title: "Clear prompt",
value: "prompt.clear",
category: "Prompt",
disabled: true,
onSelect: (dialog) => {
input.extmarks.clear()
input.clear()
dialog.clear()
},
},
{
title: "Submit prompt",
value: "prompt.submit",
disabled: true,
keybind: "input_submit",
category: "Prompt",
onSelect: (dialog) => {
if (!input.focused) return
submit()
dialog.clear()
},
},
{
title: "Paste",
value: "prompt.paste",
disabled: true,
keybind: "input_paste",
category: "Prompt",
onSelect: async () => {
const content = await Clipboard.read()
if (content?.mime.startsWith("image/")) {
await pasteImage({
filename: "clipboard",
mime: content.mime,
content: content.data,
})
}
},
},
{
title: "Interrupt session",
value: "session.interrupt",
keybind: "session_interrupt",
disabled: status().type === "idle",
category: "Session",
onSelect: (dialog) => {
if (autocomplete.visible) return
if (!input.focused) return
// TODO: this should be its own command
if (store.mode === "shell") {
setStore("mode", "normal")
return
}
if (!props.sessionID) return
setStore("interrupt", store.interrupt + 1)
setTimeout(() => {
setStore("interrupt", 0)
}, 5000)
if (store.interrupt >= 2) {
sdk.client.session.abort({
path: {
id: props.sessionID,
},
})
setStore("interrupt", 0)
}
dialog.clear()
},
},
]
})