import { Markdown } from "@earendil-works/pi-tui"; import { setFullTheme } from "../theme/theme-extras.ts"; import { boxBg, boxBgLines } from "../tools/common.ts"; import { renderBoxedMessageBlock } from "./boxed-message-block.ts"; const PATCH_FLAG = "__piUiCoreMessageBlocksPatched__"; let cachedTheme: any = null; export function setCoreMessageBlockTheme(theme: any): void { cachedTheme = theme; setFullTheme(theme); } type ComponentCtor = { prototype?: any }; export function installCoreMessageBlockStyling(ctors: { CompactionSummaryMessageComponent?: ComponentCtor; SkillInvocationMessageComponent?: ComponentCtor; BranchSummaryMessageComponent?: ComponentCtor; CustomMessageComponent?: ComponentCtor; }): void { const globalState = globalThis as Record; if (globalState[PATCH_FLAG]) return; globalState[PATCH_FLAG] = true; patchCompaction(ctors.CompactionSummaryMessageComponent); patchSkill(ctors.SkillInvocationMessageComponent); patchBranch(ctors.BranchSummaryMessageComponent); patchCustomMessage(ctors.CustomMessageComponent); } function setMessageBlockBackground(component: any, theme: any): void { if (typeof component?.setBgFn !== "function") return; component.setBgFn((text: string) => boxBg(theme, text, "customMessageBg")); } function withMessageBlockBackground(component: any, theme: any): any { return { invalidate() { if (typeof component?.invalidate === "function") component.invalidate(); }, render(width: number): string[] { return boxBgLines(theme, component.render(width), "customMessageBg"); }, }; } function attachCustomMessageBlock(instance: any, theme: any, block: any): void { if (instance.box && typeof instance.box.clear === "function" && typeof instance.box.addChild === "function") { setMessageBlockBackground(instance.box, theme); instance.addChild(instance.box); instance.box.clear(); instance.box.addChild(block); } else { instance.customComponent = withMessageBlockBackground(block, theme); instance.addChild(instance.customComponent); } } function createMarkdownBody(text: string, markdownTheme: any, theme: any): (contentWidth: number) => string[] { const md = new Markdown(text || "", 0, 0, markdownTheme, { color: (t: string) => theme.fg("customMessageText", t), }); return (contentWidth: number) => md.render(contentWidth); } function patchCompaction(ctor?: ComponentCtor): void { const proto = ctor?.prototype; if (!proto || typeof proto.updateDisplay !== "function") return; const base = proto.updateDisplay; proto.updateDisplay = function patchedCompactionUpdateDisplay(this: any) { const theme = cachedTheme; if (!theme || this.message == null) return base.call(this); const tokensBefore = this.message.tokensBefore; if (tokensBefore == null) return base.call(this); setMessageBlockBackground(this, theme); this.clear(); const expanded = Boolean(this.expanded); const summary = typeof this.message.summary === "string" ? this.message.summary : ""; const markdownTheme = this.markdownTheme; const body = expanded && summary && markdownTheme ? createMarkdownBody(summary, markdownTheme, theme) : () => []; const tokenStr = tokensBefore.toLocaleString(); try { const block = renderBoxedMessageBlock(theme, { kind: "Compaction", title: `${tokenStr} tokens`, right: expanded ? undefined : "(Ctrl+O to expand)", body, icon: "⊟", hasDivider: expanded, }); this.addChild(block); } catch { return base.call(this); } }; } function patchSkill(ctor?: ComponentCtor): void { const proto = ctor?.prototype; if (!proto || typeof proto.updateDisplay !== "function") return; const base = proto.updateDisplay; proto.updateDisplay = function patchedSkillUpdateDisplay(this: any) { const theme = cachedTheme; if (!theme || this.skillBlock == null) return base.call(this); const skillName = this.skillBlock.name; if (!skillName) return base.call(this); setMessageBlockBackground(this, theme); this.clear(); const expanded = Boolean(this.expanded); const content = typeof this.skillBlock.content === "string" ? this.skillBlock.content : ""; const markdownTheme = this.markdownTheme; const body = expanded && content && markdownTheme ? createMarkdownBody(content, markdownTheme, theme) : () => []; try { const block = renderBoxedMessageBlock(theme, { kind: "Skill", title: skillName, right: expanded ? undefined : "(Ctrl+O to expand)", body, icon: "⊟", hasDivider: expanded, }); this.addChild(block); } catch { return base.call(this); } }; } function patchBranch(ctor?: ComponentCtor): void { const proto = ctor?.prototype; if (!proto || typeof proto.updateDisplay !== "function") return; const base = proto.updateDisplay; proto.updateDisplay = function patchedBranchUpdateDisplay(this: any) { const theme = cachedTheme; if (!theme || this.message == null) return base.call(this); setMessageBlockBackground(this, theme); this.clear(); const expanded = Boolean(this.expanded); const summary = typeof this.message?.summary === "string" ? this.message.summary : ""; const markdownTheme = this.markdownTheme; const body = expanded && summary && markdownTheme ? createMarkdownBody(summary, markdownTheme, theme) : () => []; try { const block = renderBoxedMessageBlock(theme, { kind: "Branch", right: expanded ? undefined : "(Ctrl+O to expand)", body, icon: "⊟", hasDivider: expanded, }); this.addChild(block); } catch { return base.call(this); } }; } function patchCustomMessage(ctor?: ComponentCtor): void { const proto = ctor?.prototype; if (!proto || typeof proto.rebuild !== "function") return; const base = proto.rebuild; proto.rebuild = function patchedCustomMessageRebuild(this: any) { // Remove previous content component if (this.customComponent) { this.removeChild(this.customComponent); this.customComponent = undefined; } this.removeChild(this.box); const theme = cachedTheme; if (!theme) return base.call(this); const customType = this.message?.customType || "Custom"; // Try custom renderer first, but keep the special block shell/background owned here. if (this.customRenderer) { try { const component = this.customRenderer(this.message, { expanded: this._expanded }, theme); if (component && typeof component.render === "function") { const block = renderBoxedMessageBlock(theme, { kind: "Custom", title: customType, body: (contentWidth) => component.render(contentWidth), icon: "⊟", hasDivider: "auto", cache: false, }); attachCustomMessageBlock(this, theme, block); return; } } catch { // Fall through to default rendering } } // Default rendering: use boxed message block // Extract text content let text: string; if (typeof this.message.content === "string") { text = this.message.content; } else if (Array.isArray(this.message.content)) { text = this.message.content .filter((c: any) => c.type === "text") .map((c: any) => c.text) .join("\n"); } else { text = ""; } const markdownTheme = this.markdownTheme; const body = text && markdownTheme ? createMarkdownBody(text, markdownTheme, theme) : () => []; try { const block = renderBoxedMessageBlock(theme, { kind: "Custom", title: customType, body, icon: "⊟", hasDivider: Boolean(text), }); attachCustomMessageBlock(this, theme, block); } catch { return base.call(this); } }; }