From c32e43cac8298f06d6b30e4c33af8bafbf64f891 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Thu, 18 Dec 2025 22:21:48 -0800 Subject: [PATCH] chore: misc UI improvements (#317) Co-authored-by: Letta --- src/cli/App.tsx | 45 ++++++++--------------------- src/cli/components/InputRich.tsx | 7 +++-- src/cli/components/ShimmerText.tsx | 12 ++++++-- src/cli/components/colors.ts | 2 +- src/cli/helpers/thinkingMessages.ts | 16 ++++++++-- src/models.json | 11 ------- 6 files changed, 40 insertions(+), 53 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 90ec16e..5eab8c7 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -102,7 +102,7 @@ import { clearCompletedSubagents, clearSubagentsByIds, } from "./helpers/subagentState"; -import { getRandomThinkingMessage } from "./helpers/thinkingMessages"; +import { getRandomThinkingVerb } from "./helpers/thinkingMessages"; import { isFancyUITool, isTaskTool } from "./helpers/toolNameMapping.js"; import { useSuspend } from "./hooks/useSuspend/useSuspend.ts"; import { useSyncedState } from "./hooks/useSyncedState"; @@ -465,7 +465,7 @@ export default function App({ // Current thinking message (rotates each turn) const [thinkingMessage, setThinkingMessage] = useState( - getRandomThinkingMessage(agentName), + getRandomThinkingVerb(), ); // Session stats tracking @@ -1237,7 +1237,7 @@ export default function App({ } // Rotate to a new thinking message - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); refreshDerived(); await processConversation([ @@ -1410,13 +1410,7 @@ export default function App({ abortControllerRef.current = null; } }, - [ - appendError, - refreshDerived, - refreshDerivedThrottled, - setStreaming, - agentName, - ], + [appendError, refreshDerived, refreshDerivedThrottled, setStreaming], ); const handleExit = useCallback(() => { @@ -3020,7 +3014,7 @@ ${recentCommits} // Reset token counter for this turn (only count the agent's response) buffersRef.current.tokenCount = 0; // Rotate to a new thinking message for this turn - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); // Show streaming state immediately for responsiveness setStreaming(true); refreshDerived(); @@ -3499,7 +3493,7 @@ ${recentCommits} } // Rotate to a new thinking message - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); refreshDerived(); const wasAborted = approvalAbortController.signal.aborted; @@ -3536,7 +3530,6 @@ ${recentCommits} processConversation, refreshDerived, appendError, - agentName, setStreaming, ], ); @@ -3653,7 +3646,7 @@ ${recentCommits} if (currentIndex + 1 >= pendingApprovals.length) { // All approvals collected, execute and send to backend // sendAllResults owns the lock release via its finally block - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); await sendAllResults(decision); } else { // Not done yet, store decision and show next approval @@ -3674,7 +3667,6 @@ ${recentCommits} sendAllResults, appendError, isExecutingTool, - agentName, setStreaming, ], ); @@ -4104,7 +4096,7 @@ ${recentCommits} stderr: toolResult.stderr, }); - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); refreshDerived(); const decision = { @@ -4132,7 +4124,6 @@ ${recentCommits} sendAllResults, appendError, refreshDerived, - agentName, setStreaming, ], ); @@ -4215,7 +4206,7 @@ ${recentCommits} stderr: null, }); - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); refreshDerived(); const decision = { @@ -4231,13 +4222,7 @@ ${recentCommits} setApprovalResults((prev) => [...prev, decision]); } }, - [ - pendingApprovals, - approvalResults, - sendAllResults, - refreshDerived, - agentName, - ], + [pendingApprovals, approvalResults, sendAllResults, refreshDerived], ); const handleEnterPlanModeApprove = useCallback(async () => { @@ -4287,7 +4272,7 @@ Plan file path: ${planFilePath}`; stderr: null, }); - setThinkingMessage(getRandomThinkingMessage(agentName)); + setThinkingMessage(getRandomThinkingVerb()); refreshDerived(); const decision = { @@ -4302,13 +4287,7 @@ Plan file path: ${planFilePath}`; } else { setApprovalResults((prev) => [...prev, decision]); } - }, [ - pendingApprovals, - approvalResults, - sendAllResults, - refreshDerived, - agentName, - ]); + }, [pendingApprovals, approvalResults, sendAllResults, refreshDerived]); const handleEnterPlanModeReject = useCallback(async () => { const currentIndex = approvalResults.length; diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index aa7618c..1da8d42 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -395,14 +395,16 @@ export function Input({ const id = setInterval(() => { setShimmerOffset((prev) => { - const len = thinkingMessage.length; + // Include agent name length (+1 for space) in shimmer cycle + const prefixLen = agentName ? agentName.length + 1 : 0; + const len = prefixLen + thinkingMessage.length; const next = prev + 1; return next > len + 3 ? -3 : next; }); }, 120); // Speed of shimmer animation return () => clearInterval(id); - }, [streaming, thinkingMessage, visible]); + }, [streaming, thinkingMessage, visible, agentName]); const handleSubmit = async () => { // Don't submit if autocomplete is active with matches @@ -527,6 +529,7 @@ export function Input({ diff --git a/src/cli/components/ShimmerText.tsx b/src/cli/components/ShimmerText.tsx index 54d1337..f64e354 100644 --- a/src/cli/components/ShimmerText.tsx +++ b/src/cli/components/ShimmerText.tsx @@ -5,16 +5,19 @@ import { colors } from "./colors.js"; interface ShimmerTextProps { color?: string; + boldPrefix?: string; message: string; shimmerOffset: number; } export const ShimmerText: React.FC = ({ color = colors.status.processing, + boldPrefix, message, shimmerOffset, }) => { - const fullText = `${message}…`; + const fullText = `${boldPrefix ? `${boldPrefix} ` : ""}${message}…`; + const prefixLength = boldPrefix ? boldPrefix.length + 1 : 0; // +1 for space // Create the shimmer effect - simple 3-char highlight const shimmerText = fullText @@ -22,11 +25,14 @@ export const ShimmerText: React.FC = ({ .map((char, i) => { // Check if this character is within the 3-char shimmer window const isInShimmer = i >= shimmerOffset && i < shimmerOffset + 3; + const isInPrefix = i < prefixLength; if (isInShimmer) { - return chalk.hex(colors.status.processingShimmer)(char); + const styledChar = chalk.hex(colors.status.processingShimmer)(char); + return isInPrefix ? chalk.bold(styledChar) : styledChar; } - return chalk.hex(color)(char); + const styledChar = chalk.hex(color)(char); + return isInPrefix ? chalk.bold(styledChar) : styledChar; }) .join(""); diff --git a/src/cli/components/colors.ts b/src/cli/components/colors.ts index c826db3..2966362 100644 --- a/src/cli/components/colors.ts +++ b/src/cli/components/colors.ts @@ -99,7 +99,7 @@ export const colors = { streaming: brandColors.textDisabled, // solid gray dot (streaming/in progress) running: brandColors.statusWarning, // blinking yellow dot (executing) error: brandColors.statusError, // solid red dot (failed) - memoryName: brandColors.orange, // memory tool name highlight + memoryName: brandColors.primaryAccent, // memory tool name highlight (matches thinking spinner) }, // Input box diff --git a/src/cli/helpers/thinkingMessages.ts b/src/cli/helpers/thinkingMessages.ts index 012e5e4..d2d6089 100644 --- a/src/cli/helpers/thinkingMessages.ts +++ b/src/cli/helpers/thinkingMessages.ts @@ -34,10 +34,20 @@ const THINKING_VERBS = [ "initializing", ] as const; -// Get a random thinking message -export function getRandomThinkingMessage(agentName?: string | null): string { +// Get a random thinking verb (e.g., "thinking", "processing") +function getRandomVerb(): string { const index = Math.floor(Math.random() * THINKING_VERBS.length); - const verb = THINKING_VERBS[index] ?? "thinking"; + return THINKING_VERBS[index] ?? "thinking"; +} + +// Get a random thinking verb phrase (e.g., "is thinking", "is processing") +export function getRandomThinkingVerb(): string { + return `is ${getRandomVerb()}`; +} + +// Get a random thinking message (full string with agent name) +export function getRandomThinkingMessage(agentName?: string | null): string { + const verb = getRandomVerb(); if (agentName) { return `${agentName} is ${verb}`; diff --git a/src/models.json b/src/models.json index a4a6062..7510212 100644 --- a/src/models.json +++ b/src/models.json @@ -35,17 +35,6 @@ "max_reasoning_tokens": 31999 } }, - { - "id": "opus-4.1", - "handle": "anthropic/claude-opus-4-1-20250805", - "label": "Claude Opus 4.1", - "description": "Anthropic's previous version of Opus", - "updateArgs": { - "context_window": 180000, - "max_output_tokens": 64000, - "max_reasoning_tokens": 31999 - } - }, { "id": "haiku", "handle": "anthropic/claude-haiku-4-5-20251001",