From 54e2b1f399946128579b38a341eb1730b09d5c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 31 Oct 2025 16:39:08 +0100 Subject: [PATCH] opentui: feat: Implement POST /tui/toast-show (#3604) --- packages/opencode/src/cli/cmd/tui/app.tsx | 14 ++- .../cli/cmd/tui/component/prompt/index.tsx | 3 +- .../src/cli/cmd/tui/context/local.tsx | 11 +-- packages/opencode/src/cli/cmd/tui/event.ts | 7 +- .../opencode/src/cli/cmd/tui/ui/toast.tsx | 27 ++++-- packages/sdk/js/src/gen/types.gen.ts | 94 +++---------------- 6 files changed, 53 insertions(+), 103 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 584f987cf..3ada4d3da 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -25,6 +25,7 @@ import { DialogAlert } from "./ui/dialog-alert" import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider } from "./context/exit" import type { SessionRoute } from "./context/route" +import { TuiEvent } from "./event" export function tui(input: { url: string @@ -119,7 +120,7 @@ function App() { await sync.session.sync(data.sessionID).catch(() => { toast.show({ message: `Session not found: ${data.sessionID}`, - type: "error", + variant: "error", }) return route.navigate({ type: "home" }) }) @@ -231,10 +232,19 @@ function App() { } }) - event.on("tui.command.execute", (evt) => { + event.on(TuiEvent.CommandExecute.type, (evt) => { command.trigger(evt.properties.command) }) + event.on(TuiEvent.ToastShow.type, (evt) => { + toast.show({ + title: evt.properties.title, + message: evt.properties.message, + variant: evt.properties.variant, + duration: evt.properties.duration, + }) + }) + return ( { + sdk.event.on(TuiEvent.PromptAppend.type, (evt) => { setStore( "prompt", produce((draft) => { diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index f80b8d896..25ec00b32 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -8,7 +8,6 @@ import { Global } from "@/global" import { iife } from "@/util/iife" import { createSimpleContext } from "./helper" import { useToast } from "../ui/toast" -import type { Provider } from "@opencode-ai/sdk" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", @@ -40,7 +39,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const [providerID, modelID] = props.initialModel.split("/") if (!providerID || !modelID) return toast.show({ - type: "warning", + variant: "warning", message: `Invalid model format: ${props.initialModel}`, duration: 3000, }) @@ -60,7 +59,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) else toast.show({ - type: "warning", + variant: "warning", message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`, duration: 3000, }) @@ -93,7 +92,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ set(name: string) { if (!agents().some((x) => x.name === name)) return toast.show({ - type: "warning", + variant: "warning", message: `Agent not found: ${name}`, duration: 3000, }) @@ -211,7 +210,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ if (!isModelValid(model)) { toast.show({ message: `Model ${model.providerID}/${model.modelID} is not valid`, - type: "warning", + variant: "warning", duration: 3000, }) return @@ -274,4 +273,4 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } return result }, -}) \ No newline at end of file +}) diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index 49ef2e857..d5b2d7860 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,5 +1,6 @@ import { Bus } from "@/bus" import z from "zod" +import { Schema as ToastSchema } from "./ui/toast" export const TuiEvent = { PromptAppend: Bus.event("tui.prompt.append", z.object({ text: z.string() })), @@ -29,10 +30,6 @@ export const TuiEvent = { ), ToastShow: Bus.event( "tui.toast.show", - z.object({ - title: z.string().optional(), - message: z.string(), - variant: z.enum(["info", "success", "warning", "error"]), - }), + ToastSchema, ), } diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index bffbf0476..91047af41 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -2,12 +2,17 @@ import { createContext, useContext, type ParentProps, Show } from "solid-js" import { createStore } from "solid-js/store" import { useTheme } from "@tui/context/theme" import { SplitBorder } from "../component/border" +import { TextAttributes } from "@opentui/core" +import z from "zod" -export interface ToastOptions { - message: string | null - duration?: number - type: "info" | "success" | "warning" | "error" -} +export const Schema = z.object({ + title: z.string().optional(), + message: z.string(), + variant: z.enum(["info", "success", "warning", "error"]), + duration: z.number().default(5000).optional().describe("Duration in milliseconds"), +}) + +export type ToastOptions = z.infer export function Toast() { const toast = useToast() @@ -19,7 +24,7 @@ export function Toast() { + + {current().title} + {current().message} )} @@ -47,12 +55,13 @@ function init() { return { show(options: ToastOptions) { - const { duration, ...currentToast } = options + const parsedOptions = Schema.parse(options) + const { duration, ...currentToast } = parsedOptions setStore("currentToast", currentToast) if (timeoutHandle) clearTimeout(timeoutHandle) timeoutHandle = setTimeout(() => { setStore("currentToast", null) - }, duration ?? 5000).unref() + }, duration).unref() }, get currentToast(): ToastOptions | null { return store.currentToast diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 1981b63f8..e999d935b 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -18,10 +18,6 @@ export type KeybindsConfig = { * Leader key for keybind combinations */ leader?: string - /** - * Show help dialog - */ - app_help?: string /** * Exit the application */ @@ -34,18 +30,6 @@ export type KeybindsConfig = { * List available themes */ theme_list?: string - /** - * Create/update AGENTS.md - */ - project_init?: string - /** - * Toggle tool details - */ - tool_details?: string - /** - * Toggle thinking blocks - */ - thinking_blocks?: string /** * Toggle sidebar */ @@ -86,14 +70,6 @@ export type KeybindsConfig = { * Compact the session */ session_compact?: string - /** - * Cycle to next child session - */ - session_child_cycle?: string - /** - * Cycle to previous child session - */ - session_child_cycle_reverse?: string /** * Scroll messages up by one page */ @@ -138,14 +114,6 @@ export type KeybindsConfig = { * List available commands */ command_list?: string - /** - * Next recent model - */ - model_cycle_recent?: string - /** - * Previous recent model - */ - model_cycle_recent_reverse?: string /** * List agents */ @@ -174,54 +142,6 @@ export type KeybindsConfig = { * Insert newline in input */ input_newline?: string - /** - * @deprecated use agent_cycle. Next mode - */ - switch_mode?: string - /** - * @deprecated use agent_cycle_reverse. Previous mode - */ - switch_mode_reverse?: string - /** - * @deprecated use agent_cycle. Next agent - */ - switch_agent?: string - /** - * @deprecated use agent_cycle_reverse. Previous agent - */ - switch_agent_reverse?: string - /** - * @deprecated Currently not available. List files - */ - file_list?: string - /** - * @deprecated Close file - */ - file_close?: string - /** - * @deprecated Search file - */ - file_search?: string - /** - * @deprecated Split/unified diff - */ - file_diff_toggle?: string - /** - * @deprecated Navigate to previous message - */ - messages_previous?: string - /** - * @deprecated Navigate to next message - */ - messages_next?: string - /** - * @deprecated Toggle layout - */ - messages_layout_toggle?: string - /** - * @deprecated use messages_undo. Revert message - */ - messages_revert?: string } export type AgentConfig = { @@ -442,6 +362,9 @@ export type Config = { options?: { [key: string]: unknown } + headers?: { + [key: string]: string + } provider?: { npm: string } @@ -1009,6 +932,9 @@ export type Model = { options: { [key: string]: unknown } + headers?: { + [key: string]: string + } provider?: { npm: string } @@ -1157,6 +1083,10 @@ export type EventTuiToastShow = { title?: string message: string variant: "info" | "success" | "warning" | "error" + /** + * Duration in milliseconds + */ + duration?: number } } @@ -2728,6 +2658,10 @@ export type TuiShowToastData = { title?: string message: string variant: "info" | "success" | "warning" | "error" + /** + * Duration in milliseconds + */ + duration?: number } path?: never query?: {