diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 3681038e..fb37adb4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - **`--append-system-prompt` Flag**: Append additional text or file contents to the system prompt. Supports both inline text and file paths. Complements `--system-prompt` for layering custom instructions without replacing the base system prompt. ([#114](https://github.com/badlogic/pi-mono/pull/114) by [@markusylisiurunen](https://github.com/markusylisiurunen)) +- **Thinking Block Toggle**: Added `Ctrl+T` shortcut to toggle visibility of LLM thinking blocks. When toggled off, shows a static "Thinking..." label instead of full content. Useful for reducing visual clutter during long conversations. ([#113](https://github.com/badlogic/pi-mono/pull/113) by [@markusylisiurunen](https://github.com/markusylisiurunen)) ## [0.12.10] - 2025-12-04 diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 1d41ac70..f085404e 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -684,6 +684,7 @@ Change queue mode with `/queue` command. Setting is saved in `~/.pi/agent/settin - **Shift+Tab**: Cycle thinking level (for reasoning-capable models) - **Ctrl+P**: Cycle models (use `--models` to scope) - **Ctrl+O**: Toggle tool output expansion (collapsed ↔ full output) +- **Ctrl+T**: Toggle thinking block visibility (shows full content ↔ static "Thinking..." label) ## Project Context Files diff --git a/packages/coding-agent/src/settings-manager.ts b/packages/coding-agent/src/settings-manager.ts index a2306854..f9147f28 100644 --- a/packages/coding-agent/src/settings-manager.ts +++ b/packages/coding-agent/src/settings-manager.ts @@ -16,6 +16,7 @@ export interface Settings { queueMode?: "all" | "one-at-a-time"; theme?: string; compaction?: CompactionSettings; + hideThinkingBlock?: boolean; } export class SettingsManager { @@ -143,4 +144,13 @@ export class SettingsManager { keepRecentTokens: this.getCompactionKeepRecentTokens(), }; } + + getHideThinkingBlock(): boolean { + return this.settings.hideThinkingBlock ?? false; + } + + setHideThinkingBlock(hide: boolean): void { + this.settings.hideThinkingBlock = hide; + this.save(); + } } diff --git a/packages/coding-agent/src/tui/assistant-message.ts b/packages/coding-agent/src/tui/assistant-message.ts index e1a33fba..8757e76c 100644 --- a/packages/coding-agent/src/tui/assistant-message.ts +++ b/packages/coding-agent/src/tui/assistant-message.ts @@ -7,10 +7,13 @@ import { getMarkdownTheme, theme } from "../theme/theme.js"; */ export class AssistantMessageComponent extends Container { private contentContainer: Container; + private hideThinkingBlock: boolean; - constructor(message?: AssistantMessage) { + constructor(message?: AssistantMessage, hideThinkingBlock = false) { super(); + this.hideThinkingBlock = hideThinkingBlock; + // Container for text/thinking content this.contentContainer = new Container(); this.addChild(this.contentContainer); @@ -20,6 +23,10 @@ export class AssistantMessageComponent extends Container { } } + setHideThinkingBlock(hide: boolean): void { + this.hideThinkingBlock = hide; + } + updateContent(message: AssistantMessage): void { // Clear content container this.contentContainer.clear(); @@ -34,21 +41,33 @@ export class AssistantMessageComponent extends Container { } // Render content in order - for (const content of message.content) { + for (let i = 0; i < message.content.length; i++) { + const content = message.content[i]; if (content.type === "text" && content.text.trim()) { // Assistant text messages with no background - trim the text // Set paddingY=0 to avoid extra spacing before tool executions this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme())); } else if (content.type === "thinking" && content.thinking.trim()) { - // Thinking traces in muted color, italic - // Use Markdown component with default text style for consistent styling - this.contentContainer.addChild( - new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), { - color: (text: string) => theme.fg("muted", text), - italic: true, - }), - ); - this.contentContainer.addChild(new Spacer(1)); + // Check if there's text content after this thinking block + const hasTextAfter = message.content.slice(i + 1).some((c) => c.type === "text" && c.text.trim()); + + if (this.hideThinkingBlock) { + // Show static "Thinking..." label when hidden + this.contentContainer.addChild(new Text(theme.fg("muted", "Thinking..."), 1, 0)); + if (hasTextAfter) { + this.contentContainer.addChild(new Spacer(1)); + } + } else { + // Thinking traces in muted color, italic + // Use Markdown component with default text style for consistent styling + this.contentContainer.addChild( + new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), { + color: (text: string) => theme.fg("muted", text), + italic: true, + }), + ); + this.contentContainer.addChild(new Spacer(1)); + } } } diff --git a/packages/coding-agent/src/tui/custom-editor.ts b/packages/coding-agent/src/tui/custom-editor.ts index 49e41703..a63932bd 100644 --- a/packages/coding-agent/src/tui/custom-editor.ts +++ b/packages/coding-agent/src/tui/custom-editor.ts @@ -9,8 +9,15 @@ export class CustomEditor extends Editor { public onShiftTab?: () => void; public onCtrlP?: () => void; public onCtrlO?: () => void; + public onCtrlT?: () => void; handleInput(data: string): void { + // Intercept Ctrl+T for thinking block visibility toggle + if (data === "\x14" && this.onCtrlT) { + this.onCtrlT(); + return; + } + // Intercept Ctrl+O for tool output expansion if (data === "\x0f" && this.onCtrlO) { this.onCtrlO(); diff --git a/packages/coding-agent/src/tui/tui-renderer.ts b/packages/coding-agent/src/tui/tui-renderer.ts index 5cba73e7..888d5d94 100644 --- a/packages/coding-agent/src/tui/tui-renderer.ts +++ b/packages/coding-agent/src/tui/tui-renderer.ts @@ -107,6 +107,9 @@ export class TuiRenderer { // Tool output expansion state private toolOutputExpanded = false; + // Thinking block visibility state + private hideThinkingBlock = false; + // Agent subscription unsubscribe function private unsubscribe?: () => void; @@ -211,6 +214,9 @@ export class TuiRenderer { description: "Toggle automatic context compaction", }; + // Load hide thinking block setting + this.hideThinkingBlock = settingsManager.getHideThinkingBlock(); + // Load file-based slash commands this.fileCommands = loadSlashCommands(); @@ -272,6 +278,9 @@ export class TuiRenderer { theme.fg("dim", "ctrl+o") + theme.fg("muted", " to expand tools") + "\n" + + theme.fg("dim", "ctrl+t") + + theme.fg("muted", " to toggle thinking") + + "\n" + theme.fg("dim", "/") + theme.fg("muted", " for commands") + "\n" + @@ -362,6 +371,10 @@ export class TuiRenderer { this.toggleToolOutputExpansion(); }; + this.editor.onCtrlT = () => { + this.toggleThinkingBlockVisibility(); + }; + // Handle editor submission this.editor.onSubmit = async (text: string) => { text = text.trim(); @@ -648,7 +661,7 @@ export class TuiRenderer { this.ui.requestRender(); } else if (event.message.role === "assistant") { // Create assistant component for streaming - this.streamingComponent = new AssistantMessageComponent(); + this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock); this.chatContainer.addChild(this.streamingComponent); this.streamingComponent.updateContent(event.message as AssistantMessage); this.ui.requestRender(); @@ -788,7 +801,7 @@ export class TuiRenderer { const assistantMsg = message; // Add assistant message component - const assistantComponent = new AssistantMessageComponent(assistantMsg); + const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock); this.chatContainer.addChild(assistantComponent); } // Note: tool calls and results are now handled via tool_execution_start/end events @@ -834,7 +847,7 @@ export class TuiRenderer { } } else if (message.role === "assistant") { const assistantMsg = message as AssistantMessage; - const assistantComponent = new AssistantMessageComponent(assistantMsg); + const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock); this.chatContainer.addChild(assistantComponent); // Create tool execution components for any tool calls @@ -918,7 +931,7 @@ export class TuiRenderer { } } else if (message.role === "assistant") { const assistantMsg = message; - const assistantComponent = new AssistantMessageComponent(assistantMsg); + const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock); this.chatContainer.addChild(assistantComponent); for (const content of assistantMsg.content) { @@ -1121,6 +1134,28 @@ export class TuiRenderer { this.ui.requestRender(); } + private toggleThinkingBlockVisibility(): void { + this.hideThinkingBlock = !this.hideThinkingBlock; + this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock); + + // Update all assistant message components and rebuild their content + for (const child of this.chatContainer.children) { + if (child instanceof AssistantMessageComponent) { + child.setHideThinkingBlock(this.hideThinkingBlock); + } + } + + // Rebuild chat to apply visibility change + this.chatContainer.clear(); + this.rebuildChatFromMessages(); + + // Show brief notification + const status = this.hideThinkingBlock ? "hidden" : "visible"; + this.chatContainer.addChild(new Spacer(1)); + this.chatContainer.addChild(new Text(theme.fg("dim", `Thinking blocks: ${status}`), 1, 0)); + this.ui.requestRender(); + } + clearEditor(): void { this.editor.setText(""); this.ui.requestRender();