feat(tui): add mermaid rendering toggle
This commit is contained in:
@@ -790,6 +790,17 @@ export const SETTINGS_SCHEMA = {
|
||||
},
|
||||
},
|
||||
|
||||
"tui.renderMermaid": {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
ui: {
|
||||
tab: "appearance",
|
||||
group: "Display",
|
||||
label: "Render Mermaid Diagrams",
|
||||
description: "Render Mermaid fenced code blocks as ASCII diagrams",
|
||||
},
|
||||
},
|
||||
|
||||
"tui.hyperlinks": {
|
||||
type: "enum",
|
||||
values: ["off", "auto", "always"] as const,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
getSymbolTheme,
|
||||
previewTheme,
|
||||
setColorBlindMode,
|
||||
setMarkdownMermaidRendering,
|
||||
setSymbolPreset,
|
||||
setTheme,
|
||||
theme,
|
||||
@@ -374,6 +375,15 @@ export class SelectorController {
|
||||
this.ctx.ui.requestRender();
|
||||
break;
|
||||
|
||||
case "tui.renderMermaid":
|
||||
setMarkdownMermaidRendering(value as boolean);
|
||||
this.ctx.session.refreshBaseSystemPrompt().catch(err => {
|
||||
this.ctx.showError(`Failed to apply Mermaid rendering setting: ${err}`);
|
||||
});
|
||||
this.ctx.rebuildChatFromMessages();
|
||||
this.ctx.ui.resetDisplay();
|
||||
break;
|
||||
|
||||
case "theme": {
|
||||
setTheme(value as string, true).then(result => {
|
||||
this.ctx.statusLine.invalidate();
|
||||
|
||||
@@ -167,6 +167,7 @@ import {
|
||||
getSymbolTheme,
|
||||
onTerminalAppearanceChange,
|
||||
onThemeChange,
|
||||
setMarkdownMermaidRendering,
|
||||
theme,
|
||||
} from "./theme/theme";
|
||||
import type {
|
||||
@@ -588,6 +589,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
}
|
||||
|
||||
setTuiTight(settings.get("tui.tight"));
|
||||
setMarkdownMermaidRendering(settings.get("tui.renderMermaid"));
|
||||
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
||||
this.ui.setMaxInlineImages(settings.get("tui.maxInlineImages"));
|
||||
// OSC 66 text-sizing is Kitty-only; resolve the setting against the terminal's
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from "bun:test";
|
||||
import { Markdown } from "@oh-my-pi/pi-tui";
|
||||
import { Settings } from "../../config/settings";
|
||||
import { buildSystemPrompt } from "../../system-prompt";
|
||||
import { getMarkdownTheme, getThemeByName, setMarkdownMermaidRendering, setThemeInstance } from "./theme";
|
||||
|
||||
const workspaceTree = {
|
||||
rootPath: "/tmp/project",
|
||||
rendered: "",
|
||||
truncated: false,
|
||||
totalLines: 0,
|
||||
agentsMdFiles: [],
|
||||
};
|
||||
|
||||
function stripAnsi(text: string): string {
|
||||
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await Settings.init({ inMemory: true });
|
||||
const theme = await getThemeByName("dark");
|
||||
if (!theme) throw new Error("theme unavailable");
|
||||
setThemeInstance(theme);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setMarkdownMermaidRendering(true);
|
||||
});
|
||||
|
||||
describe("Mermaid rendering setting", () => {
|
||||
it("removes the Mermaid prompt note when rendering is disabled", async () => {
|
||||
const { systemPrompt } = await buildSystemPrompt({
|
||||
renderMermaid: false,
|
||||
contextFiles: [],
|
||||
skills: [],
|
||||
toolNames: [],
|
||||
workspaceTree,
|
||||
});
|
||||
|
||||
expect(systemPrompt.join("\n")).not.toContain("```mermaid");
|
||||
});
|
||||
|
||||
it("falls back to a highlighted code fence when rendering is disabled", () => {
|
||||
setMarkdownMermaidRendering(false);
|
||||
|
||||
const markdown = new Markdown("```mermaid\ngraph TD\n A --> B\n```", 0, 0, getMarkdownTheme());
|
||||
const lines = stripAnsi(markdown.render(80).join("\n"));
|
||||
|
||||
expect(lines).toContain("```mermaid");
|
||||
expect(lines).toContain("graph TD");
|
||||
expect(lines).toContain("-->");
|
||||
});
|
||||
});
|
||||
@@ -2824,23 +2824,36 @@ export function getSymbolTheme(): SymbolTheme {
|
||||
|
||||
let cachedMarkdownTheme: MarkdownTheme | undefined;
|
||||
let cachedMarkdownThemeRef: Theme | undefined;
|
||||
let markdownMermaidRendering = true;
|
||||
|
||||
export function setMarkdownMermaidRendering(enabled: boolean): void {
|
||||
if (markdownMermaidRendering === enabled) return;
|
||||
markdownMermaidRendering = enabled;
|
||||
cachedMarkdownTheme = undefined;
|
||||
}
|
||||
|
||||
export function getMarkdownTheme(): MarkdownTheme {
|
||||
if (cachedMarkdownTheme !== undefined && cachedMarkdownThemeRef === theme) {
|
||||
return cachedMarkdownTheme;
|
||||
}
|
||||
// Mermaid ASCII diagrams render with the active palette so they read as
|
||||
// content rather than raw monochrome. Roles mirror the SVG renderer's
|
||||
// mapping; `text`/`muted`/`border`/`borderMuted`/`accent` exist in every theme.
|
||||
const mermaidColorMode = theme.getColorMode() === "truecolor" ? "truecolor" : "ansi256";
|
||||
const mermaidTheme = {
|
||||
fg: theme.getColorHex("text"),
|
||||
border: theme.getColorHex("border"),
|
||||
line: theme.getColorHex("muted"),
|
||||
arrow: theme.getColorHex("accent"),
|
||||
corner: theme.getColorHex("muted"),
|
||||
junction: theme.getColorHex("borderMuted"),
|
||||
};
|
||||
const mermaid = markdownMermaidRendering
|
||||
? (() => {
|
||||
// Mermaid ASCII diagrams render with the active palette so they read as
|
||||
// content rather than raw monochrome. Roles mirror the SVG renderer's
|
||||
// mapping; `text`/`muted`/`border`/`borderMuted`/`accent` exist in every theme.
|
||||
const mermaidColorMode =
|
||||
theme.getColorMode() === "truecolor" ? ("truecolor" as const) : ("ansi256" as const);
|
||||
const mermaidTheme = {
|
||||
fg: theme.getColorHex("text"),
|
||||
border: theme.getColorHex("border"),
|
||||
line: theme.getColorHex("muted"),
|
||||
arrow: theme.getColorHex("accent"),
|
||||
corner: theme.getColorHex("muted"),
|
||||
junction: theme.getColorHex("borderMuted"),
|
||||
};
|
||||
return { mermaidColorMode, mermaidTheme };
|
||||
})()
|
||||
: undefined;
|
||||
const markdownTheme: MarkdownTheme = {
|
||||
heading: (text: string) => theme.fg("mdHeading", text),
|
||||
link: (text: string) => theme.fg("mdLink", text),
|
||||
@@ -2857,8 +2870,14 @@ export function getMarkdownTheme(): MarkdownTheme {
|
||||
underline: (text: string) => theme.underline(text),
|
||||
strikethrough: (text: string) => chalk.strikethrough(text),
|
||||
symbols: getSymbolTheme(),
|
||||
resolveMermaidAscii: (source, maxWidth) =>
|
||||
resolveMermaidAscii(source, { maxWidth, theme: mermaidTheme, colorMode: mermaidColorMode }),
|
||||
resolveMermaidAscii: mermaid
|
||||
? (source, maxWidth) =>
|
||||
resolveMermaidAscii(source, {
|
||||
maxWidth,
|
||||
theme: mermaid.mermaidTheme,
|
||||
colorMode: mermaid.mermaidColorMode,
|
||||
})
|
||||
: undefined,
|
||||
highlightCode: (code: string, lang?: string): string[] => {
|
||||
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
||||
const highlighted = highlightCached(code, validLang, theme);
|
||||
|
||||
@@ -16,7 +16,9 @@ You are a helpful assistant the team trusts with load-bearing changes, operating
|
||||
- Consider what code compiles to. NEVER allocate avoidably; no needless copies or computation.
|
||||
- You are not alone in this repo. Treat unexpected changes as the user's work and adapt.
|
||||
- In terminal prose and final chat, you MAY use LaTeX math (`$`, `$$`, `\text`, `\times`) and color (`\textcolor`, `\colorbox`, `\fcolorbox`).
|
||||
{{#if renderMermaid}}
|
||||
- To show a diagram, you MAY emit a ` ```mermaid ` block — the terminal renders it as ASCII. Use it for genuine structure or flow, not trivia.
|
||||
{{/if}}
|
||||
|
||||
RUNTIME
|
||||
==============
|
||||
|
||||
@@ -2221,6 +2221,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
memoryRootEnabled: memoryBackend.id === "local",
|
||||
model: settings.get("includeModelInPrompt") ? getActiveModelString() : undefined,
|
||||
personality: agentKind === "sub" ? "none" : settings.get("personality"),
|
||||
renderMermaid: settings.get("tui.renderMermaid"),
|
||||
});
|
||||
|
||||
if (options.systemPrompt === undefined) {
|
||||
|
||||
@@ -423,6 +423,8 @@ export interface BuildSystemPromptOptions {
|
||||
personality?: Personality;
|
||||
/** Whether to include the workspace directory tree in the system prompt. Default: false */
|
||||
includeWorkspaceTree?: boolean;
|
||||
/** Whether Mermaid fenced blocks render as terminal ASCII diagrams. Default: true */
|
||||
renderMermaid?: boolean;
|
||||
}
|
||||
|
||||
/** Result of building provider-facing system prompt messages. */
|
||||
@@ -464,6 +466,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
||||
model,
|
||||
personality = "default",
|
||||
includeWorkspaceTree = false,
|
||||
renderMermaid = true,
|
||||
} = options;
|
||||
const inlineToolDescriptors = providedInlineToolDescriptors ?? false;
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
@@ -682,6 +685,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
||||
hasMemoryRoot: memoryRootEnabled,
|
||||
hasObsidian: hasObsidian(),
|
||||
includeWorkspaceTree,
|
||||
renderMermaid,
|
||||
};
|
||||
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
||||
const systemPrompt = [rendered];
|
||||
|
||||
Reference in New Issue
Block a user