Kaynağa Gözat

docs: share improve markdown rendering of ai responses

Jay V 7 ay önce
ebeveyn
işleme
143fd8e076

+ 3 - 5
bun.lock

@@ -31,7 +31,6 @@
         "@openauthjs/openauth": "0.4.3",
         "@standard-schema/spec": "1.0.0",
         "ai": "catalog:",
-        "air": "0.4.14",
         "decimal.js": "10.5.0",
         "diff": "8.0.2",
         "env-paths": "3.0.0",
@@ -79,6 +78,7 @@
         "lang-map": "0.4.0",
         "luxon": "3.6.1",
         "marked": "15.0.12",
+        "marked-shiki": "1.2.0",
         "rehype-autolink-headings": "7.1.0",
         "sharp": "0.32.5",
         "shiki": "3.4.2",
@@ -517,8 +517,6 @@
 
     "ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="],
 
-    "air": ["[email protected]", "", { "dependencies": { "zephyr": "~1.3.5" } }, "sha512-E8bl9LlSGSQqjxxjeGIrpYpf8jVyJplsdK1bTobh61F7ks+3aLeXL4KbGSJIFsiaSSz5ZExLU51DGztmQSlZTQ=="],
-
     "ansi-align": ["[email protected]", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
 
     "ansi-regex": ["[email protected]", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
@@ -1055,6 +1053,8 @@
 
     "marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
 
+    "marked-shiki": ["[email protected]", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-N924hp8veE6Mc91g5/kCNVoTU7TkeJfB2G2XEWb+k1fVA0Bck2T0rVt93d39BlOYH6ohP4Q9BFlPk+UkblhXbg=="],
+
     "math-intrinsics": ["[email protected]", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
 
     "mdast-util-definitions": ["[email protected]", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
@@ -1703,8 +1703,6 @@
 
     "youch": ["[email protected]", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
 
-    "zephyr": ["[email protected]", "", {}, "sha512-oYH52DGZzIbXNrkijskaR8YpVKnXAe8jNgH1KirglVBnTFOn6mK9/0SVCxGn+73l0Hjhr4UYNzYkO07LXSWy6w=="],
-
     "zod": ["[email protected]", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
 
     "zod-openapi": ["[email protected]", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="],

+ 1 - 0
packages/web/package.json

@@ -24,6 +24,7 @@
     "lang-map": "0.4.0",
     "luxon": "3.6.1",
     "marked": "15.0.12",
+    "marked-shiki": "1.2.0",
     "rehype-autolink-headings": "7.1.0",
     "sharp": "0.32.5",
     "shiki": "3.4.2",

+ 25 - 7
packages/web/src/components/MarkdownView.tsx

@@ -1,21 +1,39 @@
 import { type JSX, splitProps, createResource } from "solid-js"
 import { marked } from "marked"
+import markedShiki from "marked-shiki"
+import { codeToHtml } from "shiki"
+import { transformerNotationDiff } from "@shikijs/transformers"
 import styles from "./markdownview.module.css"
 
 interface MarkdownViewProps extends JSX.HTMLAttributes<HTMLDivElement> {
   markdown: string
 }
 
+const markedWithShiki = marked.use(
+  markedShiki({
+    highlight(code, lang) {
+      return codeToHtml(code, {
+        lang: lang || "text",
+        themes: {
+          light: "github-light",
+          dark: "github-dark",
+        },
+        transformers: [transformerNotationDiff()],
+      })
+    },
+  }),
+)
+
 function MarkdownView(props: MarkdownViewProps) {
   const [local, rest] = splitProps(props, ["markdown"])
-  const [html] = createResource(() => local.markdown, async (markdown) => {
-    return marked.parse(markdown)
-  })
-
-  return (
-    <div innerHTML={html()} class={styles["markdown-body"]} {...rest} />
+  const [html] = createResource(
+    () => local.markdown,
+    async (markdown) => {
+      return markedWithShiki.parse(markdown)
+    },
   )
+
+  return <div innerHTML={html()} class={styles["markdown-body"]} {...rest} />
 }
 
 export default MarkdownView
-

+ 1 - 8
packages/web/src/components/Share.tsx

@@ -294,15 +294,11 @@ function ResultsButton(props: ResultsButtonProps) {
 interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
   text: string
   expand?: boolean
-  invert?: boolean
-  highlight?: boolean
 }
 function TextPart(props: TextPartProps) {
   const [local, rest] = splitProps(props, [
     "text",
     "expand",
-    "invert",
-    "highlight",
   ])
   const [expanded, setExpanded] = createSignal(false)
   const [overflowed, setOverflowed] = createSignal(false)
@@ -332,8 +328,6 @@ function TextPart(props: TextPartProps) {
   return (
     <div
       class={styles["message-text"]}
-      data-invert={local.invert}
-      data-highlight={local.highlight}
       data-expanded={expanded() || local.expand === true}
       {...rest}
     >
@@ -991,9 +985,9 @@ export default function Share(props: {
                                   </div>
                                   <div data-section="content">
                                     <TextPart
-                                      invert
                                       text={part().text}
                                       expand={isLastPart()}
+                                      data-background="blue"
                                     />
                                   </div>
                                 </div>
@@ -1021,7 +1015,6 @@ export default function Share(props: {
                                   </div>
                                   <div data-section="content">
                                     <MarkdownPart
-                                      highlight
                                       expand={isLastPart()}
                                       text={stripEnclosingTag(part().text)}
                                     />

+ 45 - 3
packages/web/src/components/markdownview.module.css

@@ -40,11 +40,17 @@
   }
 
   pre {
-    white-space: pre-wrap;
-    border-radius: 0.25rem;
-    border: 1px solid rgba(0, 0, 0, 0.2);
+    --shiki-dark-bg: var(--sl-color-bg-surface) !important;
+    background-color: var(--sl-color-bg-surface) !important;
     padding: 0.5rem 0.75rem;
+    line-height: 1.6;
     font-size: 0.75rem;
+    white-space: pre-wrap;
+    word-break: break-word;
+
+    span {
+      white-space: break-spaces;
+    }
   }
 
   code {
@@ -61,4 +67,40 @@
       }
     }
   }
+
+  table {
+    border-collapse: collapse;
+    width: 100%;
+  }
+
+  th,
+  td {
+    border: 1px solid var(--sl-color-border);
+    padding: 0.5rem 0.75rem;
+    text-align: left;
+  }
+
+  th {
+    border-bottom: 1px solid var(--sl-color-border);
+  }
+
+  /* Remove outer borders */
+  table tr:first-child th,
+  table tr:first-child td {
+    border-top: none;
+  }
+
+  table tr:last-child td {
+    border-bottom: none;
+  }
+
+  table th:first-child,
+  table td:first-child {
+    border-left: none;
+  }
+
+  table th:last-child,
+  table td:last-child {
+    border-right: none;
+  }
 }

+ 3 - 4
packages/web/src/components/share.module.css

@@ -493,9 +493,8 @@
     }
   }
 
-  &[data-highlight="true"] {
-    background-color: var(--sl-color-blue-low);
-  }
+  &[data-background="none"] { background-color: transparent; }
+  &[data-background="blue"] { background-color: var(--sl-color-blue-low); }
 
   &[data-expanded="true"] {
     pre {
@@ -669,7 +668,7 @@
 }
 
 .message-markdown {
-  background-color: var(--sl-color-bg-surface);
+  border: 1px solid var(--sl-color-blue-high);
   padding: 0.5rem calc(0.5rem + 3px);
   border-radius: 0.25rem;
   display: flex;