Merge hide-thinking branch (PR #113)

This commit is contained in:
Mario Zechner 2025-12-05 10:48:53 +01:00
commit 4c6d3b0bf6
6 changed files with 88 additions and 15 deletions

View file

@ -5,6 +5,7 @@
### Added ### 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)) - **`--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 ## [0.12.10] - 2025-12-04

View file

@ -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) - **Shift+Tab**: Cycle thinking level (for reasoning-capable models)
- **Ctrl+P**: Cycle models (use `--models` to scope) - **Ctrl+P**: Cycle models (use `--models` to scope)
- **Ctrl+O**: Toggle tool output expansion (collapsed ↔ full output) - **Ctrl+O**: Toggle tool output expansion (collapsed ↔ full output)
- **Ctrl+T**: Toggle thinking block visibility (shows full content ↔ static "Thinking..." label)
## Project Context Files ## Project Context Files

View file

@ -16,6 +16,7 @@ export interface Settings {
queueMode?: "all" | "one-at-a-time"; queueMode?: "all" | "one-at-a-time";
theme?: string; theme?: string;
compaction?: CompactionSettings; compaction?: CompactionSettings;
hideThinkingBlock?: boolean;
} }
export class SettingsManager { export class SettingsManager {
@ -143,4 +144,13 @@ export class SettingsManager {
keepRecentTokens: this.getCompactionKeepRecentTokens(), keepRecentTokens: this.getCompactionKeepRecentTokens(),
}; };
} }
getHideThinkingBlock(): boolean {
return this.settings.hideThinkingBlock ?? false;
}
setHideThinkingBlock(hide: boolean): void {
this.settings.hideThinkingBlock = hide;
this.save();
}
} }

View file

@ -7,10 +7,13 @@ import { getMarkdownTheme, theme } from "../theme/theme.js";
*/ */
export class AssistantMessageComponent extends Container { export class AssistantMessageComponent extends Container {
private contentContainer: Container; private contentContainer: Container;
private hideThinkingBlock: boolean;
constructor(message?: AssistantMessage) { constructor(message?: AssistantMessage, hideThinkingBlock = false) {
super(); super();
this.hideThinkingBlock = hideThinkingBlock;
// Container for text/thinking content // Container for text/thinking content
this.contentContainer = new Container(); this.contentContainer = new Container();
this.addChild(this.contentContainer); this.addChild(this.contentContainer);
@ -20,6 +23,10 @@ export class AssistantMessageComponent extends Container {
} }
} }
setHideThinkingBlock(hide: boolean): void {
this.hideThinkingBlock = hide;
}
updateContent(message: AssistantMessage): void { updateContent(message: AssistantMessage): void {
// Clear content container // Clear content container
this.contentContainer.clear(); this.contentContainer.clear();
@ -34,12 +41,23 @@ export class AssistantMessageComponent extends Container {
} }
// Render content in order // 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()) { if (content.type === "text" && content.text.trim()) {
// Assistant text messages with no background - trim the text // Assistant text messages with no background - trim the text
// Set paddingY=0 to avoid extra spacing before tool executions // Set paddingY=0 to avoid extra spacing before tool executions
this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme())); this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme()));
} else if (content.type === "thinking" && content.thinking.trim()) { } else if (content.type === "thinking" && content.thinking.trim()) {
// 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 // Thinking traces in muted color, italic
// Use Markdown component with default text style for consistent styling // Use Markdown component with default text style for consistent styling
this.contentContainer.addChild( this.contentContainer.addChild(
@ -51,6 +69,7 @@ export class AssistantMessageComponent extends Container {
this.contentContainer.addChild(new Spacer(1)); this.contentContainer.addChild(new Spacer(1));
} }
} }
}
// Check if aborted - show after partial content // Check if aborted - show after partial content
// But only if there are no tool calls (tool execution components will show the error) // But only if there are no tool calls (tool execution components will show the error)

View file

