mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 09:02:08 +00:00
Add thinkingText theme token, fix streaming toggle bug
- Add configurable thinkingText color for thinking blocks (defaults to muted) - Make 'Thinking...' label italic when collapsed - Fix Ctrl+T during streaming hiding the current message - Track streamingMessage to properly re-render on toggle Based on #366 by @paulbettner
This commit is contained in:
parent
c15efdbcd9
commit
506e63a969
7 changed files with 36 additions and 18 deletions
|
|
@ -190,6 +190,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Snake game example hook**: Demonstrates `ui.custom()`, `registerCommand()`, and session persistence. See [examples/hooks/snake.ts](examples/hooks/snake.ts).
|
- **Snake game example hook**: Demonstrates `ui.custom()`, `registerCommand()`, and session persistence. See [examples/hooks/snake.ts](examples/hooks/snake.ts).
|
||||||
|
- **`thinkingText` theme token**: Configurable color for thinking block text. ([#366](https://github.com/badlogic/pi-mono/pull/366) by [@paulbettner](https://github.com/paulbettner))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
@ -198,6 +199,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **Toggling thinking blocks during streaming shows nothing**: Pressing Ctrl+T while streaming would hide the current message until streaming completed.
|
||||||
- **Resuming session resets thinking level to off**: Initial model and thinking level were not saved to session file, causing `--resume`/`--continue` to default to `off`. ([#342](https://github.com/badlogic/pi-mono/issues/342) by [@aliou](https://github.com/aliou))
|
- **Resuming session resets thinking level to off**: Initial model and thinking level were not saved to session file, causing `--resume`/`--continue` to default to `off`. ([#342](https://github.com/badlogic/pi-mono/issues/342) by [@aliou](https://github.com/aliou))
|
||||||
- **Hook `tool_result` event ignores errors from custom tools**: The `tool_result` hook event was never emitted when tools threw errors, and always had `isError: false` for successful executions. Now emits the event with correct `isError` value in both success and error cases. ([#374](https://github.com/badlogic/pi-mono/issues/374) by [@nicobailon](https://github.com/nicobailon))
|
- **Hook `tool_result` event ignores errors from custom tools**: The `tool_result` hook event was never emitted when tools threw errors, and always had `isError: false` for successful executions. Now emits the event with correct `isError` value in both success and error cases. ([#374](https://github.com/badlogic/pi-mono/issues/374) by [@nicobailon](https://github.com/nicobailon))
|
||||||
- **Edit tool fails on Windows due to CRLF line endings**: Files with CRLF line endings now match correctly when LLMs send LF-only text. Line endings are normalized before matching and restored to original style on write. ([#355](https://github.com/badlogic/pi-mono/issues/355) by [@Pratham-Dubey](https://github.com/Pratham-Dubey))
|
- **Edit tool fails on Windows due to CRLF line endings**: Files with CRLF line endings now match correctly when LLMs send LF-only text. Line endings are normalized before matching and restored to original style on write. ([#355](https://github.com/badlogic/pi-mono/issues/355) by [@Pratham-Dubey](https://github.com/Pratham-Dubey))
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ Every theme must define all color tokens. There are no optional colors.
|
||||||
| `muted` | Secondary/dimmed text | Metadata, descriptions, output |
|
| `muted` | Secondary/dimmed text | Metadata, descriptions, output |
|
||||||
| `dim` | Very dimmed text | Less important info, placeholders |
|
| `dim` | Very dimmed text | Less important info, placeholders |
|
||||||
| `text` | Default text color | Main content (usually `""`) |
|
| `text` | Default text color | Main content (usually `""`) |
|
||||||
|
| `thinkingText` | Thinking block text | Assistant reasoning traces |
|
||||||
|
|
||||||
### Backgrounds & Content Text (11 colors)
|
### Backgrounds & Content Text (11 colors)
|
||||||
|
|
||||||
|
|
@ -119,6 +120,7 @@ Themes are defined in JSON files with the following structure:
|
||||||
"colors": {
|
"colors": {
|
||||||
"accent": "blue",
|
"accent": "blue",
|
||||||
"muted": "gray",
|
"muted": "gray",
|
||||||
|
"thinkingText": "gray",
|
||||||
"text": "",
|
"text": "",
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,15 @@ export class AssistantMessageComponent extends Container {
|
||||||
|
|
||||||
if (this.hideThinkingBlock) {
|
if (this.hideThinkingBlock) {
|
||||||
// Show static "Thinking..." label when hidden
|
// Show static "Thinking..." label when hidden
|
||||||
this.contentContainer.addChild(new Text(theme.fg("muted", "Thinking..."), 1, 0));
|
this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0));
|
||||||
if (hasTextAfter) {
|
if (hasTextAfter) {
|
||||||
this.contentContainer.addChild(new Spacer(1));
|
this.contentContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Thinking traces in muted color, italic
|
// Thinking traces in thinkingText color, italic
|
||||||
// Use Markdown component with default text style for consistent styling
|
|
||||||
this.contentContainer.addChild(
|
this.contentContainer.addChild(
|
||||||
new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
|
new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
|
||||||
color: (text: string) => theme.fg("muted", text),
|
color: (text: string) => theme.fg("thinkingText", text),
|
||||||
italic: true,
|
italic: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ export class InteractiveMode {
|
||||||
|
|
||||||
// Streaming message tracking
|
// Streaming message tracking
|
||||||
private streamingComponent: AssistantMessageComponent | undefined = undefined;
|
private streamingComponent: AssistantMessageComponent | undefined = undefined;
|
||||||
|
private streamingMessage: AssistantMessage | undefined = undefined;
|
||||||
|
|
||||||
// Tool execution tracking: toolCallId -> component
|
// Tool execution tracking: toolCallId -> component
|
||||||
private pendingTools = new Map<string, ToolExecutionComponent>();
|
private pendingTools = new Map<string, ToolExecutionComponent>();
|
||||||
|
|
@ -839,18 +840,19 @@ export class InteractiveMode {
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
} else if (event.message.role === "assistant") {
|
} else if (event.message.role === "assistant") {
|
||||||
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock);
|
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock);
|
||||||
|
this.streamingMessage = event.message;
|
||||||
this.chatContainer.addChild(this.streamingComponent);
|
this.chatContainer.addChild(this.streamingComponent);
|
||||||
this.streamingComponent.updateContent(event.message);
|
this.streamingComponent.updateContent(this.streamingMessage);
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "message_update":
|
case "message_update":
|
||||||
if (this.streamingComponent && event.message.role === "assistant") {
|
if (this.streamingComponent && event.message.role === "assistant") {
|
||||||
const assistantMsg = event.message as AssistantMessage;
|
this.streamingMessage = event.message;
|
||||||
this.streamingComponent.updateContent(assistantMsg);
|
this.streamingComponent.updateContent(this.streamingMessage);
|
||||||
|
|
||||||
for (const content of assistantMsg.content) {
|
for (const content of this.streamingMessage.content) {
|
||||||
if (content.type === "toolCall") {
|
if (content.type === "toolCall") {
|
||||||
if (!this.pendingTools.has(content.id)) {
|
if (!this.pendingTools.has(content.id)) {
|
||||||
this.chatContainer.addChild(new Text("", 0, 0));
|
this.chatContainer.addChild(new Text("", 0, 0));
|
||||||
|
|
@ -881,12 +883,14 @@ export class InteractiveMode {
|
||||||
case "message_end":
|
case "message_end":
|
||||||
if (event.message.role === "user") break;
|
if (event.message.role === "user") break;
|
||||||
if (this.streamingComponent && event.message.role === "assistant") {
|
if (this.streamingComponent && event.message.role === "assistant") {
|
||||||
const assistantMsg = event.message as AssistantMessage;
|
this.streamingMessage = event.message;
|
||||||
this.streamingComponent.updateContent(assistantMsg);
|
this.streamingComponent.updateContent(this.streamingMessage);
|
||||||
|
|
||||||
if (assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error") {
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
assistantMsg.stopReason === "aborted" ? "Operation aborted" : assistantMsg.errorMessage || "Error";
|
this.streamingMessage.stopReason === "aborted"
|
||||||
|
? "Operation aborted"
|
||||||
|
: this.streamingMessage.errorMessage || "Error";
|
||||||
for (const [, component] of this.pendingTools.entries()) {
|
for (const [, component] of this.pendingTools.entries()) {
|
||||||
component.updateResult({
|
component.updateResult({
|
||||||
content: [{ type: "text", text: errorMessage }],
|
content: [{ type: "text", text: errorMessage }],
|
||||||
|
|
@ -896,6 +900,7 @@ export class InteractiveMode {
|
||||||
this.pendingTools.clear();
|
this.pendingTools.clear();
|
||||||
}
|
}
|
||||||
this.streamingComponent = undefined;
|
this.streamingComponent = undefined;
|
||||||
|
this.streamingMessage = undefined;
|
||||||
this.footer.invalidate();
|
this.footer.invalidate();
|
||||||
}
|
}
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
|
|
@ -948,6 +953,7 @@ export class InteractiveMode {
|
||||||
if (this.streamingComponent) {
|
if (this.streamingComponent) {
|
||||||
this.chatContainer.removeChild(this.streamingComponent);
|
this.chatContainer.removeChild(this.streamingComponent);
|
||||||
this.streamingComponent = undefined;
|
this.streamingComponent = undefined;
|
||||||
|
this.streamingMessage = undefined;
|
||||||
}
|
}
|
||||||
this.pendingTools.clear();
|
this.pendingTools.clear();
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
|
|
@ -1329,14 +1335,17 @@ export class InteractiveMode {
|
||||||
this.hideThinkingBlock = !this.hideThinkingBlock;
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
||||||
this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
|
this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
|
||||||
|
|
||||||
for (const child of this.chatContainer.children) {
|
// Rebuild chat from session messages
|
||||||
if (child instanceof AssistantMessageComponent) {
|
|
||||||
child.setHideThinkingBlock(this.hideThinkingBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chatContainer.clear();
|
this.chatContainer.clear();
|
||||||
this.rebuildChatFromMessages();
|
this.rebuildChatFromMessages();
|
||||||
|
|
||||||
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
||||||
|
if (this.streamingComponent && this.streamingMessage) {
|
||||||
|
this.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);
|
||||||
|
this.streamingComponent.updateContent(this.streamingMessage);
|
||||||
|
this.chatContainer.addChild(this.streamingComponent);
|
||||||
|
}
|
||||||
|
|
||||||
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1738,6 +1747,7 @@ export class InteractiveMode {
|
||||||
// Clear UI state
|
// Clear UI state
|
||||||
this.pendingMessagesContainer.clear();
|
this.pendingMessagesContainer.clear();
|
||||||
this.streamingComponent = undefined;
|
this.streamingComponent = undefined;
|
||||||
|
this.streamingMessage = undefined;
|
||||||
this.pendingTools.clear();
|
this.pendingTools.clear();
|
||||||
|
|
||||||
// Switch session via AgentSession (emits hook and tool session events)
|
// Switch session via AgentSession (emits hook and tool session events)
|
||||||
|
|
@ -2004,6 +2014,7 @@ export class InteractiveMode {
|
||||||
this.chatContainer.clear();
|
this.chatContainer.clear();
|
||||||
this.pendingMessagesContainer.clear();
|
this.pendingMessagesContainer.clear();
|
||||||
this.streamingComponent = undefined;
|
this.streamingComponent = undefined;
|
||||||
|
this.streamingMessage = undefined;
|
||||||
this.pendingTools.clear();
|
this.pendingTools.clear();
|
||||||
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"muted": "gray",
|
"muted": "gray",
|
||||||
"dim": "dimGray",
|
"dim": "dimGray",
|
||||||
"text": "",
|
"text": "",
|
||||||
|
"thinkingText": "gray",
|
||||||
|
|
||||||
"selectedBg": "selectedBg",
|
"selectedBg": "selectedBg",
|
||||||
"userMessageBg": "userMsgBg",
|
"userMessageBg": "userMsgBg",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
"muted": "mediumGray",
|
"muted": "mediumGray",
|
||||||
"dim": "dimGray",
|
"dim": "dimGray",
|
||||||
"text": "",
|
"text": "",
|
||||||
|
"thinkingText": "mediumGray",
|
||||||
|
|
||||||
"selectedBg": "selectedBg",
|
"selectedBg": "selectedBg",
|
||||||
"userMessageBg": "userMsgBg",
|
"userMessageBg": "userMsgBg",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const ThemeJsonSchema = Type.Object({
|
||||||
muted: ColorValueSchema,
|
muted: ColorValueSchema,
|
||||||
dim: ColorValueSchema,
|
dim: ColorValueSchema,
|
||||||
text: ColorValueSchema,
|
text: ColorValueSchema,
|
||||||
|
thinkingText: ColorValueSchema,
|
||||||
// Backgrounds & Content Text (11 colors)
|
// Backgrounds & Content Text (11 colors)
|
||||||
selectedBg: ColorValueSchema,
|
selectedBg: ColorValueSchema,
|
||||||
userMessageBg: ColorValueSchema,
|
userMessageBg: ColorValueSchema,
|
||||||
|
|
@ -98,6 +99,7 @@ export type ThemeColor =
|
||||||
| "muted"
|
| "muted"
|
||||||
| "dim"
|
| "dim"
|
||||||
| "text"
|
| "text"
|
||||||
|
| "thinkingText"
|
||||||
| "userMessageText"
|
| "userMessageText"
|
||||||
| "customMessageText"
|
| "customMessageText"
|
||||||
| "customMessageLabel"
|
| "customMessageLabel"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue