mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 08:03:39 +00:00
Release v0.7.18
This commit is contained in:
parent
a11c1aa4ff
commit
22d8a0ae4a
16 changed files with 284 additions and 174 deletions
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -3195,11 +3195,11 @@
|
|||
},
|
||||
"packages/agent": {
|
||||
"name": "@mariozechner/pi-agent",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-ai": "^0.7.16",
|
||||
"@mariozechner/pi-tui": "^0.7.16"
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"@mariozechner/pi-tui": "^0.7.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
|
|
@ -3225,7 +3225,7 @@
|
|||
},
|
||||
"packages/ai": {
|
||||
"name": "@mariozechner/pi-ai",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.61.0",
|
||||
|
|
@ -3272,11 +3272,11 @@
|
|||
},
|
||||
"packages/coding-agent": {
|
||||
"name": "@mariozechner/pi-coding-agent",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent": "^0.7.16",
|
||||
"@mariozechner/pi-ai": "^0.7.16",
|
||||
"@mariozechner/pi-agent": "^0.7.17",
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"chalk": "^5.5.0",
|
||||
"diff": "^8.0.2",
|
||||
"glob": "^11.0.3"
|
||||
|
|
@ -3319,10 +3319,10 @@
|
|||
},
|
||||
"packages/pods": {
|
||||
"name": "@mariozechner/pi",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent": "^0.7.16",
|
||||
"@mariozechner/pi-agent": "^0.7.17",
|
||||
"chalk": "^5.5.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -3345,7 +3345,7 @@
|
|||
},
|
||||
"packages/proxy": {
|
||||
"name": "@mariozechner/pi-proxy",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.0",
|
||||
"hono": "^4.6.16"
|
||||
|
|
@ -3361,7 +3361,7 @@
|
|||
},
|
||||
"packages/tui": {
|
||||
"name": "@mariozechner/pi-tui",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mime-types": "^2.1.4",
|
||||
|
|
@ -3400,12 +3400,12 @@
|
|||
},
|
||||
"packages/web-ui": {
|
||||
"name": "@mariozechner/pi-web-ui",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lmstudio/sdk": "^1.5.0",
|
||||
"@mariozechner/pi-ai": "^0.7.16",
|
||||
"@mariozechner/pi-tui": "^0.7.16",
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"@mariozechner/pi-tui": "^0.7.17",
|
||||
"docx-preview": "^0.3.7",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide": "^0.544.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-agent",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"@mariozechner/pi-tui": "^0.7.17"
|
||||
"@mariozechner/pi-ai": "^0.7.18",
|
||||
"@mariozechner/pi-tui": "^0.7.18"
|
||||
},
|
||||
"keywords": [
|
||||
"ai",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-ai",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.7.18] - 2025-11-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Bash Tool Error Handling**: Bash tool now properly throws errors for failed commands (non-zero exit codes), timeouts, and aborted executions. This ensures tool execution components display with red background when bash commands fail.
|
||||
- **Thinking Traces Styling**: Thinking traces now maintain gray italic styling throughout, even when containing inline code blocks, bold text, or other inline formatting
|
||||
|
||||
## [0.7.17] - 2025-11-18
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-coding-agent",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent": "^0.7.17",
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"@mariozechner/pi-agent": "^0.7.18",
|
||||
"@mariozechner/pi-ai": "^0.7.18",
|
||||
"chalk": "^5.5.0",
|
||||
"diff": "^8.0.2",
|
||||
"glob": "^11.0.3"
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
|||
}
|
||||
if (output) output += "\n\n";
|
||||
output += "Command aborted";
|
||||
resolve({ content: [{ type: "text", text: `Command failed\n\n${output}` }], details: undefined });
|
||||
_reject(new Error(output));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
|||
}
|
||||
if (output) output += "\n\n";
|
||||
output += `Command timed out after ${timeout} seconds`;
|
||||
resolve({ content: [{ type: "text", text: `Command failed\n\n${output}` }], details: undefined });
|
||||
_reject(new Error(output));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -163,10 +163,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
|||
|
||||
if (code !== 0 && code !== null) {
|
||||
if (output) output += "\n\n";
|
||||
resolve({
|
||||
content: [{ type: "text", text: `Command failed\n\n${output}Command exited with code ${code}` }],
|
||||
details: undefined,
|
||||
});
|
||||
_reject(new Error(`${output}Command exited with code ${code}`));
|
||||
} else {
|
||||
resolve({ content: [{ type: "text", text: output || "(no output)" }], details: undefined });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,16 @@ export class AssistantMessageComponent extends Container {
|
|||
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(), undefined, undefined, undefined, 1, 0));
|
||||
this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0));
|
||||
} else if (content.type === "thinking" && content.thinking.trim()) {
|
||||
// Thinking traces in dark gray italic
|
||||
// Use Markdown component because it preserves ANSI codes across wrapped lines
|
||||
const thinkingText = chalk.gray.italic(content.thinking);
|
||||
this.contentContainer.addChild(new Markdown(thinkingText, undefined, undefined, undefined, 1, 0));
|
||||
// Use Markdown component with default text style for consistent styling
|
||||
this.contentContainer.addChild(
|
||||
new Markdown(content.thinking.trim(), 1, 0, {
|
||||
color: "gray",
|
||||
italic: true,
|
||||
}),
|
||||
);
|
||||
this.contentContainer.addChild(new Spacer(1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export class TuiRenderer {
|
|||
this.ui.addChild(new DynamicBorder(chalk.cyan));
|
||||
this.ui.addChild(new Text(chalk.bold.cyan("What's New"), 1, 0));
|
||||
this.ui.addChild(new Spacer(1));
|
||||
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), undefined, undefined, undefined, 1, 0));
|
||||
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0));
|
||||
this.ui.addChild(new Spacer(1));
|
||||
this.ui.addChild(new DynamicBorder(chalk.cyan));
|
||||
}
|
||||
|
|
@ -989,7 +989,7 @@ export class TuiRenderer {
|
|||
this.chatContainer.addChild(new DynamicBorder(chalk.cyan));
|
||||
this.ui.addChild(new Text(chalk.bold.cyan("What's New"), 1, 0));
|
||||
this.ui.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(new Markdown(changelogMarkdown));
|
||||
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1));
|
||||
this.chatContainer.addChild(new DynamicBorder(chalk.cyan));
|
||||
this.ui.requestRender();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export class UserMessageComponent extends Container {
|
|||
}
|
||||
|
||||
// User messages with dark gray background
|
||||
this.markdown = new Markdown(text, undefined, undefined, { r: 52, g: 53, b: 65 });
|
||||
this.markdown = new Markdown(text, 1, 1, { bgColor: "#343541" });
|
||||
this.addChild(this.markdown);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "CLI tool for managing vLLM deployments on GPU pods",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent": "^0.7.17",
|
||||
"@mariozechner/pi-agent": "^0.7.18",
|
||||
"chalk": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-proxy",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"type": "module",
|
||||
"description": "CORS and authentication proxy for pi-ai",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-tui",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
|||
|
|
@ -1,55 +1,46 @@
|
|||
import chalk from "chalk";
|
||||
import { Chalk } from "chalk";
|
||||
import { marked, type Token } from "marked";
|
||||
import type { Component } from "../tui.js";
|
||||
import { visibleWidth } from "../utils.js";
|
||||
|
||||
type Color =
|
||||
| "black"
|
||||
| "red"
|
||||
| "green"
|
||||
| "yellow"
|
||||
| "blue"
|
||||
| "magenta"
|
||||
| "cyan"
|
||||
| "white"
|
||||
| "gray"
|
||||
| "bgBlack"
|
||||
| "bgRed"
|
||||
| "bgGreen"
|
||||
| "bgYellow"
|
||||
| "bgBlue"
|
||||
| "bgMagenta"
|
||||
| "bgCyan"
|
||||
| "bgWhite"
|
||||
| "bgGray";
|
||||
// Use a chalk instance with color level 3 for consistent ANSI output
|
||||
const colorChalk = new Chalk({ level: 3 });
|
||||
|
||||
/**
|
||||
* Default text styling for markdown content.
|
||||
* Applied to all text unless overridden by markdown formatting.
|
||||
*/
|
||||
export interface DefaultTextStyle {
|
||||
/** Foreground color - named color or hex string like "#ff0000" */
|
||||
color?: string;
|
||||
/** Background color - named color or hex string like "#ff0000" */
|
||||
bgColor?: string;
|
||||
/** Bold text */
|
||||
bold?: boolean;
|
||||
/** Italic text */
|
||||
italic?: boolean;
|
||||
/** Strikethrough text */
|
||||
strikethrough?: boolean;
|
||||
/** Underline text */
|
||||
underline?: boolean;
|
||||
}
|
||||
|
||||
export class Markdown implements Component {
|
||||
private text: string;
|
||||
private bgColor?: Color;
|
||||
private fgColor?: Color;
|
||||
private customBgRgb?: { r: number; g: number; b: number };
|
||||
private paddingX: number; // Left/right padding
|
||||
private paddingY: number; // Top/bottom padding
|
||||
private defaultTextStyle?: DefaultTextStyle;
|
||||
|
||||
// Cache for rendered output
|
||||
private cachedText?: string;
|
||||
private cachedWidth?: number;
|
||||
private cachedLines?: string[];
|
||||
|
||||
constructor(
|
||||
text: string = "",
|
||||
bgColor?: Color,
|
||||
fgColor?: Color,
|
||||
customBgRgb?: { r: number; g: number; b: number },
|
||||
paddingX: number = 1,
|
||||
paddingY: number = 1,
|
||||
) {
|
||||
constructor(text: string = "", paddingX: number = 1, paddingY: number = 1, defaultTextStyle?: DefaultTextStyle) {
|
||||
this.text = text;
|
||||
this.bgColor = bgColor;
|
||||
this.fgColor = fgColor;
|
||||
this.customBgRgb = customBgRgb;
|
||||
this.paddingX = paddingX;
|
||||
this.paddingY = paddingY;
|
||||
this.defaultTextStyle = defaultTextStyle;
|
||||
}
|
||||
|
||||
setText(text: string): void {
|
||||
|
|
@ -60,30 +51,6 @@ export class Markdown implements Component {
|
|||
this.cachedLines = undefined;
|
||||
}
|
||||
|
||||
setBgColor(bgColor?: Color): void {
|
||||
this.bgColor = bgColor;
|
||||
// Invalidate cache when color changes
|
||||
this.cachedText = undefined;
|
||||
this.cachedWidth = undefined;
|
||||
this.cachedLines = undefined;
|
||||
}
|
||||
|
||||
setFgColor(fgColor?: Color): void {
|
||||
this.fgColor = fgColor;
|
||||
// Invalidate cache when color changes
|
||||
this.cachedText = undefined;
|
||||
this.cachedWidth = undefined;
|
||||
this.cachedLines = undefined;
|
||||
}
|
||||
|
||||
setCustomBgRgb(customBgRgb?: { r: number; g: number; b: number }): void {
|
||||
this.customBgRgb = customBgRgb;
|
||||
// Invalidate cache when color changes
|
||||
this.cachedText = undefined;
|
||||
this.cachedWidth = undefined;
|
||||
this.cachedLines = undefined;
|
||||
}
|
||||
|
||||
render(width: number): string[] {
|
||||
// Check cache
|
||||
if (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) {
|
||||
|
|
@ -125,7 +92,7 @@ export class Markdown implements Component {
|
|||
wrappedLines.push(...this.wrapLine(line, contentWidth));
|
||||
}
|
||||
|
||||
// Add padding and apply colors
|
||||
// Add padding and apply background color if specified
|
||||
const leftPad = " ".repeat(this.paddingX);
|
||||
const paddedLines: string[] = [];
|
||||
|
||||
|
|
@ -139,16 +106,9 @@ export class Markdown implements Component {
|
|||
// Add left padding, content, and right padding
|
||||
let paddedLine = leftPad + line + rightPad;
|
||||
|
||||
// Apply foreground color if specified
|
||||
if (this.fgColor) {
|
||||
paddedLine = (chalk as any)[this.fgColor](paddedLine);
|
||||
}
|
||||
|
||||
// Apply background color if specified
|
||||
if (this.customBgRgb) {
|
||||
paddedLine = chalk.bgRgb(this.customBgRgb.r, this.customBgRgb.g, this.customBgRgb.b)(paddedLine);
|
||||
} else if (this.bgColor) {
|
||||
paddedLine = (chalk as any)[this.bgColor](paddedLine);
|
||||
// Apply background color to entire line if specified
|
||||
if (this.defaultTextStyle?.bgColor) {
|
||||
paddedLine = this.applyBgColor(paddedLine);
|
||||
}
|
||||
|
||||
paddedLines.push(paddedLine);
|
||||
|
|
@ -158,25 +118,15 @@ export class Markdown implements Component {
|
|||
const emptyLine = " ".repeat(width);
|
||||
const topPadding: string[] = [];
|
||||
for (let i = 0; i < this.paddingY; i++) {
|
||||
let emptyPaddedLine = emptyLine;
|
||||
if (this.customBgRgb) {
|
||||
emptyPaddedLine = chalk.bgRgb(this.customBgRgb.r, this.customBgRgb.g, this.customBgRgb.b)(emptyPaddedLine);
|
||||
} else if (this.bgColor) {
|
||||
emptyPaddedLine = (chalk as any)[this.bgColor](emptyPaddedLine);
|
||||
}
|
||||
topPadding.push(emptyPaddedLine);
|
||||
const paddedEmptyLine = this.defaultTextStyle?.bgColor ? this.applyBgColor(emptyLine) : emptyLine;
|
||||
topPadding.push(paddedEmptyLine);
|
||||
}
|
||||
|
||||
// Add bottom padding (empty lines)
|
||||
const bottomPadding: string[] = [];
|
||||
for (let i = 0; i < this.paddingY; i++) {
|
||||
let emptyPaddedLine = emptyLine;
|
||||
if (this.customBgRgb) {
|
||||
emptyPaddedLine = chalk.bgRgb(this.customBgRgb.r, this.customBgRgb.g, this.customBgRgb.b)(emptyPaddedLine);
|
||||
} else if (this.bgColor) {
|
||||
emptyPaddedLine = (chalk as any)[this.bgColor](emptyPaddedLine);
|
||||
}
|
||||
bottomPadding.push(emptyPaddedLine);
|
||||
const paddedEmptyLine = this.defaultTextStyle?.bgColor ? this.applyBgColor(emptyLine) : emptyLine;
|
||||
bottomPadding.push(paddedEmptyLine);
|
||||
}
|
||||
|
||||
// Combine top padding, content, and bottom padding
|
||||
|
|
@ -190,6 +140,85 @@ export class Markdown implements Component {
|
|||
return result.length > 0 ? result : [""];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply only background color from default style.
|
||||
* Used for padding lines that don't have text content.
|
||||
*/
|
||||
private applyBgColor(text: string): string {
|
||||
if (!this.defaultTextStyle?.bgColor) {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (this.defaultTextStyle.bgColor.startsWith("#")) {
|
||||
// Hex color
|
||||
const hex = this.defaultTextStyle.bgColor.substring(1);
|
||||
const r = Number.parseInt(hex.substring(0, 2), 16);
|
||||
const g = Number.parseInt(hex.substring(2, 4), 16);
|
||||
const b = Number.parseInt(hex.substring(4, 6), 16);
|
||||
return colorChalk.bgRgb(r, g, b)(text);
|
||||
}
|
||||
// Named background color (bgRed, bgBlue, etc.)
|
||||
return (colorChalk as any)[this.defaultTextStyle.bgColor](text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default text style to a string.
|
||||
* This is the base styling applied to all text content.
|
||||
*/
|
||||
private applyDefaultStyle(text: string): string {
|
||||
if (!this.defaultTextStyle) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let styled = text;
|
||||
|
||||
// Apply color
|
||||
if (this.defaultTextStyle.color) {
|
||||
if (this.defaultTextStyle.color.startsWith("#")) {
|
||||
// Hex color
|
||||
const hex = this.defaultTextStyle.color.substring(1);
|
||||
const r = Number.parseInt(hex.substring(0, 2), 16);
|
||||
const g = Number.parseInt(hex.substring(2, 4), 16);
|
||||
const b = Number.parseInt(hex.substring(4, 6), 16);
|
||||
styled = colorChalk.rgb(r, g, b)(styled);
|
||||
} else {
|
||||
// Named color
|
||||
styled = (colorChalk as any)[this.defaultTextStyle.color](styled);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply background color
|
||||
if (this.defaultTextStyle.bgColor) {
|
||||
if (this.defaultTextStyle.bgColor.startsWith("#")) {
|
||||
// Hex color
|
||||
const hex = this.defaultTextStyle.bgColor.substring(1);
|
||||
const r = Number.parseInt(hex.substring(0, 2), 16);
|
||||
const g = Number.parseInt(hex.substring(2, 4), 16);
|
||||
const b = Number.parseInt(hex.substring(4, 6), 16);
|
||||
styled = colorChalk.bgRgb(r, g, b)(styled);
|
||||
} else {
|
||||
// Named background color (bgRed, bgBlue, etc.)
|
||||
styled = (colorChalk as any)[this.defaultTextStyle.bgColor](styled);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply text decorations
|
||||
if (this.defaultTextStyle.bold) {
|
||||
styled = colorChalk.bold(styled);
|
||||
}
|
||||
if (this.defaultTextStyle.italic) {
|
||||
styled = colorChalk.italic(styled);
|
||||
}
|
||||
if (this.defaultTextStyle.strikethrough) {
|
||||
styled = colorChalk.strikethrough(styled);
|
||||
}
|
||||
if (this.defaultTextStyle.underline) {
|
||||
styled = colorChalk.underline(styled);
|
||||
}
|
||||
|
||||
return styled;
|
||||
}
|
||||
|
||||
private renderToken(token: Token, width: number, nextTokenType?: string): string[] {
|
||||
const lines: string[] = [];
|
||||
|
||||
|
|
@ -199,11 +228,11 @@ export class Markdown implements Component {
|
|||
const headingPrefix = "#".repeat(headingLevel) + " ";
|
||||
const headingText = this.renderInlineTokens(token.tokens || []);
|
||||
if (headingLevel === 1) {
|
||||
lines.push(chalk.bold.underline.yellow(headingText));
|
||||
lines.push(colorChalk.bold.underline.yellow(headingText));
|
||||
} else if (headingLevel === 2) {
|
||||
lines.push(chalk.bold.yellow(headingText));
|
||||
lines.push(colorChalk.bold.yellow(headingText));
|
||||
} else {
|
||||
lines.push(chalk.bold(headingPrefix + headingText));
|
||||
lines.push(colorChalk.bold(headingPrefix + headingText));
|
||||
}
|
||||
lines.push(""); // Add spacing after headings
|
||||
break;
|
||||
|
|
@ -220,13 +249,13 @@ export class Markdown implements Component {
|
|||
}
|
||||
|
||||
case "code": {
|
||||
lines.push(chalk.gray("```" + (token.lang || "")));
|
||||
lines.push(colorChalk.gray("```" + (token.lang || "")));
|
||||
// Split code by newlines and style each line
|
||||
const codeLines = token.text.split("\n");
|
||||
for (const codeLine of codeLines) {
|
||||
lines.push(chalk.dim(" ") + chalk.green(codeLine));
|
||||
lines.push(colorChalk.dim(" ") + colorChalk.green(codeLine));
|
||||
}
|
||||
lines.push(chalk.gray("```"));
|
||||
lines.push(colorChalk.gray("```"));
|
||||
lines.push(""); // Add spacing after code blocks
|
||||
break;
|
||||
}
|
||||
|
|
@ -249,14 +278,14 @@ export class Markdown implements Component {
|
|||
const quoteText = this.renderInlineTokens(token.tokens || []);
|
||||
const quoteLines = quoteText.split("\n");
|
||||
for (const quoteLine of quoteLines) {
|
||||
lines.push(chalk.gray("│ ") + chalk.italic(quoteLine));
|
||||
lines.push(colorChalk.gray("│ ") + colorChalk.italic(quoteLine));
|
||||
}
|
||||
lines.push(""); // Add spacing after blockquotes
|
||||
break;
|
||||
}
|
||||
|
||||
case "hr":
|
||||
lines.push(chalk.gray("─".repeat(Math.min(width, 80))));
|
||||
lines.push(colorChalk.gray("─".repeat(Math.min(width, 80))));
|
||||
lines.push(""); // Add spacing after horizontal rules
|
||||
break;
|
||||
|
||||
|
|
@ -289,29 +318,44 @@ export class Markdown implements Component {
|
|||
if (token.tokens && token.tokens.length > 0) {
|
||||
result += this.renderInlineTokens(token.tokens);
|
||||
} else {
|
||||
result += token.text;
|
||||
// Apply default style to plain text
|
||||
result += this.applyDefaultStyle(token.text);
|
||||
}
|
||||
break;
|
||||
|
||||
case "strong":
|
||||
result += chalk.bold(this.renderInlineTokens(token.tokens || []));
|
||||
case "strong": {
|
||||
// Apply bold, then reapply default style after
|
||||
const boldContent = this.renderInlineTokens(token.tokens || []);
|
||||
result += colorChalk.bold(boldContent) + this.applyDefaultStyle("");
|
||||
break;
|
||||
}
|
||||
|
||||
case "em":
|
||||
result += chalk.italic(this.renderInlineTokens(token.tokens || []));
|
||||
case "em": {
|
||||
// Apply italic, then reapply default style after
|
||||
const italicContent = this.renderInlineTokens(token.tokens || []);
|
||||
result += colorChalk.italic(italicContent) + this.applyDefaultStyle("");
|
||||
break;
|
||||
}
|
||||
|
||||
case "codespan":
|
||||
result += chalk.gray("`") + chalk.cyan(token.text) + chalk.gray("`");
|
||||
// Apply code styling, then reapply default style after
|
||||
result +=
|
||||
colorChalk.gray("`") +
|
||||
colorChalk.cyan(token.text) +
|
||||
colorChalk.gray("`") +
|
||||
this.applyDefaultStyle("");
|
||||
break;
|
||||
|
||||
case "link": {
|
||||
const linkText = this.renderInlineTokens(token.tokens || []);
|
||||
// If link text matches href, only show the link once
|
||||
if (linkText === token.href) {
|
||||
result += chalk.underline.blue(linkText);
|
||||
result += colorChalk.underline.blue(linkText) + this.applyDefaultStyle("");
|
||||
} else {
|
||||
result += chalk.underline.blue(linkText) + chalk.gray(` (${token.href})`);
|
||||
result +=
|
||||
colorChalk.underline.blue(linkText) +
|
||||
colorChalk.gray(` (${token.href})`) +
|
||||
this.applyDefaultStyle("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -320,14 +364,16 @@ export class Markdown implements Component {
|
|||
result += "\n";
|
||||
break;
|
||||
|
||||
case "del":
|
||||
result += chalk.strikethrough(this.renderInlineTokens(token.tokens || []));
|
||||
case "del": {
|
||||
const delContent = this.renderInlineTokens(token.tokens || []);
|
||||
result += colorChalk.strikethrough(delContent) + this.applyDefaultStyle("");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle any other inline token types as plain text
|
||||
if ("text" in token && typeof token.text === "string") {
|
||||
result += token.text;
|
||||
result += this.applyDefaultStyle(token.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +515,7 @@ export class Markdown implements Component {
|
|||
lines.push(firstLine);
|
||||
} else {
|
||||
// Regular text content - add indent and bullet
|
||||
lines.push(indent + chalk.cyan(bullet) + firstLine);
|
||||
lines.push(indent + colorChalk.cyan(bullet) + firstLine);
|
||||
}
|
||||
|
||||
// Rest of the lines
|
||||
|
|
@ -486,7 +532,7 @@ export class Markdown implements Component {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
lines.push(indent + chalk.cyan(bullet));
|
||||
lines.push(indent + colorChalk.cyan(bullet));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -517,12 +563,12 @@ export class Markdown implements Component {
|
|||
lines.push(text);
|
||||
} else if (token.type === "code") {
|
||||
// Code block in list item
|
||||
lines.push(chalk.gray("```" + (token.lang || "")));
|
||||
lines.push(colorChalk.gray("```" + (token.lang || "")));
|
||||
const codeLines = token.text.split("\n");
|
||||
for (const codeLine of codeLines) {
|
||||
lines.push(chalk.dim(" ") + chalk.green(codeLine));
|
||||
lines.push(colorChalk.dim(" ") + colorChalk.green(codeLine));
|
||||
}
|
||||
lines.push(chalk.gray("```"));
|
||||
lines.push(colorChalk.gray("```"));
|
||||
} else {
|
||||
// Other token types - try to render as inline
|
||||
const text = this.renderInlineTokens([token]);
|
||||
|
|
@ -569,7 +615,7 @@ export class Markdown implements Component {
|
|||
// Render header
|
||||
const headerCells = token.header.map((cell, i) => {
|
||||
const text = this.renderInlineTokens(cell.tokens || []);
|
||||
return chalk.bold(text.padEnd(columnWidths[i]));
|
||||
return colorChalk.bold(text.padEnd(columnWidths[i]));
|
||||
});
|
||||
lines.push("│ " + headerCells.join(" │ ") + " │");
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ editor.onSubmit = (value: string) => {
|
|||
isResponding = true;
|
||||
editor.disableSubmit = true;
|
||||
|
||||
const userMessage = new Markdown(value, undefined, undefined, { r: 52, g: 53, b: 65 });
|
||||
const userMessage = new Markdown(value, 1, 1, { bgColor: "#343541" });
|
||||
|
||||
const children = tui.children;
|
||||
children.splice(children.length - 1, 0, userMessage);
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ describe("Markdown component", () => {
|
|||
- Nested 1.1
|
||||
- Nested 1.2
|
||||
- Item 2`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -38,9 +35,6 @@ describe("Markdown component", () => {
|
|||
- Level 2
|
||||
- Level 3
|
||||
- Level 4`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -61,9 +55,6 @@ describe("Markdown component", () => {
|
|||
1. Nested first
|
||||
2. Nested second
|
||||
2. Second`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -84,9 +75,6 @@ describe("Markdown component", () => {
|
|||
- Another nested
|
||||
2. Second ordered
|
||||
- More nested`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -107,9 +95,6 @@ describe("Markdown component", () => {
|
|||
| --- | --- |
|
||||
| Alice | 30 |
|
||||
| Bob | 25 |`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -133,9 +118,6 @@ describe("Markdown component", () => {
|
|||
| :--- | :---: | ---: |
|
||||
| A | B | C |
|
||||
| Long text | Middle | End |`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -157,9 +139,6 @@ describe("Markdown component", () => {
|
|||
| --- | --- |
|
||||
| A | This is a much longer cell content |
|
||||
| B | Short |`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -187,9 +166,6 @@ describe("Markdown component", () => {
|
|||
| Col1 | Col2 |
|
||||
| --- | --- |
|
||||
| A | B |`,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
|
@ -207,4 +183,84 @@ describe("Markdown component", () => {
|
|||
assert.ok(plainLines.some((line) => line.includes("│")));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Pre-styled text (thinking traces)", () => {
|
||||
it("should preserve gray italic styling after inline code", () => {
|
||||
// This replicates how thinking content is rendered in assistant-message.ts
|
||||
const markdown = new Markdown("This is thinking with `inline code` and more text after", 1, 0, {
|
||||
color: "gray",
|
||||
italic: true,
|
||||
});
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const joinedOutput = lines.join("\n");
|
||||
|
||||
// Should contain the inline code block
|
||||
assert.ok(joinedOutput.includes("inline code"));
|
||||
|
||||
// The output should have ANSI codes for gray (90) and italic (3)
|
||||
assert.ok(joinedOutput.includes("\x1b[90m"), "Should have gray color code");
|
||||
assert.ok(joinedOutput.includes("\x1b[3m"), "Should have italic code");
|
||||
|
||||
// Verify that after the inline code (cyan text), we reapply gray italic
|
||||
const hasCyan = joinedOutput.includes("\x1b[36m"); // cyan
|
||||
assert.ok(hasCyan, "Should have cyan for inline code");
|
||||
});
|
||||
|
||||
it("should preserve gray italic styling after bold text", () => {
|
||||
const markdown = new Markdown("This is thinking with **bold text** and more after", 1, 0, {
|
||||
color: "gray",
|
||||
italic: true,
|
||||
});
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const joinedOutput = lines.join("\n");
|
||||
|
||||
// Should contain bold text
|
||||
assert.ok(joinedOutput.includes("bold text"));
|
||||
|
||||
// The output should have ANSI codes for gray (90) and italic (3)
|
||||
assert.ok(joinedOutput.includes("\x1b[90m"), "Should have gray color code");
|
||||
assert.ok(joinedOutput.includes("\x1b[3m"), "Should have italic code");
|
||||
|
||||
// Should have bold codes (1 or 22 for bold on/off)
|
||||
assert.ok(joinedOutput.includes("\x1b[1m"), "Should have bold code");
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTML-like tags in text", () => {
|
||||
it("should render content with HTML-like tags as text", () => {
|
||||
// When the model emits something like <thinking>content</thinking> in regular text,
|
||||
// marked might treat it as HTML and hide the content
|
||||
const markdown = new Markdown(
|
||||
"This is text with <thinking>hidden content</thinking> that should be visible",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// The content inside the tags should be visible
|
||||
assert.ok(
|
||||
joinedPlain.includes("hidden content") || joinedPlain.includes("<thinking>"),
|
||||
"Should render HTML-like tags or their content as text, not hide them",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render HTML tags in code blocks correctly", () => {
|
||||
const markdown = new Markdown("```html\n<div>Some HTML</div>\n```", 0, 0);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join("\n");
|
||||
|
||||
// HTML in code blocks should be visible
|
||||
assert.ok(
|
||||
joinedPlain.includes("<div>") && joinedPlain.includes("</div>"),
|
||||
"Should render HTML in code blocks",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-web-ui",
|
||||
"version": "0.7.17",
|
||||
"version": "0.7.18",
|
||||
"description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@lmstudio/sdk": "^1.5.0",
|
||||
"@mariozechner/pi-ai": "^0.7.17",
|
||||
"@mariozechner/pi-tui": "^0.7.17",
|
||||
"@mariozechner/pi-ai": "^0.7.18",
|
||||
"@mariozechner/pi-tui": "^0.7.18",
|
||||
"docx-preview": "^0.3.7",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide": "^0.544.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue