feat: add slash commands to the history so it doesnt get lost

This commit is contained in:
Brooklyn Nicholson 2026-04-09 18:51:17 -05:00
parent 7e813a30e0
commit 17ecdce936
5 changed files with 52 additions and 10 deletions

View file

@ -709,7 +709,10 @@ export function App({ gw }: { gw: GatewayClient }) {
historyDraftRef.current = ''
}
if (full.startsWith('/') && slashRef.current(full)) {
if (full.startsWith('/')) {
appendMessage({ role: 'system', text: full, kind: 'slash' })
pushHistory(full)
slashRef.current(full)
clearInput()
return
@ -793,7 +796,7 @@ export function App({ gw }: { gw: GatewayClient }) {
send(full)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[busy, enqueue, gw, listPasteIds, pastes, resolvePasteTokens, sid]
[appendMessage, busy, enqueue, gw, listPasteIds, pastes, pushHistory, resolvePasteTokens, sid]
)
// ── Input handling ───────────────────────────────────────────────

View file

@ -31,6 +31,10 @@ export const MessageLine = memo(function MessageLine({
const { body, glyph, prefix } = ROLE[msg.role](t)
const content = (() => {
if (msg.kind === 'slash') {
return <Text color={t.color.dim}>{msg.text}</Text>
}
if (msg.role === 'assistant') {
return hasAnsi(msg.text) ? <Text wrap="wrap">{msg.text}</Text> : <Md compact={compact} t={t} text={msg.text} />
}
@ -41,9 +45,11 @@ export const MessageLine = memo(function MessageLine({
return (
<Text color={body}>
{head}
<Text color={t.color.dim} dimColor>
[long message]
</Text>
{rest.join('')}
</Text>
)
@ -53,7 +59,7 @@ export const MessageLine = memo(function MessageLine({
})()
return (
<Box flexDirection="column" marginTop={msg.role === 'user' ? 1 : 0}>
<Box flexDirection="column" marginTop={msg.role === 'user' || msg.kind === 'slash' ? 1 : 0}>
{msg.thinking && (
<Text color={t.color.dim} dimColor wrap="truncate-end">
💭 {msg.thinking.replace(/\n/g, ' ').slice(0, 200)}

View file

@ -3,15 +3,21 @@ import { memo, useEffect, useState } from 'react'
import spinners, { type BrailleSpinnerName } from 'unicode-animations'
import { FACES, TOOL_VERBS, VERBS } from '../constants.js'
import { isToolTrailResultLine, lastCotTrailIndex } from '../lib/text.js'
import {
isToolTrailResultLine,
lastCotTrailIndex,
pick,
scaleHex,
THINKING_COT_FADE,
THINKING_COT_MAX,
thinkingCotTail
} from '../lib/text.js'
import type { Theme } from '../theme.js'
import type { ActiveTool, ActivityItem } from '../types.js'
const THINK: BrailleSpinnerName[] = ['helix', 'breathe', 'orbit', 'dna', 'waverows', 'snake', 'pulse']
const TOOL: BrailleSpinnerName[] = ['cascade', 'scan', 'diagswipe', 'fillsweep', 'rain', 'columns', 'sparkle']
const pick = <T,>(a: T[]) => a[Math.floor(Math.random() * a.length)]!
const tone = (item: ActivityItem, t: Theme) =>
item.tone === 'error' ? t.color.error : item.tone === 'warn' ? t.color.warn : t.color.dim
@ -128,7 +134,8 @@ export const Thinking = memo(function Thinking({ reasoning, t }: { reasoning: st
return () => clearInterval(id)
}, [])
const tail = reasoning.slice(-160).replace(/\n/g, ' ')
const tail = thinkingCotTail(reasoning)
const clipped = reasoning.length > THINKING_COT_MAX
return (
<>
@ -138,8 +145,17 @@ export const Thinking = memo(function Thinking({ reasoning, t }: { reasoning: st
</Text>
{tail ? (
<Text color={t.color.dim} dimColor wrap="truncate-end">
💭 {tail}
<Text wrap="truncate-end">
{clipped &&
Array.from({ length: Math.min(THINKING_COT_FADE, tail.length) }, (_, i) => (
<Text color={scaleHex(t.color.dim, (i + 1) / (THINKING_COT_FADE + 1))} key={i}>
{tail[i]}
</Text>
))}
<Text color={t.color.dim} dimColor>
{clipped ? tail.slice(THINKING_COT_FADE) : tail}
</Text>
</Text>
) : null}
</>

View file

@ -53,6 +53,23 @@ export const lastCotTrailIndex = (trail: readonly string[]) => {
return -1
}
export const THINKING_COT_MAX = 160
export const THINKING_COT_FADE = 5
export const thinkingCotTail = (reasoning: string) => reasoning.replace(/\n/g, ' ').slice(-THINKING_COT_MAX)
/** Scale #RRGGBB by k ∈ [0,1] — used for left-edge fade toward terminal bg. */
export const scaleHex = (hex: string, k: number) => {
const h = hex.replace('#', '')
const ch = (o: number) =>
Math.round(parseInt(h.slice(o, o + 2), 16) * k)
.toString(16)
.padStart(2, '0')
return `#${ch(0)}${ch(2)}${ch(4)}`
}
export const estimateRows = (text: string, w: number, compact = false) => {
let inCode = false
let rows = 0

View file

@ -24,7 +24,7 @@ export interface ClarifyReq {
export interface Msg {
role: Role
text: string
kind?: 'intro'
kind?: 'intro' | 'slash'
info?: SessionInfo
thinking?: string
tools?: string[]