Merge pull request #72 from langchain-ai/brace/fix-code-highlighting

feat: Improved markdown rendering
This commit is contained in:
Brace Sproul
2025-03-17 12:35:24 -07:00
committed by GitHub
5 changed files with 174 additions and 414 deletions

View File

@@ -1,8 +1,11 @@
{
"name": "chat-langgraph",
"readme": "https://github.com/langchain-ai/chat-langgraph/blob/main/README.md",
"homepage": "https://chat-langgraph.vercel.app",
"repository": "https://github.com/langchain-ai/chat-langgraph",
"name": "agent-chat-ui",
"readme": "https://github.com/langchain-ai/agent-chat-ui/blob/main/README.md",
"homepage": "https://agentchat.vercel.app",
"repository": {
"type": "git",
"url": "git+https://github.com/langchain-ai/agent-chat-ui.git"
},
"private": true,
"version": "0.0.0",
"type": "module",
@@ -15,9 +18,6 @@
"preview": "vite preview"
},
"dependencies": {
"@assistant-ui/react": "^0.8.0",
"@assistant-ui/react-markdown": "^0.8.0",
"@assistant-ui/react-syntax-highlighter": "^0.7.2",
"@langchain/core": "^0.3.41",
"@langchain/langgraph": "^0.2.54",
"@langchain/langgraph-api": "^0.0.15",

322
pnpm-lock.yaml generated
View File

@@ -7,15 +7,6 @@ settings:
importers:
.:
dependencies:
"@assistant-ui/react":
specifier: ^0.8.0
version: 0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@assistant-ui/react-markdown":
specifier: ^0.8.0
version: 0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@assistant-ui/react-syntax-highlighter":
specifier: ^0.7.2
version: 0.7.10(@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-syntax-highlighter@15.5.13)(@types/react@19.0.10)(react-syntax-highlighter@15.6.1(react@19.0.0))(react@19.0.0)
"@langchain/core":
specifier: ^0.3.41
version: 0.3.41(openai@4.85.4(zod@3.24.2))
@@ -196,13 +187,6 @@ importers:
version: 6.2.0(@types/node@22.13.5)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3)(yaml@2.7.0)
packages:
"@ai-sdk/provider@1.0.9":
resolution:
{
integrity: sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==,
}
engines: { node: ">=18" }
"@alloc/quick-lru@5.2.0":
resolution:
{
@@ -217,54 +201,6 @@ packages:
}
engines: { node: ">=6.0.0" }
"@assistant-ui/react-markdown@0.8.0":
resolution:
{
integrity: sha512-p7ewz+AUH+DTSBQkvDpOskkXNu2RtNxrGbHONEjGoCufm3jE3zJEC2KkTg71lrscp7aHXJ7Bf1l+5V9zsenc8w==,
}
peerDependencies:
"@assistant-ui/react": ^0.8.0
"@types/react": "*"
react: ^18 || ^19 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@assistant-ui/react-syntax-highlighter@0.7.10":
resolution:
{
integrity: sha512-3kMxAl46ks3zW5ruW1a0dpcH57Ef19CIiXE4mYQp1+JXLQnaaaSHfd5x+TuzVlbH/wQyAtZIeHtceM3J4TUAeQ==,
}
peerDependencies:
"@assistant-ui/react": ^0.7.71
"@assistant-ui/react-markdown": ^0.7.18
"@types/react": "*"
"@types/react-syntax-highlighter": "*"
react: ^18 || ^19 || ^19.0.0-rc
react-syntax-highlighter: ^15.5.0
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-syntax-highlighter":
optional: true
"@assistant-ui/react@0.8.0":
resolution:
{
integrity: sha512-GgB9pggntKR73wIZgRhubOr6O/W4RL9NFnbhbvqhRBG0p4u0QmpUhzZf0QUqG/iOBlgFmC5MkWNynabcMdbuAg==,
}
engines: { node: ">=20.10.0" }
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
"@babel/code-frame@7.26.2":
resolution:
{
@@ -1079,22 +1015,6 @@ packages:
"@types/react-dom":
optional: true
"@radix-ui/react-popover@1.1.6":
resolution:
{
integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==,
}
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
"@radix-ui/react-popper@1.2.2":
resolution:
{
@@ -2019,12 +1939,6 @@ packages:
}
engines: { node: ">=10" }
assistant-stream@0.0.21:
resolution:
{
integrity: sha512-tQuGIuTGtmNnHSc6hxANVjO23LPmwFBjZnOt0xOhoBQBqW3vBv6DK2QOObgT9wnfLY8i2DRDwDbw8fSvYVzdMA==,
}
async@3.2.6:
resolution:
{
@@ -2207,12 +2121,6 @@ packages:
integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==,
}
classnames@2.5.1:
resolution:
{
integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==,
}
clsx@2.1.1:
resolution:
{
@@ -3482,12 +3390,6 @@ packages:
integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==,
}
json-schema@0.4.0:
resolution:
{
integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==,
}
json-stable-stringify-without-jsonify@1.0.1:
resolution:
{
@@ -4507,15 +4409,6 @@ packages:
"@types/react": ">=18"
react: ">=18"
react-markdown@9.1.0:
resolution:
{
integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==,
}
peerDependencies:
"@types/react": ">=18"
react: ">=18"
react-refresh@0.14.2:
resolution:
{
@@ -4598,15 +4491,6 @@ packages:
peerDependencies:
react: ">= 0.14.0"
react-textarea-autosize@8.5.7:
resolution:
{
integrity: sha512-2MqJ3p0Jh69yt9ktFIaZmORHXw4c4bxSIhCeWiFwmJ9EYKgLmuNII3e9c9b2UO+ijl4StnpZdqpxNIhTdHvqtQ==,
}
engines: { node: ">=10" }
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-transition-group@4.4.5:
resolution:
{
@@ -4775,12 +4659,6 @@ packages:
integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==,
}
secure-json-parse@3.0.2:
resolution:
{
integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==,
}
semver@6.3.1:
resolution:
{
@@ -5193,42 +5071,6 @@ packages:
"@types/react":
optional: true
use-composed-ref@1.4.0:
resolution:
{
integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==,
}
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
use-isomorphic-layout-effect@1.2.0:
resolution:
{
integrity: sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==,
}
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
use-latest@1.3.0:
resolution:
{
integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==,
}
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
use-query-params@2.2.1:
resolution:
{
@@ -5502,27 +5344,6 @@ packages:
integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==,
}
zustand@5.0.3:
resolution:
{
integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==,
}
engines: { node: ">=12.20.0" }
peerDependencies:
"@types/react": ">=18.0.0"
immer: ">=9.0.6"
react: ">=18.0.0"
use-sync-external-store: ">=1.2.0"
peerDependenciesMeta:
"@types/react":
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
zwitch@2.0.4:
resolution:
{
@@ -5530,10 +5351,6 @@ packages:
}
snapshots:
"@ai-sdk/provider@1.0.9":
dependencies:
json-schema: 0.4.0
"@alloc/quick-lru@5.2.0": {}
"@ampproject/remapping@2.3.0":
@@ -5541,60 +5358,6 @@ snapshots:
"@jridgewell/gen-mapping": 0.3.8
"@jridgewell/trace-mapping": 0.3.25
"@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
dependencies:
"@assistant-ui/react": 0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-primitive": 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-use-callback-ref": 1.1.0(@types/react@19.0.10)(react@19.0.0)
"@types/hast": 3.0.4
classnames: 2.5.1
react: 19.0.0
react-markdown: 9.1.0(@types/react@19.0.10)(react@19.0.0)
optionalDependencies:
"@types/react": 19.0.10
transitivePeerDependencies:
- "@types/react-dom"
- react-dom
- supports-color
"@assistant-ui/react-syntax-highlighter@0.7.10(@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-syntax-highlighter@15.5.13)(@types/react@19.0.10)(react-syntax-highlighter@15.6.1(react@19.0.0))(react@19.0.0)":
dependencies:
"@assistant-ui/react": 0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@assistant-ui/react-markdown": 0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-syntax-highlighter: 15.6.1(react@19.0.0)
optionalDependencies:
"@types/react": 19.0.10
"@types/react-syntax-highlighter": 15.5.13
"@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
dependencies:
"@ai-sdk/provider": 1.0.9
"@radix-ui/primitive": 1.1.1
"@radix-ui/react-compose-refs": 1.1.1(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-context": 1.1.1(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-popover": 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-primitive": 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-slot": 1.1.2(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-use-callback-ref": 1.1.0(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-use-escape-keydown": 1.1.0(@types/react@19.0.10)(react@19.0.0)
assistant-stream: 0.0.21
json-schema: 0.4.0
nanoid: 3.3.8
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-textarea-autosize: 8.5.7(@types/react@19.0.10)(react@19.0.0)
secure-json-parse: 3.0.2
zod: 3.24.2
zod-to-json-schema: 3.24.3(zod@3.24.2)
zustand: 5.0.3(@types/react@19.0.10)(react@19.0.0)
optionalDependencies:
"@types/react": 19.0.10
"@types/react-dom": 19.0.4(@types/react@19.0.10)
transitivePeerDependencies:
- immer
- use-sync-external-store
"@babel/code-frame@7.26.2":
dependencies:
"@babel/helper-validator-identifier": 7.25.9
@@ -6129,29 +5892,6 @@ snapshots:
"@types/react": 19.0.10
"@types/react-dom": 19.0.4(@types/react@19.0.10)
"@radix-ui/react-popover@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
dependencies:
"@radix-ui/primitive": 1.1.1
"@radix-ui/react-compose-refs": 1.1.1(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-context": 1.1.1(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-dismissable-layer": 1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-focus-guards": 1.1.1(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-focus-scope": 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-id": 1.1.0(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-popper": 1.2.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-portal": 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-presence": 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-primitive": 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
"@radix-ui/react-slot": 1.1.2(@types/react@19.0.10)(react@19.0.0)
"@radix-ui/react-use-controllable-state": 1.1.0(@types/react@19.0.10)(react@19.0.0)
aria-hidden: 1.2.4
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-remove-scroll: 2.6.3(@types/react@19.0.10)(react@19.0.0)
optionalDependencies:
"@types/react": 19.0.10
"@types/react-dom": 19.0.4(@types/react@19.0.10)
"@radix-ui/react-popper@1.2.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
dependencies:
"@floating-ui/react-dom": 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -6697,11 +6437,6 @@ snapshots:
dependencies:
tslib: 2.8.1
assistant-stream@0.0.21:
dependencies:
nanoid: 3.3.8
secure-json-parse: 3.0.2
async@3.2.6: {}
asynckit@0.4.0:
@@ -6792,8 +6527,6 @@ snapshots:
dependencies:
clsx: 2.1.1
classnames@2.5.1: {}
clsx@2.1.1: {}
color-convert@1.9.3:
@@ -7534,8 +7267,6 @@ snapshots:
json-schema-traverse@0.4.1: {}
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1: {}
json5@2.2.3: {}
@@ -8329,24 +8060,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
react-markdown@9.1.0(@types/react@19.0.10)(react@19.0.0):
dependencies:
"@types/hast": 3.0.4
"@types/mdast": 4.0.4
"@types/react": 19.0.10
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.5
html-url-attributes: 3.0.1
mdast-util-to-hast: 13.2.0
react: 19.0.0
remark-parse: 11.0.0
remark-rehype: 11.1.1
unified: 11.0.5
unist-util-visit: 5.0.0
vfile: 6.0.3
transitivePeerDependencies:
- supports-color
react-refresh@0.14.2: {}
react-remove-scroll-bar@2.3.8(@types/react@19.0.10)(react@19.0.0):
@@ -8406,15 +8119,6 @@ snapshots:
react: 19.0.0
refractor: 3.6.0
react-textarea-autosize@8.5.7(@types/react@19.0.10)(react@19.0.0):
dependencies:
"@babel/runtime": 7.26.9
react: 19.0.0
use-composed-ref: 1.4.0(@types/react@19.0.10)(react@19.0.0)
use-latest: 1.3.0(@types/react@19.0.10)(react@19.0.0)
transitivePeerDependencies:
- "@types/react"
react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
"@babel/runtime": 7.26.9
@@ -8561,8 +8265,6 @@ snapshots:
scheduler@0.25.0: {}
secure-json-parse@3.0.2: {}
semver@6.3.1: {}
semver@7.7.1: {}
@@ -8788,25 +8490,6 @@ snapshots:
optionalDependencies:
"@types/react": 19.0.10
use-composed-ref@1.4.0(@types/react@19.0.10)(react@19.0.0):
dependencies:
react: 19.0.0
optionalDependencies:
"@types/react": 19.0.10
use-isomorphic-layout-effect@1.2.0(@types/react@19.0.10)(react@19.0.0):
dependencies:
react: 19.0.0
optionalDependencies:
"@types/react": 19.0.10
use-latest@1.3.0(@types/react@19.0.10)(react@19.0.0):
dependencies:
react: 19.0.0
use-isomorphic-layout-effect: 1.2.0(@types/react@19.0.10)(react@19.0.0)
optionalDependencies:
"@types/react": 19.0.10
use-query-params@2.2.1(react-dom@19.0.0(react@19.0.0))(react-router-dom@6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0):
dependencies:
react: 19.0.0
@@ -8963,9 +8646,4 @@ snapshots:
zod@3.24.2: {}
zustand@5.0.3(@types/react@19.0.10)(react@19.0.0):
optionalDependencies:
"@types/react": 19.0.10
react: 19.0.0
zwitch@2.0.4: {}

View File

@@ -0,0 +1,45 @@
/* Base markdown styles */
.markdown-content code:not(pre code) {
background-color: rgba(0, 0, 0, 0.05);
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
.markdown-content a {
color: #0070f3;
text-decoration: none;
}
.markdown-content a:hover {
text-decoration: underline;
}
.markdown-content blockquote {
border-left: 4px solid #ddd;
padding-left: 1rem;
color: #666;
}
.markdown-content pre {
overflow-x: auto;
}
.markdown-content table {
border-collapse: collapse;
width: 100%;
}
.markdown-content th,
.markdown-content td {
border: 1px solid #ddd;
padding: 8px;
}
.markdown-content th {
background-color: #f2f2f2;
}
.markdown-content tr:nth-child(even) {
background-color: #f9f9f9;
}

View File

@@ -1,12 +1,7 @@
"use client";
import "@assistant-ui/react-markdown/styles/dot.css";
import "./markdown-styles.css";
import {
CodeHeaderProps,
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
useIsMarkdownCodeBlock,
} from "@assistant-ui/react-markdown";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeKatex from "rehype-katex";
@@ -20,37 +15,10 @@ import { cn } from "@/lib/utils";
import "katex/dist/katex.min.css";
const MarkdownTextImpl = ({ children }: { children: string }) => {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
components={defaultComponents}
>
{children}
</ReactMarkdown>
);
};
export const MarkdownText = memo(MarkdownTextImpl);
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
const { isCopied, copyToClipboard } = useCopyToClipboard();
const onCopy = () => {
if (!code || isCopied) return;
copyToClipboard(code);
};
return (
<div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
<span className="lowercase [&>span]:text-xs">{language}</span>
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
{!isCopied && <CopyIcon />}
{isCopied && <CheckIcon />}
</TooltipIconButton>
</div>
);
};
interface CodeHeaderProps {
language?: string;
code: string;
}
const useCopyToClipboard = ({
copiedDuration = 3000,
@@ -71,8 +39,26 @@ const useCopyToClipboard = ({
return { isCopied, copyToClipboard };
};
const defaultComponents = memoizeMarkdownComponents({
h1: ({ className, ...props }) => (
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
const { isCopied, copyToClipboard } = useCopyToClipboard();
const onCopy = () => {
if (!code || isCopied) return;
copyToClipboard(code);
};
return (
<div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
<span className="lowercase [&>span]:text-xs">{language}</span>
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
{!isCopied && <CopyIcon />}
{isCopied && <CheckIcon />}
</TooltipIconButton>
</div>
);
};
const defaultComponents: any = {
h1: ({ className, ...props }: { className?: string }) => (
<h1
className={cn(
"mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
@@ -81,7 +67,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
h2: ({ className, ...props }) => (
h2: ({ className, ...props }: { className?: string }) => (
<h2
className={cn(
"mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
@@ -90,7 +76,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
h3: ({ className, ...props }) => (
h3: ({ className, ...props }: { className?: string }) => (
<h3
className={cn(
"mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
@@ -99,7 +85,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
h4: ({ className, ...props }) => (
h4: ({ className, ...props }: { className?: string }) => (
<h4
className={cn(
"mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
@@ -108,7 +94,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
h5: ({ className, ...props }) => (
h5: ({ className, ...props }: { className?: string }) => (
<h5
className={cn(
"my-4 text-lg font-semibold first:mt-0 last:mb-0",
@@ -117,19 +103,19 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
h6: ({ className, ...props }) => (
h6: ({ className, ...props }: { className?: string }) => (
<h6
className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
{...props}
/>
),
p: ({ className, ...props }) => (
p: ({ className, ...props }: { className?: string }) => (
<p
className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
{...props}
/>
),
a: ({ className, ...props }) => (
a: ({ className, ...props }: { className?: string }) => (
<a
className={cn(
"text-primary font-medium underline underline-offset-4",
@@ -138,28 +124,28 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
blockquote: ({ className, ...props }) => (
blockquote: ({ className, ...props }: { className?: string }) => (
<blockquote
className={cn("border-l-2 pl-6 italic", className)}
{...props}
/>
),
ul: ({ className, ...props }) => (
ul: ({ className, ...props }: { className?: string }) => (
<ul
className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
{...props}
/>
),
ol: ({ className, ...props }) => (
ol: ({ className, ...props }: { className?: string }) => (
<ol
className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
{...props}
/>
),
hr: ({ className, ...props }) => (
hr: ({ className, ...props }: { className?: string }) => (
<hr className={cn("my-5 border-b", className)} {...props} />
),
table: ({ className, ...props }) => (
table: ({ className, ...props }: { className?: string }) => (
<table
className={cn(
"my-5 w-full border-separate border-spacing-0 overflow-y-auto",
@@ -168,7 +154,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
th: ({ className, ...props }) => (
th: ({ className, ...props }: { className?: string }) => (
<th
className={cn(
"bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
@@ -177,7 +163,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
td: ({ className, ...props }) => (
td: ({ className, ...props }: { className?: string }) => (
<td
className={cn(
"border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
@@ -186,7 +172,7 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
tr: ({ className, ...props }) => (
tr: ({ className, ...props }: { className?: string }) => (
<tr
className={cn(
"m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
@@ -195,30 +181,65 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
sup: ({ className, ...props }) => (
sup: ({ className, ...props }: { className?: string }) => (
<sup
className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
{...props}
/>
),
pre: ({ className, ...props }) => (
pre: ({ className, ...props }: { className?: string }) => (
<pre
className={cn(
"overflow-x-auto rounded-b-lg bg-black p-4 text-white max-w-4xl",
"overflow-x-auto rounded-lg bg-black text-white max-w-4xl",
className,
)}
{...props}
/>
),
code: function Code({ className, ...props }) {
const isCodeBlock = useIsMarkdownCodeBlock();
code: ({
className,
children,
...props
}: {
className?: string;
children: React.ReactNode;
}) => {
const match = /language-(\w+)/.exec(className || "");
if (match) {
const language = match[1];
const code = String(children).replace(/\n$/, "");
return (
<>
<CodeHeader language={language} code={code} />
<SyntaxHighlighter language={language} className={className}>
{code}
</SyntaxHighlighter>
</>
);
}
return (
<code
className={cn(!isCodeBlock && "rounded font-semibold", className)}
{...props}
/>
<code className={cn("rounded font-semibold", className)} {...props}>
{children}
</code>
);
},
CodeHeader,
SyntaxHighlighter,
});
};
const MarkdownTextImpl: FC<{ children: string }> = ({ children }) => {
return (
<div className="markdown-content">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
components={defaultComponents}
>
{children}
</ReactMarkdown>
</div>
);
};
export const MarkdownText = memo(MarkdownTextImpl);

View File

@@ -1,24 +1,40 @@
import { PrismAsyncLight } from "react-syntax-highlighter";
import { makePrismAsyncLightSyntaxHighlighter } from "@assistant-ui/react-syntax-highlighter";
import { PrismAsyncLight as SyntaxHighlighterPrism } from "react-syntax-highlighter";
import tsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx";
import python from "react-syntax-highlighter/dist/esm/languages/prism/python";
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { FC } from "react";
// register languages you want to support
PrismAsyncLight.registerLanguage("js", tsx);
PrismAsyncLight.registerLanguage("jsx", tsx);
PrismAsyncLight.registerLanguage("ts", tsx);
PrismAsyncLight.registerLanguage("tsx", tsx);
PrismAsyncLight.registerLanguage("python", python);
// Register languages you want to support
SyntaxHighlighterPrism.registerLanguage("js", tsx);
SyntaxHighlighterPrism.registerLanguage("jsx", tsx);
SyntaxHighlighterPrism.registerLanguage("ts", tsx);
SyntaxHighlighterPrism.registerLanguage("tsx", tsx);
SyntaxHighlighterPrism.registerLanguage("python", python);
export const SyntaxHighlighter = makePrismAsyncLightSyntaxHighlighter({
style: coldarkDark,
customStyle: {
margin: 0,
width: "100%",
background: "transparent",
padding: "1.5rem 1rem",
},
});
interface SyntaxHighlighterProps {
children: string;
language: string;
className?: string;
}
export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
children,
language,
className,
}) => {
return (
<SyntaxHighlighterPrism
language={language}
style={coldarkDark}
customStyle={{
margin: 0,
width: "100%",
background: "transparent",
padding: "1.5rem 1rem",
}}
className={className}
>
{children}
</SyntaxHighlighterPrism>
);
};