diff --git a/packages/opencode/src/cli/cmd/opentui/session.tsx b/packages/opencode/src/cli/cmd/opentui/session.tsx index bacf6e577..bca22de00 100644 --- a/packages/opencode/src/cli/cmd/opentui/session.tsx +++ b/packages/opencode/src/cli/cmd/opentui/session.tsx @@ -5,7 +5,7 @@ import { useRouteData } from "./context/route" import { useSync } from "./context/sync" import { SplitBorder } from "./component/border" import { Theme } from "./context/theme" -import { hastToStyledText, RGBA, ScrollBoxRenderable, SyntaxStyle } from "@opentui/core" +import { BoxRenderable, hastToStyledText, RGBA, ScrollBoxRenderable, SyntaxStyle } from "@opentui/core" import { Prompt } from "./component/prompt" import type { AssistantMessage, Part, ToolPart, UserMessage } from "@opencode-ai/sdk" import type { TextPart } from "ai" @@ -25,7 +25,7 @@ import type { EditTool } from "../../../tool/edit" import type { PatchTool } from "../../../tool/patch" import type { WebFetchTool } from "../../../tool/webfetch" import type { TaskTool } from "../../../tool/task" -import { useKeyboard, type JSX } from "@opentui/solid" +import { useKeyboard, type BoxProps, type JSX } from "@opentui/solid" import { createStore } from "solid-js/store" export function Session() { @@ -39,8 +39,8 @@ export function Session() { createEffect(() => sync.session.sync(route.sessionID)) useKeyboard((evt) => { - if (evt.name === "pageup") scroll.scrollBy(-scroll.height) - if (evt.name === "pagedown") scroll.scrollBy(scroll.height) + if (evt.name === "pageup") scroll.scrollBy(-scroll.height / 2) + if (evt.name === "pagedown") scroll.scrollBy(scroll.height / 2) }) const [store, setStore] = createStore({ @@ -136,23 +136,25 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) { {(part) => { const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING]) - const [margin, setMargin] = createSignal(0) + function resize(el: BoxRenderable) { + const parent = el.parent + if (!parent) return + const children = parent.getChildren() + const index = children.indexOf(el) + const previous = children[index - 1] + if (!previous) return + console.log(previous.height, el.height) + if (el.height > 1 || previous.height > 1) { + el.marginTop = 1 + } + } return ( 1 || el.height > 1) { - setMargin(1) - return - } - setMargin(0) + resize(this) }} + ref={(el) => resize(el!)} > @@ -194,8 +196,23 @@ function ToolPart(props: { part: ToolPart; message: AssistantMessage }) { const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {}) const input = props.part.state.input + const container: BoxProps = + ToolRegistry.container(props.part.tool) === "block" + ? { + border: ["left"] as const, + paddingTop: 1, + paddingBottom: 1, + paddingLeft: 2, + backgroundColor: Theme.backgroundPanel, + customBorderChars: SplitBorder.customBorderChars, + borderColor: Theme.background, + } + : { + paddingLeft: 3, + } + return ( - + {props.part.state.status === "error" && ( - + {props.part.state.error.replace("Error: ", "")} )} @@ -211,11 +228,7 @@ function ToolPart(props: { part: ToolPart; message: AssistantMessage }) { ) }) - return ( - - {component()} - - ) + return {component()} } type ToolProps = { @@ -225,13 +238,20 @@ type ToolProps = { } const ToolRegistry = (() => { - const state: Record> }> = {} - function register(input: { name: string; ready?: Component> }) { + const state: Record> }> = {} + function register(input: { + name: string + container: "inline" | "block" + ready?: Component> + }) { state[input.name] = input return input } return { register, + container(name: string) { + return state[name]?.container + }, ready(name: string) { return state[name]?.ready }, @@ -240,7 +260,7 @@ const ToolRegistry = (() => { function ToolTitle(props: { fallback: string; when: any; icon: string; children: JSX.Element }) { return ( - + ~ {props.fallback}} when={props.when}> {props.icon} {props.children} @@ -250,21 +270,22 @@ function ToolTitle(props: { fallback: string; when: any; icon: string; children: ToolRegistry.register({ name: "bash", + container: "block", ready(props) { return ( - <> + {props.input.description} - - $ {props.input.command} + + $ {props.input.command} - {props.output?.trim()} + {props.output?.trim()} - + ) }, }) @@ -285,6 +306,7 @@ const syntax = new SyntaxStyle({ ToolRegistry.register({ name: "read", + container: "inline", ready(props) { return ( <> @@ -298,6 +320,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "write", + container: "block", ready(props) { const lines = createMemo(() => { return props.input.content?.split("\n") ?? [] @@ -337,6 +360,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "glob", + container: "inline", ready(props) { return ( <> @@ -350,9 +374,10 @@ ToolRegistry.register({ ToolRegistry.register({ name: "grep", + container: "inline", ready(props) { return ( - + Grep "{props.input.pattern}" ({props.metadata.matches} matches) ) @@ -361,6 +386,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "list", + container: "inline", ready(props) { const dir = createMemo(() => { if (props.input.path) { @@ -380,6 +406,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "task", + container: "block", ready(props) { return ( <> @@ -387,7 +414,7 @@ ToolRegistry.register({ Task {props.input.description} - + {(task) => ( @@ -404,6 +431,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "webfetch", + container: "block", ready(props) { return ( <> @@ -422,6 +450,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "edit", + container: "block", ready(props) { const code = createMemo(() => { if (!props.metadata.diff) return "" @@ -447,6 +476,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "patch", + container: "block", ready(props) { return ( <> @@ -465,13 +495,16 @@ ToolRegistry.register({ ToolRegistry.register({ name: "todowrite", + container: "block", ready(props) { return ( - <> - - Updated todos - - + + {(todo) => ( + + [{todo.status === "completed" ? "✓" : " "}] {todo.content} + + )} + ) }, })