Skip to content

Commit 23ea8ba

Browse files
thdxractions-user
andauthored
Tui onboarding (#4569)
Co-authored-by: GitHub Action <action@github.com>
1 parent c417fec commit 23ea8ba

File tree

23 files changed

+1253
-277
lines changed

23 files changed

+1253
-277
lines changed

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentu
22
import { Clipboard } from "@tui/util/clipboard"
33
import { TextAttributes } from "@opentui/core"
44
import { RouteProvider, useRoute } from "@tui/context/route"
5-
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch } from "solid-js"
5+
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show } from "solid-js"
66
import { Installation } from "@/installation"
77
import { Global } from "@/global"
88
import { DialogProvider, useDialog } from "@tui/ui/dialog"
9+
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
910
import { SDKProvider, useSDK } from "@tui/context/sdk"
1011
import { SyncProvider, useSync } from "@tui/context/sync"
1112
import { LocalProvider, useLocal } from "@tui/context/local"
@@ -293,6 +294,14 @@ function App() {
293294
},
294295
category: "System",
295296
},
297+
{
298+
title: "Connect provider",
299+
value: "provider.connect",
300+
onSelect: () => {
301+
dialog.replace(() => <DialogProviderList />)
302+
},
303+
category: "System",
304+
},
296305
{
297306
title: `Switch to ${mode() === "dark" ? "light" : "dark"} mode`,
298307
value: "theme.switch_mode",
@@ -451,16 +460,18 @@ function App() {
451460
<text fg={theme.textMuted}>{process.cwd().replace(Global.Path.home, "~")}</text>
452461
</box>
453462
</box>
454-
<box flexDirection="row" flexShrink={0}>
455-
<text fg={theme.textMuted} paddingRight={1}>
456-
tab
457-
</text>
458-
<text fg={local.agent.color(local.agent.current().name)}>{""}</text>
459-
<text bg={local.agent.color(local.agent.current().name)} fg={theme.background} wrapMode={undefined}>
460-
<span style={{ bold: true }}> {local.agent.current().name.toUpperCase()}</span>
461-
<span> AGENT </span>
462-
</text>
463-
</box>
463+
<Show when={false}>
464+
<box flexDirection="row" flexShrink={0}>
465+
<text fg={theme.textMuted} paddingRight={1}>
466+
tab
467+
</text>
468+
<text fg={local.agent.color(local.agent.current().name)}>{""}</text>
469+
<text bg={local.agent.color(local.agent.current().name)} fg={theme.background} wrapMode={undefined}>
470+
<span style={{ bold: true }}> {local.agent.current().name.toUpperCase()}</span>
471+
<span> AGENT </span>
472+
</text>
473+
</box>
474+
</Show>
464475
</box>
465476
</box>
466477
)
Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
export const EmptyBorder = {
2+
topLeft: "",
3+
bottomLeft: "",
4+
vertical: "",
5+
topRight: "",
6+
bottomRight: "",
7+
horizontal: " ",
8+
bottomT: "",
9+
topT: "",
10+
cross: "",
11+
leftT: "",
12+
rightT: "",
13+
}
14+
115
export const SplitBorder = {
216
border: ["left" as const, "right" as const],
317
customBorderChars: {
4-
topLeft: "",
5-
bottomLeft: "",
18+
...EmptyBorder,
619
vertical: "┃",
7-
topRight: "",
8-
bottomRight: "",
9-
horizontal: "",
10-
bottomT: "",
11-
topT: "",
12-
cross: "",
13-
leftT: "",
14-
rightT: "",
1520
},
1621
}

packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda
55
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
66
import { useDialog } from "@tui/ui/dialog"
77
import { useTheme } from "../context/theme"
8+
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
89

910
function Free() {
1011
const { theme } = useTheme()
11-
return <span style={{ fg: theme.secondary }}>Free</span>
12+
return <span style={{ fg: theme.text }}>Free</span>
13+
}
14+
const PROVIDER_PRIORITY: Record<string, number> = {
15+
opencode: 0,
16+
anthropic: 1,
17+
"github-copilot": 2,
18+
openai: 3,
19+
google: 4,
20+
openrouter: 5,
21+
vercel: 6,
1222
}
1323

1424
export function DialogModel() {
@@ -17,9 +27,16 @@ export function DialogModel() {
1727
const dialog = useDialog()
1828
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
1929

30+
const connected = createMemo(() =>
31+
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
32+
)
33+
34+
const showRecent = createMemo(() => !ref()?.filter && local.model.recent().length > 0 && connected())
35+
const providers = createDialogProviderOptions()
36+
2037
const options = createMemo(() => {
2138
return [
22-
...(!ref()?.filter
39+
...(showRecent()
2340
? local.model.recent().flatMap((item) => {
2441
const provider = sync.data.provider.find((x) => x.id === item.providerID)!
2542
if (!provider) return []
@@ -35,7 +52,17 @@ export function DialogModel() {
3552
title: model.name ?? item.modelID,
3653
description: provider.name,
3754
category: "Recent",
38-
footer: model.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
55+
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
56+
onSelect: () => {
57+
dialog.clear()
58+
local.model.set(
59+
{
60+
providerID: provider.id,
61+
modelID: model.id,
62+
},
63+
{ recent: true },
64+
)
65+
},
3966
},
4067
]
4168
})
@@ -56,28 +83,56 @@ export function DialogModel() {
5683
modelID: model,
5784
},
5885
title: info.name ?? model,
59-
description: provider.name,
60-
category: provider.name,
61-
footer: info.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
86+
description: connected() ? provider.name : undefined,
87+
category: connected() ? provider.name : undefined,
88+
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
89+
onSelect() {
90+
dialog.clear()
91+
local.model.set(
92+
{
93+
providerID: provider.id,
94+
modelID: model,
95+
},
96+
{ recent: true },
97+
)
98+
},
6299
})),
63-
filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
100+
filter((x) => !showRecent() || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
64101
sortBy((x) => x.title),
65102
),
66103
),
67104
),
105+
...(!connected()
106+
? pipe(
107+
providers(),
108+
map((option) => {
109+
return {
110+
...option,
111+
category: "Popular providers",
112+
}
113+
}),
114+
filter((x) => PROVIDER_PRIORITY[x.value] !== undefined),
115+
sortBy((x) => PROVIDER_PRIORITY[x.value] ?? 99),
116+
)
117+
: []),
68118
]
69119
})
70120

71121
return (
72122
<DialogSelect
123+
keybind={[
124+
{
125+
keybind: { ctrl: true, name: "a", meta: false, shift: false, leader: false },
126+
title: connected() ? "Connect provider" : "More providers",
127+
onTrigger() {
128+
dialog.replace(() => <DialogProvider />)
129+
},
130+
},
131+
]}
73132
ref={setRef}
74133
title="Select model"
75134
current={local.model.current()}
76135
options={options()}
77-
onSelect={(option) => {
78-
dialog.clear()
79-
local.model.set(option.value, { recent: true })
80-
}}
81136
/>
82137
)
83138
}

0 commit comments

Comments
 (0)