| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 772059a commit 38af99d
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,5 @@ | |||
| 1 | 1 | /** @jsxImportSource @opentui/solid */ | |
| 2 | - import { useKeyboard, useTerminalDimensions } from "@opentui/solid" | ||
| 2 | + import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid" | ||
| 3 | 3 | import { RGBA, VignetteEffect } from "@opentui/core" | |
| 4 | 4 | import type { | |
| 5 | 5 | TuiKeybindSet, | |
@@ -615,7 +615,7 @@ const Modal = (props: { | |||
| 615 | 615 | ) | |
| 616 | 616 | } | |
| 617 | 617 | ||
| 618 | - const home = (input: Cfg): TuiSlotPlugin => ({ | ||
| 618 | + const home = (api: TuiPluginApi, input: Cfg) => ({ | ||
| 619 | 619 | slots: { | |
| 620 | 620 | home_logo(ctx) { | |
| 621 | 621 | const map = ctx.theme.current | |
@@ -649,6 +649,36 @@ const home = (input: Cfg): TuiSlotPlugin => ({ | |||
| 649 | 649 | </box> | |
| 650 | 650 | ) | |
| 651 | 651 | }, | |
| 652 | + home_prompt(ctx, value) { | ||
| 653 | + const skin = look(ctx.theme.current) | ||
| 654 | + type Prompt = (props: { | ||
| 655 | + workspaceID?: string | ||
| 656 | + hint?: JSX.Element | ||
| 657 | + placeholders?: { | ||
| 658 | + normal?: string[] | ||
| 659 | + shell?: string[] | ||
| 660 | + } | ||
| 661 | + }) => JSX.Element | ||
| 662 | + if (!("Prompt" in api.ui)) return null | ||
| 663 | + const view = api.ui.Prompt | ||
| 664 | + if (typeof view !== "function") return null | ||
| 665 | + const Prompt = view as Prompt | ||
| 666 | + const normal = [ | ||
| 667 | + `[SMOKE] route check for ${input.label}`, | ||
| 668 | + "[SMOKE] confirm home_prompt slot override", | ||
| 669 | + "[SMOKE] verify api.ui.Prompt rendering", | ||
| 670 | + ] | ||
| 671 | + const shell = ["printf '[SMOKE] home prompt\n'", "git status --short", "bun --version"] | ||
| 672 | + const Hint = ( | ||
| 673 | + <box flexShrink={0} flexDirection="row" gap={1}> | ||
| 674 | + <text fg={skin.muted}> | ||
| 675 | + <span style={{ fg: skin.accent }}>•</span> smoke home prompt | ||
| 676 | + </text> | ||
| 677 | + </box> | ||
| 678 | + ) | ||
| 679 | + | ||
| 680 | + return <Prompt workspaceID={value.workspace_id} hint={Hint} placeholders={{ normal, shell }} /> | ||
| 681 | + }, | ||
| 652 | 682 | home_bottom(ctx) { | |
| 653 | 683 | const skin = look(ctx.theme.current) | |
| 654 | 684 | const text = "extra content in the unified home bottom slot" | |
@@ -706,8 +736,8 @@ const block = (input: Cfg, order: number, title: string, text: string): TuiSlotP | |||
| 706 | 736 | }, | |
| 707 | 737 | }) | |
| 708 | 738 | ||
| 709 | - const slot = (input: Cfg): TuiSlotPlugin[] => [ | ||
| 710 | - home(input), | ||
| 739 | + const slot = (api: TuiPluginApi, input: Cfg): TuiSlotPlugin[] => [ | ||
| 740 | + home(api, input), | ||
| 711 | 741 | block(input, 50, "Smoke above", "renders above internal sidebar blocks"), | |
| 712 | 742 | block(input, 250, "Smoke between", "renders between internal sidebar blocks"), | |
| 713 | 743 | block(input, 650, "Smoke below", "renders below internal sidebar blocks"), | |
@@ -848,7 +878,7 @@ const tui: TuiPlugin = async (api, options, meta) => { | |||
| 848 | 878 | ]) | |
| 849 | 879 | ||
| 850 | 880 | reg(api, value, keys) | |
| 851 | - for (const item of slot(value)) { | ||
| 881 | + for (const item of slot(api, value)) { | ||
| 852 | 882 | api.slots.register(item) | |
| 853 | 883 | } | |
| 854 | 884 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -102,8 +102,8 @@ | |||
| 102 | 102 | "@opencode-ai/sdk": "workspace:*", | |
| 103 | 103 | "@opencode-ai/util": "workspace:*", | |
| 104 | 104 | "@openrouter/ai-sdk-provider": "2.3.3", | |
| 105 | - "@opentui/core": "0.1.91", | ||
| 106 | - "@opentui/solid": "0.1.91", | ||
| 105 | + "@opentui/core": "0.1.92", | ||
| 106 | + "@opentui/solid": "0.1.92", | ||
| 107 | 107 | "@parcel/watcher": "2.5.1", | |
| 108 | 108 | "@pierre/diffs": "catalog:", | |
| 109 | 109 | "@solid-primitives/event-bus": "1.1.2", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,6 +45,10 @@ export type PromptProps = { | |||
| 45 | 45 | ref?: (ref: PromptRef) => void | |
| 46 | 46 | hint?: JSX.Element | |
| 47 | 47 | showPlaceholder?: boolean | |
| 48 | + placeholders?: { | ||
| 49 | + normal?: string[] | ||
| 50 | + shell?: string[] | ||
| 51 | + } | ||
| 48 | 52 | } | |
| 49 | 53 | ||
| 50 | 54 | export type PromptRef = { | |
@@ -57,13 +61,16 @@ export type PromptRef = { | |||
| 57 | 61 | submit(): void | |
| 58 | 62 | } | |
| 59 | 63 | ||
| 60 | - const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"] | ||
| 61 | - const SHELL_PLACEHOLDERS = ["ls -la", "git status", "pwd"] | ||
| 62 | 64 | const money = new Intl.NumberFormat("en-US", { | |
| 63 | 65 | style: "currency", | |
| 64 | 66 | currency: "USD", | |
| 65 | 67 | }) | |
| 66 | 68 | ||
| 69 | + function randomIndex(count: number) { | ||
| 70 | + if (count <= 0) return 0 | ||
| 71 | + return Math.floor(Math.random() * count) | ||
| 72 | + } | ||
| 73 | + | ||
| 67 | 74 | export function Prompt(props: PromptProps) { | |
| 68 | 75 | let input: TextareaRenderable | |
| 69 | 76 | let anchor: BoxRenderable | |
@@ -83,6 +90,8 @@ export function Prompt(props: PromptProps) { | |||
| 83 | 90 | const renderer = useRenderer() | |
| 84 | 91 | const { theme, syntax } = useTheme() | |
| 85 | 92 | const kv = useKV() | |
| 93 | + const list = createMemo(() => props.placeholders?.normal ?? []) | ||
| 94 | + const shell = createMemo(() => props.placeholders?.shell ?? []) | ||
| 86 | 95 | ||
| 87 | 96 | function promptModelWarning() { | |
| 88 | 97 | toast.show({ | |
@@ -152,7 +161,7 @@ export function Prompt(props: PromptProps) { | |||
| 152 | 161 | interrupt: number | |
| 153 | 162 | placeholder: number | |
| 154 | 163 | }>({ | |
| 155 | - placeholder: Math.floor(Math.random() * PLACEHOLDERS.length), | ||
| 164 | + placeholder: randomIndex(list().length), | ||
| 156 | 165 | prompt: { | |
| 157 | 166 | input: "", | |
| 158 | 167 | parts: [], | |
@@ -166,7 +175,7 @@ export function Prompt(props: PromptProps) { | |||
| 166 | 175 | on( | |
| 167 | 176 | () => props.sessionID, | |
| 168 | 177 | () => { | |
| 169 | - setStore("placeholder", Math.floor(Math.random() * PLACEHOLDERS.length)) | ||
| 178 | + setStore("placeholder", randomIndex(list().length)) | ||
| 170 | 179 | }, | |
| 171 | 180 | { defer: true }, | |
| 172 | 181 | ), | |
@@ -801,12 +810,14 @@ export function Prompt(props: PromptProps) { | |||
| 801 | 810 | }) | |
| 802 | 811 | ||
| 803 | 812 | const placeholderText = createMemo(() => { | |
| 804 | - if (props.sessionID) return undefined | ||
| 813 | + if (props.showPlaceholder === false) return undefined | ||
| 805 | 814 | if (store.mode === "shell") { | |
| 806 | - const example = SHELL_PLACEHOLDERS[store.placeholder % SHELL_PLACEHOLDERS.length] | ||
| 815 | + if (!shell().length) return undefined | ||
| 816 | + const example = shell()[store.placeholder % shell().length] | ||
| 807 | 817 | return `Run a command... "${example}"` | |
| 808 | 818 | } | |
| 809 | - return `Ask anything... "${PLACEHOLDERS[store.placeholder % PLACEHOLDERS.length]}"` | ||
| 819 | + if (!list().length) return undefined | ||
| 820 | + return `Ask anything... "${list()[store.placeholder % list().length]}"` | ||
| 810 | 821 | }) | |
| 811 | 822 | ||
| 812 | 823 | const spinnerDef = createMemo(() => { | |
@@ -922,7 +933,7 @@ export function Prompt(props: PromptProps) { | |||
| 922 | 933 | } | |
| 923 | 934 | } | |
| 924 | 935 | if (e.name === "!" && input.visualCursor.offset === 0) { | |
| 925 | - setStore("placeholder", Math.floor(Math.random() * SHELL_PLACEHOLDERS.length)) | ||
| 936 | + setStore("placeholder", randomIndex(shell().length)) | ||
| 926 | 937 | setStore("mode", "shell") | |
| 927 | 938 | e.preventDefault() | |
| 928 | 939 | return | |
@@ -1097,7 +1108,7 @@ export function Prompt(props: PromptProps) { | |||
| 1097 | 1108 | /> | |
| 1098 | 1109 | </box> | |
| 1099 | 1110 | <box flexDirection="row" justifyContent="space-between"> | |
| 1100 | - <Show when={status().type !== "idle"} fallback={<text />}> | ||
| 1111 | + <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}> | ||
| 1101 | 1112 | <box | |
| 1102 | 1113 | flexDirection="row" | |
| 1103 | 1114 | gap={1} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ import { DialogAlert } from "../ui/dialog-alert" | |||
| 14 | 14 | import { DialogConfirm } from "../ui/dialog-confirm" | |
| 15 | 15 | import { DialogPrompt } from "../ui/dialog-prompt" | |
| 16 | 16 | import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dialog-select" | |
| 17 | + import { Prompt } from "../component/prompt" | ||
| 17 | 18 | import type { useToast } from "../ui/toast" | |
| 18 | 19 | import { Installation } from "@/installation" | |
| 19 | 20 | import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2" | |
@@ -287,6 +288,19 @@ export function createTuiApi(input: Input): TuiHostPluginApi { | |||
| 287 | 288 | /> | |
| 288 | 289 | ) | |
| 289 | 290 | }, | |
| 291 | + Prompt(props) { | ||
| 292 | + return ( | ||
| 293 | + <Prompt | ||
| 294 | + workspaceID={props.workspaceID} | ||
| 295 | + visible={props.visible} | ||
| 296 | + disabled={props.disabled} | ||
| 297 | + onSubmit={props.onSubmit} | ||
| 298 | + hint={props.hint} | ||
| 299 | + showPlaceholder={props.showPlaceholder} | ||
| 300 | + placeholders={props.placeholders} | ||
| 301 | + /> | ||
| 302 | + ) | ||
| 303 | + }, | ||
| 290 | 304 | toast(inputToast) { | |
| 291 | 305 | input.toast.show({ | |
| 292 | 306 | title: inputToast.title, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,6 +15,10 @@ import { TuiPluginRuntime } from "../plugin" | |||
| 15 | 15 | ||
| 16 | 16 | // TODO: what is the best way to do this? | |
| 17 | 17 | let once = false | |
| 18 | + const placeholder = { | ||
| 19 | + normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"], | ||
| 20 | + shell: ["ls -la", "git status", "pwd"], | ||
| 21 | + } | ||
| 18 | 22 | ||
| 19 | 23 | export function Home() { | |
| 20 | 24 | const sync = useSync() | |
@@ -49,11 +53,12 @@ export function Home() { | |||
| 49 | 53 | </box> | |
| 50 | 54 | ) | |
| 51 | 55 | ||
| 52 | - let prompt: PromptRef | ||
| 56 | + let prompt: PromptRef | undefined | ||
| 53 | 57 | const args = useArgs() | |
| 54 | 58 | const local = useLocal() | |
| 55 | 59 | onMount(() => { | |
| 56 | 60 | if (once) return | |
| 61 | + if (!prompt) return | ||
| 57 | 62 | if (route.initialPrompt) { | |
| 58 | 63 | prompt.set(route.initialPrompt) | |
| 59 | 64 | once = true | |
@@ -69,6 +74,7 @@ export function Home() { | |||
| 69 | 74 | () => sync.ready && local.model.ready, | |
| 70 | 75 | (ready) => { | |
| 71 | 76 | if (!ready) return | |
| 77 | + if (!prompt) return | ||
| 72 | 78 | if (!args.prompt) return | |
| 73 | 79 | if (prompt.current?.input !== args.prompt) return | |
| 74 | 80 | prompt.submit() | |
@@ -89,14 +95,17 @@ export function Home() { | |||
| 89 | 95 | </box> | |
| 90 | 96 | <box height={1} minHeight={0} flexShrink={1} /> | |
| 91 | 97 | <box width="100%" maxWidth={75} zIndex={1000} paddingTop={1} flexShrink={0}> | |
| 92 | - <Prompt | ||
| 93 | - ref={(r) => { | ||
| 94 | - prompt = r | ||
| 95 | - promptRef.set(r) | ||
| 96 | - }} | ||
| 97 | - hint={Hint} | ||
| 98 | - workspaceID={route.workspaceID} | ||
| 99 | - /> | ||
| 98 | + <TuiPluginRuntime.Slot name="home_prompt" mode="replace" workspace_id={route.workspaceID}> | ||
| 99 | + <Prompt | ||
| 100 | + ref={(r) => { | ||
| 101 | + prompt = r | ||
| 102 | + promptRef.set(r) | ||
| 103 | + }} | ||
| 104 | + hint={Hint} | ||
| 105 | + workspaceID={route.workspaceID} | ||
| 106 | + placeholders={placeholder} | ||
| 107 | + /> | ||
| 108 | + </TuiPluginRuntime.Slot> | ||
| 100 | 109 | </box> | |
| 101 | 110 | <TuiPluginRuntime.Slot name="home_bottom" /> | |
| 102 | 111 | <box flexGrow={1} minHeight={0} /> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -231,6 +231,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { | |||
| 231 | 231 | DialogConfirm: () => null, | |
| 232 | 232 | DialogPrompt: () => null, | |
| 233 | 233 | DialogSelect: () => null, | |
| 234 | + Prompt: () => null, | ||
| 234 | 235 | toast: () => {}, | |
| 235 | 236 | dialog: { | |
| 236 | 237 | replace: () => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,8 +21,8 @@ | |||
| 21 | 21 | "zod": "catalog:" | |
| 22 | 22 | }, | |
| 23 | 23 | "peerDependencies": { | |
| 24 | - "@opentui/core": ">=0.1.91", | ||
| 25 | - "@opentui/solid": ">=0.1.91" | ||
| 24 | + "@opentui/core": ">=0.1.92", | ||
| 25 | + "@opentui/solid": ">=0.1.92" | ||
| 26 | 26 | }, | |
| 27 | 27 | "peerDependenciesMeta": { | |
| 28 | 28 | "@opentui/core": { | |
@@ -33,8 +33,8 @@ | |||
| 33 | 33 | } | |
| 34 | 34 | }, | |
| 35 | 35 | "devDependencies": { | |
| 36 | - "@opentui/core": "0.1.91", | ||
| 37 | - "@opentui/solid": "0.1.91", | ||
| 36 | + "@opentui/core": "0.1.92", | ||
| 37 | + "@opentui/solid": "0.1.92", | ||
| 38 | 38 | "@tsconfig/node22": "catalog:", | |
| 39 | 39 | "@types/node": "catalog:", | |
| 40 | 40 | "typescript": "catalog:", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -135,6 +135,19 @@ export type TuiDialogSelectProps<Value = unknown> = { | |||
| 135 | 135 | current?: Value | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | + export type TuiPromptProps = { | ||
| 139 | + workspaceID?: string | ||
| 140 | + visible?: boolean | ||
| 141 | + disabled?: boolean | ||
| 142 | + onSubmit?: () => void | ||
| 143 | + hint?: JSX.Element | ||
| 144 | + showPlaceholder?: boolean | ||
| 145 | + placeholders?: { | ||
| 146 | + normal?: string[] | ||
| 147 | + shell?: string[] | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | + | ||
| 138 | 151 | export type TuiToast = { | |
| 139 | 152 | variant?: "info" | "success" | "warning" | "error" | |
| 140 | 153 | title?: string | |
@@ -279,6 +292,9 @@ export type TuiSidebarFileItem = { | |||
| 279 | 292 | export type TuiSlotMap = { | |
| 280 | 293 | app: {} | |
| 281 | 294 | home_logo: {} | |
| 295 | + home_prompt: { | ||
| 296 | + workspace_id?: string | ||
| 297 | + } | ||
| 282 | 298 | home_bottom: {} | |
| 283 | 299 | sidebar_title: { | |
| 284 | 300 | session_id: string | |
@@ -386,6 +402,7 @@ export type TuiPluginApi = { | |||
| 386 | 402 | DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element | |
| 387 | 403 | DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element | |
| 388 | 404 | DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element | |
| 405 | + Prompt: (props: TuiPromptProps) => JSX.Element | ||
| 389 | 406 | toast: (input: TuiToast) => void | |
| 390 | 407 | dialog: TuiDialogStack | |
| 391 | 408 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments