Преглед изворни кода

feat(tui): add markdown rendering for assistant messages

Use marked + marked-terminal to render markdown in assistant replies,
supporting bold, italic, inline code, tables, and lists in the terminal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun пре 7 месеци
родитељ
комит
88715edfaf
3 измењених фајлова са 30 додато и 4 уклоњено
  1. 4 2
      packages/cli/package.json
  2. 3 2
      packages/cli/src/tui/dashboard.tsx
  3. 23 0
      packages/cli/src/tui/markdown.ts

+ 4 - 2
packages/cli/package.json

@@ -50,12 +50,14 @@
     "epub-gen-memory": "^1.0.10",
     "ink": "^7.0.0",
     "ink-text-input": "^6.0.0",
-    "marked": "^15.0.0",
+    "marked": "^15.0.12",
+    "marked-terminal": "^7.3.0",
     "react": "^19.2.4"
   },
   "devDependencies": {
-    "@types/react": "^19.2.14",
+    "@types/marked-terminal": "^6.1.1",
     "@types/node": "^22.0.0",
+    "@types/react": "^19.2.14",
     "ink-testing-library": "^4.0.0",
     "typescript": "^5.8.0",
     "vitest": "^3.0.0"

+ 3 - 2
packages/cli/src/tui/dashboard.tsx

@@ -13,6 +13,7 @@ import { resolveComposerCaretState } from "./composer-caret.js";
 import { resolveChatDepthProfile, type ChatDepth } from "./chat-depth.js";
 import { appendStreamingAssistantChunk, createOptimisticUserMessageSession } from "./chat-draft.js";
 import { renderComposerDisplay } from "./composer-display.js";
+import { renderMarkdown } from "./markdown.js";
 import { formatTuiResult } from "./output.js";
 import { buildDashboardViewModel, type DashboardMessageRow } from "./dashboard-model.js";
 import { buildInputHistory, moveHistoryCursor } from "./input-history.js";
@@ -447,11 +448,11 @@ function ConversationRow(props: { readonly row: DashboardMessageRow }): React.JS
     );
   }
 
-  // assistant
+  // assistant — render markdown (bold, tables, code, etc.)
   return (
     <Box marginBottom={1} columnGap={1}>
       <Text color={WARM_ACCENT}>◆</Text>
-      <Text color={WARM_REPLY}>{content}</Text>
+      <Text color={WARM_REPLY}>{renderMarkdown(content)}</Text>
     </Box>
   );
 }

+ 23 - 0
packages/cli/src/tui/markdown.ts

@@ -0,0 +1,23 @@
+import { Marked } from "marked";
+import { markedTerminal } from "marked-terminal";
+
+const marked = new Marked();
+marked.use(markedTerminal({
+  width: Math.min(process.stdout.columns ?? 80, 100) - 8,
+  reflowText: true,
+  showSectionPrefix: false,
+  tab: 2,
+}) as never);
+
+export function renderMarkdown(text: string): string {
+  try {
+    const rendered = marked.parse(text);
+    if (typeof rendered !== "string") {
+      return text;
+    }
+    // marked-terminal adds trailing newlines; trim them
+    return rendered.replace(/\n+$/, "");
+  } catch {
+    return text;
+  }
+}