Просмотр исходного кода

feat(studio): 侧边栏摘要卡改用 Streamdown 渲染 markdown

世界观/角色卡之前是 <p> 纯文本 + line-clamp,遇到 story_bible 里的
**加粗**、列表、小标题会原样显示为字面字符。现在接入 Streamdown
并带 cjk/code/math/mermaid 插件,同时用 Tailwind arbitrary variant
把标题/段落/列表的字号和间距压到卡片密度。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fanghanjun 6 месяцев назад
Родитель
Сommit
2f7bc58742
1 измененных файлов с 25 добавлено и 7 удалено
  1. 25 7
      packages/studio/src/components/sidebar/SummarySection.tsx

+ 25 - 7
packages/studio/src/components/sidebar/SummarySection.tsx

@@ -1,9 +1,25 @@
 import { useEffect } from "react";
 import { useEffect } from "react";
+import { Streamdown } from "streamdown";
+import { cjk } from "@streamdown/cjk";
+import { code } from "@streamdown/code";
+import { math } from "@streamdown/math";
+import { mermaid } from "@streamdown/mermaid";
 import { useChatStore } from "../../store/chat";
 import { useChatStore } from "../../store/chat";
 import type { BookSummary } from "../../store/chat";
 import type { BookSummary } from "../../store/chat";
 import { fetchJson } from "../../hooks/use-api";
 import { fetchJson } from "../../hooks/use-api";
 import { SidebarCard } from "./SidebarCard";
 import { SidebarCard } from "./SidebarCard";
 
 
+const streamdownPlugins = { cjk, code, math, mermaid };
+
+const SIDEBAR_MD_CLASS =
+  "text-xs text-muted-foreground leading-relaxed " +
+  "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0 " +
+  "[&>p+p]:mt-1.5 [&_strong]:text-foreground [&_strong]:font-medium " +
+  "[&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 " +
+  "[&_h1]:hidden [&_h2]:text-xs [&_h2]:font-medium [&_h2]:text-foreground [&_h2]:mt-1.5 [&_h2]:mb-0.5 " +
+  "[&_h3]:text-xs [&_h3]:font-medium [&_h3]:text-foreground [&_h3]:mt-1.5 [&_h3]:mb-0.5 " +
+  "[&_code]:text-[11px] [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-secondary/60";
+
 function parseStoryBible(content: string): BookSummary {
 function parseStoryBible(content: string): BookSummary {
   const sections = content.split(/^##\s+/m);
   const sections = content.split(/^##\s+/m);
   let world = "";
   let world = "";
@@ -47,22 +63,24 @@ export function SummarySection({ bookId }: SummarySectionProps) {
     <>
     <>
       {summary.world && (
       {summary.world && (
         <SidebarCard title="世界观">
         <SidebarCard title="世界观">
-          <p className="text-xs text-muted-foreground leading-relaxed line-clamp-4">
+          <Streamdown className={SIDEBAR_MD_CLASS} plugins={streamdownPlugins}>
             {summary.world}
             {summary.world}
-          </p>
+          </Streamdown>
         </SidebarCard>
         </SidebarCard>
       )}
       )}
       {(summary.protagonist || summary.cast) && (
       {(summary.protagonist || summary.cast) && (
         <SidebarCard title="角色">
         <SidebarCard title="角色">
           {summary.protagonist && (
           {summary.protagonist && (
-            <p className="text-xs text-muted-foreground leading-relaxed line-clamp-3">
+            <Streamdown className={SIDEBAR_MD_CLASS} plugins={streamdownPlugins}>
               {summary.protagonist}
               {summary.protagonist}
-            </p>
+            </Streamdown>
           )}
           )}
           {summary.cast && (
           {summary.cast && (
-            <p className="text-xs text-muted-foreground leading-relaxed line-clamp-3 mt-2">
-              {summary.cast}
-            </p>
+            <div className={summary.protagonist ? "mt-2" : undefined}>
+              <Streamdown className={SIDEBAR_MD_CLASS} plugins={streamdownPlugins}>
+                {summary.cast}
+              </Streamdown>
+            </div>
           )}
           )}
         </SidebarCard>
         </SidebarCard>
       )}
       )}