@ -9,8 +9,15 @@ export class CustomEditor extends Editor {
public onShiftTab?: () => void; public onShiftTab?: () => void;
public onCtrlP?: () => void; public onCtrlP?: () => void;
public onCtrlO?: () => void; public onCtrlO?: () => void;
public onCtrlT?: () => void;
handleInput(data: string): 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 // Intercept Ctrl+O for tool output expansion
if (data === "\x0f" && this.onCtrlO) { if (data === "\x0f" && this.onCtrlO) {
this.onCtrlO(); this.onCtrlO();

View file

@ -107,6 +107,9 @@ export class TuiRenderer {
// Tool output expansion state // Tool output expansion state
private toolOutputExpanded = false; private toolOutputExpanded = false;
// Thinking block visibility state
private hideThinkingBlock = false;
// Agent subscription unsubscribe function // Agent subscription unsubscribe function
private unsubscribe?: () => void; private unsubscribe?: () => void;
@ -211,6 +214,9 @@ export class TuiRenderer {
description: "Toggle automatic context compaction", description: "Toggle automatic context compaction",
}; };
// Load hide thinking block setting
this.hideThinkingBlock = settingsManager.getHideThinkingBlock();
// Load file-based slash commands // Load file-based slash commands
this.fileCommands = loadSlashCommands(); this.fileCommands = loadSlashCommands();
@ -272,6 +278,9 @@ export class TuiRenderer {
theme.fg("dim", "ctrl+o") + theme.fg("dim", "ctrl+o") +
theme.fg("muted", " to expand tools") + theme.fg("muted", " to expand tools") +
"\n" + "\n" +
theme.fg("dim", "ctrl+t") +
theme.fg("muted", " to toggle thinking") +
"\n" +
theme.fg("dim", "/") + theme.fg("dim", "/") +
theme.fg("muted", " for commands") + theme.fg("muted", " for commands") +
"\n" + "\n" +
@ -362,6 +371,10 @@ export class TuiRenderer {
this.toggleToolOutputExpansion(); this.toggleToolOutputExpansion();
}; };
this.editor.onCtrlT = () => {
this.toggleThinkingBlockVisibility();
};
// Handle editor submission // Handle editor submission
this.editor.onSubmit = async (text: string) => { this.editor.onSubmit = async (text: string) => {
text = text.trim(); text = text.trim();
@ -648,7 +661,7 @@ export class TuiRenderer {
this.ui.requestRender(); this.ui.requestRender();
} else if (event.message.role === "assistant") { } else if (event.message.role === "assistant") {
// Create assistant component for streaming // Create assistant component for streaming
this.streamingComponent = new AssistantMessageComponent(); this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock);
this.chatContainer.addChild(this.streamingComponent); this.chatContainer.addChild(this.streamingComponent);
this.streamingComponent.updateContent(event.message as AssistantMessage); this.streamingComponent.updateContent(event.message as AssistantMessage);
this.ui.requestRender(); this.ui.requestRender();
@ -788,7 +801,7 @@ export class TuiRenderer {
const assistantMsg = message; const assistantMsg = message;
// Add assistant message component // Add assistant message component
const assistantComponent = new AssistantMessageComponent(assistantMsg); const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock);
this.chatContainer.addChild(assistantComponent); this.chatContainer.addChild(assistantComponent);
} }
// Note: tool calls and results are now handled via tool_execution_start/end events // 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") { } else if (message.role === "assistant") {
const assistantMsg = message as AssistantMessage; const assistantMsg = message as AssistantMessage;
const assistantComponent = new AssistantMessageComponent(assistantMsg); const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock);
this.chatContainer.addChild(assistantComponent); this.chatContainer.addChild(assistantComponent);
// Create tool execution components for any tool calls // Create tool execution components for any tool calls
@ -918,7 +931,7 @@ export class TuiRenderer {
} }
} else if (message.role === "assistant") { } else if (message.role === "assistant") {
const assistantMsg = message; const assistantMsg = message;
const assistantComponent = new AssistantMessageComponent(assistantMsg); const assistantComponent = new AssistantMessageComponent(assistantMsg, this.hideThinkingBlock);
this.chatContainer.addChild(assistantComponent); this.chatContainer.addChild(assistantComponent);
for (const content of assistantMsg.content) { for (const content of assistantMsg.content) {
@ -1121,6 +1134,28 @@ export class TuiRenderer {
this.ui.requestRender(); 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 { clearEditor(): void {
this.editor.setText(""); this.editor.setText("");
this.ui.requestRender(); this.ui.requestRender();