Merge branch 'dev' into feat/canceled-prompts-in-history

This commit is contained in:
Ariane Emory
2026-02-11 12:46:47 -05:00
477 changed files with 15945 additions and 13671 deletions
@@ -1,5 +1,5 @@
import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, t, dim, fg } from "@opentui/core"
import { createEffect, createMemo, type JSX, onMount, createSignal, onCleanup, Show, Switch, Match } from "solid-js"
import { createEffect, createMemo, type JSX, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
import "opentui-spinner/solid"
import { useLocal } from "@tui/context/local"
import { useTheme } from "@tui/context/theme"
@@ -54,6 +54,7 @@ export type PromptRef = {
}
const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"]
const SHELL_PLACEHOLDERS = ["ls -la", "git status", "pwd"]
export function Prompt(props: PromptProps) {
let input: TextareaRenderable
@@ -134,6 +135,16 @@ export function Prompt(props: PromptProps) {
interrupt: 0,
})
createEffect(
on(
() => props.sessionID,
() => {
setStore("placeholder", Math.floor(Math.random() * PLACEHOLDERS.length))
},
{ defer: true },
),
)
// Initialize agent/model/variant from last user message when session changes
let syncedSessionID: string | undefined
createEffect(() => {
@@ -736,6 +747,15 @@ export function Prompt(props: PromptProps) {
return !!current
})
const placeholderText = createMemo(() => {
if (props.sessionID) return undefined
if (store.mode === "shell") {
const example = SHELL_PLACEHOLDERS[store.placeholder % SHELL_PLACEHOLDERS.length]
return `Run a command... "${example}"`
}
return `Ask anything... "${PLACEHOLDERS[store.placeholder % PLACEHOLDERS.length]}"`
})
const spinnerDef = createMemo(() => {
const color = local.agent.color(local.agent.current().name)
return {
@@ -797,7 +817,7 @@ export function Prompt(props: PromptProps) {
flexGrow={1}
>
<textarea
placeholder={props.sessionID ? undefined : `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`}
placeholder={placeholderText()}
textColor={keybind.leader ? theme.textMuted : theme.text}
focusedTextColor={keybind.leader ? theme.textMuted : theme.text}
minHeight={1}
@@ -856,6 +876,7 @@ export function Prompt(props: PromptProps) {
}
}
if (e.name === "!" && input.visualCursor.offset === 0) {
setStore("placeholder", Math.floor(Math.random() * SHELL_PLACEHOLDERS.length))
setStore("mode", "shell")
e.preventDefault()
return
@@ -93,10 +93,14 @@ export function Home() {
return (
<>
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
<box height={3} />
<Logo />
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
<box flexGrow={1} alignItems="center" paddingLeft={2} paddingRight={2}>
<box flexGrow={1} minHeight={0} />
<box height={4} minHeight={0} flexShrink={1} />
<box flexShrink={0}>
<Logo />
</box>
<box height={1} minHeight={0} flexShrink={1} />
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1} flexShrink={0}>
<Prompt
ref={(r) => {
prompt = r
@@ -105,11 +109,12 @@ export function Home() {
hint={Hint}
/>
</box>
<box height={3} width="100%" maxWidth={75} alignItems="center" paddingTop={2}>
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
<Show when={showTips()}>
<Tips />
</Show>
</box>
<box flexGrow={1} minHeight={0} />
<Toast />
</box>
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexDirection="row" flexShrink={0} gap={2}>
@@ -141,7 +141,7 @@ export function Session() {
})
const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "hide")
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const [conceal, setConceal] = createSignal(true)
const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true)
@@ -314,26 +314,31 @@ export function Session() {
const command = useCommandDialog()
command.register(() => [
{
title: "Share session",
title: session()?.share?.url ? "Copy share link" : "Share session",
value: "session.share",
suggested: route.type === "session",
keybind: "session_share",
category: "Session",
enabled: sync.data.config.share !== "disabled" && !session()?.share?.url,
enabled: sync.data.config.share !== "disabled",
slash: {
name: "share",
},
onSelect: async (dialog) => {
const copy = (url: string) =>
Clipboard.copy(url)
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
.catch(() => toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }))
const url = session()?.share?.url
if (url) {
await copy(url)
dialog.clear()
return
}
await sdk.client.session
.share({
sessionID: route.sessionID,
})
.then((res) =>
Clipboard.copy(res.data!.share!.url).catch(() =>
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
),
)
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
.then((res) => copy(res.data!.share!.url))
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
dialog.clear()
},
@@ -57,7 +57,7 @@ function init() {
})
useKeyboard((evt) => {
if (evt.name === "escape" && store.stack.length > 0) {
if ((evt.name === "escape" || (evt.ctrl && evt.name === "c")) && store.stack.length > 0) {
const current = store.stack.at(-1)!
current.onClose?.()
setStore("stack", store.stack.slice(0, -1))